first commit

This commit is contained in:
Evan Gray 2022-09-07 18:43:05 +00:00
commit e7852ca52b
11 changed files with 1900 additions and 0 deletions

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2022 Wormhole Project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# Wormhole Explorer
## Components
### Mongo
### Mongo Express
### Fly
## References
https://hub.docker.com/_/mongo
https://www.mongodb.com/docs/drivers/go/current/

55
Tiltfile Normal file
View File

@ -0,0 +1,55 @@
# Disable telemetry by default
analytics_settings(False)
# Moar updates (default is 3)
update_settings(max_parallel_updates = 10)
# When running Tilt on a server, this can be used to set the public hostname Tilt runs on
# for service links in the UI to work.
config.define_string("webHost", False, "Public hostname for port forwards")
# Components
config.define_bool("mongo", False, "Enable mongo component")
config.define_bool("mongo-express", False, "Enable mongo-express component")
config.define_bool("fly", False, "Enable fly component")
cfg = config.parse()
webHost = cfg.get("webHost", "localhost")
mongo = cfg.get("mongo", True)
mongoExpress = cfg.get("mongo-express", True)
fly = cfg.get("fly", True)
if mongo:
k8s_yaml("devnet/mongo.yaml")
k8s_resource(
"mongo",
port_forwards = [
port_forward(27017, name = "Mongo [:27017]", host = webHost),
],
)
if mongoExpress:
k8s_yaml("devnet/mongo-express.yaml")
k8s_resource(
"mongo-express",
port_forwards = [
port_forward(8081, name = "Mongo Express [:8081]", host = webHost),
],
resource_deps = ["mongo"]
)
if fly:
docker_build(
ref = "fly",
context = "fly",
dockerfile = "fly/Dockerfile",
)
k8s_yaml("devnet/fly.yaml")
k8s_resource(
"fly",
resource_deps = ["mongo"]
)

42
devnet/fly.yaml Normal file
View File

@ -0,0 +1,42 @@
---
apiVersion: v1
kind: Service
metadata:
name: fly
labels:
app: fly
spec:
clusterIP: None
selector:
app: fly
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: fly
spec:
selector:
matchLabels:
app: fly
serviceName: fly
template:
metadata:
labels:
app: fly
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 0
containers:
- name: fly
image: fly:latest
env:
- name: MONGODB_URI
value: mongodb://root:example@mongo:27017/
readinessProbe:
exec:
command:
- test
- -e
- "/tmp/node.key"
initialDelaySeconds: 5
periodSeconds: 5

51
devnet/mongo-express.yaml Normal file
View File

@ -0,0 +1,51 @@
---
apiVersion: v1
kind: Service
metadata:
name: mongo-express
labels:
app: mongo-express
spec:
clusterIP: None
selector:
app: mongo-express
ports:
- port: 8081
name: mongo-express
protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo-express
spec:
selector:
matchLabels:
app: mongo-express
serviceName: mongo-express
template:
metadata:
labels:
app: mongo-express
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 0
containers:
- name: mongo-express
image: mongo-express:latest
env:
- name: ME_CONFIG_MONGODB_ADMINUSERNAME
value: root
- name: ME_CONFIG_MONGODB_ADMINPASSWORD
value: example
- name: ME_CONFIG_MONGODB_URL
value: mongodb://root:example@mongo:27017/
readinessProbe:
tcpSocket:
port: 8081
periodSeconds: 1
failureThreshold: 300
ports:
- containerPort: 8081
name: mongo-express
protocol: TCP

49
devnet/mongo.yaml Normal file
View File

@ -0,0 +1,49 @@
---
apiVersion: v1
kind: Service
metadata:
name: mongo
labels:
app: mongo
spec:
clusterIP: None
selector:
app: mongo
ports:
- port: 27017
name: mongo
protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
spec:
selector:
matchLabels:
app: mongo
serviceName: mongo
template:
metadata:
labels:
app: mongo
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 0
containers:
- name: mongo
image: mongo:latest
env:
- name: MONGO_INITDB_ROOT_USERNAME
value: root
- name: MONGO_INITDB_ROOT_PASSWORD
value: example
readinessProbe:
tcpSocket:
port: 27017
periodSeconds: 1
failureThreshold: 300
ports:
- containerPort: 27017
name: mongo
protocol: TCP

11
fly/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
FROM --platform=linux/amd64 docker.io/golang:1.17.5@sha256:90d1ab81f3d157ca649a9ff8d251691b810d95ea6023a03cdca139df58bca599 AS build
WORKDIR /app
COPY . .
RUN --mount=type=cache,target=/root/.cache --mount=type=cache,target=/go \
go build -o /fly main.go
ENTRYPOINT ["/fly"]

3
fly/README.md Normal file
View File

@ -0,0 +1,3 @@
```bash
MONGODB_URI=mongodb://root:example@localhost:27017/ go run main.go
```

164
fly/go.mod Normal file
View File

@ -0,0 +1,164 @@
module fly
go 1.17
require (
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/certusone/wormhole/node v0.0.0-20220907133901-8e231501b6cd // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dgraph-io/badger/v3 v3.2103.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/ethereum/go-ethereum v1.10.6 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.3 // indirect
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v1.12.0 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/huin/goupnp v1.0.3 // indirect
github.com/ipfs/go-cid v0.2.0 // indirect
github.com/ipfs/go-datastore v0.5.1 // indirect
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-ipns v0.2.0 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipld/go-ipld-prime v0.9.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 // indirect
github.com/klauspost/compress v1.15.1 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/koron/go-ssdp v0.0.3 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.22.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect
github.com/libp2p/go-libp2p-core v0.20.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.18.0 // indirect
github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect
github.com/libp2p/go-libp2p-pubsub v0.8.0 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
github.com/libp2p/go-msgio v0.2.0 // indirect
github.com/libp2p/go-nat v0.1.0 // indirect
github.com/libp2p/go-netroute v0.2.0 // indirect
github.com/libp2p/go-openssl v0.1.0 // indirect
github.com/libp2p/go-reuseport v0.2.0 // indirect
github.com/libp2p/go-yamux/v3 v3.1.2 // indirect
github.com/lucas-clemente/quic-go v0.28.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-pointer v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.0.4 // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multiaddr v0.6.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.1.1 // indirect
github.com/multiformats/go-multicodec v0.5.0 // indirect
github.com/multiformats/go-multihash v0.2.1 // indirect
github.com/multiformats/go-multistream v0.3.3 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/tsdb v0.7.1 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/tyler-smith/go-bip39 v1.0.2 // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.mongodb.org/mongo-driver v1.10.2 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.22.0 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404 // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)
// Needed for cosmos-sdk based chains. See
// https://github.com/cosmos/cosmos-sdk/issues/10925 for more details.
replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1

1293
fly/go.sum Normal file

File diff suppressed because it is too large Load Diff

204
fly/main.go Normal file
View File

@ -0,0 +1,204 @@
package main
import (
"context"
"fmt"
"os"
"github.com/certusone/wormhole/node/pkg/common"
"github.com/certusone/wormhole/node/pkg/p2p"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/certusone/wormhole/node/pkg/supervisor"
eth_common "github.com/ethereum/go-ethereum/common"
ipfslog "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p-core/crypto"
"go.uber.org/zap"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
rootCtx context.Context
rootCtxCancel context.CancelFunc
)
var (
p2pNetworkID string
p2pPort uint
p2pBootstrap string
nodeKeyPath string
logLevel string
)
func main() {
p2pNetworkID = "/wormhole/mainnet/2"
p2pPort = 8999
p2pBootstrap = "/dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7"
nodeKeyPath = "/tmp/node.key"
logLevel = "info"
common.SetRestrictiveUmask()
lvl, err := ipfslog.LevelFromString(logLevel)
if err != nil {
fmt.Println("Invalid log level")
os.Exit(1)
}
logger := ipfslog.Logger("wormhole-spy").Desugar()
ipfslog.SetAllLoggers(lvl)
// Verify flags
if nodeKeyPath == "" {
logger.Fatal("Please specify --nodeKey")
}
if p2pBootstrap == "" {
logger.Fatal("Please specify --bootstrap")
}
// Setup DB
if err := godotenv.Load(); err != nil {
logger.Info("No .env file found")
}
uri := os.Getenv("MONGODB_URI")
if uri == "" {
logger.Fatal("You must set your 'MONGODB_URI' environmental variable. See\n\t https://www.mongodb.com/docs/drivers/go/current/usage-examples/#environment-variable")
}
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
if err != nil {
panic(err)
}
defer func() {
if err := client.Disconnect(context.TODO()); err != nil {
panic(err)
}
}()
hbColl := client.Database("wormhole").Collection("heartbeats")
obsColl := client.Database("wormhole").Collection("observations")
vaaColl := client.Database("wormhole").Collection("vaas")
// Node's main lifecycle context.
rootCtx, rootCtxCancel = context.WithCancel(context.Background())
defer rootCtxCancel()
// Outbound gossip message queue
sendC := make(chan []byte)
// Inbound observations
obsvC := make(chan *gossipv1.SignedObservation, 50)
// Inbound signed VAAs
signedInC := make(chan *gossipv1.SignedVAAWithQuorum, 50)
// Heartbeat updates
heartbeatC := make(chan *gossipv1.Heartbeat, 50)
// Guardian set state managed by processor
gst := common.NewGuardianSetState(heartbeatC)
// Bootstrap guardian set, otherwise heartbeats would be skipped
gst.Set(&common.GuardianSet{
Index: 2,
Keys: []eth_common.Address{
eth_common.HexToAddress("0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5"),
eth_common.HexToAddress("0xfF6CB952589BDE862c25Ef4392132fb9D4A42157"),
eth_common.HexToAddress("0x114De8460193bdf3A2fCf81f86a09765F4762fD1"),
eth_common.HexToAddress("0x107A0086b32d7A0977926A205131d8731D39cbEB"),
eth_common.HexToAddress("0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2"),
eth_common.HexToAddress("0x11b39756C042441BE6D8650b69b54EbE715E2343"),
eth_common.HexToAddress("0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd"),
eth_common.HexToAddress("0x66B9590e1c41e0B226937bf9217D1d67Fd4E91F5"),
eth_common.HexToAddress("0x74a3bf913953D695260D88BC1aA25A4eeE363ef0"),
eth_common.HexToAddress("0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e"),
eth_common.HexToAddress("0xAF45Ced136b9D9e24903464AE889F5C8a723FC14"),
eth_common.HexToAddress("0xf93124b7c738843CBB89E864c862c38cddCccF95"),
eth_common.HexToAddress("0xD2CC37A4dc036a8D232b48f62cDD4731412f4890"),
eth_common.HexToAddress("0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811"),
eth_common.HexToAddress("0x71AA1BE1D36CaFE3867910F99C09e347899C19C3"),
eth_common.HexToAddress("0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf"),
eth_common.HexToAddress("0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8"),
eth_common.HexToAddress("0x5E1487F35515d02A92753504a8D75471b9f49EdB"),
eth_common.HexToAddress("0x6FbEBc898F403E4773E95feB15E80C9A99c8348d"),
},
})
// Ignore observations
go func() {
for {
select {
case <-rootCtx.Done():
return
case o := <- obsvC:
logger.Info("Received observation", zap.Any("observation", o))
result, err := obsColl.InsertOne(context.TODO(), o)
if err != nil {
logger.Error("Error inserting observation", zap.Error(err))
}
logger.Info("Inserted document", zap.Any("id", result.InsertedID))
}
}
}()
// Log signed VAAs
go func() {
for {
select {
case <-rootCtx.Done():
return
case v := <-signedInC:
logger.Info("Received signed VAA",
zap.Any("vaa", v.Vaa))
result, err := vaaColl.InsertOne(context.TODO(), v)
if err != nil {
logger.Error("Error inserting vaa", zap.Error(err))
}
logger.Info("Inserted document", zap.Any("id", result.InsertedID))
}
}
}()
// Ignore heartbeats
go func() {
for {
select {
case <-rootCtx.Done():
return
case hb := <- heartbeatC:
logger.Info("Received heartbeat", zap.Any("heartbeat", hb))
result, err := hbColl.InsertOne(context.TODO(), hb)
if err != nil {
logger.Error("Error inserting heartbeat", zap.Error(err))
}
logger.Info("Inserted document", zap.Any("id", result.InsertedID))
}
}
}()
// Load p2p private key
var priv crypto.PrivKey
priv, err = common.GetOrCreateNodeKey(logger, nodeKeyPath)
if err != nil {
logger.Fatal("Failed to load node key", zap.Error(err))
}
// Run supervisor.
supervisor.New(rootCtx, logger, func(ctx context.Context) error {
if err := supervisor.Run(ctx, "p2p", p2p.Run(obsvC, nil, nil, sendC, signedInC, priv, nil, gst, p2pPort, p2pNetworkID, p2pBootstrap, "", false, rootCtxCancel, nil)); err != nil {
return err
}
logger.Info("Started internal services")
<-ctx.Done()
return nil
},
// It's safer to crash and restart the process in case we encounter a panic,
// rather than attempting to reschedule the runnable.
supervisor.WithPropagatePanic)
<-rootCtx.Done()
logger.Info("root context cancelled, exiting...")
// TODO: wait for things to shut down gracefully
}