├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE.txt ├── Makefile ├── README.md ├── collector ├── buildinfo.go ├── mempool.go └── network.go ├── go-tezos ├── LICENSE.txt ├── README.md ├── block.go ├── client.go ├── docker-compose.yml ├── errors.go ├── fixtures │ ├── block │ │ ├── contract_balance.json │ │ ├── delegate_balance.json │ │ └── pending_operations.json │ ├── chains │ │ ├── block.json │ │ └── invalid_blocks.json │ ├── empty.json │ ├── empty_error.json │ ├── error.json │ ├── malformed_error.json │ ├── monitor │ │ ├── bootstrapped.chunked │ │ ├── heads.chunked │ │ └── mempool_operations.chunked │ ├── network │ │ ├── connections.json │ │ ├── peer.json │ │ ├── peer_log.chunked │ │ ├── peer_log.json │ │ ├── peers.json │ │ ├── point.json │ │ ├── point_log.chunked │ │ ├── point_log.json │ │ ├── points.json │ │ └── stat.json │ └── votes │ │ ├── ballot_list.json │ │ ├── ballots.json │ │ ├── current_period_kind.json │ │ ├── current_proposal.json │ │ ├── current_quorum.json │ │ ├── listings.json │ │ └── proposals.json ├── operations.go ├── service.go ├── service_test.go ├── utils.go └── votes.go ├── go.mod ├── go.sum ├── health.go └── main.go /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - 21 | name: Set up Docker Buildx 22 | uses: docker/setup-buildx-action@v1 23 | - 24 | name: Set up Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: '1.16' 28 | - 29 | name: Cache Go modules 30 | uses: actions/cache@v2 31 | with: 32 | path: ~/go/pkg/mod 33 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 34 | restore-keys: | 35 | ${{ runner.os }}-go- 36 | - 37 | name: Login to Docker Hub 38 | if: startsWith(github.ref, 'refs/tags/v') 39 | uses: docker/login-action@v1 40 | with: 41 | username: ${{ secrets.DOCKER_USERNAME }} 42 | password: ${{ secrets.DOCKER_PASSWORD }} 43 | - 44 | name: Login to GitHub Container Registry 45 | if: startsWith(github.ref, 'refs/tags/v') 46 | uses: docker/login-action@v1 47 | with: 48 | registry: ghcr.io 49 | username: ${{ github.repository_owner }} 50 | password: ${{ secrets.GITHUB_TOKEN }} 51 | - 52 | name: Run GoReleaser 53 | uses: goreleaser/goreleaser-action@v2 54 | with: 55 | # either 'goreleaser' (default) or 'goreleaser-pro' 56 | distribution: goreleaser 57 | version: latest 58 | args: release --rm-dist 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | tezos_exporter 3 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - windows 15 | - darwin 16 | goarch: 17 | - amd64 18 | - arm 19 | - arm64 20 | dockers: 21 | - image_templates: 22 | - 'ecadlabs/tezos_exporter:{{ .Tag }}-amd64' 23 | - 'ecadlabs/tezos_exporter:latest-amd64' 24 | - 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}-amd64' 25 | - 'ghcr.io/ecadlabs/tezos_exporter:latest-amd64' 26 | dockerfile: Dockerfile 27 | use: buildx 28 | build_flag_templates: 29 | - "--pull" 30 | - "--label=org.opencontainers.image.created={{.Date}}" 31 | - "--label=org.opencontainers.image.name={{.ProjectName}}" 32 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 33 | - "--label=org.opencontainers.image.version={{.Version}}" 34 | - "--label=org.opencontainers.image.source={{.GitURL}}" 35 | - "--platform=linux/amd64" 36 | - image_templates: 37 | - 'ecadlabs/tezos_exporter:{{ .Tag }}-arm64' 38 | - 'ecadlabs/tezos_exporter:latest-arm64' 39 | - 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}-arm64' 40 | - 'ghcr.io/ecadlabs/tezos_exporter:latest-arm64' 41 | dockerfile: Dockerfile 42 | use: buildx 43 | build_flag_templates: 44 | - "--pull" 45 | - "--label=org.opencontainers.image.created={{.Date}}" 46 | - "--label=org.opencontainers.image.name={{.ProjectName}}" 47 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 48 | - "--label=org.opencontainers.image.version={{.Version}}" 49 | - "--label=org.opencontainers.image.source={{.GitURL}}" 50 | - "--platform=linux/arm64" 51 | goarch: arm64 52 | docker_manifests: 53 | - name_template: 'ecadlabs/tezos_exporter:{{ .Tag }}' 54 | image_templates: 55 | - 'ecadlabs/tezos_exporter:{{ .Tag }}-amd64' 56 | - 'ecadlabs/tezos_exporter:{{ .Tag }}-arm64' 57 | - name_template: 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}' 58 | image_templates: 59 | - 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}-amd64' 60 | - 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}-arm64' 61 | - name_template: 'ecadlabs/tezos_exporter:latest' 62 | image_templates: 63 | - 'ecadlabs/tezos_exporter:{{ .Tag }}-amd64' 64 | - 'ecadlabs/tezos_exporter:{{ .Tag }}-arm64' 65 | - name_template: 'ghcr.io/ecadlabs/tezos_exporter:latest' 66 | image_templates: 67 | - 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}-amd64' 68 | - 'ghcr.io/ecadlabs/tezos_exporter:{{ .Tag }}-arm64' 69 | 70 | 71 | archives: 72 | - replacements: 73 | darwin: Darwin 74 | linux: Linux 75 | windows: Windows 76 | amd64: x86_64 77 | checksum: 78 | name_template: 'checksums.txt' 79 | snapshot: 80 | name_template: "{{ .Tag }}-next" 81 | changelog: 82 | sort: asc 83 | filters: 84 | exclude: 85 | - '^docs:' 86 | - '^test:' 87 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ENTRYPOINT ["/usr/bin/tezos_exporter"] 4 | CMD ["-tezos-node-url" "http://localhost:8732/"] 5 | 6 | 7 | COPY tezos_exporter /usr/bin/tezos_exporter 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ECAD Labs Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GIT_REVISION := $(shell git rev-parse HEAD) 2 | GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 3 | 4 | COLLECTOR_PKG = github.com/ecadlabs/tezos_exporter/collector 5 | 6 | tezos_exporter: 7 | go build -ldflags "-X $(COLLECTOR_PKG).GitRevision=$(GIT_REVISION) -X $(COLLECTOR_PKG).GitBranch=$(GIT_BRANCH)" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tezos Exporter for Prometheus (WIP) 2 | 3 | ![Build](https://github.com/ecadlabs/tezos_exporter/workflows/Build/badge.svg) 4 | ![Release Docker image](https://github.com/ecadlabs/tezos_exporter/workflows/Release%20Docker%20image/badge.svg) 5 | 6 | `tezos_exporter` produces metrics by querying the RPC methods of a Tezos node. 7 | 8 | ## Getting Started using docker 9 | 10 | To get started using the provided docker images, run: 11 | 12 | ```sh 13 | docker run -it --rm --name tezos_exporter ecadlabs/tezos_exporter \ 14 | -tezos-node-url http://YOUR_TEZOS_NODE:8732/ 15 | ``` 16 | 17 | You will need to configure a prometheus server to scrape the metrics from your 18 | newly running exporter. Add the following scrape job to your `promethus.yml` 19 | configuration file. 20 | 21 | ```yaml 22 | - job_name: tezos 23 | scrape_interval: 30s 24 | file_sd_configs: 25 | static_configs: 26 | - targets: ['EXPORTER_ADDRESS:9489'] 27 | ``` 28 | 29 | Restart promethues, and you should see the new job named `tezos` by looking at 30 | `Targets` via the prometheus UI. 31 | 32 | Metric names are as follows; 33 | 34 | * tezos_node_bootstrapped 35 | * tezos_node_connections 36 | * tezos_node_mempool_operations 37 | * tezos_node_peers 38 | * tezos_node_points 39 | * tezos_node_recv_bytes_total 40 | * tezos_node_sent_bytes_total 41 | * tezos_rpc_failed 42 | 43 | To request a new metric be added, please file a new feature request Issue in 44 | the github tracker, or submit a Pull Request. Contributors welcome! 45 | 46 | ## Reporting issues/feature requests 47 | 48 | Please use the [GitHub issue 49 | tracker](https://github.com/ecadlabs/go-tezos/issues) to report bugs or request 50 | features. 51 | 52 | ## Contributions 53 | 54 | To contribute, please check the issue tracker to see if an existing issue 55 | exists for your planned contribution. If there's no Issue, please create one 56 | first, and then submit a pull request with your contribution. 57 | 58 | For a contribution to be merged, it must be well documented, come with unit 59 | tests, and integration tests where appropriate. Submitting a "Work in progress" 60 | pull request is welcome! 61 | 62 | ## Reporting Security Issues 63 | 64 | If a security vulnerability in this project is discovered, please report the 65 | issue to security@ecadlabs.com or to `jevonearth` on keybase.io 66 | 67 | Reports may be encrypted using keys published on keybase.io 68 | -------------------------------------------------------------------------------- /collector/buildinfo.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "runtime/debug" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | var ( 10 | GitRevision string 11 | GitBranch string 12 | ) 13 | 14 | type constCollector struct { 15 | metric prometheus.Metric 16 | } 17 | 18 | // NewBuildInfoCollector returns a collector collecting a single metric "go_build_info" 19 | func NewBuildInfoCollector(prefix string) prometheus.Collector { 20 | var path, version, sum, revision, branch = "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)" 21 | if bi, ok := debug.ReadBuildInfo(); ok { 22 | path = bi.Main.Path 23 | version = bi.Main.Version 24 | sum = bi.Main.Sum 25 | } 26 | 27 | if GitRevision != "" { 28 | revision = GitRevision 29 | } 30 | 31 | if GitBranch != "" { 32 | branch = GitBranch 33 | } 34 | 35 | if prefix == "" { 36 | prefix = "go" 37 | } 38 | 39 | return &constCollector{ 40 | metric: prometheus.MustNewConstMetric( 41 | prometheus.NewDesc( 42 | prefix+"_build_info", 43 | "Build information about the main Go module.", 44 | nil, prometheus.Labels{ 45 | "path": path, 46 | "version": version, 47 | "checksum": sum, 48 | "revision": revision, 49 | "branch": branch, 50 | }, 51 | ), 52 | prometheus.GaugeValue, 1), 53 | } 54 | } 55 | 56 | // Describe implements prometheus.Collector 57 | func (c *constCollector) Describe(ch chan<- *prometheus.Desc) { 58 | ch <- c.metric.Desc() 59 | } 60 | 61 | // Collect implements prometheus.Collector 62 | func (c *constCollector) Collect(ch chan<- prometheus.Metric) { 63 | ch <- c.metric 64 | } 65 | -------------------------------------------------------------------------------- /collector/mempool.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "time" 7 | 8 | tezos "github.com/ecadlabs/tezos_exporter/go-tezos" 9 | "github.com/prometheus/client_golang/prometheus" 10 | "github.com/prometheus/client_golang/prometheus/promhttp" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // MempoolOperationsCollector collects mempool operations count 15 | type MempoolOperationsCollector struct { 16 | counter *prometheus.CounterVec 17 | rpcTotalHist prometheus.ObserverVec 18 | rpcConnectHist prometheus.Histogram 19 | service *tezos.Service 20 | chainID string 21 | interval time.Duration 22 | } 23 | 24 | func (m *MempoolOperationsCollector) listener(pool string) { 25 | ch := make(chan []*tezos.Operation, 100) 26 | defer close(ch) 27 | 28 | go func() { 29 | for ops := range ch { 30 | for _, op := range ops { 31 | for _, elem := range op.Contents { 32 | m.counter.WithLabelValues(pool, op.Protocol, elem.OperationElemKind()).Inc() 33 | } 34 | } 35 | } 36 | }() 37 | 38 | for { 39 | err := m.service.MonitorMempoolOperations(context.Background(), m.chainID, pool, ch) 40 | if err != nil { 41 | log.WithError(err).WithField("pool", pool).Error("error monitoring mempool operations") 42 | <-time.After(m.interval) 43 | } 44 | } 45 | } 46 | 47 | // NewMempoolOperationsCollectorCollector returns new mempool collector for given pools like "applied", "refused" etc. 48 | func NewMempoolOperationsCollectorCollector(service *tezos.Service, chainID string, pools []string, interval time.Duration) *MempoolOperationsCollector { 49 | c := &MempoolOperationsCollector{ 50 | counter: prometheus.NewCounterVec( 51 | prometheus.CounterOpts{ 52 | Namespace: "tezos_node", 53 | Subsystem: "mempool", 54 | Name: "operations_total", 55 | Help: "The total number of mempool operations.", 56 | }, 57 | []string{"pool", "proto", "kind"}, 58 | ), 59 | rpcTotalHist: prometheus.NewHistogramVec( 60 | prometheus.HistogramOpts{ 61 | Namespace: "tezos_rpc", 62 | Subsystem: "mempool", 63 | Name: "monitor_connection_total_duration_seconds", 64 | Help: "The total life time of the mempool monitor RPC connection.", 65 | Buckets: prometheus.ExponentialBuckets(0.25, 2, 12), 66 | }, 67 | []string{}, 68 | ), 69 | rpcConnectHist: prometheus.NewHistogram( 70 | prometheus.HistogramOpts{ 71 | Namespace: "tezos_rpc", 72 | Subsystem: "mempool", 73 | Name: "monitor_connection_connect_duration_seconds", 74 | Help: "Mempool monitor (re)connection duration (time until HTTP header arrives).", 75 | Buckets: prometheus.ExponentialBuckets(0.25, 2, 12), 76 | }, 77 | ), 78 | chainID: chainID, 79 | interval: interval, 80 | } 81 | 82 | it := promhttp.InstrumentTrace{ 83 | GotConn: func(t float64) { 84 | c.rpcConnectHist.Observe(t) 85 | }, 86 | } 87 | 88 | client := *service.Client 89 | if client.Transport == nil { 90 | client.Transport = http.DefaultTransport 91 | } 92 | 93 | client.Transport = promhttp.InstrumentRoundTripperDuration(c.rpcTotalHist, client.Transport) 94 | client.Transport = promhttp.InstrumentRoundTripperTrace(&it, client.Transport) 95 | 96 | srv := *service 97 | srv.Client = &client 98 | c.service = &srv 99 | 100 | for _, p := range pools { 101 | log.WithField("pool", p).Info("starting mempool monitor") 102 | go c.listener(p) 103 | } 104 | 105 | return c 106 | } 107 | 108 | // Describe implements prometheus.Collector 109 | func (m *MempoolOperationsCollector) Describe(ch chan<- *prometheus.Desc) { 110 | m.counter.Describe(ch) 111 | m.rpcTotalHist.Describe(ch) 112 | m.rpcConnectHist.Describe(ch) 113 | } 114 | 115 | // Collect implements prometheus.Collector 116 | func (m *MempoolOperationsCollector) Collect(ch chan<- prometheus.Metric) { 117 | m.counter.Collect(ch) 118 | m.rpcTotalHist.Collect(ch) 119 | m.rpcConnectHist.Collect(ch) 120 | } 121 | -------------------------------------------------------------------------------- /collector/network.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | tezos "github.com/ecadlabs/tezos_exporter/go-tezos" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | const bootstrappedTimeout = 5 * time.Second 16 | const bootstrappedPollInterval = 30 * time.Second 17 | 18 | var ( 19 | sentBytesDesc = prometheus.NewDesc( 20 | "tezos_node_sent_bytes_total", 21 | "Total number of bytes sent from this node.", 22 | nil, 23 | nil) 24 | 25 | recvBytesDesc = prometheus.NewDesc( 26 | "tezos_node_recv_bytes_total", 27 | "Total number of bytes received by this node.", 28 | nil, 29 | nil) 30 | 31 | connsDesc = prometheus.NewDesc( 32 | "tezos_node_connections", 33 | "Current number of connections to/from this node.", 34 | []string{"direction", "private"}, 35 | nil) 36 | 37 | peersDesc = prometheus.NewDesc( 38 | "tezos_node_peers", 39 | "Stats about all peers this node ever met.", 40 | []string{"trusted", "state"}, 41 | nil) 42 | 43 | pointsDesc = prometheus.NewDesc( 44 | "tezos_node_points", 45 | "Stats about known network points.", 46 | []string{"trusted", "event_kind"}, 47 | nil) 48 | 49 | rpcFailedDesc = prometheus.NewDesc( 50 | "tezos_rpc_failed", 51 | "A gauge that is set to 1 when a metrics collection RPC failed during the current scrape, 0 otherwise.", 52 | []string{"rpc"}, 53 | nil) 54 | ) 55 | 56 | // NetworkCollector collects metrics about a Tezos node's network properties. 57 | type NetworkCollector struct { 58 | service *tezos.Service 59 | timeout time.Duration 60 | chainID string 61 | bootstrapped prometheus.Gauge 62 | } 63 | 64 | // NewNetworkCollector returns a new NetworkCollector. 65 | func NewNetworkCollector(service *tezos.Service, timeout time.Duration, chainID string) *NetworkCollector { 66 | c := &NetworkCollector{ 67 | service: service, 68 | timeout: timeout, 69 | chainID: chainID, 70 | bootstrapped: prometheus.NewGauge(prometheus.GaugeOpts{ 71 | Namespace: "tezos_node", 72 | Name: "bootstrapped", 73 | Help: "Returns 1 if the node has synchronized its chain with a few peers.", 74 | }), 75 | } 76 | 77 | go c.bootstrappedPollLoop() 78 | return c 79 | } 80 | 81 | func (c *NetworkCollector) getBootstrapped() (bool, error) { 82 | ctx, cancel := context.WithTimeout(context.Background(), bootstrappedTimeout) 83 | defer cancel() 84 | 85 | ch := make(chan *tezos.BootstrappedBlock, 10) 86 | var err error 87 | 88 | go func() { 89 | err = c.service.MonitorBootstrapped(ctx, ch) 90 | close(ch) 91 | }() 92 | 93 | var cnt int 94 | for range ch { 95 | if cnt > 0 { 96 | // More than one record returned 97 | return false, nil 98 | } 99 | cnt++ 100 | } 101 | 102 | if err != nil { 103 | if e, ok := err.(net.Error); ok && e.Timeout() { 104 | return false, nil 105 | } 106 | return false, err 107 | } 108 | 109 | return true, nil 110 | } 111 | 112 | func (c *NetworkCollector) bootstrappedPollLoop() { 113 | t := time.NewTicker(bootstrappedPollInterval) 114 | 115 | for range t.C { 116 | ok, err := c.getBootstrapped() 117 | var v float64 118 | if err != nil { 119 | log.WithError(err).Error("error getting bootstrap status") 120 | } else { 121 | if ok { 122 | v = 1 123 | } 124 | } 125 | c.bootstrapped.Set(v) 126 | } 127 | } 128 | 129 | // Describe implements prometheus.Collector. 130 | func (c *NetworkCollector) Describe(ch chan<- *prometheus.Desc) { 131 | prometheus.DescribeByCollect(c, ch) 132 | } 133 | 134 | func getConnStats(ctx context.Context, service *tezos.Service) (map[string]map[string]int, error) { 135 | conns, err := service.GetNetworkConnections(ctx) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | connStats := map[string]map[string]int{ 141 | "incoming": { 142 | "false": 0, 143 | "true": 0, 144 | }, 145 | "outgoing": { 146 | "false": 0, 147 | "true": 0, 148 | }, 149 | } 150 | 151 | for _, conn := range conns { 152 | direction := "outgoing" 153 | if conn.Incoming { 154 | direction = "incoming" 155 | } 156 | private := "false" 157 | if conn.Private { 158 | private = "true" 159 | } 160 | 161 | connStats[direction][private]++ 162 | } 163 | 164 | return connStats, nil 165 | } 166 | 167 | func getPointStats(ctx context.Context, service *tezos.Service) (map[string]map[string]int, error) { 168 | points, err := service.GetNetworkPoints(ctx, "") 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | pointStats := map[string]map[string]int{ 174 | "false": {}, 175 | "true": {}, 176 | } 177 | 178 | for _, point := range points { 179 | trusted := "false" 180 | if point.Trusted { 181 | trusted = "true" 182 | } 183 | 184 | pointStats[trusted][point.State.EventKind]++ 185 | } 186 | 187 | return pointStats, nil 188 | } 189 | func getPeerStats(ctx context.Context, service *tezos.Service) (map[string]map[string]int, error) { 190 | peers, err := service.GetNetworkPeers(ctx, "") 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | peerStats := map[string]map[string]int{ 196 | "false": {}, 197 | "true": {}, 198 | } 199 | 200 | for _, peer := range peers { 201 | trusted := "false" 202 | if peer.Trusted { 203 | trusted = "true" 204 | } 205 | 206 | peerStats[trusted][peer.State]++ 207 | } 208 | 209 | return peerStats, nil 210 | } 211 | 212 | // Collect implements prometheus.Collector and is called by the Prometheus registry when collecting metrics. 213 | func (c *NetworkCollector) Collect(ch chan<- prometheus.Metric) { 214 | ctx, cancel := context.WithTimeout(context.Background(), c.timeout) 215 | defer cancel() 216 | 217 | client := *c.service.Client 218 | transport := client.Transport 219 | if transport == nil { 220 | transport = http.DefaultTransport 221 | } 222 | 223 | var path string 224 | client.Transport = promhttp.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 225 | path = r.URL.Path 226 | return transport.RoundTrip(r) 227 | }) 228 | 229 | srv := *c.service 230 | srv.Client = &client 231 | 232 | stats, err := srv.GetNetworkStats(ctx) 233 | if err == nil { 234 | ch <- prometheus.MustNewConstMetric(sentBytesDesc, prometheus.CounterValue, float64(stats.TotalBytesSent)) 235 | ch <- prometheus.MustNewConstMetric(recvBytesDesc, prometheus.CounterValue, float64(stats.TotalBytesRecv)) 236 | } 237 | var val float64 238 | if err != nil { 239 | log.WithError(err).Error("error getting network stats") 240 | val = 1 241 | } 242 | ch <- prometheus.MustNewConstMetric(rpcFailedDesc, prometheus.GaugeValue, val, path) 243 | 244 | connStats, err := getConnStats(ctx, &srv) 245 | if err == nil { 246 | for direction, stats := range connStats { 247 | for private, count := range stats { 248 | ch <- prometheus.MustNewConstMetric(connsDesc, prometheus.GaugeValue, float64(count), direction, private) 249 | } 250 | } 251 | } 252 | if err != nil { 253 | log.WithError(err).Error("error getting connections stats") 254 | val = 1 255 | } else { 256 | val = 0 257 | } 258 | ch <- prometheus.MustNewConstMetric(rpcFailedDesc, prometheus.GaugeValue, val, path) 259 | 260 | peerStats, err := getPeerStats(ctx, &srv) 261 | if err == nil { 262 | for trusted, stats := range peerStats { 263 | for state, count := range stats { 264 | ch <- prometheus.MustNewConstMetric(peersDesc, prometheus.GaugeValue, float64(count), trusted, state) 265 | } 266 | } 267 | } 268 | if err != nil { 269 | log.WithError(err).Error("error getting peer stats") 270 | val = 1 271 | } else { 272 | val = 0 273 | } 274 | ch <- prometheus.MustNewConstMetric(rpcFailedDesc, prometheus.GaugeValue, val, path) 275 | 276 | pointStats, err := getPointStats(ctx, &srv) 277 | if err == nil { 278 | for trusted, stats := range pointStats { 279 | for eventKind, count := range stats { 280 | ch <- prometheus.MustNewConstMetric(pointsDesc, prometheus.GaugeValue, float64(count), trusted, eventKind) 281 | } 282 | } 283 | } 284 | if err != nil { 285 | log.WithError(err).Error("error getting point stats") 286 | val = 1 287 | } else { 288 | val = 0 289 | } 290 | ch <- prometheus.MustNewConstMetric(rpcFailedDesc, prometheus.GaugeValue, val, path) 291 | 292 | c.bootstrapped.Collect(ch) 293 | } 294 | -------------------------------------------------------------------------------- /go-tezos/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ECAD Labs Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go-tezos/README.md: -------------------------------------------------------------------------------- 1 | # go-tezos 2 | 3 | go-tezos is a Go client library that is used to interact with a Tezos' nodes 4 | RPC methods. 5 | 6 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2349/badge)](https://bestpractices.coreinfrastructure.org/projects/2349) 7 | [![GitHub Actions](https://github.com/ecadlabs/go-tezos/workflows/Test/badge.svg)](https://github.com/ecadlabs/go-tezos/actions) 8 | [![Maintainability](https://api.codeclimate.com/v1/badges/b4e610a400c496532cd3/maintainability)](https://codeclimate.com/github/ecadlabs/go-tezos/maintainability) 9 | [![Test Coverage](https://api.codeclimate.com/v1/badges/b4e610a400c496532cd3/test_coverage)](https://codeclimate.com/github/ecadlabs/go-tezos/test_coverage) 10 | 11 | 12 | ## Work in progress, contributions welcome. 13 | 14 | This client RPC library is in development and should be considered alpha. 15 | Contributors are welcome. We will start to tag releases of this library in 16 | November 2018. 17 | 18 | The library will be useful to anyone wanting to build tools, products or 19 | services on top of the Tezos RPC API in go. 20 | 21 | The library will be: 22 | 23 | * Well tested 24 | * Nightly Integration tests against official Tezos docker images 25 | * Written in Idiomatic Go 26 | * Aim to have complete coverage of the Tezos API and stay up to date with new 27 | RPCs or changes to existing RPCs 28 | 29 | # Documentation 30 | 31 | Library documentation lives in the code as godoc comments. Readers can view 32 | up-to-date documentation here: https://godoc.org/github.com/ecadlabs/go-tezos 33 | 34 | # Contributions 35 | 36 | ## Reporting issues/feature requests 37 | 38 | Please use the [GitHub issue 39 | tracker](https://github.com/ecadlabs/go-tezos/issues) to report bugs or request 40 | features. 41 | 42 | ## Contribution 43 | 44 | To contribute, please check the issue tracker to see if an existing issue 45 | exists for your planned contribution. If there's no Issue, please create one 46 | first, and then submit a pull request with your contribution. 47 | 48 | For a contribution to be merged, it must be well documented, come with unit 49 | tests, and integration tests where appropriate. Submitting a "Work in progress" 50 | pull request is welcome! 51 | 52 | ## Reporting Security Issues 53 | 54 | If a security vulnerabiltiy in this project is discovered, please report the 55 | issue to go-tezos@ecadlabs.com or to `jevonearth` on keybase.io 56 | 57 | Reports may be encrypted using keys published on keybase.io 58 | 59 | # Tezos RPC API documentation 60 | 61 | The best known RPC API docs are available here: http://tezos.gitlab.io/mainnet/ 62 | 63 | # Users of `go-tezos` 64 | 65 | * A prometheus metrics exporter for a Tezos node https://github.com/ecadlabs/tezos_exporter 66 | 67 | ## Development 68 | 69 | ### Running a tezos RPC node using docker-compose 70 | 71 | To run a local tezos RPC node using docker, run the following command: 72 | 73 | `docker-compose up` 74 | 75 | The node will generate an identity and then, it will the chain from other nodes 76 | on the network. The process of synchronizing or downloading the chain can take 77 | some time, but most of the RPC will work while this process completes. 78 | 79 | The `alphanet` image tag means you are not interacting with the live `mainnet`. 80 | You can connect to `mainnet` with the `tezos/tezos:mainnet` image, but it takes 81 | longer to sync. 82 | 83 | The `docker-compose.yml` file uses volumes, so when you restart the node, it 84 | won't have to regenerate an identity, or sync the entire chain. 85 | 86 | ### Running a tezos RPC node using docker 87 | 88 | If you want to run a tezos node quickly, without using `docker-compose` try: 89 | 90 | `docker run -it --rm --name tezos_node -p 8732:8732 tezos/tezos:alphanet tezos-node` 91 | 92 | ### Interacting with tezos RPC 93 | 94 | With the tezos-node docker image, you can test that the RPC interface is 95 | working: 96 | 97 | `curl localhost:8732/network/stat` 98 | 99 | The tezos-client cli is available in the docker image, and can be run as 100 | follows: 101 | 102 | `docker exec -it tezos_node tezos-client -A 0.0.0.0 man` 103 | 104 | `docker exec -it tezos_node tezos-client -A 0.0.0.0 rpc list` 105 | 106 | Create a shell alias that you can run from your docker host for convenience; 107 | 108 | `alias tezos-client='sudo docker exec -it -e TEZOS_CLIENT_UNSAFE_DISABLE_DISCLAIMER=Y tezos_node tezos-client -A 0.0.0.0'` 109 | -------------------------------------------------------------------------------- /go-tezos/block.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // HexBytes represents bytes as a JSON string of hexadecimal digits 11 | type HexBytes []byte 12 | 13 | // UnmarshalText umarshalls a hex string to bytes 14 | func (hb *HexBytes) UnmarshalText(data []byte) error { 15 | dst := make([]byte, hex.DecodedLen(len(data))) 16 | if _, err := hex.Decode(dst, data); err != nil { 17 | return err 18 | } 19 | *hb = dst 20 | return nil 21 | } 22 | 23 | // BlockInfo holds information about block returned by monitor heads endpoint 24 | type BlockInfo struct { 25 | Hash string `json:"hash" yaml:"hash"` 26 | Level int `json:"level" yaml:"level"` 27 | Proto int `json:"proto" yaml:"proto"` 28 | Predecessor string `json:"predecessor" yaml:"predecessor"` 29 | Timestamp time.Time `json:"timestamp" yaml:"timestamp"` 30 | ValidationPass int `json:"validation_pass" yaml:"validation_pass"` 31 | OperationsHash string `json:"operations_hash" yaml:"operations_hash"` 32 | Fitness []HexBytes `json:"fitness" yaml:"fitness,flow"` 33 | Context string `json:"context" yaml:"context"` 34 | ProtocolData string `json:"protocol_data" yaml:"protocol_data"` 35 | } 36 | 37 | // RawBlockHeader is a part of the Tezos block data 38 | type RawBlockHeader struct { 39 | Level int `json:"level" yaml:"level"` 40 | Proto int `json:"proto" yaml:"proto"` 41 | Predecessor string `json:"predecessor" yaml:"predecessor"` 42 | Timestamp time.Time `json:"timestamp" yaml:"timestamp"` 43 | ValidationPass int `json:"validation_pass" yaml:"validation_pass"` 44 | OperationsHash string `json:"operations_hash" yaml:"operations_hash"` 45 | Fitness []HexBytes `json:"fitness" yaml:"fitness,flow"` 46 | Context string `json:"context" yaml:"context"` 47 | Priority int `json:"priority" yaml:"priority"` 48 | ProofOfWorkNonce HexBytes `json:"proof_of_work_nonce" yaml:"proof_of_work_nonce,flow"` 49 | SeedNonceHash string `json:"seed_nonce_hash" yaml:"seed_nonce_hash"` 50 | Signature string `json:"signature" yaml:"signature"` 51 | } 52 | 53 | // TestChainStatus is a variable structure depending on the Status field 54 | type TestChainStatus interface { 55 | TestChainStatus() string 56 | } 57 | 58 | // GenericTestChainStatus holds the common values among all TestChainStatus variants 59 | type GenericTestChainStatus struct { 60 | Status string `json:"status" yaml:"status"` 61 | } 62 | 63 | // TestChainStatus gets the TestChainStatus's Status field 64 | func (t *GenericTestChainStatus) TestChainStatus() string { 65 | return t.Status 66 | } 67 | 68 | // NotRunningTestChainStatus is a TestChainStatus variant for Status=not_running 69 | type NotRunningTestChainStatus struct { 70 | GenericTestChainStatus 71 | } 72 | 73 | // ForkingTestChainStatus is a TestChainStatus variant for Status=forking 74 | type ForkingTestChainStatus struct { 75 | GenericTestChainStatus 76 | Protocol string `json:"protocol" yaml:"protocol"` 77 | Expiration string `json:"expiration" yaml:"expiration"` 78 | } 79 | 80 | // RunningTestChainStatus is a TestChainStatus variant for Status=running 81 | type RunningTestChainStatus struct { 82 | GenericTestChainStatus 83 | ChainID string `json:"chain_id" yaml:"chain_id"` 84 | Genesis string `json:"genesis" yaml:"genesis"` 85 | Protocol string `json:"protocol" yaml:"protocol"` 86 | Expiration string `json:"expiration" yaml:"expiration"` 87 | } 88 | 89 | // MaxOperationListLength is a part of the BlockHeaderMetadata 90 | type MaxOperationListLength struct { 91 | MaxSize int `json:"max_size" yaml:"max_size"` 92 | MaxOp int `json:"max_op" yaml:"max_op"` 93 | } 94 | 95 | // BlockHeaderMetadataLevel is a part of BlockHeaderMetadata 96 | type BlockHeaderMetadataLevel struct { 97 | Level int `json:"level" yaml:"level"` 98 | LevelPosition int `json:"level_position" yaml:"level_position"` 99 | Cycle int `json:"cycle" yaml:"cycle"` 100 | CyclePosition int `json:"cycle_position" yaml:"cycle_position"` 101 | VotingPeriod int `json:"voting_period" yaml:"voting_period"` 102 | VotingPeriodPosition int `json:"voting_period_position" yaml:"voting_period_position"` 103 | ExpectedCommitment bool `json:"expected_commitment" yaml:"expected_commitment"` 104 | } 105 | 106 | // BlockHeaderMetadata is a part of the Tezos block data 107 | type BlockHeaderMetadata struct { 108 | Protocol string `json:"protocol" yaml:"protocol"` 109 | NextProtocol string `json:"next_protocol" yaml:"next_protocol"` 110 | TestChainStatus TestChainStatus `json:"-" yaml:"-"` 111 | MaxOperationsTTL int `json:"max_operations_ttl" yaml:"max_operations_ttl"` 112 | MaxOperationDataLength int `json:"max_operation_data_length" yaml:"max_operation_data_length"` 113 | MaxBlockHeaderLength int `json:"max_block_header_length" yaml:"max_block_header_length"` 114 | MaxOperationListLength []*MaxOperationListLength `json:"max_operation_list_length" yaml:"max_operation_list_length"` 115 | Baker string `json:"baker" yaml:"baker"` 116 | Level BlockHeaderMetadataLevel `json:"level" yaml:"level"` 117 | VotingPeriodKind string `json:"voting_period_kind" yaml:"voting_period_kind"` 118 | NonceHash string `json:"nonce_hash" yaml:"nonce_hash"` 119 | ConsumedGas *BigInt `json:"consumed_gas" yaml:"consumed_gas"` 120 | Deactivated []string `json:"deactivated" yaml:"deactivated"` 121 | BalanceUpdates BalanceUpdates `json:"balance_updates" yaml:"balance_updates"` 122 | } 123 | 124 | func unmarshalTestChainStatus(data []byte) (TestChainStatus, error) { 125 | var tmp GenericTestChainStatus 126 | if err := json.Unmarshal(data, &tmp); err != nil { 127 | return nil, err 128 | } 129 | 130 | var v TestChainStatus 131 | 132 | switch tmp.Status { 133 | case "not_running": 134 | v = &NotRunningTestChainStatus{} 135 | case "forking": 136 | v = &ForkingTestChainStatus{} 137 | case "running": 138 | v = &RunningTestChainStatus{} 139 | 140 | default: 141 | return nil, fmt.Errorf("unknown TestChainStatus.Status: %v", tmp.Status) 142 | } 143 | 144 | if err := json.Unmarshal(data, v); err != nil { 145 | return nil, err 146 | } 147 | 148 | return v, nil 149 | } 150 | 151 | // UnmarshalJSON unmarshals the BlockHeaderMetadata JSON 152 | func (bhm *BlockHeaderMetadata) UnmarshalJSON(data []byte) error { 153 | type suppressJSONUnmarshaller BlockHeaderMetadata 154 | if err := json.Unmarshal(data, (*suppressJSONUnmarshaller)(bhm)); err != nil { 155 | return err 156 | } 157 | 158 | var tmp struct { 159 | TestChainStatus json.RawMessage `json:"test_chain_status" yaml:"test_chain_status"` 160 | } 161 | 162 | if err := json.Unmarshal(data, &tmp); err != nil { 163 | return err 164 | } 165 | 166 | tcs, err := unmarshalTestChainStatus(tmp.TestChainStatus) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | bhm.TestChainStatus = tcs 172 | 173 | return nil 174 | } 175 | 176 | // Block holds information about a Tezos block 177 | type Block struct { 178 | Protocol string `json:"protocol" yaml:"protocol"` 179 | ChainID string `json:"chain_id" yaml:"chain_id"` 180 | Hash string `json:"hash" yaml:"hash"` 181 | Header RawBlockHeader `json:"header" yaml:"header"` 182 | Metadata BlockHeaderMetadata `json:"metadata" yaml:"metadata"` 183 | Operations [][]*Operation `json:"operations" yaml:"operations"` 184 | } 185 | -------------------------------------------------------------------------------- /go-tezos/client.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | "reflect" 13 | "strings" 14 | 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | const ( 19 | libraryVersion = "0.0.1" 20 | defaultUserAgent = "go-tezos/" + libraryVersion 21 | mediaType = "application/json" 22 | ) 23 | 24 | // NewRequest creates a Tezos RPC request. 25 | func (c *RPCClient) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { 26 | rel, err := url.Parse(urlStr) 27 | if err != nil { 28 | return nil, err 29 | } 30 | u := c.BaseURL.ResolveReference(rel) 31 | 32 | var bodyReader io.Reader 33 | if body != nil { 34 | var buf bytes.Buffer 35 | err = json.NewEncoder(&buf).Encode(body) 36 | if err != nil { 37 | return nil, err 38 | } 39 | bodyReader = &buf 40 | } 41 | 42 | if ctx == nil { 43 | ctx = context.Background() 44 | } 45 | req, err := http.NewRequestWithContext(ctx, method, u.String(), bodyReader) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | if body != nil { 51 | req.Header.Add("Content-Type", mediaType) 52 | } 53 | req.Header.Add("Accept", mediaType) 54 | 55 | userAgent := c.UserAgent 56 | if userAgent == "" { 57 | userAgent = defaultUserAgent 58 | } 59 | req.Header.Add("User-Agent", c.UserAgent) 60 | 61 | return req, nil 62 | } 63 | 64 | // RPCClient manages communication with a Tezos RPC server. 65 | type RPCClient struct { 66 | // Logger 67 | Logger Logger 68 | // HTTP transport used to communicate with the Tezos node API. Can be used for side effects. 69 | Transport http.RoundTripper 70 | // Base URL for API requests. 71 | BaseURL *url.URL 72 | // User agent name for client. 73 | UserAgent string 74 | } 75 | 76 | // NewRPCClient returns a new Tezos RPC client. 77 | func NewRPCClient(baseURL string) (*RPCClient, error) { 78 | u, err := url.Parse(baseURL) 79 | if err != nil { 80 | return nil, err 81 | } 82 | return &RPCClient{ 83 | BaseURL: u, 84 | }, nil 85 | } 86 | 87 | func (c *RPCClient) log() Logger { 88 | if c.Logger != nil { 89 | return c.Logger 90 | } 91 | return log.StandardLogger() 92 | } 93 | 94 | func (c *RPCClient) handleNormalResponse(ctx context.Context, resp *http.Response, v interface{}) error { 95 | // Normal return 96 | typ := reflect.TypeOf(v) 97 | 98 | if typ.Kind() == reflect.Chan { 99 | // Handle channel 100 | dumpResponse(c.log(), log.DebugLevel, resp, false) 101 | dec := json.NewDecoder(resp.Body) 102 | 103 | cases := []reflect.SelectCase{ 104 | { 105 | Dir: reflect.SelectSend, 106 | Chan: reflect.ValueOf(v), 107 | }, 108 | { 109 | Dir: reflect.SelectRecv, 110 | Chan: reflect.ValueOf(ctx.Done()), 111 | }, 112 | } 113 | 114 | for { 115 | chunkVal := reflect.New(typ.Elem()) 116 | 117 | if err := dec.Decode(chunkVal.Interface()); err != nil { 118 | if err == io.EOF || err == io.ErrUnexpectedEOF { 119 | // Tezos doesn't output the trailing zero lenght chunk leading to io.ErrUnexpectedEOF 120 | break 121 | } 122 | return err 123 | } 124 | 125 | spewDump(c.log(), log.TraceLevel, chunkVal.Interface()) 126 | 127 | cases[0].Send = chunkVal.Elem() 128 | if chosen, _, _ := reflect.Select(cases); chosen == 1 { 129 | return ctx.Err() 130 | } 131 | } 132 | 133 | return nil 134 | } 135 | 136 | // Handle single object 137 | dumpResponse(c.log(), log.DebugLevel, resp, true) 138 | dec := json.NewDecoder(resp.Body) 139 | if err := dec.Decode(&v); err != nil { 140 | return err 141 | } 142 | 143 | spewDump(c.log(), log.TraceLevel, v) 144 | 145 | return nil 146 | } 147 | 148 | func (c *RPCClient) transport() http.RoundTripper { 149 | if c.Transport != nil { 150 | return c.Transport 151 | } 152 | return http.DefaultTransport 153 | } 154 | 155 | // Do retrieves values from the API and marshals them into the provided interface. 156 | func (c *RPCClient) Do(req *http.Request, v interface{}) (err error) { 157 | dumpRequest(c.log(), log.DebugLevel, req) 158 | 159 | client := &http.Client{ 160 | Transport: c.transport(), 161 | } 162 | resp, err := client.Do(req) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | defer func() { 168 | if rerr := resp.Body.Close(); err == nil { 169 | err = rerr 170 | } 171 | }() 172 | if resp.StatusCode == http.StatusNoContent { 173 | return nil 174 | } 175 | 176 | statusClass := resp.StatusCode / 100 177 | if statusClass == 2 { 178 | if v == nil { 179 | return nil 180 | } 181 | return c.handleNormalResponse(req.Context(), resp, v) 182 | } 183 | 184 | // Handle errors 185 | dumpResponse(c.log(), log.DebugLevel, resp, true) 186 | 187 | body, err := ioutil.ReadAll(resp.Body) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | httpErr := httpError{ 193 | response: resp, 194 | body: body, 195 | } 196 | 197 | if statusClass != 5 || !strings.Contains(resp.Header.Get("Content-Type"), "application/json") { 198 | // Other errors with unknown body format (usually human readable string) 199 | return &httpErr 200 | } 201 | 202 | var errs Errors 203 | if err := json.Unmarshal(body, &errs); err != nil { 204 | return &plainError{&httpErr, fmt.Sprintf("tezos: error decoding RPC error: %v", err)} 205 | } 206 | 207 | if len(errs) == 0 { 208 | return &plainError{&httpErr, "tezos: empty error response"} 209 | } 210 | 211 | return &rpcError{ 212 | httpError: &httpErr, 213 | errors: errs, 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /go-tezos/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | tezos_node: 4 | image: tezos/tezos:alphanet 5 | command: tezos-node 6 | ports: 7 | - 8732:8732 8 | volumes: 9 | - node_data:/var/run/tezos/node 10 | - client_data:/var/run/tezos/client 11 | 12 | volumes: 13 | node_data: 14 | client_data: 15 | -------------------------------------------------------------------------------- /go-tezos/errors.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | const ( 10 | // ErrorKindPermanent Tezos RPC error kind. 11 | ErrorKindPermanent = "permanent" 12 | // ErrorKindTemporary Tezos RPC error kind. 13 | ErrorKindTemporary = "temporary" 14 | // ErrorKindBranch Tezos RPC error kind. 15 | ErrorKindBranch = "branch" 16 | ) 17 | 18 | // Error is a Tezos error as documented on http://tezos.gitlab.io/mainnet/api/errors.html. 19 | type Error interface { 20 | error 21 | ErrorID() string 22 | ErrorKind() string 23 | } 24 | 25 | // GenericError is a basic error type 26 | type GenericError struct { 27 | ID string `json:"id"` 28 | Kind string `json:"kind"` 29 | } 30 | 31 | func (e *GenericError) Error() string { 32 | return fmt.Sprintf("tezos: kind = %q, id = %q", e.Kind, e.ID) 33 | } 34 | 35 | // ErrorID returns Tezos error id 36 | func (e *GenericError) ErrorID() string { 37 | return e.ID 38 | } 39 | 40 | // ErrorKind returns Tezos error kind 41 | func (e *GenericError) ErrorKind() string { 42 | return e.Kind 43 | } 44 | 45 | // HTTPStatus interface represents an unprocessed HTTP reply 46 | type HTTPStatus interface { 47 | Response() *http.Response 48 | Status() string // e.g. "200 OK" 49 | StatusCode() int // e.g. 200 50 | Body() []byte 51 | } 52 | 53 | // HTTPError retains HTTP status 54 | type HTTPError interface { 55 | error 56 | HTTPStatus 57 | } 58 | 59 | // RPCError is a Tezos RPC error as documented on http://tezos.gitlab.io/mainnet/api/errors.html. 60 | type RPCError interface { 61 | Error 62 | HTTPStatus 63 | Errors() []Error // returns all errors as a slice 64 | } 65 | 66 | // Errors is a slice of Error with custom JSON unmarshaller 67 | type Errors []Error 68 | 69 | // UnmarshalJSON implements json.Unmarshaler 70 | func (e *Errors) UnmarshalJSON(data []byte) error { 71 | var errs []*GenericError 72 | 73 | if err := json.Unmarshal(data, &errs); err != nil { 74 | return err 75 | } 76 | 77 | *e = make(Errors, len(errs)) 78 | for i, g := range errs { 79 | // TODO: handle different kinds 80 | (*e)[i] = g 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (e Errors) Error() string { 87 | if len(e) == 0 { 88 | return "" 89 | } 90 | return e[0].Error() 91 | } 92 | 93 | // ErrorID returns Tezos error id 94 | func (e Errors) ErrorID() string { 95 | if len(e) == 0 { 96 | return "" 97 | } 98 | return e[0].ErrorID() 99 | } 100 | 101 | // ErrorKind returns Tezos error kind 102 | func (e Errors) ErrorKind() string { 103 | if len(e) == 0 { 104 | return "" 105 | } 106 | return e[0].ErrorKind() 107 | } 108 | 109 | type httpError struct { 110 | response *http.Response 111 | body []byte 112 | } 113 | 114 | func (e *httpError) Error() string { 115 | return fmt.Sprintf("tezos: HTTP status %v", e.response.StatusCode) 116 | } 117 | 118 | func (e *httpError) Status() string { 119 | return e.response.Status 120 | } 121 | 122 | func (e *httpError) StatusCode() int { 123 | return e.response.StatusCode 124 | } 125 | 126 | func (e *httpError) Body() []byte { 127 | return e.body 128 | } 129 | 130 | func (e *httpError) Response() *http.Response { 131 | return e.response 132 | } 133 | 134 | type rpcError struct { 135 | *httpError 136 | errors Errors 137 | } 138 | 139 | func (e *rpcError) Error() string { 140 | return e.errors.Error() 141 | } 142 | 143 | func (e *rpcError) ErrorID() string { 144 | return e.errors.ErrorID() 145 | } 146 | 147 | func (e *rpcError) ErrorKind() string { 148 | return e.errors.ErrorKind() 149 | } 150 | 151 | func (e *rpcError) Errors() []Error { 152 | return e.errors 153 | } 154 | 155 | type plainError struct { 156 | *httpError 157 | msg string 158 | } 159 | 160 | func (e *plainError) Error() string { 161 | return e.msg 162 | } 163 | 164 | var ( 165 | _ Error = &GenericError{} 166 | _ Error = Errors{} 167 | _ RPCError = &rpcError{} 168 | ) 169 | -------------------------------------------------------------------------------- /go-tezos/fixtures/block/contract_balance.json: -------------------------------------------------------------------------------- 1 | "4700354460878" 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/block/delegate_balance.json: -------------------------------------------------------------------------------- 1 | "13490453135591" 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/block/pending_operations.json: -------------------------------------------------------------------------------- 1 | { 2 | "applied": [ 3 | { 4 | "hash": "opLHEC3xm8qPRP9g44oBpB45RzRVUoMX1NsX75sKKtNvA8pvSm2", 5 | "branch": "BMLvebSvhTyZ7GG2vykV8hpGEc8egzcwn9fc3JJKrtCk8FssT9M", 6 | "contents": [ 7 | { 8 | "kind": "endorsement", 9 | "level": 208806 10 | } 11 | ], 12 | "signature": "sigtTW5Y3xQaTKo5vEiqr8zG4YnPv7GbVbUgo7XYw7UZduz9jvdxzFbKUmftKFsFGH1UEZBbxyhyH5DLUUMh5KrQ3MENzUwC" 13 | }, 14 | { 15 | "hash": "ooSEFHRfArRSjeWhHhcmBa5aL2E3MqsN1HucCm3xiR2gLuzGSYN", 16 | "branch": "BMLvebSvhTyZ7GG2vykV8hpGEc8egzcwn9fc3JJKrtCk8FssT9M", 17 | "contents": [ 18 | { 19 | "kind": "endorsement", 20 | "level": 208806 21 | } 22 | ], 23 | "signature": "sigeVFaHCGk9S6P9MhNNyZjHMcfPgYZw5cTwejtbGDEZdp58XKcxVkP3CFCKiPHesiEDqCxvrPGHZUpQLNmmqaSgrmv1ePNZ" 24 | } 25 | ], 26 | "refused": [], 27 | "branch_refused": [], 28 | "branch_delayed": [ 29 | [ 30 | "oo1Z19oCkTWibLp7mJwFKP3UFVxuf6eV1iNWwJS7gZs8uZbrduS", 31 | { 32 | "protocol": "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", 33 | "branch": "BMTSuKyFBhgmD7e3UDt9jLtjC2ftTUosTGEiiYc61Lu6F3xSkvJ", 34 | "contents": [ 35 | { 36 | "kind": "endorsement", 37 | "level": 208804 38 | } 39 | ], 40 | "signature": "sigZXm4SGNcHwh5qsfjsFYmhSCwtimifq4EPje5rnJxvNDkymC2o3Yv8cJWgug3dDxiQWDexRDeBBu8Pf5qFxA6SckKypiau", 41 | "error": [ 42 | { 43 | "kind": "temporary", 44 | "id": "proto.002-PsYLVpVv.operation.wrong_endorsement_predecessor", 45 | "expected": "BMLvebSvhTyZ7GG2vykV8hpGEc8egzcwn9fc3JJKrtCk8FssT9M", 46 | "provided": "BMTSuKyFBhgmD7e3UDt9jLtjC2ftTUosTGEiiYc61Lu6F3xSkvJ" 47 | } 48 | ] 49 | } 50 | ], 51 | [ 52 | "ooCaHemWe76uiBLDUXY2uhbhuiyLG7w7rqUFaJPxr7v56z6DVPS", 53 | { 54 | "protocol": "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", 55 | "branch": "BL1pULCBFDJkqDHmYqK8yrVM3mHQHi72JFg6dT5qJ96ncjDbPpn", 56 | "contents": [ 57 | { 58 | "kind": "endorsement", 59 | "level": 208773 60 | } 61 | ], 62 | "signature": "sigpkWpkY25KDBo7YcaLYx5Q61ypcfFWXjXgvbMG6uFrnStboCxCoCnJbDNri7CGzad35zLUvXCVxu2uj4WBSPgfxsnGKUBn", 63 | "error": [ 64 | { 65 | "kind": "temporary", 66 | "id": "proto.002-PsYLVpVv.operation.wrong_endorsement_predecessor", 67 | "expected": "BMLvebSvhTyZ7GG2vykV8hpGEc8egzcwn9fc3JJKrtCk8FssT9M", 68 | "provided": "BL1pULCBFDJkqDHmYqK8yrVM3mHQHi72JFg6dT5qJ96ncjDbPpn" 69 | } 70 | ] 71 | } 72 | ] 73 | ], 74 | "unprocessed": [] 75 | } 76 | -------------------------------------------------------------------------------- /go-tezos/fixtures/chains/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", 3 | "chain_id": "NetXZUqeBjDnWde", 4 | "hash": "BLnoArJNPCyYFK2z3Mnomi36Jo3FwrjriJ6hvzgTJGYYDKEkDXm", 5 | "header": { 6 | "level": 219133, 7 | "proto": 1, 8 | "predecessor": "BLNWdEensT9MFq8pkDwjHfGVFsV1reYUhVcMAVzq3LCMS1WdKZ8", 9 | "timestamp": "2018-11-27T17:49:57Z", 10 | "validation_pass": 4, 11 | "operations_hash": "LLoZamNeucV8tqPAcqJQYsNEsMwnCuL1xu1kJMiGFCx9MBVCGcWJF", 12 | "fitness": [ 13 | "00", 14 | "00000000005a125f" 15 | ], 16 | "context": "CoW5zHjWVHfUAbSgzqnZ938eDXG37P9oJVn3Lb3NyQJBheUDvdVf", 17 | "priority": 0, 18 | "proof_of_work_nonce": "7d949582fe024862", 19 | "signature": "sigktdiZpdykWEjgeTB3N1qFJ5bsh3SxVNB8wc5FAutbJPG7puWQAPrxwL6BZPJVKLRj2uLnCw54Akx4KA48DS5Jg8tthCLY" 20 | }, 21 | "metadata": { 22 | "protocol": "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", 23 | "next_protocol": "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", 24 | "test_chain_status": { 25 | "status": "not_running" 26 | }, 27 | "max_operations_ttl": 60, 28 | "max_operation_data_length": 16384, 29 | "max_block_header_length": 238, 30 | "max_operation_list_length": [ 31 | { 32 | "max_size": 32768, 33 | "max_op": 32 34 | } 35 | ], 36 | "baker": "tz3gN8NTLNLJg5KRsUU47NHNVHbdhcFXjjaB", 37 | "level": { 38 | "level": 219133, 39 | "level_position": 219132, 40 | "cycle": 106, 41 | "cycle_position": 2044, 42 | "voting_period": 6, 43 | "voting_period_position": 22524, 44 | "expected_commitment": false 45 | }, 46 | "voting_period_kind": "proposal", 47 | "nonce_hash": null, 48 | "consumed_gas": "0", 49 | "deactivated": [], 50 | "balance_updates": [ 51 | { 52 | "kind": "contract", 53 | "contract": "tz3gN8NTLNLJg5KRsUU47NHNVHbdhcFXjjaB", 54 | "change": "-512000000" 55 | }, 56 | { 57 | "kind": "freezer", 58 | "category": "deposits", 59 | "delegate": "tz3gN8NTLNLJg5KRsUU47NHNVHbdhcFXjjaB", 60 | "level": 106, 61 | "change": "512000000" 62 | } 63 | ] 64 | }, 65 | "operations": [ 66 | [ 67 | { 68 | "protocol": "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", 69 | "chain_id": "NetXZUqeBjDnWde", 70 | "hash": "opEatwYFvwuUM2aEa9cUU1ofMzsi46bYwiUhPLENXpLkjpps4Xq", 71 | "branch": "BLNWdEensT9MFq8pkDwjHfGVFsV1reYUhVcMAVzq3LCMS1WdKZ8", 72 | "contents": [ 73 | { 74 | "kind": "endorsement", 75 | "level": 219132, 76 | "metadata": { 77 | "balance_updates": [ 78 | { 79 | "kind": "contract", 80 | "contract": "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", 81 | "change": "-128000000" 82 | }, 83 | { 84 | "kind": "freezer", 85 | "category": "deposits", 86 | "delegate": "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", 87 | "level": 106, 88 | "change": "128000000" 89 | }, 90 | { 91 | "kind": "freezer", 92 | "category": "rewards", 93 | "delegate": "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", 94 | "level": 106, 95 | "change": "2000000" 96 | } 97 | ], 98 | "delegate": "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", 99 | "slots": [ 100 | 18, 101 | 16 102 | ] 103 | } 104 | } 105 | ], 106 | "signature": "sigS3d9wfEFuChEqLetCxf4G8QYAjWL7ND3F8amMPVPDS2RwQqkeKU9hbrEXk7GG7U2aPcWkTA3uTdNzz4gkAb8jSy8hUc51" 107 | } 108 | ], 109 | [], 110 | [], 111 | [] 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /go-tezos/fixtures/chains/invalid_blocks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "block": "BM31cpbqfXu3WNYLQ8Tch21tXjcnwbyFzvcqohHL1BSnkhnhzwp", 4 | "level": 42, 5 | "error": [] 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /go-tezos/fixtures/empty.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecadlabs/tezos_exporter/3ead917321b564d6614d8b8288eaeb82bee9612a/go-tezos/fixtures/empty.json -------------------------------------------------------------------------------- /go-tezos/fixtures/empty_error.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/error.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": "permanent", 4 | "id": "proto.002-PsYLVpVv.context.storage_error", 5 | "missing_key": [ 6 | "contracts", 7 | "index", 8 | "ed25519", 9 | "8f", 10 | "b5", 11 | "ce", 12 | "a6", 13 | "2d", 14 | "147c696afd9a93dbce962f4c8a9c91", 15 | "balance" 16 | ], 17 | "function": "get" 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /go-tezos/fixtures/malformed_error.json: -------------------------------------------------------------------------------- 1 | , 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/monitor/bootstrapped.chunked: -------------------------------------------------------------------------------- 1 | {"block":"BLgz6z8w5bYtn2AAEmsfMD3aH9o8SUnVygUpVUsCe6dkRpEt5Qy","timestamp":"2018-09-17T00:46:12Z"} 2 | {"block":"BLc3Y6zsb7PT6QnScu8VKcUPGkCoeCLPWLVTQoQjk5QQ7pbmHs5","timestamp":"2018-09-17T00:46:42Z"} 3 | {"block":"BKiqiXgqAPHX4bRzk2p1jEKHijaxLPdcQi8hqVfGhBwngcticEk","timestamp":"2018-09-17T00:48:32Z"} 4 | -------------------------------------------------------------------------------- /go-tezos/fixtures/monitor/heads.chunked: -------------------------------------------------------------------------------- 1 | {"hash":"BKq199p1Hm1phfJ4DhuRjB6yBSJnDNG8sgMSnja9pXR96T2Hyy1","level":390397,"proto":3,"predecessor":"BKihh4Bd3nAypX5bZtYy7xoxQDRbygkoyjB9w171exm2mbXHQWj","timestamp":"2019-04-10T22:37:08Z","validation_pass":4,"operations_hash":"LLobC6LA4T2STTa3D77YDuDsrw6xEY8DakpkvR9kd7DL9HpvchUtb","fitness":["00","00000000005a125f"],"context":"CoUiJrzomxKms5eELzgpULo2iyf7dJAqW3gEBnFE7WHv3cy9pfVE","protocol_data":"000000000003bcf5f72d00320dffeb51c154077ce7dd2af6057f0370485a738345d3cb5c722db6df6ddb9b48c4e7a4282a3b994bca1cc52f6b95c889f23906e1d4e3e20203e171ff924004"} 2 | 3 | -------------------------------------------------------------------------------- /go-tezos/fixtures/monitor/mempool_operations.chunked: -------------------------------------------------------------------------------- 1 | [] 2 | [{"protocol":"Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd","branch":"BKvSZMWpcDc9RkKg11sQ5oRDyHrMDiKX5RmTdU455XnPHuYZWRS","contents":[{"kind":"endorsement","level":489922}],"signature":"sigbdfHsA4XHTB3ToUMzRRAYmSJBCvJ52jdE7SrFp7BD3jUnd9sVBdzytHKTD6ygy343jRjJvc4E8kuZRiEqUdExH333RaqP"}] 3 | [{"protocol":"Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd","branch":"BKvSZMWpcDc9RkKg11sQ5oRDyHrMDiKX5RmTdU455XnPHuYZWRS","contents":[{"kind":"endorsement","level":489922}],"signature":"sigk5ep31BR1gSFSD37aiiAbT2azciyBdBaZD8Xp4Ef1NCT37L9ggucZySHhrNEnmqKZSRq5LKq5MJDVhj4tKmP1z8GqmY5j"}] -------------------------------------------------------------------------------- /go-tezos/fixtures/network/connections.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "incoming": false, 4 | "peer_id": "idt5qvkLiJ15rb6yJU1bjpGmdyYnPJ", 5 | "id_point": { 6 | "addr": "::ffff:34.253.64.43", 7 | "port": 9732 8 | }, 9 | "remote_socket_port": 9732, 10 | "versions": [ 11 | { 12 | "name": "TEZOS_ALPHANET_2018-07-31T16:22:39Z", 13 | "major": 0, 14 | "minor": 0 15 | } 16 | ], 17 | "private": false, 18 | "local_metadata": { 19 | "disable_mempool": false, 20 | "private_node": false 21 | }, 22 | "remote_metadata": { 23 | "disable_mempool": false, 24 | "private_node": false 25 | } 26 | }, 27 | { 28 | "incoming": true, 29 | "peer_id": "ids8VJTHEuyND6B8ahGgXPAJ3BDp1c", 30 | "id_point": { 31 | "addr": "::ffff:176.31.255.202", 32 | "port": 9732 33 | }, 34 | "remote_socket_port": 9732, 35 | "versions": [ 36 | { 37 | "name": "TEZOS_ALPHANET_2018-07-31T16:22:39Z", 38 | "major": 0, 39 | "minor": 0 40 | } 41 | ], 42 | "private": true, 43 | "local_metadata": { 44 | "disable_mempool": true, 45 | "private_node": true 46 | }, 47 | "remote_metadata": { 48 | "disable_mempool": true, 49 | "private_node": true 50 | } 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/peer.json: -------------------------------------------------------------------------------- 1 | { 2 | "score": 0, 3 | "trusted": false, 4 | "conn_metadata": { 5 | "disable_mempool": false, 6 | "private_node": false 7 | }, 8 | "state": "running", 9 | "reachable_at": { 10 | "addr": "::ffff:104.248.233.63", 11 | "port": 9732 12 | }, 13 | "stat": { 14 | "total_sent": "1196571", 15 | "total_recv": "1302211", 16 | "current_inflow": 0, 17 | "current_outflow": 1 18 | }, 19 | "last_established_connection": [ 20 | { 21 | "addr": "::ffff:104.248.233.63", 22 | "port": 9732 23 | }, 24 | "2018-11-14T11:47:07Z" 25 | ], 26 | "last_disconnection": [ 27 | { 28 | "addr": "::ffff:104.248.233.63", 29 | "port": 9732 30 | }, 31 | "2018-11-14T11:44:57Z" 32 | ], 33 | "last_seen": [ 34 | { 35 | "addr": "::ffff:104.248.233.63", 36 | "port": 9732 37 | }, 38 | "2018-11-14T11:47:07Z" 39 | ], 40 | "last_miss": [ 41 | { 42 | "addr": "::ffff:104.248.233.63", 43 | "port": 9732 44 | }, 45 | "2018-11-14T11:44:57Z" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/peer_log.chunked: -------------------------------------------------------------------------------- 1 | [{"kind":"incoming_request","timestamp":"2018-11-13T15:20:14Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"connection_established","timestamp":"2018-11-13T15:20:14Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"external_disconnection","timestamp":"2018-11-13T16:30:08Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"incoming_request","timestamp":"2018-11-13T16:39:20Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"connection_established","timestamp":"2018-11-13T16:39:20Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"external_disconnection","timestamp":"2018-11-13T19:48:58Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"incoming_request","timestamp":"2018-11-13T20:56:30Z","addr":"::ffff:51.15.242.114","port":9732},{"kind":"connection_established","timestamp":"2018-11-13T20:56:30Z","addr":"::ffff:51.15.242.114","port":9732}] 2 | [{"kind":"external_disconnection","timestamp":"2018-11-13T22:25:07Z","addr":"::ffff:51.15.242.114","port":9732}] 3 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/peer_log.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": "incoming_request", 4 | "timestamp": "2018-11-13T15:35:17Z", 5 | "addr": "::ffff:13.81.43.51", 6 | "port": 9732 7 | }, 8 | { 9 | "kind": "connection_established", 10 | "timestamp": "2018-11-13T15:35:19Z", 11 | "addr": "::ffff:13.81.43.51", 12 | "port": 9732 13 | }, 14 | { 15 | "kind": "external_disconnection", 16 | "timestamp": "2018-11-13T18:02:51Z", 17 | "addr": "::ffff:13.81.43.51", 18 | "port": 9732 19 | }, 20 | { 21 | "kind": "incoming_request", 22 | "timestamp": "2018-11-13T20:56:14Z", 23 | "addr": "::ffff:13.81.43.51", 24 | "port": 9732 25 | }, 26 | { 27 | "kind": "connection_established", 28 | "timestamp": "2018-11-13T20:56:15Z", 29 | "addr": "::ffff:13.81.43.51", 30 | "port": 9732 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/peers.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "idrnHcGMrFxiYsmxf5Cqd6NhUTUU8X", 4 | { 5 | "score": 0, 6 | "trusted": false, 7 | "conn_metadata": { 8 | "disable_mempool": false, 9 | "private_node": false 10 | }, 11 | "state": "running", 12 | "reachable_at": { 13 | "addr": "::ffff:45.79.146.133", 14 | "port": 39732 15 | }, 16 | "stat": { 17 | "total_sent": "4908012", 18 | "total_recv": "14560268", 19 | "current_inflow": 66, 20 | "current_outflow": 177 21 | }, 22 | "last_rejected_connection": [ 23 | { 24 | "addr": "::ffff:45.79.146.133", 25 | "port": 39732 26 | }, 27 | "2018-11-13T15:22:41Z" 28 | ], 29 | "last_established_connection": [ 30 | { 31 | "addr": "::ffff:45.79.146.133", 32 | "port": 39732 33 | }, 34 | "2018-11-13T20:56:14Z" 35 | ], 36 | "last_disconnection": [ 37 | { 38 | "addr": "::ffff:45.79.146.133", 39 | "port": 39732 40 | }, 41 | "2018-11-13T18:04:12Z" 42 | ], 43 | "last_seen": [ 44 | { 45 | "addr": "::ffff:45.79.146.133", 46 | "port": 39732 47 | }, 48 | "2018-11-13T20:56:14Z" 49 | ], 50 | "last_miss": [ 51 | { 52 | "addr": "::ffff:45.79.146.133", 53 | "port": 39732 54 | }, 55 | "2018-11-13T18:04:12Z" 56 | ] 57 | } 58 | ], 59 | [ 60 | "idsXeq1zboupwXXDdDDiWhBjimeJe3", 61 | { 62 | "score": 0, 63 | "trusted": false, 64 | "state": "disconnected", 65 | "stat": { 66 | "total_sent": "0", 67 | "total_recv": "0", 68 | "current_inflow": 0, 69 | "current_outflow": 0 70 | }, 71 | "last_established_connection": [ 72 | { 73 | "addr": "::ffff:104.155.17.238", 74 | "port": 9732 75 | }, 76 | "2018-11-13T17:57:18Z" 77 | ], 78 | "last_disconnection": [ 79 | { 80 | "addr": "::ffff:104.155.17.238", 81 | "port": 9732 82 | }, 83 | "2018-11-13T19:48:57Z" 84 | ], 85 | "last_seen": [ 86 | { 87 | "addr": "::ffff:104.155.17.238", 88 | "port": 9732 89 | }, 90 | "2018-11-13T19:48:57Z" 91 | ], 92 | "last_miss": [ 93 | { 94 | "addr": "::ffff:104.155.17.238", 95 | "port": 9732 96 | }, 97 | "2018-11-13T19:48:57Z" 98 | ] 99 | } 100 | ] 101 | ] 102 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/point.json: -------------------------------------------------------------------------------- 1 | { 2 | "trusted": false, 3 | "greylisted_until": "2018-11-14T16:24:57Z", 4 | "state": { 5 | "event_kind": "running", 6 | "p2p_peer_id": "ids496Ey2BKHVJYZdsk72XCwbZteTj" 7 | }, 8 | "p2p_peer_id": "ids496Ey2BKHVJYZdsk72XCwbZteTj", 9 | "last_failed_connection": "2018-11-14T11:47:13Z", 10 | "last_rejected_connection": [ 11 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 12 | "2018-11-14T12:03:11Z" 13 | ], 14 | "last_established_connection": [ 15 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 16 | "2018-11-14T16:48:56Z" 17 | ], 18 | "last_disconnection": [ 19 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 20 | "2018-11-14T16:23:57Z" 21 | ], 22 | "last_seen": [ 23 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 24 | "2018-11-14T16:48:56Z" 25 | ], 26 | "last_miss": "2018-11-14T16:23:57Z" 27 | } 28 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/point_log.chunked: -------------------------------------------------------------------------------- 1 | [{"kind":{"event_kind":"outgoing_request"},"timestamp":"2018-11-15T18:00:39Z"},{"kind":{"event_kind":"request_rejected"},"timestamp":"2018-11-15T18:00:49Z"}] 2 | [{"kind":{"event_kind":"outgoing_request"},"timestamp":"2018-11-15T18:16:18Z"}] 3 | [{"kind":{"event_kind":"request_rejected"},"timestamp":"2018-11-15T18:16:28Z"}] 4 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/point_log.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": { 4 | "event_kind": "outgoing_request" 5 | }, 6 | "timestamp": "2018-11-15T17:56:18Z" 7 | }, 8 | { 9 | "kind": { 10 | "event_kind": "accepting_request", 11 | "p2p_peer_id": "idrBJarh4t32gN9s52kxMWmeSi76Jk" 12 | }, 13 | "timestamp": "2018-11-15T17:56:19Z" 14 | }, 15 | { 16 | "kind": { 17 | "event_kind": "rejecting_request", 18 | "p2p_peer_id": "idrBJarh4t32gN9s52kxMWmeSi76Jk" 19 | }, 20 | "timestamp": "2018-11-15T17:56:19Z" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/points.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "73.247.92.150:9732", 4 | { 5 | "trusted": false, 6 | "greylisted_until": "2018-11-14T19:01:28Z", 7 | "state": { 8 | "event_kind": "disconnected" 9 | }, 10 | "last_failed_connection": "2018-11-14T19:01:16Z", 11 | "last_miss": "2018-11-14T19:01:16Z" 12 | } 13 | ], 14 | [ 15 | "40.119.159.28:9732", 16 | { 17 | "trusted": false, 18 | "greylisted_until": "2018-11-14T16:24:57Z", 19 | "state": { 20 | "event_kind": "running", 21 | "p2p_peer_id": "ids496Ey2BKHVJYZdsk72XCwbZteTj" 22 | }, 23 | "p2p_peer_id": "ids496Ey2BKHVJYZdsk72XCwbZteTj", 24 | "last_failed_connection": "2018-11-14T11:47:13Z", 25 | "last_rejected_connection": [ 26 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 27 | "2018-11-14T12:03:11Z" 28 | ], 29 | "last_established_connection": [ 30 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 31 | "2018-11-14T16:48:56Z" 32 | ], 33 | "last_disconnection": [ 34 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 35 | "2018-11-14T16:23:57Z" 36 | ], 37 | "last_seen": [ 38 | "ids496Ey2BKHVJYZdsk72XCwbZteTj", 39 | "2018-11-14T16:48:56Z" 40 | ], 41 | "last_miss": "2018-11-14T16:23:57Z" 42 | } 43 | ] 44 | ] 45 | -------------------------------------------------------------------------------- /go-tezos/fixtures/network/stat.json: -------------------------------------------------------------------------------- 1 | {"total_sent":"291690080","total_recv":"532639553","current_inflow":23596,"current_outflow":14972} 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/ballot_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "pkh": "tz3e75hU4EhDU3ukyJueh5v6UvEHzGwkg3yC", "ballot": "yay" }, 3 | { "pkh": "tz1iEWcNL383qiDJ3Q3qt5W2T4aSKUbEU4An", "ballot": "nay" }, 4 | { "pkh": "tz3bvNMQ95vfAYtG8193ymshqjSvmxiCUuR5", "ballot": "pass" } 5 | ] 6 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/ballots.json: -------------------------------------------------------------------------------- 1 | { "yay": 26776, "nay": 11, "pass": 19538 } 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/current_period_kind.json: -------------------------------------------------------------------------------- 1 | "testing_vote" 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/current_proposal.json: -------------------------------------------------------------------------------- 1 | "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd" 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/current_quorum.json: -------------------------------------------------------------------------------- 1 | 8000 2 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/listings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "pkh": "tz1KfCukgwoU32Z4or88467mMM3in5smtv8k", "rolls": 5 }, 3 | { "pkh": "tz1KfEsrtDaA1sX7vdM4qmEPWuSytuqCDp5j", "rolls": 307 } 4 | ] 5 | -------------------------------------------------------------------------------- /go-tezos/fixtures/votes/proposals.json: -------------------------------------------------------------------------------- 1 | [["Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", 11]] 2 | -------------------------------------------------------------------------------- /go-tezos/operations.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | ) 7 | 8 | // OperationElem must be implemented by all operation elements 9 | type OperationElem interface { 10 | OperationElemKind() string 11 | } 12 | 13 | // BalanceUpdatesOperation is implemented by operations providing balance updates 14 | type BalanceUpdatesOperation interface { 15 | BalanceUpdates() BalanceUpdates 16 | } 17 | 18 | // OperationWithFee is implemented by operations with fees 19 | type OperationWithFee interface { 20 | OperationFee() *big.Int 21 | } 22 | 23 | // GenericOperationElem is a most generic element type 24 | type GenericOperationElem struct { 25 | Kind string `json:"kind" yaml:"kind"` 26 | } 27 | 28 | // OperationElemKind implements OperationElem 29 | func (e *GenericOperationElem) OperationElemKind() string { 30 | return e.Kind 31 | } 32 | 33 | // OperationElements is a slice of OperationElem with custom JSON unmarshaller 34 | type OperationElements []OperationElem 35 | 36 | // UnmarshalJSON implements json.Unmarshaler 37 | func (e *OperationElements) UnmarshalJSON(data []byte) error { 38 | var raw []json.RawMessage 39 | 40 | if err := json.Unmarshal(data, &raw); err != nil { 41 | return err 42 | } 43 | 44 | *e = make(OperationElements, len(raw)) 45 | 46 | opLoop: 47 | for i, r := range raw { 48 | var tmp GenericOperationElem 49 | if err := json.Unmarshal(r, &tmp); err != nil { 50 | return err 51 | } 52 | 53 | switch tmp.Kind { 54 | case "endorsement": 55 | (*e)[i] = &EndorsementOperationElem{} 56 | case "endorsement_with_slot": 57 | (*e)[i] = &EndorsementWithSlotOperationElem{} 58 | case "transaction": 59 | (*e)[i] = &TransactionOperationElem{} 60 | case "ballot": 61 | (*e)[i] = &BallotOperationElem{} 62 | case "proposals": 63 | (*e)[i] = &ProposalOperationElem{} 64 | case "seed_nonce_revelation": 65 | (*e)[i] = &SeedNonceRevelationOperationElem{} 66 | case "double_endorsement_evidence": 67 | (*e)[i] = &DoubleEndorsementEvidenceOperationElem{} 68 | case "double_baking_evidence": 69 | (*e)[i] = &DoubleBakingEvidenceOperationElem{} 70 | case "activate_account": 71 | (*e)[i] = &ActivateAccountOperationElem{} 72 | case "reveal": 73 | (*e)[i] = &RevealOperationElem{} 74 | case "origination": 75 | (*e)[i] = &OriginationOperationElem{} 76 | case "delegation": 77 | (*e)[i] = &DelegationOperationElem{} 78 | default: 79 | (*e)[i] = &tmp 80 | continue opLoop 81 | } 82 | 83 | if err := json.Unmarshal(r, (*e)[i]); err != nil { 84 | return err 85 | } 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // EndorsementOperationElem represents an endorsement operation 92 | type EndorsementOperationElem struct { 93 | GenericOperationElem `yaml:",inline"` 94 | Level int `json:"level" yaml:"level"` 95 | Metadata EndorsementOperationMetadata `json:"metadata" yaml:"metadata"` 96 | } 97 | 98 | // EndorsementOperationElem represents an endorsement_with_slot operation that was introduced in Edo 99 | type EndorsementWithSlotOperationElem struct { 100 | GenericOperationElem `yaml:",inline"` 101 | Level int `json:"level" yaml:"level"` 102 | } 103 | 104 | // BalanceUpdates implements BalanceUpdateOperation 105 | func (el *EndorsementOperationElem) BalanceUpdates() BalanceUpdates { 106 | return el.Metadata.BalanceUpdates 107 | } 108 | 109 | // EndorsementOperationMetadata represents an endorsement operation metadata 110 | type EndorsementOperationMetadata struct { 111 | BalanceUpdates BalanceUpdates `json:"balance_updates" yaml:"balance_updates"` 112 | Delegate string `json:"delegate" yaml:"delegate"` 113 | Slots []int `json:"slots" yaml:"slots,flow"` 114 | } 115 | 116 | // TransactionOperationElem represents a transaction operation 117 | type TransactionOperationElem struct { 118 | GenericOperationElem `yaml:",inline"` 119 | Source string `json:"source" yaml:"source"` 120 | Fee *BigInt `json:"fee" yaml:"fee"` 121 | Counter *BigInt `json:"counter" yaml:"counter"` 122 | GasLimit *BigInt `json:"gas_limit" yaml:"gas_limit"` 123 | StorageLimit *BigInt `json:"storage_limit" yaml:"storage_limit"` 124 | Amount *BigInt `json:"amount" yaml:"amount"` 125 | Destination string `json:"destination" yaml:"destination"` 126 | Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` 127 | Metadata TransactionOperationMetadata `json:"metadata" yaml:"metadata"` 128 | } 129 | 130 | // BalanceUpdates implements BalanceUpdateOperation 131 | func (el *TransactionOperationElem) BalanceUpdates() BalanceUpdates { 132 | return el.Metadata.BalanceUpdates 133 | } 134 | 135 | // OperationFee implements OperationWithFee 136 | func (el *TransactionOperationElem) OperationFee() *big.Int { 137 | if el.Fee != nil { 138 | return &el.Fee.Int 139 | } 140 | return big.NewInt(0) 141 | } 142 | 143 | // TransactionOperationMetadata represents a transaction operation metadata 144 | type TransactionOperationMetadata struct { 145 | BalanceUpdates BalanceUpdates `json:"balance_updates" yaml:"balance_updates"` 146 | OperationResult TransactionOperationResult `json:"operation_result" yaml:"operation_result"` 147 | } 148 | 149 | // TransactionOperationResult represents a transaction operation result 150 | type TransactionOperationResult struct { 151 | Status string `json:"status" yaml:"status"` 152 | Storage map[string]interface{} `json:"storage,omitempty" yaml:"storage,omitempty"` 153 | BalanceUpdates BalanceUpdates `json:"balance_updates,omitempty" yaml:"balance_updates,omitempty"` 154 | OriginatedContracts []string `json:"originated_contracts,omitempty" yaml:"originated_contracts,omitempty"` 155 | ConsumedGas *BigInt `json:"consumed_gas,omitempty" yaml:"consumed_gas,omitempty"` 156 | StorageSize *BigInt `json:"storage_size,omitempty" yaml:"storage_size,omitempty"` 157 | PaidStorageSizeDiff *BigInt `json:"paid_storage_size_diff,omitempty" yaml:"paid_storage_size_diff,omitempty"` 158 | Errors Errors `json:"errors,omitempty" yaml:"errors,omitempty"` 159 | } 160 | 161 | // BallotOperationElem represents a ballot operation 162 | type BallotOperationElem struct { 163 | GenericOperationElem `yaml:",inline"` 164 | Source string `json:"source" yaml:"source"` 165 | Period int `json:"period" yaml:"period"` 166 | Proposal string `json:"proposal" yaml:"proposal"` 167 | Ballot string `json:"ballot" yaml:"ballot"` 168 | Metadata map[string]interface{} `json:"metadata" yaml:"metadata"` 169 | } 170 | 171 | // ProposalOperationElem represents a proposal operation 172 | type ProposalOperationElem struct { 173 | GenericOperationElem `yaml:",inline"` 174 | Source string `json:"source" yaml:"source"` 175 | Period int `json:"period" yaml:"period"` 176 | Proposals []string `json:"proposals" yaml:"proposals"` 177 | Metadata map[string]interface{} `json:"metadata" yaml:"metadata"` 178 | } 179 | 180 | // SeedNonceRevelationOperationElem represents seed_nonce_revelation operation 181 | type SeedNonceRevelationOperationElem struct { 182 | GenericOperationElem `yaml:",inline"` 183 | Level int32 `json:"level" yaml:"level"` 184 | Nonce string `json:"nonce" yaml:"nonce"` 185 | Metadata BalanceUpdatesOperationMetadata `json:"metadata" yaml:"metadata"` 186 | } 187 | 188 | // BalanceUpdates implements BalanceUpdateOperation 189 | func (el *SeedNonceRevelationOperationElem) BalanceUpdates() BalanceUpdates { 190 | return el.Metadata.BalanceUpdates 191 | } 192 | 193 | // BalanceUpdatesOperationMetadata contains balance updates only 194 | type BalanceUpdatesOperationMetadata struct { 195 | BalanceUpdates BalanceUpdates `json:"balance_updates" yaml:"balance_updates"` 196 | } 197 | 198 | // InlinedEndorsement corresponds to $inlined.endorsement 199 | type InlinedEndorsement struct { 200 | Branch string `json:"branch" yaml:"branch"` 201 | Operations InlinedEndorsementContents `json:"operations" yaml:"operations"` 202 | Signature string `json:"signature" yaml:"signature"` 203 | } 204 | 205 | // InlinedEndorsementContents corresponds to $inlined.endorsement.contents 206 | type InlinedEndorsementContents struct { 207 | Kind string `json:"endorsement" yaml:"endorsement"` 208 | Level int `json:"level" yaml:"level"` 209 | } 210 | 211 | // DoubleEndorsementEvidenceOperationElem represents double_endorsement_evidence operation 212 | type DoubleEndorsementEvidenceOperationElem struct { 213 | GenericOperationElem `yaml:",inline"` 214 | Operation1 InlinedEndorsement `json:"op1" yaml:"op1"` 215 | Operation2 InlinedEndorsement `json:"op2" yaml:"op2"` 216 | Metadata BalanceUpdatesOperationMetadata `json:"metadata" yaml:"metadata"` 217 | } 218 | 219 | // BalanceUpdates implements BalanceUpdateOperation 220 | func (el *DoubleEndorsementEvidenceOperationElem) BalanceUpdates() BalanceUpdates { 221 | return el.Metadata.BalanceUpdates 222 | } 223 | 224 | // DoubleBakingEvidenceOperationElem represents double_baking_evidence operation 225 | type DoubleBakingEvidenceOperationElem struct { 226 | GenericOperationElem `yaml:",inline"` 227 | BlockHeader1 RawBlockHeader `json:"bh1" yaml:"bh1"` 228 | BlockHeader2 RawBlockHeader `json:"bh2" yaml:"bh2"` 229 | Metadata BalanceUpdatesOperationMetadata `json:"metadata" yaml:"metadata"` 230 | } 231 | 232 | // BalanceUpdates implements BalanceUpdateOperation 233 | func (el *DoubleBakingEvidenceOperationElem) BalanceUpdates() BalanceUpdates { 234 | return el.Metadata.BalanceUpdates 235 | } 236 | 237 | // ActivateAccountOperationElem represents activate_account operation 238 | type ActivateAccountOperationElem struct { 239 | GenericOperationElem `yaml:",inline"` 240 | PKH string `json:"pkh" yaml:"pkh"` 241 | Secret string `json:"secret" yaml:"secret"` 242 | Metadata BalanceUpdatesOperationMetadata `json:"metadata" yaml:"metadata"` 243 | } 244 | 245 | // BalanceUpdates implements BalanceUpdateOperation 246 | func (el *ActivateAccountOperationElem) BalanceUpdates() BalanceUpdates { 247 | return el.Metadata.BalanceUpdates 248 | } 249 | 250 | // RevealOperationElem represents a reveal operation 251 | type RevealOperationElem struct { 252 | GenericOperationElem `yaml:",inline"` 253 | Source string `json:"source" yaml:"source"` 254 | Fee *BigInt `json:"fee" yaml:"fee"` 255 | Counter *BigInt `json:"counter" yaml:"counter"` 256 | GasLimit *BigInt `json:"gas_limit" yaml:"gas_limit"` 257 | StorageLimit *BigInt `json:"storage_limit" yaml:"storage_limit"` 258 | PublicKey string `json:"public_key" yaml:"public_key"` 259 | Metadata RevealOperationMetadata `json:"metadata" yaml:"metadata"` 260 | } 261 | 262 | // OperationFee implements OperationWithFee 263 | func (el *RevealOperationElem) OperationFee() *big.Int { 264 | if el.Fee != nil { 265 | return &el.Fee.Int 266 | } 267 | return big.NewInt(0) 268 | } 269 | 270 | // BalanceUpdates implements BalanceUpdateOperation 271 | func (el *RevealOperationElem) BalanceUpdates() BalanceUpdates { 272 | return el.Metadata.BalanceUpdates 273 | } 274 | 275 | // RevealOperationMetadata represents a reveal operation metadata 276 | type RevealOperationMetadata DelegationOperationMetadata 277 | 278 | // OriginationOperationElem represents a origination operation 279 | type OriginationOperationElem struct { 280 | GenericOperationElem `yaml:",inline"` 281 | Source string `json:"source" yaml:"source"` 282 | Fee *BigInt `json:"fee" yaml:"fee"` 283 | Counter *BigInt `json:"counter" yaml:"counter"` 284 | GasLimit *BigInt `json:"gas_limit" yaml:"gas_limit"` 285 | StorageLimit *BigInt `json:"storage_limit" yaml:"storage_limit"` 286 | ManagerPubKey string `json:"managerPubkey" yaml:"managerPubkey"` 287 | Balance *BigInt `json:"balance" yaml:"balance"` 288 | Spendable *bool `json:"spendable,omitempty" yaml:"spendable,omitempty"` 289 | Delegatable *bool `json:"delegatable,omitempty" yaml:"delegatable,omitempty"` 290 | Delegate string `json:"delegate,omitempty" yaml:"delegate,omitempty"` 291 | Script *ScriptedContracts `json:"script,omitempty" yaml:"script,omitempty"` 292 | Metadata OriginationOperationMetadata `json:"metadata" yaml:"metadata"` 293 | } 294 | 295 | // OperationFee implements OperationWithFee 296 | func (el *OriginationOperationElem) OperationFee() *big.Int { 297 | if el.Fee != nil { 298 | return &el.Fee.Int 299 | } 300 | return big.NewInt(0) 301 | } 302 | 303 | // BalanceUpdates implements BalanceUpdateOperation 304 | func (el *OriginationOperationElem) BalanceUpdates() BalanceUpdates { 305 | return el.Metadata.BalanceUpdates 306 | } 307 | 308 | // ScriptedContracts corresponds to $scripted.contracts 309 | type ScriptedContracts struct { 310 | Code map[string]interface{} `json:"code" yaml:"code"` 311 | Storage map[string]interface{} `json:"storage" yaml:"storage"` 312 | } 313 | 314 | // OriginationOperationMetadata represents a origination operation metadata 315 | type OriginationOperationMetadata struct { 316 | BalanceUpdates BalanceUpdates `json:"balance_updates" yaml:"balance_updates"` 317 | OperationResult OriginationOperationResult `json:"operation_result" yaml:"operation_result"` 318 | } 319 | 320 | // OriginationOperationResult represents a origination operation result 321 | type OriginationOperationResult struct { 322 | Status string `json:"status" yaml:"status"` 323 | BalanceUpdates BalanceUpdates `json:"balance_updates,omitempty" yaml:"balance_updates,omitempty"` 324 | OriginatedContracts []string `json:"originated_contracts,omitempty" yaml:"originated_contracts,omitempty"` 325 | ConsumedGas *BigInt `json:"consumed_gas,omitempty" yaml:"consumed_gas,omitempty"` 326 | StorageSize *BigInt `json:"storage_size,omitempty" yaml:"storage_size,omitempty"` 327 | PaidStorageSizeDiff *BigInt `json:"paid_storage_size_diff,omitempty" yaml:"paid_storage_size_diff,omitempty"` 328 | Errors Errors `json:"errors,omitempty" yaml:"errors,omitempty"` 329 | } 330 | 331 | // DelegationOperationElem represents a delegation operation 332 | type DelegationOperationElem struct { 333 | GenericOperationElem `yaml:",inline"` 334 | Source string `json:"source" yaml:"source"` 335 | Fee *BigInt `json:"fee" yaml:"fee"` 336 | Counter *BigInt `json:"counter" yaml:"counter"` 337 | GasLimit *BigInt `json:"gas_limit" yaml:"gas_limit"` 338 | StorageLimit *BigInt `json:"storage_limit" yaml:"storage_limit"` 339 | ManagerPubKey string `json:"managerPubkey" yaml:"managerPubkey"` 340 | Balance *BigInt `json:"balance" yaml:"balance"` 341 | Spendable *bool `json:"spendable,omitempty" yaml:"spendable,omitempty"` 342 | Delegatable *bool `json:"delegatable,omitempty" yaml:"delegatable,omitempty"` 343 | Delegate string `json:"delegate,omitempty" yaml:"delegate,omitempty"` 344 | Script *ScriptedContracts `json:"script,omitempty" yaml:"script,omitempty"` 345 | Metadata DelegationOperationMetadata `json:"metadata" yaml:"metadata"` 346 | } 347 | 348 | // OperationFee implements OperationWithFee 349 | func (el *DelegationOperationElem) OperationFee() *big.Int { 350 | if el.Fee != nil { 351 | return &el.Fee.Int 352 | } 353 | return big.NewInt(0) 354 | } 355 | 356 | // BalanceUpdates implements BalanceUpdateOperation 357 | func (el *DelegationOperationElem) BalanceUpdates() BalanceUpdates { 358 | return el.Metadata.BalanceUpdates 359 | } 360 | 361 | // DelegationOperationMetadata represents a delegation operation metadata 362 | type DelegationOperationMetadata struct { 363 | BalanceUpdates BalanceUpdates `json:"balance_updates" yaml:"balance_updates"` 364 | OperationResult DelegationOperationResult `json:"operation_result" yaml:"operation_result"` 365 | } 366 | 367 | // DelegationOperationResult represents a delegation operation result 368 | type DelegationOperationResult struct { 369 | Status string `json:"status" yaml:"status"` 370 | Errors Errors `json:"errors" yaml:"errors"` 371 | } 372 | 373 | // BalanceUpdate is a variable structure depending on the Kind field 374 | type BalanceUpdate interface { 375 | BalanceUpdateKind() string 376 | } 377 | 378 | // GenericBalanceUpdate holds the common values among all BalanceUpdatesType variants 379 | type GenericBalanceUpdate struct { 380 | Kind string `json:"kind" yaml:"kind"` 381 | Change int64 `json:"change,string" yaml:"change"` 382 | } 383 | 384 | // BalanceUpdateKind returns the BalanceUpdateType's Kind field 385 | func (g *GenericBalanceUpdate) BalanceUpdateKind() string { 386 | return g.Kind 387 | } 388 | 389 | // ContractBalanceUpdate is a BalanceUpdatesType variant for Kind=contract 390 | type ContractBalanceUpdate struct { 391 | GenericBalanceUpdate `yaml:",inline"` 392 | Contract string `json:"contract" yaml:"contract"` 393 | } 394 | 395 | // FreezerBalanceUpdate is a BalanceUpdatesType variant for Kind=freezer 396 | type FreezerBalanceUpdate struct { 397 | GenericBalanceUpdate `yaml:",inline"` 398 | Category string `json:"category" yaml:"category"` 399 | Delegate string `json:"delegate" yaml:"delegate"` 400 | Level int `json:"level" yaml:"level"` 401 | } 402 | 403 | // BalanceUpdates is a list of balance update operations 404 | type BalanceUpdates []BalanceUpdate 405 | 406 | // UnmarshalJSON implements json.Unmarshaler 407 | func (b *BalanceUpdates) UnmarshalJSON(data []byte) error { 408 | var raw []json.RawMessage 409 | 410 | if err := json.Unmarshal(data, &raw); err != nil { 411 | return err 412 | } 413 | 414 | *b = make(BalanceUpdates, len(raw)) 415 | 416 | opLoop: 417 | for i, r := range raw { 418 | var tmp GenericBalanceUpdate 419 | if err := json.Unmarshal(r, &tmp); err != nil { 420 | return err 421 | } 422 | 423 | switch tmp.Kind { 424 | case "contract": 425 | (*b)[i] = &ContractBalanceUpdate{} 426 | 427 | case "freezer": 428 | (*b)[i] = &FreezerBalanceUpdate{} 429 | 430 | default: 431 | (*b)[i] = &tmp 432 | continue opLoop 433 | } 434 | 435 | if err := json.Unmarshal(r, (*b)[i]); err != nil { 436 | return err 437 | } 438 | } 439 | 440 | return nil 441 | } 442 | 443 | // Operation represents an operation included into block 444 | type Operation struct { 445 | Protocol string `json:"protocol" yaml:"protocol"` 446 | ChainID string `json:"chain_id" yaml:"chain_id"` 447 | Hash string `json:"hash" yaml:"hash"` 448 | Branch string `json:"branch" yaml:"branch"` 449 | Contents OperationElements `json:"contents" yaml:"contents"` 450 | Signature string `json:"signature" yaml:"signature"` 451 | } 452 | 453 | /* 454 | OperationAlt is a heterogeneously encoded Operation with hash as a first array member, i.e. 455 | [ 456 | "...", // hash 457 | { 458 | "protocol": "...", 459 | ... 460 | } 461 | ] 462 | instead of 463 | { 464 | "protocol": "...", 465 | "hash": "...", 466 | ... 467 | } 468 | */ 469 | type OperationAlt Operation 470 | 471 | // UnmarshalJSON implements json.Unmarshaler 472 | func (o *OperationAlt) UnmarshalJSON(data []byte) error { 473 | return unmarshalHeterogeneousJSONArray(data, &o.Hash, (*Operation)(o)) 474 | } 475 | 476 | // OperationWithError represents unsuccessful operation 477 | type OperationWithError struct { 478 | Operation 479 | Error Errors `json:"error" yaml:"error"` 480 | } 481 | 482 | // OperationWithErrorAlt is a heterogeneously encoded OperationWithError with hash as a first array member. 483 | // See OperationAlt for details 484 | type OperationWithErrorAlt OperationWithError 485 | 486 | // UnmarshalJSON implements json.Unmarshaler 487 | func (o *OperationWithErrorAlt) UnmarshalJSON(data []byte) error { 488 | return unmarshalHeterogeneousJSONArray(data, &o.Hash, (*OperationWithError)(o)) 489 | } 490 | 491 | var ( 492 | _ BalanceUpdatesOperation = &EndorsementOperationElem{} 493 | _ BalanceUpdatesOperation = &TransactionOperationElem{} 494 | _ BalanceUpdatesOperation = &SeedNonceRevelationOperationElem{} 495 | _ BalanceUpdatesOperation = &DoubleEndorsementEvidenceOperationElem{} 496 | _ BalanceUpdatesOperation = &DoubleBakingEvidenceOperationElem{} 497 | _ BalanceUpdatesOperation = &ActivateAccountOperationElem{} 498 | _ BalanceUpdatesOperation = &RevealOperationElem{} 499 | _ BalanceUpdatesOperation = &OriginationOperationElem{} 500 | _ BalanceUpdatesOperation = &DelegationOperationElem{} 501 | 502 | _ OperationWithFee = &TransactionOperationElem{} 503 | _ OperationWithFee = &RevealOperationElem{} 504 | _ OperationWithFee = &OriginationOperationElem{} 505 | _ OperationWithFee = &DelegationOperationElem{} 506 | ) 507 | -------------------------------------------------------------------------------- /go-tezos/service.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | "net/http" 10 | "net/url" 11 | "time" 12 | 13 | "gopkg.in/yaml.v3" 14 | ) 15 | 16 | // Service implements fetching of information from Tezos nodes via JSON. 17 | type Service struct { 18 | Client *RPCClient 19 | } 20 | 21 | // NetworkStats models global network bandwidth totals and usage in B/s. 22 | type NetworkStats struct { 23 | TotalBytesSent int64 `json:"total_sent,string"` 24 | TotalBytesRecv int64 `json:"total_recv,string"` 25 | CurrentInflow int64 `json:"current_inflow"` 26 | CurrentOutflow int64 `json:"current_outflow"` 27 | } 28 | 29 | // NetworkConnection models detailed information for one network connection. 30 | type NetworkConnection struct { 31 | Incoming bool `json:"incoming"` 32 | PeerID string `json:"peer_id"` 33 | IDPoint NetworkAddress `json:"id_point"` 34 | RemoteSocketPort uint16 `json:"remote_socket_port"` 35 | Versions []*NetworkVersion `json:"versions"` 36 | Private bool `json:"private"` 37 | LocalMetadata NetworkMetadata `json:"local_metadata"` 38 | RemoteMetadata NetworkMetadata `json:"remote_metadata"` 39 | } 40 | 41 | // NetworkAddress models a point's address and port. 42 | type NetworkAddress struct { 43 | Addr string `json:"addr"` 44 | Port uint16 `json:"port"` 45 | } 46 | 47 | // NetworkVersion models a network-layer version of a node. 48 | type NetworkVersion struct { 49 | Name string `json:"name"` 50 | Major uint16 `json:"major"` 51 | Minor uint16 `json:"minor"` 52 | } 53 | 54 | // NetworkMetadata models metadata of a node. 55 | type NetworkMetadata struct { 56 | DisableMempool bool `json:"disable_mempool"` 57 | PrivateNode bool `json:"private_node"` 58 | } 59 | 60 | // BootstrappedBlock represents bootstrapped block stream message 61 | type BootstrappedBlock struct { 62 | Block string `json:"block"` 63 | Timestamp time.Time `json:"timestamp"` 64 | } 65 | 66 | // NetworkConnectionTimestamp represents peer address with timestamp added 67 | type NetworkConnectionTimestamp struct { 68 | NetworkAddress 69 | Timestamp time.Time 70 | } 71 | 72 | // UnmarshalJSON implements json.Unmarshaler 73 | func (n *NetworkConnectionTimestamp) UnmarshalJSON(data []byte) error { 74 | return unmarshalHeterogeneousJSONArray(data, &n.NetworkAddress, &n.Timestamp) 75 | } 76 | 77 | // NetworkPeer represents peer info 78 | type NetworkPeer struct { 79 | PeerID string `json:"-"` 80 | Score int64 `json:"score"` 81 | Trusted bool `json:"trusted"` 82 | ConnMetadata *NetworkMetadata `json:"conn_metadata"` 83 | State string `json:"state"` 84 | ReachableAt *NetworkAddress `json:"reachable_at"` 85 | Stat NetworkStats `json:"stat"` 86 | LastEstablishedConnection *NetworkConnectionTimestamp `json:"last_established_connection"` 87 | LastSeen *NetworkConnectionTimestamp `json:"last_seen"` 88 | LastFailedConnection *NetworkConnectionTimestamp `json:"last_failed_connection"` 89 | LastRejectedConnection *NetworkConnectionTimestamp `json:"last_rejected_connection"` 90 | LastDisconnection *NetworkConnectionTimestamp `json:"last_disconnection"` 91 | LastMiss *NetworkConnectionTimestamp `json:"last_miss"` 92 | } 93 | 94 | // networkPeerWithID is a heterogeneously encoded NetworkPeer with ID as a first array member 95 | // See OperationAlt for details 96 | type networkPeerWithID NetworkPeer 97 | 98 | func (n *networkPeerWithID) UnmarshalJSON(data []byte) error { 99 | return unmarshalHeterogeneousJSONArray(data, &n.PeerID, (*NetworkPeer)(n)) 100 | } 101 | 102 | // NetworkPeerLogEntry represents peer log entry 103 | type NetworkPeerLogEntry struct { 104 | NetworkAddress 105 | Kind string `json:"kind"` 106 | Timestamp time.Time `json:"timestamp"` 107 | } 108 | 109 | // NetworkPoint represents network point info 110 | type NetworkPoint struct { 111 | Address string `json:"-"` 112 | Trusted bool `json:"trusted"` 113 | GreylistedUntil time.Time `json:"greylisted_until"` 114 | State NetworkPointState `json:"state"` 115 | P2PPeerID string `json:"p2p_peer_id"` 116 | LastFailedConnection time.Time `json:"last_failed_connection"` 117 | LastRejectedConnection *IDTimestamp `json:"last_rejected_connection"` 118 | LastEstablishedConnection *IDTimestamp `json:"last_established_connection"` 119 | LastDisconnection *IDTimestamp `json:"last_disconnection"` 120 | LastSeen *IDTimestamp `json:"last_seen"` 121 | LastMiss time.Time `json:"last_miss"` 122 | } 123 | 124 | // networkPointAlt is a heterogeneously encoded NetworkPoint with address as a first array member 125 | // See OperationAlt for details 126 | type networkPointAlt NetworkPoint 127 | 128 | func (n *networkPointAlt) UnmarshalJSON(data []byte) error { 129 | return unmarshalHeterogeneousJSONArray(data, &n.Address, (*NetworkPoint)(n)) 130 | } 131 | 132 | // NetworkPointState represents point state 133 | type NetworkPointState struct { 134 | EventKind string `json:"event_kind"` 135 | P2PPeerID string `json:"p2p_peer_id"` 136 | } 137 | 138 | // IDTimestamp represents peer id with timestamp 139 | type IDTimestamp struct { 140 | ID string 141 | Timestamp time.Time 142 | } 143 | 144 | // UnmarshalJSON implements json.Unmarshaler 145 | func (i *IDTimestamp) UnmarshalJSON(data []byte) error { 146 | return unmarshalHeterogeneousJSONArray(data, &i.ID, &i.Timestamp) 147 | } 148 | 149 | // NetworkPointLogEntry represents point's log entry 150 | type NetworkPointLogEntry struct { 151 | Kind NetworkPointState `json:"kind"` 152 | Timestamp time.Time `json:"timestamp"` 153 | } 154 | 155 | // MempoolOperations represents mempool operations 156 | type MempoolOperations struct { 157 | Applied []*Operation `json:"applied"` 158 | Refused []*OperationWithErrorAlt `json:"refused"` 159 | BranchRefused []*OperationWithErrorAlt `json:"branch_refused"` 160 | BranchDelayed []*OperationWithErrorAlt `json:"branch_delayed"` 161 | Unprocessed []*OperationAlt `json:"unprocessed"` 162 | } 163 | 164 | // InvalidBlock represents invalid block hash along with the errors that led to it being declared invalid 165 | type InvalidBlock struct { 166 | Block string `json:"block"` 167 | Level int `json:"level"` 168 | Error Errors `json:"error"` 169 | } 170 | 171 | type SyncState string 172 | 173 | const ( 174 | SyncStateStuck SyncState = "stuck" 175 | SyncStateSynced SyncState = "synced" 176 | SyncStateUnsynced SyncState = "unsynced" 177 | ) 178 | 179 | type BootstrappedStatus struct { 180 | Bootstrapped bool `json:"bootstrapped"` 181 | SyncState SyncState `json:"sync_state"` 182 | } 183 | 184 | type proposalsRPCResponse = [][]interface{} 185 | 186 | // BigInt overrides UnmarshalJSON for big.Int 187 | type BigInt struct { 188 | big.Int 189 | } 190 | 191 | // UnmarshalJSON implements json.Unmarshaler 192 | func (z *BigInt) UnmarshalJSON(data []byte) error { 193 | var s string 194 | // basically unquote only 195 | if err := json.Unmarshal(data, &s); err != nil { 196 | return err 197 | } 198 | 199 | return z.UnmarshalText([]byte(s)) 200 | } 201 | 202 | // MarshalYAML implements yaml.Marshaler 203 | func (z *BigInt) MarshalYAML() (interface{}, error) { 204 | return &yaml.Node{ 205 | Kind: yaml.ScalarNode, 206 | Value: z.String(), 207 | }, nil 208 | } 209 | 210 | // GetNetworkStats returns current network stats https://tezos.gitlab.io/betanet/api/rpc.html#get-network-stat 211 | func (s *Service) GetNetworkStats(ctx context.Context) (*NetworkStats, error) { 212 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/stat", nil) 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | var stats NetworkStats 218 | if err = s.Client.Do(req, &stats); err != nil { 219 | return nil, err 220 | } 221 | return &stats, err 222 | } 223 | 224 | // GetNetworkConnections returns all network connections http://tezos.gitlab.io/mainnet/api/rpc.html#get-network-connections 225 | func (s *Service) GetNetworkConnections(ctx context.Context) ([]*NetworkConnection, error) { 226 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/connections", nil) 227 | if err != nil { 228 | return nil, err 229 | } 230 | 231 | var conns []*NetworkConnection 232 | if err = s.Client.Do(req, &conns); err != nil { 233 | return nil, err 234 | } 235 | return conns, err 236 | } 237 | 238 | // GetNetworkPeers returns the list the peers the node ever met. 239 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers 240 | func (s *Service) GetNetworkPeers(ctx context.Context, filter string) ([]*NetworkPeer, error) { 241 | u := url.URL{ 242 | Path: "/network/peers", 243 | } 244 | 245 | if filter != "" { 246 | q := url.Values{ 247 | "filter": []string{filter}, 248 | } 249 | u.RawQuery = q.Encode() 250 | } 251 | 252 | req, err := s.Client.NewRequest(ctx, http.MethodGet, u.String(), nil) 253 | if err != nil { 254 | return nil, err 255 | } 256 | 257 | var peers []*networkPeerWithID 258 | if err = s.Client.Do(req, &peers); err != nil { 259 | return nil, err 260 | } 261 | 262 | ret := make([]*NetworkPeer, len(peers)) 263 | for i, p := range peers { 264 | ret[i] = (*NetworkPeer)(p) 265 | } 266 | 267 | return ret, err 268 | } 269 | 270 | // GetNetworkPeer returns details about a given peer. 271 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id 272 | func (s *Service) GetNetworkPeer(ctx context.Context, peerID string) (*NetworkPeer, error) { 273 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/peers/"+peerID, nil) 274 | if err != nil { 275 | return nil, err 276 | } 277 | 278 | var peer NetworkPeer 279 | if err = s.Client.Do(req, &peer); err != nil { 280 | return nil, err 281 | } 282 | peer.PeerID = peerID 283 | 284 | return &peer, err 285 | } 286 | 287 | // BanNetworkPeer blacklists the given peer. 288 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-ban 289 | func (s *Service) BanNetworkPeer(ctx context.Context, peerID string) error { 290 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/peers/"+peerID+"/ban", nil) 291 | if err != nil { 292 | return err 293 | } 294 | 295 | if err := s.Client.Do(req, nil); err != nil { 296 | return err 297 | } 298 | return nil 299 | } 300 | 301 | // TrustNetworkPeer used to trust a given peer permanently: the peer cannot be blocked (but its host IP still can). 302 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-trust 303 | func (s *Service) TrustNetworkPeer(ctx context.Context, peerID string) error { 304 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/peers/"+peerID+"/trust", nil) 305 | if err != nil { 306 | return err 307 | } 308 | 309 | if err := s.Client.Do(req, nil); err != nil { 310 | return err 311 | } 312 | return nil 313 | } 314 | 315 | // GetNetworkPeerBanned checks if a given peer is blacklisted or greylisted. 316 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-banned 317 | func (s *Service) GetNetworkPeerBanned(ctx context.Context, peerID string) (bool, error) { 318 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/peers/"+peerID+"/banned", nil) 319 | if err != nil { 320 | return false, err 321 | } 322 | 323 | var banned bool 324 | if err = s.Client.Do(req, &banned); err != nil { 325 | return false, err 326 | } 327 | 328 | return banned, err 329 | } 330 | 331 | // GetNetworkPeerLog monitors network events related to a given peer. 332 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-log 333 | func (s *Service) GetNetworkPeerLog(ctx context.Context, peerID string) ([]*NetworkPeerLogEntry, error) { 334 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/peers/"+peerID+"/log", nil) 335 | if err != nil { 336 | return nil, err 337 | } 338 | 339 | var log []*NetworkPeerLogEntry 340 | if err = s.Client.Do(req, &log); err != nil { 341 | return nil, err 342 | } 343 | 344 | return log, err 345 | } 346 | 347 | // MonitorNetworkPeerLog monitors network events related to a given peer. 348 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-log 349 | func (s *Service) MonitorNetworkPeerLog(ctx context.Context, peerID string, results chan<- []*NetworkPeerLogEntry) error { 350 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/peers/"+peerID+"/log?monitor", nil) 351 | if err != nil { 352 | return err 353 | } 354 | 355 | return s.Client.Do(req, results) 356 | } 357 | 358 | // GetNetworkPoints returns list the pool of known `IP:port` used for establishing P2P connections. 359 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-points 360 | func (s *Service) GetNetworkPoints(ctx context.Context, filter string) ([]*NetworkPoint, error) { 361 | u := url.URL{ 362 | Path: "/network/points", 363 | } 364 | 365 | if filter != "" { 366 | q := url.Values{ 367 | "filter": []string{filter}, 368 | } 369 | u.RawQuery = q.Encode() 370 | } 371 | 372 | req, err := s.Client.NewRequest(ctx, http.MethodGet, u.String(), nil) 373 | if err != nil { 374 | return nil, err 375 | } 376 | 377 | var points []*networkPointAlt 378 | if err = s.Client.Do(req, &points); err != nil { 379 | return nil, err 380 | } 381 | 382 | ret := make([]*NetworkPoint, len(points)) 383 | for i, p := range points { 384 | ret[i] = (*NetworkPoint)(p) 385 | } 386 | 387 | return ret, err 388 | } 389 | 390 | // GetNetworkPoint returns details about a given `IP:addr`. 391 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-points-point 392 | func (s *Service) GetNetworkPoint(ctx context.Context, address string) (*NetworkPoint, error) { 393 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/points/"+address, nil) 394 | if err != nil { 395 | return nil, err 396 | } 397 | 398 | var point NetworkPoint 399 | if err = s.Client.Do(req, &point); err != nil { 400 | return nil, err 401 | } 402 | point.Address = address 403 | 404 | return &point, err 405 | } 406 | 407 | // ConnectToNetworkPoint used to connect to a peer. 408 | // https://tezos.gitlab.io/mainnet/api/rpc.html#put-network-points-point 409 | func (s *Service) ConnectToNetworkPoint(ctx context.Context, address string, timeout time.Duration) error { 410 | u := url.URL{ 411 | Path: "/network/points/" + address, 412 | } 413 | 414 | if timeout > 0 { 415 | q := url.Values{ 416 | "timeout": []string{fmt.Sprintf("%f", float64(timeout)/float64(time.Second))}, 417 | } 418 | u.RawQuery = q.Encode() 419 | } 420 | 421 | req, err := s.Client.NewRequest(ctx, http.MethodPut, u.String(), &struct{}{}) 422 | if err != nil { 423 | return err 424 | } 425 | 426 | if err := s.Client.Do(req, nil); err != nil { 427 | return err 428 | } 429 | 430 | return nil 431 | } 432 | 433 | // BanNetworkPoint blacklists the given address. 434 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-points-point-ban 435 | func (s *Service) BanNetworkPoint(ctx context.Context, address string) error { 436 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/points/"+address+"/ban", nil) 437 | if err != nil { 438 | return err 439 | } 440 | 441 | if err := s.Client.Do(req, nil); err != nil { 442 | return err 443 | } 444 | return nil 445 | } 446 | 447 | // TrustNetworkPoint used to trust a given address permanently. Connections from this address can still be closed on authentication if the peer is blacklisted or greylisted. 448 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-points-point-trust 449 | func (s *Service) TrustNetworkPoint(ctx context.Context, address string) error { 450 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/points/"+address+"/trust", nil) 451 | if err != nil { 452 | return err 453 | } 454 | 455 | if err := s.Client.Do(req, nil); err != nil { 456 | return err 457 | } 458 | return nil 459 | } 460 | 461 | // GetNetworkPointBanned check is a given address is blacklisted or greylisted. 462 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-points-point-banned 463 | func (s *Service) GetNetworkPointBanned(ctx context.Context, address string) (bool, error) { 464 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/points/"+address+"/banned", nil) 465 | if err != nil { 466 | return false, err 467 | } 468 | 469 | var banned bool 470 | if err = s.Client.Do(req, &banned); err != nil { 471 | return false, err 472 | } 473 | 474 | return banned, err 475 | } 476 | 477 | // GetNetworkPointLog monitors network events related to an `IP:addr`. 478 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-log 479 | func (s *Service) GetNetworkPointLog(ctx context.Context, address string) ([]*NetworkPointLogEntry, error) { 480 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/points/"+address+"/log", nil) 481 | if err != nil { 482 | return nil, err 483 | } 484 | 485 | var log []*NetworkPointLogEntry 486 | if err = s.Client.Do(req, &log); err != nil { 487 | return nil, err 488 | } 489 | 490 | return log, err 491 | } 492 | 493 | // MonitorNetworkPointLog monitors network events related to an `IP:addr`. 494 | // https://tezos.gitlab.io/mainnet/api/rpc.html#get-network-peers-peer-id-log 495 | func (s *Service) MonitorNetworkPointLog(ctx context.Context, address string, results chan<- []*NetworkPointLogEntry) error { 496 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/network/points/"+address+"/log?monitor", nil) 497 | if err != nil { 498 | return err 499 | } 500 | 501 | return s.Client.Do(req, results) 502 | } 503 | 504 | // GetDelegateBalance returns a delegate's balance http://tezos.gitlab.io/mainnet/api/rpc.html#get-block-id-context-delegates-pkh-balance 505 | func (s *Service) GetDelegateBalance(ctx context.Context, chainID string, blockID string, pkh string) (*big.Int, error) { 506 | u := "/chains/" + chainID + "/blocks/" + blockID + "/context/delegates/" + pkh + "/balance" 507 | req, err := s.Client.NewRequest(ctx, http.MethodGet, u, nil) 508 | if err != nil { 509 | return nil, err 510 | } 511 | 512 | var balance BigInt 513 | if err := s.Client.Do(req, &balance); err != nil { 514 | return nil, err 515 | } 516 | 517 | return (*big.Int)(&balance.Int), nil 518 | } 519 | 520 | // GetContractBalance returns a contract's balance http://tezos.gitlab.io/mainnet/api/rpc.html#get-block-id-context-contracts-contract-id-balance 521 | func (s *Service) GetContractBalance(ctx context.Context, chainID string, blockID string, contractID string) (*big.Int, error) { 522 | u := "/chains/" + chainID + "/blocks/" + blockID + "/context/contracts/" + contractID + "/balance" 523 | req, err := s.Client.NewRequest(ctx, http.MethodGet, u, nil) 524 | if err != nil { 525 | return nil, err 526 | } 527 | 528 | var balance BigInt 529 | if err := s.Client.Do(req, &balance); err != nil { 530 | return nil, err 531 | } 532 | 533 | return (*big.Int)(&balance.Int), nil 534 | } 535 | 536 | // MonitorBootstrapped reads from the bootstrapped blocks stream http://tezos.gitlab.io/mainnet/api/rpc.html#get-monitor-bootstrapped 537 | func (s *Service) MonitorBootstrapped(ctx context.Context, results chan<- *BootstrappedBlock) error { 538 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/monitor/bootstrapped", nil) 539 | if err != nil { 540 | return err 541 | } 542 | 543 | return s.Client.Do(req, results) 544 | } 545 | 546 | // MonitorHeads reads from the heads blocks stream https://tezos.gitlab.io/mainnet/api/rpc.html#get-monitor-heads-chain-id 547 | func (s *Service) MonitorHeads(ctx context.Context, chainID string, results chan<- *BlockInfo) error { 548 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/monitor/heads/"+chainID, nil) 549 | if err != nil { 550 | return err 551 | } 552 | 553 | return s.Client.Do(req, results) 554 | } 555 | 556 | // GetMempoolPendingOperations returns mempool pending operations 557 | func (s *Service) GetMempoolPendingOperations(ctx context.Context, chainID string) (*MempoolOperations, error) { 558 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/mempool/pending_operations", nil) 559 | if err != nil { 560 | return nil, err 561 | } 562 | 563 | var ops MempoolOperations 564 | if err := s.Client.Do(req, &ops); err != nil { 565 | return nil, err 566 | } 567 | 568 | return &ops, nil 569 | } 570 | 571 | // MonitorMempoolOperations monitors mempool pending operations. 572 | // The connection is closed after every new block. 573 | func (s *Service) MonitorMempoolOperations(ctx context.Context, chainID, filter string, results chan<- []*Operation) error { 574 | if filter == "" { 575 | filter = "applied" 576 | } 577 | 578 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/mempool/monitor_operations?"+filter, nil) 579 | if err != nil { 580 | return err 581 | } 582 | 583 | return s.Client.Do(req, results) 584 | } 585 | 586 | // GetInvalidBlocks lists blocks that have been declared invalid along with the errors that led to them being declared invalid. 587 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-chains-chain-id-invalid-blocks 588 | func (s *Service) GetInvalidBlocks(ctx context.Context, chainID string) ([]*InvalidBlock, error) { 589 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/invalid_blocks", nil) 590 | if err != nil { 591 | return nil, err 592 | } 593 | 594 | var invalidBlocks []*InvalidBlock 595 | if err := s.Client.Do(req, &invalidBlocks); err != nil { 596 | return nil, err 597 | } 598 | 599 | return invalidBlocks, nil 600 | } 601 | 602 | // GetBlock returns information about a Tezos block 603 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id 604 | func (s *Service) GetBlock(ctx context.Context, chainID, blockID string) (*Block, error) { 605 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID, nil) 606 | if err != nil { 607 | return nil, err 608 | } 609 | 610 | var block Block 611 | if err := s.Client.Do(req, &block); err != nil { 612 | return nil, err 613 | } 614 | 615 | return &block, nil 616 | } 617 | 618 | // GetBallotList returns ballots casted so far during a voting period. 619 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-ballot-list 620 | func (s *Service) GetBallotList(ctx context.Context, chainID, blockID string) ([]*Ballot, error) { 621 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/ballot_list", nil) 622 | if err != nil { 623 | return nil, err 624 | } 625 | 626 | var ballots []*Ballot 627 | if err := s.Client.Do(req, &ballots); err != nil { 628 | return nil, err 629 | } 630 | 631 | return ballots, nil 632 | } 633 | 634 | // GetBallots returns sum of ballots casted so far during a voting period. 635 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-ballots 636 | func (s *Service) GetBallots(ctx context.Context, chainID, blockID string) (*Ballots, error) { 637 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/ballots", nil) 638 | if err != nil { 639 | return nil, err 640 | } 641 | 642 | var ballots Ballots 643 | if err := s.Client.Do(req, &ballots); err != nil { 644 | return nil, err 645 | } 646 | 647 | return &ballots, nil 648 | } 649 | 650 | // GetBallotListings returns a list of delegates with their voting weight, in number of rolls. 651 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-listings 652 | func (s *Service) GetBallotListings(ctx context.Context, chainID, blockID string) ([]*BallotListing, error) { 653 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/listings", nil) 654 | if err != nil { 655 | return nil, err 656 | } 657 | 658 | var listings []*BallotListing 659 | if err := s.Client.Do(req, &listings); err != nil { 660 | return nil, err 661 | } 662 | 663 | return listings, nil 664 | } 665 | 666 | // GetProposals returns a list of proposals with number of supporters. 667 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-proposals 668 | func (s *Service) GetProposals(ctx context.Context, chainID, blockID string) ([]*Proposal, error) { 669 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/proposals", nil) 670 | if err != nil { 671 | return nil, err 672 | } 673 | 674 | var proposalsResp proposalsRPCResponse 675 | if err := s.Client.Do(req, &proposalsResp); err != nil { 676 | return nil, err 677 | } 678 | 679 | proposals := make([]*Proposal, len(proposalsResp)) 680 | 681 | for i, proposalResp := range proposalsResp { 682 | if len(proposalResp) == 2 { 683 | proposal := &Proposal{} 684 | if propHash, ok := proposalResp[0].(string); ok { 685 | proposal.ProposalHash = propHash 686 | } else { 687 | return nil, fmt.Errorf("malformed request ProposalHash was expected to be a string but got: %v", proposalResp[0]) 688 | } 689 | if supporters, ok := proposalResp[1].(float64); ok { 690 | proposal.SupporterCount = int(supporters) 691 | } else { 692 | return nil, fmt.Errorf("malformed request SupporterCount was expected to be a float but got: %v", proposalResp[1]) 693 | } 694 | proposals[i] = proposal 695 | } else { 696 | return nil, errors.New("malformed request Proposal is expected to be tuple of size 2") 697 | } 698 | } 699 | 700 | return proposals, nil 701 | } 702 | 703 | // GetCurrentProposals returns the current proposal under evaluation. 704 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-current-proposal 705 | func (s *Service) GetCurrentProposals(ctx context.Context, chainID, blockID string) (string, error) { 706 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/current_proposal", nil) 707 | if err != nil { 708 | return "", err 709 | } 710 | 711 | var currentProposal string 712 | if err := s.Client.Do(req, ¤tProposal); err != nil { 713 | return "", err 714 | } 715 | 716 | return currentProposal, nil 717 | } 718 | 719 | // GetCurrentQuorum returns the current expected quorum. 720 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-current-quorum 721 | func (s *Service) GetCurrentQuorum(ctx context.Context, chainID, blockID string) (int, error) { 722 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/current_quorum", nil) 723 | if err != nil { 724 | return -1, err 725 | } 726 | 727 | var currentQuorum int 728 | if err := s.Client.Do(req, ¤tQuorum); err != nil { 729 | return -1, err 730 | } 731 | 732 | return currentQuorum, nil 733 | } 734 | 735 | // GetCurrentPeriodKind returns the current period kind 736 | // https://tezos.gitlab.io/alphanet/api/rpc.html#get-block-id-votes-current-period-kind 737 | func (s *Service) GetCurrentPeriodKind(ctx context.Context, chainID, blockID string) (PeriodKind, error) { 738 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/blocks/"+blockID+"/votes/current_period_kind", nil) 739 | if err != nil { 740 | return "", err 741 | } 742 | 743 | var periodKind PeriodKind 744 | if err := s.Client.Do(req, &periodKind); err != nil { 745 | return "", err 746 | } 747 | 748 | return periodKind, nil 749 | } 750 | 751 | func (s *Service) GetBootstrapped(ctx context.Context, chainID string) (*BootstrappedStatus, error) { 752 | req, err := s.Client.NewRequest(ctx, http.MethodGet, "/chains/"+chainID+"/is_bootstrapped", nil) 753 | if err != nil { 754 | return nil, err 755 | } 756 | 757 | var status BootstrappedStatus 758 | if err := s.Client.Do(req, &status); err != nil { 759 | return nil, err 760 | } 761 | 762 | return &status, nil 763 | } 764 | -------------------------------------------------------------------------------- /go-tezos/service_test.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "math/big" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func timeMustUnmarshalText(text string) (t time.Time) { 16 | if err := t.UnmarshalText([]byte(text)); err != nil { 17 | panic(err) 18 | } 19 | return 20 | } 21 | 22 | func TestServiceGetMethods(t *testing.T) { 23 | ctx := context.Background() 24 | tests := []struct { 25 | get func(s *Service) (interface{}, error) 26 | respFixture string 27 | respInline string 28 | respStatus int 29 | respContentType string 30 | expectedPath string 31 | expectedQuery string 32 | expectedValue interface{} 33 | expectedMethod string 34 | errMsg string 35 | errType interface{} 36 | }{ 37 | { 38 | get: func(s *Service) (interface{}, error) { return s.GetNetworkStats(ctx) }, 39 | respFixture: "fixtures/network/stat.json", 40 | respContentType: "application/json", 41 | expectedPath: "/network/stat", 42 | expectedValue: &NetworkStats{ 43 | TotalBytesSent: 291690080, 44 | TotalBytesRecv: 532639553, 45 | CurrentInflow: 23596, 46 | CurrentOutflow: 14972, 47 | }, 48 | }, 49 | { 50 | get: func(s *Service) (interface{}, error) { return s.GetNetworkConnections(ctx) }, 51 | respFixture: "fixtures/network/connections.json", 52 | respContentType: "application/json", 53 | expectedPath: "/network/connections", 54 | expectedValue: []*NetworkConnection{{Incoming: false, PeerID: "idt5qvkLiJ15rb6yJU1bjpGmdyYnPJ", IDPoint: NetworkAddress{Addr: "::ffff:34.253.64.43", Port: 0x2604}, RemoteSocketPort: 0x2604, Versions: []*NetworkVersion{{Name: "TEZOS_ALPHANET_2018-07-31T16:22:39Z", Major: 0x0, Minor: 0x0}}, Private: false, LocalMetadata: NetworkMetadata{DisableMempool: false, PrivateNode: false}, RemoteMetadata: NetworkMetadata{DisableMempool: false, PrivateNode: false}}, {Incoming: true, PeerID: "ids8VJTHEuyND6B8ahGgXPAJ3BDp1c", IDPoint: NetworkAddress{Addr: "::ffff:176.31.255.202", Port: 0x2604}, RemoteSocketPort: 0x2604, Versions: []*NetworkVersion{{Name: "TEZOS_ALPHANET_2018-07-31T16:22:39Z", Major: 0x0, Minor: 0x0}}, Private: true, LocalMetadata: NetworkMetadata{DisableMempool: true, PrivateNode: true}, RemoteMetadata: NetworkMetadata{DisableMempool: true, PrivateNode: true}}}, 55 | }, 56 | { 57 | get: func(s *Service) (interface{}, error) { return s.GetNetworkPeers(ctx, "") }, 58 | respFixture: "fixtures/network/peers.json", 59 | respContentType: "application/json", 60 | expectedPath: "/network/peers", 61 | expectedValue: []*NetworkPeer{{PeerID: "idrnHcGMrFxiYsmxf5Cqd6NhUTUU8X", ConnMetadata: &NetworkMetadata{}, State: "running", ReachableAt: &NetworkAddress{Addr: "::ffff:45.79.146.133", Port: 39732}, Stat: NetworkStats{TotalBytesSent: 4908012, TotalBytesRecv: 14560268, CurrentInflow: 66, CurrentOutflow: 177}, LastEstablishedConnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:45.79.146.133", Port: 39732}, Timestamp: timeMustUnmarshalText("2018-11-13T20:56:14Z")}, LastSeen: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:45.79.146.133", Port: 39732}, Timestamp: timeMustUnmarshalText("2018-11-13T20:56:14Z")}, LastRejectedConnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:45.79.146.133", Port: 39732}, Timestamp: timeMustUnmarshalText("2018-11-13T15:22:41Z")}, LastDisconnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:45.79.146.133", Port: 39732}, Timestamp: timeMustUnmarshalText("2018-11-13T18:04:12Z")}, LastMiss: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:45.79.146.133", Port: 39732}, Timestamp: timeMustUnmarshalText("2018-11-13T18:04:12Z")}}, {PeerID: "idsXeq1zboupwXXDdDDiWhBjimeJe3", State: "disconnected", LastEstablishedConnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.155.17.238", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-13T17:57:18Z")}, LastSeen: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.155.17.238", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-13T19:48:57Z")}, LastDisconnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.155.17.238", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-13T19:48:57Z")}, LastMiss: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.155.17.238", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-13T19:48:57Z")}}}, 62 | }, 63 | { 64 | get: func(s *Service) (interface{}, error) { return s.GetNetworkPeer(ctx, "idtTZmNapGXAcfbnPoAcDz6J2xCHZZ") }, 65 | respFixture: "fixtures/network/peer.json", 66 | respContentType: "application/json", 67 | expectedPath: "/network/peers/idtTZmNapGXAcfbnPoAcDz6J2xCHZZ", 68 | expectedValue: &NetworkPeer{PeerID: "idtTZmNapGXAcfbnPoAcDz6J2xCHZZ", ConnMetadata: &NetworkMetadata{}, State: "running", ReachableAt: &NetworkAddress{Addr: "::ffff:104.248.233.63", Port: 9732}, Stat: NetworkStats{TotalBytesSent: 1196571, TotalBytesRecv: 1302211, CurrentInflow: 0, CurrentOutflow: 1}, LastEstablishedConnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.248.233.63", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-14T11:47:07Z")}, LastSeen: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.248.233.63", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-14T11:47:07Z")}, LastDisconnection: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.248.233.63", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-14T11:44:57Z")}, LastMiss: &NetworkConnectionTimestamp{NetworkAddress: NetworkAddress{Addr: "::ffff:104.248.233.63", Port: 9732}, Timestamp: timeMustUnmarshalText("2018-11-14T11:44:57Z")}}, 69 | }, 70 | { 71 | get: func(s *Service) (interface{}, error) { 72 | return s.GetNetworkPeerBanned(ctx, "idtTZmNapGXAcfbnPoAcDz6J2xCHZZ") 73 | }, 74 | respInline: "false", 75 | respContentType: "application/json", 76 | expectedPath: "/network/peers/idtTZmNapGXAcfbnPoAcDz6J2xCHZZ/banned", 77 | expectedValue: false, 78 | }, 79 | { 80 | get: func(s *Service) (interface{}, error) { 81 | return s.GetNetworkPeerLog(ctx, "idrPSsREFE1MV1161ybEpaebFwgYWE") 82 | }, 83 | respFixture: "fixtures/network/peer_log.json", 84 | respContentType: "application/json", 85 | expectedPath: "/network/peers/idrPSsREFE1MV1161ybEpaebFwgYWE/log", 86 | expectedValue: []*NetworkPeerLogEntry{{NetworkAddress: NetworkAddress{Addr: "::ffff:13.81.43.51", Port: 9732}, Kind: "incoming_request", Timestamp: timeMustUnmarshalText("2018-11-13T15:35:17Z")}, {NetworkAddress: NetworkAddress{Addr: "::ffff:13.81.43.51", Port: 9732}, Kind: "connection_established", Timestamp: timeMustUnmarshalText("2018-11-13T15:35:19Z")}, {NetworkAddress: NetworkAddress{Addr: "::ffff:13.81.43.51", Port: 9732}, Kind: "external_disconnection", Timestamp: timeMustUnmarshalText("2018-11-13T18:02:51Z")}, {NetworkAddress: NetworkAddress{Addr: "::ffff:13.81.43.51", Port: 9732}, Kind: "incoming_request", Timestamp: timeMustUnmarshalText("2018-11-13T20:56:14Z")}, {NetworkAddress: NetworkAddress{Addr: "::ffff:13.81.43.51", Port: 9732}, Kind: "connection_established", Timestamp: timeMustUnmarshalText("2018-11-13T20:56:15Z")}}, 87 | }, 88 | { 89 | get: func(s *Service) (interface{}, error) { 90 | ch := make(chan []*NetworkPeerLogEntry, 100) 91 | if err := s.MonitorNetworkPeerLog(ctx, "idsBATisQfJu7d6vCLY4CP66dKj7CQ", ch); err != nil { 92 | return nil, err 93 | } 94 | close(ch) 95 | 96 | var res [][]*NetworkPeerLogEntry 97 | for b := range ch { 98 | res = append(res, b) 99 | } 100 | return res, nil 101 | }, 102 | respFixture: "fixtures/network/peer_log.chunked", 103 | respContentType: "application/json", 104 | expectedPath: "/network/peers/idsBATisQfJu7d6vCLY4CP66dKj7CQ/log", 105 | expectedValue: [][]*NetworkPeerLogEntry{{&NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "incoming_request", Timestamp: timeMustUnmarshalText("2018-11-13T15:20:14Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "connection_established", Timestamp: timeMustUnmarshalText("2018-11-13T15:20:14Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "external_disconnection", Timestamp: timeMustUnmarshalText("2018-11-13T16:30:08Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "incoming_request", Timestamp: timeMustUnmarshalText("2018-11-13T16:39:20Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "connection_established", Timestamp: timeMustUnmarshalText("2018-11-13T16:39:20Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "external_disconnection", Timestamp: timeMustUnmarshalText("2018-11-13T19:48:58Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "incoming_request", Timestamp: timeMustUnmarshalText("2018-11-13T20:56:30Z")}, &NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "connection_established", Timestamp: timeMustUnmarshalText("2018-11-13T20:56:30Z")}}, {&NetworkPeerLogEntry{NetworkAddress: NetworkAddress{Addr: "::ffff:51.15.242.114", Port: 9732}, Kind: "external_disconnection", Timestamp: timeMustUnmarshalText("2018-11-13T22:25:07Z")}}}, 106 | }, 107 | { 108 | get: func(s *Service) (interface{}, error) { return s.GetNetworkPoints(ctx, "") }, 109 | respFixture: "fixtures/network/points.json", 110 | respContentType: "application/json", 111 | expectedPath: "/network/points", 112 | expectedValue: []*NetworkPoint{{Address: "73.247.92.150:9732", Trusted: false, GreylistedUntil: timeMustUnmarshalText("2018-11-14T19:01:28Z"), State: NetworkPointState{EventKind: "disconnected"}, LastFailedConnection: timeMustUnmarshalText("2018-11-14T19:01:16Z"), LastMiss: timeMustUnmarshalText("2018-11-14T19:01:16Z")}, {Address: "40.119.159.28:9732", Trusted: false, GreylistedUntil: timeMustUnmarshalText("2018-11-14T16:24:57Z"), State: NetworkPointState{EventKind: "running", P2PPeerID: "ids496Ey2BKHVJYZdsk72XCwbZteTj"}, P2PPeerID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", LastFailedConnection: timeMustUnmarshalText("2018-11-14T11:47:13Z"), LastRejectedConnection: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T12:03:11Z")}, LastEstablishedConnection: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T16:48:56Z")}, LastDisconnection: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T16:23:57Z")}, LastSeen: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T16:48:56Z")}, LastMiss: timeMustUnmarshalText("2018-11-14T16:23:57Z")}}, 113 | }, 114 | { 115 | get: func(s *Service) (interface{}, error) { return s.GetNetworkPoint(ctx, "40.119.159.28:9732") }, 116 | respFixture: "fixtures/network/point.json", 117 | respContentType: "application/json", 118 | expectedPath: "/network/points/40.119.159.28:9732", 119 | expectedValue: &NetworkPoint{Address: "40.119.159.28:9732", Trusted: false, GreylistedUntil: timeMustUnmarshalText("2018-11-14T16:24:57Z"), State: NetworkPointState{EventKind: "running", P2PPeerID: "ids496Ey2BKHVJYZdsk72XCwbZteTj"}, P2PPeerID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", LastFailedConnection: timeMustUnmarshalText("2018-11-14T11:47:13Z"), LastRejectedConnection: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T12:03:11Z")}, LastEstablishedConnection: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T16:48:56Z")}, LastDisconnection: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T16:23:57Z")}, LastSeen: &IDTimestamp{ID: "ids496Ey2BKHVJYZdsk72XCwbZteTj", Timestamp: timeMustUnmarshalText("2018-11-14T16:48:56Z")}, LastMiss: timeMustUnmarshalText("2018-11-14T16:23:57Z")}, 120 | }, 121 | { 122 | get: func(s *Service) (interface{}, error) { 123 | return s.GetNetworkPointBanned(ctx, "40.119.159.28:9732") 124 | }, 125 | respInline: "false", 126 | respContentType: "application/json", 127 | expectedPath: "/network/points/40.119.159.28:9732/banned", 128 | expectedValue: false, 129 | }, 130 | { 131 | get: func(s *Service) (interface{}, error) { 132 | return s.GetNetworkPointLog(ctx, "34.255.45.196:9732") 133 | }, 134 | respFixture: "fixtures/network/point_log.json", 135 | respContentType: "application/json", 136 | expectedPath: "/network/points/34.255.45.196:9732/log", 137 | expectedValue: []*NetworkPointLogEntry{{Kind: NetworkPointState{EventKind: "outgoing_request"}, Timestamp: timeMustUnmarshalText("2018-11-15T17:56:18Z")}, {Kind: NetworkPointState{EventKind: "accepting_request", P2PPeerID: "idrBJarh4t32gN9s52kxMWmeSi76Jk"}, Timestamp: timeMustUnmarshalText("2018-11-15T17:56:19Z")}, {Kind: NetworkPointState{EventKind: "rejecting_request", P2PPeerID: "idrBJarh4t32gN9s52kxMWmeSi76Jk"}, Timestamp: timeMustUnmarshalText("2018-11-15T17:56:19Z")}}, 138 | }, 139 | { 140 | get: func(s *Service) (interface{}, error) { 141 | ch := make(chan []*NetworkPointLogEntry, 100) 142 | if err := s.MonitorNetworkPointLog(ctx, "80.214.69.170:9732", ch); err != nil { 143 | return nil, err 144 | } 145 | close(ch) 146 | 147 | var res [][]*NetworkPointLogEntry 148 | for b := range ch { 149 | res = append(res, b) 150 | } 151 | 152 | return res, nil 153 | }, 154 | respFixture: "fixtures/network/point_log.chunked", 155 | respContentType: "application/json", 156 | expectedPath: "/network/points/80.214.69.170:9732/log", 157 | expectedValue: [][]*NetworkPointLogEntry{{&NetworkPointLogEntry{Kind: NetworkPointState{EventKind: "outgoing_request"}, Timestamp: timeMustUnmarshalText("2018-11-15T18:00:39Z")}, &NetworkPointLogEntry{Kind: NetworkPointState{EventKind: "request_rejected"}, Timestamp: timeMustUnmarshalText("2018-11-15T18:00:49Z")}}, {&NetworkPointLogEntry{Kind: NetworkPointState{EventKind: "outgoing_request"}, Timestamp: timeMustUnmarshalText("2018-11-15T18:16:18Z")}}, {&NetworkPointLogEntry{Kind: NetworkPointState{EventKind: "request_rejected"}, Timestamp: timeMustUnmarshalText("2018-11-15T18:16:28Z")}}}, 158 | }, 159 | { 160 | get: func(s *Service) (interface{}, error) { 161 | return nil, s.ConnectToNetworkPoint(ctx, "80.214.69.170:9732", 10*time.Second) 162 | }, 163 | respInline: "{}", 164 | respContentType: "application/json", 165 | expectedPath: "/network/points/80.214.69.170:9732", 166 | expectedMethod: "PUT", 167 | expectedQuery: "timeout=10.000000", 168 | }, 169 | { 170 | get: func(s *Service) (interface{}, error) { 171 | return s.GetDelegateBalance(ctx, "main", "head", "tz3WXYtyDUNL91qfiCJtVUX746QpNv5i5ve5") 172 | }, 173 | respFixture: "fixtures/block/delegate_balance.json", 174 | respContentType: "application/json", 175 | expectedPath: "/chains/main/blocks/head/context/delegates/tz3WXYtyDUNL91qfiCJtVUX746QpNv5i5ve5/balance", 176 | expectedValue: big.NewInt(13490453135591), 177 | }, 178 | { 179 | get: func(s *Service) (interface{}, error) { 180 | return s.GetContractBalance(ctx, "main", "head", "tz3WXYtyDUNL91qfiCJtVUX746QpNv5i5ve5") 181 | }, 182 | respFixture: "fixtures/block/contract_balance.json", 183 | respContentType: "application/json", 184 | expectedPath: "/chains/main/blocks/head/context/contracts/tz3WXYtyDUNL91qfiCJtVUX746QpNv5i5ve5/balance", 185 | expectedValue: big.NewInt(4700354460878), 186 | }, 187 | { 188 | get: func(s *Service) (interface{}, error) { 189 | ch := make(chan *BootstrappedBlock, 100) 190 | if err := s.MonitorBootstrapped(ctx, ch); err != nil { 191 | return nil, err 192 | } 193 | close(ch) 194 | 195 | var res []*BootstrappedBlock 196 | for b := range ch { 197 | res = append(res, b) 198 | } 199 | return res, nil 200 | }, 201 | respFixture: "fixtures/monitor/bootstrapped.chunked", 202 | respContentType: "application/json", 203 | expectedPath: "/monitor/bootstrapped", 204 | expectedValue: []*BootstrappedBlock{ 205 | {Block: "BLgz6z8w5bYtn2AAEmsfMD3aH9o8SUnVygUpVUsCe6dkRpEt5Qy", Timestamp: timeMustUnmarshalText("2018-09-17T00:46:12Z")}, 206 | {Block: "BLc3Y6zsb7PT6QnScu8VKcUPGkCoeCLPWLVTQoQjk5QQ7pbmHs5", Timestamp: timeMustUnmarshalText("2018-09-17T00:46:42Z")}, 207 | {Block: "BKiqiXgqAPHX4bRzk2p1jEKHijaxLPdcQi8hqVfGhBwngcticEk", Timestamp: timeMustUnmarshalText("2018-09-17T00:48:32Z")}, 208 | }, 209 | }, 210 | { 211 | get: func(s *Service) (interface{}, error) { 212 | return s.GetMempoolPendingOperations(ctx, "main") 213 | }, 214 | respFixture: "fixtures/block/pending_operations.json", 215 | respContentType: "application/json", 216 | expectedPath: "/chains/main/mempool/pending_operations", 217 | expectedValue: &MempoolOperations{Applied: []*Operation{{Hash: "opLHEC3xm8qPRP9g44oBpB45RzRVUoMX1NsX75sKKtNvA8pvSm2", Branch: "BMLvebSvhTyZ7GG2vykV8hpGEc8egzcwn9fc3JJKrtCk8FssT9M", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 208806}}, Signature: "sigtTW5Y3xQaTKo5vEiqr8zG4YnPv7GbVbUgo7XYw7UZduz9jvdxzFbKUmftKFsFGH1UEZBbxyhyH5DLUUMh5KrQ3MENzUwC"}, {Hash: "ooSEFHRfArRSjeWhHhcmBa5aL2E3MqsN1HucCm3xiR2gLuzGSYN", Branch: "BMLvebSvhTyZ7GG2vykV8hpGEc8egzcwn9fc3JJKrtCk8FssT9M", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 208806}}, Signature: "sigeVFaHCGk9S6P9MhNNyZjHMcfPgYZw5cTwejtbGDEZdp58XKcxVkP3CFCKiPHesiEDqCxvrPGHZUpQLNmmqaSgrmv1ePNZ"}}, Refused: []*OperationWithErrorAlt{}, BranchRefused: []*OperationWithErrorAlt{}, BranchDelayed: []*OperationWithErrorAlt{{Operation: Operation{Protocol: "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", Hash: "oo1Z19oCkTWibLp7mJwFKP3UFVxuf6eV1iNWwJS7gZs8uZbrduS", Branch: "BMTSuKyFBhgmD7e3UDt9jLtjC2ftTUosTGEiiYc61Lu6F3xSkvJ", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 208804}}, Signature: "sigZXm4SGNcHwh5qsfjsFYmhSCwtimifq4EPje5rnJxvNDkymC2o3Yv8cJWgug3dDxiQWDexRDeBBu8Pf5qFxA6SckKypiau"}, Error: Errors{&GenericError{Kind: "temporary", ID: "proto.002-PsYLVpVv.operation.wrong_endorsement_predecessor"}}}, {Operation: Operation{Protocol: "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", Hash: "ooCaHemWe76uiBLDUXY2uhbhuiyLG7w7rqUFaJPxr7v56z6DVPS", Branch: "BL1pULCBFDJkqDHmYqK8yrVM3mHQHi72JFg6dT5qJ96ncjDbPpn", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 208773}}, Signature: "sigpkWpkY25KDBo7YcaLYx5Q61ypcfFWXjXgvbMG6uFrnStboCxCoCnJbDNri7CGzad35zLUvXCVxu2uj4WBSPgfxsnGKUBn"}, Error: Errors{&GenericError{Kind: "temporary", ID: "proto.002-PsYLVpVv.operation.wrong_endorsement_predecessor"}}}}, Unprocessed: []*OperationAlt{}}, 218 | }, 219 | // Handling 5xx errors from the Tezos node with RPC error information. 220 | { 221 | get: func(s *Service) (interface{}, error) { 222 | // Doesn't matter which Get* method we call here, as long as it calls RPCClient.Get 223 | // in the implementation. 224 | return s.GetNetworkStats(ctx) 225 | }, 226 | respStatus: 500, 227 | respFixture: "fixtures/error.json", 228 | respContentType: "application/json", 229 | expectedPath: "/network/stat", 230 | errMsg: `tezos: kind = "permanent", id = "proto.002-PsYLVpVv.context.storage_error"`, 231 | errType: (*rpcError)(nil), 232 | }, 233 | // Handling 5xx errors from the Tezos node with empty RPC error information. 234 | { 235 | get: func(s *Service) (interface{}, error) { 236 | // Doesn't matter which Get* method we call here, as long as it calls RPCClient.Get 237 | // in the implementation. 238 | return s.GetNetworkStats(ctx) 239 | }, 240 | respStatus: 500, 241 | respFixture: "fixtures/empty_error.json", 242 | respContentType: "application/json", 243 | expectedPath: "/network/stat", 244 | errMsg: `tezos: empty error response`, 245 | errType: (*plainError)(nil), 246 | }, 247 | // Handling 5xx errors from the Tezos node with malformed RPC error information. 248 | { 249 | get: func(s *Service) (interface{}, error) { 250 | // Doesn't matter which Get* method we call here, as long as it calls RPCClient.Get 251 | // in the implementation. 252 | return s.GetNetworkStats(ctx) 253 | }, 254 | respStatus: 500, 255 | respFixture: "fixtures/malformed_error.json", 256 | respContentType: "application/json", 257 | expectedPath: "/network/stat", 258 | errMsg: `tezos: error decoding RPC error: invalid character ',' looking for beginning of value`, 259 | errType: (*plainError)(nil), 260 | }, 261 | // Handling unexpected response status codes. 262 | { 263 | get: func(s *Service) (interface{}, error) { 264 | // Doesn't matter which Get* method we call here, as long as it calls RPCClient.Get 265 | // in the implementation. 266 | return s.GetNetworkStats(ctx) 267 | }, 268 | respStatus: 404, 269 | respFixture: "fixtures/empty.json", 270 | expectedPath: "/network/stat", 271 | errMsg: `tezos: HTTP status 404`, 272 | errType: (*httpError)(nil), 273 | }, 274 | { 275 | get: func(s *Service) (interface{}, error) { 276 | return s.GetInvalidBlocks(ctx, "main") 277 | }, 278 | respFixture: "fixtures/chains/invalid_blocks.json", 279 | respContentType: "application/json", 280 | expectedPath: "/chains/main/invalid_blocks", 281 | expectedValue: []*InvalidBlock{{Block: "BM31cpbqfXu3WNYLQ8Tch21tXjcnwbyFzvcqohHL1BSnkhnhzwp", Level: 42, Error: Errors{}}}, 282 | }, 283 | { 284 | get: func(s *Service) (interface{}, error) { 285 | return s.GetBlock(ctx, "main", "BLnoArJNPCyYFK2z3Mnomi36Jo3FwrjriJ6hvzgTJGYYDKEkDXm") 286 | }, 287 | respFixture: "fixtures/chains/block.json", 288 | respContentType: "application/json", 289 | expectedPath: "/chains/main/blocks/BLnoArJNPCyYFK2z3Mnomi36Jo3FwrjriJ6hvzgTJGYYDKEkDXm", 290 | expectedValue: &Block{Protocol: "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", ChainID: "NetXZUqeBjDnWde", Hash: "BLnoArJNPCyYFK2z3Mnomi36Jo3FwrjriJ6hvzgTJGYYDKEkDXm", Header: RawBlockHeader{Level: 219133, Proto: 1, Predecessor: "BLNWdEensT9MFq8pkDwjHfGVFsV1reYUhVcMAVzq3LCMS1WdKZ8", Timestamp: timeMustUnmarshalText("2018-11-27T17:49:57Z"), ValidationPass: 4, OperationsHash: "LLoZamNeucV8tqPAcqJQYsNEsMwnCuL1xu1kJMiGFCx9MBVCGcWJF", Fitness: []HexBytes{{0x0}, {0x0, 0x0, 0x0, 0x0, 0x0, 0x5a, 0x12, 0x5f}}, Context: "CoW5zHjWVHfUAbSgzqnZ938eDXG37P9oJVn3Lb3NyQJBheUDvdVf", ProofOfWorkNonce: HexBytes{0x7d, 0x94, 0x95, 0x82, 0xfe, 0x2, 0x48, 0x62}, Signature: "sigktdiZpdykWEjgeTB3N1qFJ5bsh3SxVNB8wc5FAutbJPG7puWQAPrxwL6BZPJVKLRj2uLnCw54Akx4KA48DS5Jg8tthCLY"}, Metadata: BlockHeaderMetadata{Protocol: "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", NextProtocol: "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", TestChainStatus: &NotRunningTestChainStatus{GenericTestChainStatus: GenericTestChainStatus{Status: "not_running"}}, MaxOperationsTTL: 60, MaxOperationDataLength: 16384, MaxBlockHeaderLength: 238, MaxOperationListLength: []*MaxOperationListLength{{MaxSize: 32768, MaxOp: 32}}, Baker: "tz3gN8NTLNLJg5KRsUU47NHNVHbdhcFXjjaB", Level: BlockHeaderMetadataLevel{Level: 219133, LevelPosition: 219132, Cycle: 106, CyclePosition: 2044, VotingPeriod: 6, VotingPeriodPosition: 22524, ExpectedCommitment: false}, VotingPeriodKind: "proposal", ConsumedGas: &BigInt{}, Deactivated: []string{}, BalanceUpdates: BalanceUpdates{&ContractBalanceUpdate{GenericBalanceUpdate: GenericBalanceUpdate{Kind: "contract", Change: -512000000}, Contract: "tz3gN8NTLNLJg5KRsUU47NHNVHbdhcFXjjaB"}, &FreezerBalanceUpdate{GenericBalanceUpdate: GenericBalanceUpdate{Kind: "freezer", Change: 512000000}, Category: "deposits", Delegate: "tz3gN8NTLNLJg5KRsUU47NHNVHbdhcFXjjaB", Level: 106}}}, Operations: [][]*Operation{{&Operation{Protocol: "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", ChainID: "NetXZUqeBjDnWde", Hash: "opEatwYFvwuUM2aEa9cUU1ofMzsi46bYwiUhPLENXpLkjpps4Xq", Branch: "BLNWdEensT9MFq8pkDwjHfGVFsV1reYUhVcMAVzq3LCMS1WdKZ8", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 219132, Metadata: EndorsementOperationMetadata{BalanceUpdates: BalanceUpdates{&ContractBalanceUpdate{GenericBalanceUpdate: GenericBalanceUpdate{Kind: "contract", Change: -128000000}, Contract: "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq"}, &FreezerBalanceUpdate{GenericBalanceUpdate: GenericBalanceUpdate{Kind: "freezer", Change: 128000000}, Category: "deposits", Delegate: "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", Level: 106}, &FreezerBalanceUpdate{GenericBalanceUpdate: GenericBalanceUpdate{Kind: "freezer", Change: 2000000}, Category: "rewards", Delegate: "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", Level: 106}}, Delegate: "tz1SfH1vxAt2TTZV7mpsN79uGas5LHhV8epq", Slots: []int{18, 16}}}}, Signature: "sigS3d9wfEFuChEqLetCxf4G8QYAjWL7ND3F8amMPVPDS2RwQqkeKU9hbrEXk7GG7U2aPcWkTA3uTdNzz4gkAb8jSy8hUc51"}}, {}, {}, {}}}, 291 | }, 292 | { 293 | get: func(s *Service) (interface{}, error) { 294 | ch := make(chan *BlockInfo, 100) 295 | if err := s.MonitorHeads(ctx, "main", ch); err != nil { 296 | return nil, err 297 | } 298 | close(ch) 299 | 300 | var res []*BlockInfo 301 | for b := range ch { 302 | res = append(res, b) 303 | } 304 | return res, nil 305 | }, 306 | respFixture: "fixtures/monitor/heads.chunked", 307 | respContentType: "application/json", 308 | expectedPath: "/monitor/heads/main", 309 | expectedValue: []*BlockInfo{ 310 | {Hash: "BKq199p1Hm1phfJ4DhuRjB6yBSJnDNG8sgMSnja9pXR96T2Hyy1", Timestamp: timeMustUnmarshalText("2019-04-10T22:37:08Z"), OperationsHash: "LLobC6LA4T2STTa3D77YDuDsrw6xEY8DakpkvR9kd7DL9HpvchUtb", Level: 390397, Context: "CoUiJrzomxKms5eELzgpULo2iyf7dJAqW3gEBnFE7WHv3cy9pfVE", Predecessor: "BKihh4Bd3nAypX5bZtYy7xoxQDRbygkoyjB9w171exm2mbXHQWj", Proto: 3, ProtocolData: "000000000003bcf5f72d00320dffeb51c154077ce7dd2af6057f0370485a738345d3cb5c722db6df6ddb9b48c4e7a4282a3b994bca1cc52f6b95c889f23906e1d4e3e20203e171ff924004", ValidationPass: 4, Fitness: []HexBytes{{0x0}, {0x0, 0x0, 0x0, 0x0, 0x0, 0x5a, 0x12, 0x5f}}}, 311 | }, 312 | }, 313 | { 314 | get: func(s *Service) (interface{}, error) { 315 | ch := make(chan []*Operation, 100) 316 | if err := s.MonitorMempoolOperations(ctx, "main", "", ch); err != nil { 317 | return nil, err 318 | } 319 | close(ch) 320 | 321 | var res []*Operation 322 | for b := range ch { 323 | res = append(res, b...) 324 | } 325 | return res, nil 326 | }, 327 | respFixture: "fixtures/monitor/mempool_operations.chunked", 328 | respContentType: "application/json", 329 | expectedPath: "/chains/main/mempool/monitor_operations", 330 | expectedValue: []*Operation{{Protocol: "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", Branch: "BKvSZMWpcDc9RkKg11sQ5oRDyHrMDiKX5RmTdU455XnPHuYZWRS", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 489922}}, Signature: "sigbdfHsA4XHTB3ToUMzRRAYmSJBCvJ52jdE7SrFp7BD3jUnd9sVBdzytHKTD6ygy343jRjJvc4E8kuZRiEqUdExH333RaqP"}, {Protocol: "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", Branch: "BKvSZMWpcDc9RkKg11sQ5oRDyHrMDiKX5RmTdU455XnPHuYZWRS", Contents: OperationElements{&EndorsementOperationElem{GenericOperationElem: GenericOperationElem{Kind: "endorsement"}, Level: 489922}}, Signature: "sigk5ep31BR1gSFSD37aiiAbT2azciyBdBaZD8Xp4Ef1NCT37L9ggucZySHhrNEnmqKZSRq5LKq5MJDVhj4tKmP1z8GqmY5j"}}, 331 | }, 332 | { 333 | get: func(s *Service) (interface{}, error) { 334 | return s.GetBallotList(ctx, "main", "head") 335 | }, 336 | respFixture: "fixtures/votes/ballot_list.json", 337 | respContentType: "application/json", 338 | expectedPath: "/chains/main/blocks/head/votes/ballot_list", 339 | expectedValue: []*Ballot{{PKH: "tz3e75hU4EhDU3ukyJueh5v6UvEHzGwkg3yC", Ballot: "yay"}, {PKH: "tz1iEWcNL383qiDJ3Q3qt5W2T4aSKUbEU4An", Ballot: "nay"}, {PKH: "tz3bvNMQ95vfAYtG8193ymshqjSvmxiCUuR5", Ballot: "pass"}}, 340 | }, 341 | { 342 | get: func(s *Service) (interface{}, error) { 343 | return s.GetBallots(ctx, "main", "head") 344 | }, 345 | respFixture: "fixtures/votes/ballots.json", 346 | respContentType: "application/json", 347 | expectedPath: "/chains/main/blocks/head/votes/ballots", 348 | expectedValue: &Ballots{Yay: 26776, Nay: 11, Pass: 19538}, 349 | }, 350 | { 351 | get: func(s *Service) (interface{}, error) { 352 | return s.GetBallotListings(ctx, "main", "head") 353 | }, 354 | respFixture: "fixtures/votes/listings.json", 355 | respContentType: "application/json", 356 | expectedPath: "/chains/main/blocks/head/votes/listings", 357 | expectedValue: []*BallotListing{{PKH: "tz1KfCukgwoU32Z4or88467mMM3in5smtv8k", Rolls: 5}, {PKH: "tz1KfEsrtDaA1sX7vdM4qmEPWuSytuqCDp5j", Rolls: 307}}, 358 | }, 359 | { 360 | get: func(s *Service) (interface{}, error) { 361 | return s.GetProposals(ctx, "main", "head") 362 | }, 363 | respFixture: "fixtures/votes/proposals.json", 364 | respContentType: "application/json", 365 | expectedPath: "/chains/main/blocks/head/votes/proposals", 366 | expectedValue: []*Proposal{{ProposalHash: "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", SupporterCount: 11}}, 367 | }, 368 | { 369 | get: func(s *Service) (interface{}, error) { 370 | return s.GetCurrentProposals(ctx, "main", "head") 371 | }, 372 | respFixture: "fixtures/votes/current_proposal.json", 373 | respContentType: "application/json", 374 | expectedPath: "/chains/main/blocks/head/votes/current_proposal", 375 | expectedValue: "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", 376 | }, 377 | { 378 | get: func(s *Service) (interface{}, error) { 379 | return s.GetCurrentQuorum(ctx, "main", "head") 380 | }, 381 | respFixture: "fixtures/votes/current_quorum.json", 382 | respContentType: "application/json", 383 | expectedPath: "/chains/main/blocks/head/votes/current_quorum", 384 | expectedValue: 8000, 385 | }, 386 | { 387 | get: func(s *Service) (interface{}, error) { 388 | return s.GetCurrentPeriodKind(ctx, "main", "head") 389 | }, 390 | respFixture: "fixtures/votes/current_period_kind.json", 391 | respContentType: "application/json", 392 | expectedPath: "/chains/main/blocks/head/votes/current_period_kind", 393 | expectedValue: PeriodKind("testing_vote"), 394 | }, 395 | } 396 | 397 | for _, test := range tests { 398 | // Start a test HTTP server that responds as specified in the test case parameters. 399 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 400 | require.Equal(t, test.expectedPath, r.URL.Path) 401 | 402 | if test.expectedQuery != "" { 403 | require.Equal(t, test.expectedQuery, r.URL.RawQuery) 404 | } 405 | 406 | m := test.expectedMethod 407 | if m == "" { 408 | m = http.MethodGet 409 | } 410 | require.Equal(t, m, r.Method) 411 | 412 | var buf []byte 413 | if test.respInline != "" { 414 | buf = []byte(test.respInline) 415 | } else { 416 | var err error 417 | buf, err = ioutil.ReadFile(test.respFixture) 418 | require.NoError(t, err, "error reading fixture %q", test.respFixture) 419 | } 420 | 421 | if test.respContentType != "" { 422 | w.Header().Set("Content-Type", test.respContentType) 423 | } 424 | 425 | if test.respStatus != 0 { 426 | w.WriteHeader(test.respStatus) 427 | } 428 | _, err := w.Write(buf) 429 | require.NoError(t, err, "error writing HTTP response") 430 | })) 431 | 432 | c, err := NewRPCClient(srv.URL) 433 | require.NoError(t, err, "error creating client") 434 | 435 | s := &Service{Client: c} 436 | 437 | value, err := test.get(s) 438 | 439 | if test.errType != nil { 440 | require.IsType(t, test.errType, err) 441 | } 442 | 443 | if test.errMsg == "" { 444 | require.NoError(t, err, "error getting value") 445 | require.Equal(t, test.expectedValue, value, "unexpected value") 446 | } else { 447 | require.EqualError(t, err, test.errMsg, "unexpected error string") 448 | } 449 | 450 | srv.Close() 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /go-tezos/utils.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httputil" 9 | 10 | "github.com/davecgh/go-spew/spew" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // Logger is an extension to logrus.FieldLogger 15 | type Logger interface { 16 | log.FieldLogger 17 | Writer() *io.PipeWriter 18 | WriterLevel(level log.Level) *io.PipeWriter 19 | } 20 | 21 | /* 22 | unmarshalHeterogeneousJSONArray is a helper function used in custom JSON 23 | unmarshallers and intended to decode array-like objects: 24 | [ 25 | "...", // object ID or hash 26 | { 27 | ... // ebject with ID ommitted 28 | } 29 | ] 30 | */ 31 | func unmarshalHeterogeneousJSONArray(data []byte, v ...interface{}) error { 32 | var raw []json.RawMessage 33 | if err := json.Unmarshal(data, &raw); err != nil { 34 | return err 35 | } 36 | 37 | if len(raw) < len(v) { 38 | return fmt.Errorf("JSON array is too short, expected %d, got %d", len(v), len(raw)) 39 | } 40 | 41 | for i, vv := range v { 42 | if err := json.Unmarshal(raw[i], vv); err != nil { 43 | return err 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func isLevelEnabled(logger Logger, level log.Level) bool { 51 | switch l := logger.(type) { 52 | case *log.Entry: 53 | return l.Logger.IsLevelEnabled(level) 54 | case *log.Logger: 55 | return l.IsLevelEnabled(level) 56 | } 57 | return false 58 | } 59 | 60 | func spewDump(logger Logger, level log.Level, v ...interface{}) { 61 | if !isLevelEnabled(logger, level) { 62 | return 63 | } 64 | 65 | w := logger.WriterLevel(level) 66 | defer w.Close() 67 | 68 | spew.Fdump(w, v...) 69 | } 70 | 71 | func dumpRequest(logger Logger, level log.Level, req *http.Request) { 72 | if !isLevelEnabled(logger, level) { 73 | return 74 | } 75 | 76 | buf, err := httputil.DumpRequestOut(req, true) 77 | if err != nil { 78 | logger.Error(err) 79 | return 80 | } 81 | 82 | w := logger.WriterLevel(level) 83 | defer w.Close() 84 | 85 | w.Write(buf) 86 | } 87 | 88 | func dumpResponse(logger Logger, level log.Level, res *http.Response, body bool) { 89 | if !isLevelEnabled(logger, level) { 90 | return 91 | } 92 | 93 | buf, err := httputil.DumpResponse(res, body) 94 | if err != nil { 95 | logger.Error(err) 96 | return 97 | } 98 | 99 | w := logger.WriterLevel(level) 100 | defer w.Close() 101 | 102 | w.Write(buf) 103 | } 104 | -------------------------------------------------------------------------------- /go-tezos/votes.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | // Ballot holds information about a Tezos ballot 4 | type Ballot struct { 5 | PKH string `json:"pkh" yaml:"pkh"` 6 | Ballot string `json:"ballot" yaml:"ballot"` 7 | } 8 | 9 | // BallotListing holds information about a Tezos delegate and his voting weight in rolls 10 | type BallotListing struct { 11 | PKH string `json:"pkh" yaml:"pkh"` 12 | Rolls int64 `json:"rolls" yaml:"rolls"` 13 | } 14 | 15 | // Ballots holds summary data about a voting period 16 | type Ballots struct { 17 | Yay int64 `json:"yay" yaml:"yay"` 18 | Nay int64 `json:"nay" yaml:"nay"` 19 | Pass int64 `json:"pass" yaml:"pass"` 20 | } 21 | 22 | // Proposal holds summary data about a proposal and his number of supporter 23 | type Proposal struct { 24 | ProposalHash string 25 | SupporterCount int 26 | } 27 | 28 | // PeriodKind contains information about tezos voting period kind 29 | type PeriodKind string 30 | 31 | // IsProposal return true if period kind is proposal 32 | func (p PeriodKind) IsProposal() bool { 33 | return p == "proposal" 34 | } 35 | 36 | // IsTestingVote return true if period kind is testing vote 37 | func (p PeriodKind) IsTestingVote() bool { 38 | return p == "testing_vote" 39 | } 40 | 41 | // IsTesting return true if period kind is testing 42 | func (p PeriodKind) IsTesting() bool { 43 | return p == "testing" 44 | } 45 | 46 | // IsPromotionVote true if period kind is promotion vote 47 | func (p PeriodKind) IsPromotionVote() bool { 48 | return p == "promotion_vote" 49 | } 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ecadlabs/tezos_exporter 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/golang/protobuf v1.5.2 // indirect 8 | github.com/prometheus/client_golang v1.11.0 9 | github.com/prometheus/common v0.29.0 // indirect 10 | github.com/sirupsen/logrus v1.8.1 11 | github.com/stretchr/testify v1.7.0 12 | golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect 13 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 37 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 38 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 39 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 40 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 41 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 42 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 46 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 47 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 48 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 49 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 50 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 51 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 52 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 53 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 54 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 55 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 57 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 58 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 59 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 60 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 61 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 62 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 63 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 64 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 65 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 66 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 67 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 68 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 69 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 70 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 71 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 72 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 73 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 74 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 75 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 76 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 77 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 78 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 79 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 80 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 81 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 82 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 83 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 84 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 85 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 86 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 87 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 88 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 89 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 90 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 91 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 92 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 93 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 94 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 95 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 96 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 97 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 98 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 99 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 100 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 101 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 102 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 103 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 104 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 105 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 106 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 107 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 108 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 109 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 110 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 111 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 112 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 113 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 114 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 115 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 116 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 117 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 118 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 119 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 120 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 121 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 122 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 123 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 124 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 125 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 126 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 127 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 128 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 129 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 130 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 131 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 132 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 133 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 134 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 135 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 136 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 137 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 138 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 139 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 140 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 141 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 142 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 143 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 144 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 145 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 146 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 147 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 148 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 149 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 150 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 151 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 152 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 153 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 154 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 155 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 156 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 157 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 158 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 159 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 160 | github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= 161 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 162 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 163 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 164 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 165 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 166 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 167 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 168 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 169 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 170 | github.com/prometheus/common v0.29.0 h1:3jqPBvKT4OHAbje2Ql7KeaaSicDBCxMYwEJU1zRJceE= 171 | github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 172 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 173 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 174 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 175 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= 176 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 177 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 178 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 179 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 180 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 181 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 182 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 183 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 184 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 185 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 186 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 187 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 188 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 189 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 190 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 191 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 192 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 193 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 194 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 195 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 196 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 197 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 198 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 199 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 200 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 201 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 202 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 203 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 204 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 205 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 206 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 207 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 208 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 209 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 210 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 211 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 212 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 213 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 214 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 215 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 216 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 217 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 218 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 219 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 220 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 221 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 222 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 223 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 224 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 225 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 226 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 227 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 228 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 229 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 230 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 231 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 232 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 233 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 234 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 235 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 236 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 237 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 238 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 239 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 240 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 241 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 242 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 243 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 244 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 245 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 246 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 247 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 248 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 249 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 250 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 251 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 252 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 253 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 254 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 255 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 256 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 257 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 258 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 259 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 260 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 261 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 262 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 263 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 264 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 265 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 266 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 267 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 268 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 269 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 270 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 271 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 272 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 273 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 274 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 275 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 277 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 278 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 279 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 280 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 281 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 282 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 283 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 284 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 285 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 286 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 287 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 288 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 289 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 290 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 314 | golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ= 315 | golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 316 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 317 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 318 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 319 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 320 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 321 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 322 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 323 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 324 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 325 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 326 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 327 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 328 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 329 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 330 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 331 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 332 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 333 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 334 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 335 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 336 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 337 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 338 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 339 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 340 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 341 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 342 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 343 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 344 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 345 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 346 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 347 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 348 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 349 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 350 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 351 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 352 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 353 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 354 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 355 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 356 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 357 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 358 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 359 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 360 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 361 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 362 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 363 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 364 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 365 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 366 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 367 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 368 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 369 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 370 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 371 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 372 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 373 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 374 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 375 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 376 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 377 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 378 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 379 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 380 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 381 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 382 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 383 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 384 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 385 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 386 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 387 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 388 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 389 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 390 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 391 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 392 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 393 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 394 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 395 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 396 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 397 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 398 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 399 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 400 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 401 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 402 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 403 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 404 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 405 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 406 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 407 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 408 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 409 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 410 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 411 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 412 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 413 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 414 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 415 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 416 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 417 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 418 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 419 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 420 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 421 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 422 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 423 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 424 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 425 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 426 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 427 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 428 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 429 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 430 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 431 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 432 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 433 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 434 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 435 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 436 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 437 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 438 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 439 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 440 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 441 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 442 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 443 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 444 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 445 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 446 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 447 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 448 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 449 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 450 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 451 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 452 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 453 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 454 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 455 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 456 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 457 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 458 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 459 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 460 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 461 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 462 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 463 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 464 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 465 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 466 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 467 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 468 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 469 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 470 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 471 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 472 | -------------------------------------------------------------------------------- /health.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | "time" 8 | 9 | tezos "github.com/ecadlabs/tezos_exporter/go-tezos" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type HealthHandler struct { 14 | service *tezos.Service 15 | interval time.Duration 16 | chainID string 17 | threshold int 18 | tcount int 19 | ok bool 20 | } 21 | 22 | func (h *HealthHandler) poll() { 23 | status, err := h.service.GetBootstrapped(context.Background(), h.chainID) 24 | if err != nil { 25 | log.WithError(err).Error("error getting bootstrap status") 26 | h.ok = false 27 | } else { 28 | h.ok = status.Bootstrapped && status.SyncState == tezos.SyncStateSynced 29 | } 30 | h.tcount = h.threshold 31 | 32 | tick := time.Tick(h.interval) 33 | for range tick { 34 | status, err := h.service.GetBootstrapped(context.Background(), h.chainID) 35 | if err != nil { 36 | log.WithError(err).Error("error getting bootstrap status") 37 | h.ok = false 38 | h.tcount = h.threshold 39 | continue 40 | } 41 | 42 | ok := status.Bootstrapped && status.SyncState == tezos.SyncStateSynced 43 | if ok != h.ok { 44 | h.tcount-- 45 | if h.tcount == 0 { 46 | h.tcount = h.threshold 47 | h.ok = ok 48 | } 49 | } else { 50 | h.tcount = h.threshold 51 | } 52 | } 53 | } 54 | 55 | func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 56 | var res struct { 57 | Bootstrapped bool `json:"bootstrapped"` 58 | } 59 | 60 | var status int 61 | if h.ok { 62 | status = http.StatusOK 63 | res.Bootstrapped = true 64 | } else { 65 | status = http.StatusInternalServerError 66 | res.Bootstrapped = false 67 | } 68 | 69 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 70 | w.WriteHeader(status) 71 | json.NewEncoder(w).Encode(&res) 72 | } 73 | 74 | func NewHealthHandler(service *tezos.Service, chainID string, interval time.Duration, threshold int) *HealthHandler { 75 | h := HealthHandler{ 76 | service: service, 77 | interval: interval, 78 | threshold: threshold, 79 | chainID: chainID, 80 | } 81 | go h.poll() 82 | return &h 83 | } 84 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/ecadlabs/tezos_exporter/collector" 11 | tezos "github.com/ecadlabs/tezos_exporter/go-tezos" 12 | "github.com/prometheus/client_golang/prometheus" 13 | "github.com/prometheus/client_golang/prometheus/promhttp" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | func main() { 18 | metricsAddr := flag.String("metrics-listen-addr", ":9489", "TCP address on which to serve Prometheus metrics") 19 | tezosAddr := flag.String("tezos-node-url", "http://localhost:8732", "URL of Tezos node to monitor") 20 | chainID := flag.String("chain-id", "main", "ID of chain about which to report chain-related stats") 21 | rpcTimeout := flag.Duration("rpc-timeout", 10*time.Second, "Timeout for connecting to tezos RPCs") 22 | noHealthEp := flag.Bool("disable-health-endpoint", false, "Disable /health endpoint") 23 | isBootstrappedPollInterval := flag.Duration("bootstraped-poll-interval", 10*time.Second, "is_bootstrapped endpoint polling interval") 24 | isBootstrappedThreshold := flag.Int("bootstraped-threshold", 3, "Report is_bootstrapped change after N samples of the same value") 25 | mempoolRetryInterval := flag.Duration("mempool-retry-delay", 30*time.Second, "Retry mempool monitoring after a delay in case of an error") 26 | pools := flag.String("mempool-pools", "applied,branch_refused,refused,branch_delayed", "Mempool pools") 27 | 28 | flag.Parse() 29 | 30 | client, err := tezos.NewRPCClient(*tezosAddr) 31 | if err != nil { 32 | log.WithError(err).Error("error initializing Tezos RPC client") 33 | os.Exit(1) 34 | } 35 | 36 | service := &tezos.Service{Client: client} 37 | 38 | reg := prometheus.NewRegistry() 39 | reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) 40 | reg.MustRegister(prometheus.NewGoCollector()) 41 | reg.MustRegister(collector.NewBuildInfoCollector("")) 42 | reg.MustRegister(collector.NewNetworkCollector(service, *rpcTimeout, *chainID)) 43 | reg.MustRegister(collector.NewMempoolOperationsCollectorCollector(service, *chainID, strings.Split(*pools, ","), *mempoolRetryInterval)) 44 | 45 | http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) 46 | if !*noHealthEp { 47 | http.Handle("/health", NewHealthHandler(service, *chainID, *isBootstrappedPollInterval, *isBootstrappedThreshold)) 48 | } 49 | 50 | log.WithField("address", *metricsAddr).Info("tezos_exporter starting...") 51 | 52 | if err := http.ListenAndServe(*metricsAddr, nil); err != nil { 53 | log.WithError(err).Error("error starting webserver") 54 | os.Exit(1) 55 | } 56 | } 57 | --------------------------------------------------------------------------------