diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 397d07f36..0173fcda1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,6 +68,7 @@ jobs: # - rmb-sdk-go - user-contracts-mon - tfrobot + - messenger steps: - name: Checkout the repo diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6ecda3c61..e1a1b319b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -37,3 +37,4 @@ jobs: - rmb-sdk-go - user-contracts-mon - tfrobot + - messenger \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdfaad428..402a74f61 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,3 +41,4 @@ jobs: - rmb-sdk-go - user-contracts-mon - tfrobot + - messenger diff --git a/Makefile b/Makefile index 1d62df32b..d4099a7d2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,14 @@ -DIRS := "activation-service" "farmerbot" "grid-cli" "grid-client" "grid-proxy" "gridify" "monitoring-bot" "rmb-sdk-go" "user-contracts-mon" "tfrobot" "node-registrar" +DIRS := activation-service \ + farmerbot \ + grid-cli \ + grid-client \ + grid-proxy \ + gridify \ + monitoring-bot \ + rmb-sdk-go \ + user-contracts-mon \ + tfrobot \ + messenger mainnet-release: cd grid-client && go get github.com/threefoldtech/tfchain/clients/tfchain-client-go@5d6a2dd diff --git a/README.md b/README.md index 44ee026c0..c3543f8c6 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This repo contains the go clients for Threefold grid. - [user contracts mon](./user-contracts-mon/README.md) - [activation service](./activation-service/README.md) - [farmerbot](./farmerbot/README.md) +- [messenger](./messenger/README.md) ## Release diff --git "a/\\" "b/\\" deleted file mode 100644 index 9c8406d03..000000000 --- "a/\\" +++ /dev/null @@ -1,27 +0,0 @@ -FROM golang:1.23-alpine as builder - -WORKDIR /src/rmb-sdk-go - -COPY /rmb-sdk-go . - -WORKDIR /src/grid-client - -COPY /grid-client . - -WORKDIR /src/grid-proxy - -COPY /grid-proxy . - -WORKDIR /src/grid-cli - -COPY /grid-cli . - -RUN go build -o bin/grid-cli main.go - -FROM alpine:3.19 - -WORKDIR /app - -COPY --from=builder /src/grid-cli/bin/grid-cli . - -ENTRYPOINT [ "./grid-cli" ] diff --git a/go.work b/go.work index d34d0d12b..19bb148da 100644 --- a/go.work +++ b/go.work @@ -9,6 +9,7 @@ use ( ./grid-client ./grid-proxy ./gridify + ./messenger ./monitoring-bot ./rmb-sdk-go ./tfrobot diff --git a/go.work.sum b/go.work.sum index 707168416..c5ffbaa9a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2,6 +2,7 @@ Wgithub.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= +cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -278,7 +279,6 @@ github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20201021020641-d3c6d3118d10/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/ChainSafe/go-schnorrkel v0.0.0-20210318173838-ccb5cd955283/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= @@ -286,6 +286,7 @@ github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= @@ -328,7 +329,6 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.26.4/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= @@ -556,6 +556,7 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= @@ -681,11 +682,9 @@ github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -717,13 +716,10 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= -github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw= github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= @@ -802,9 +798,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= github.com/ethereum/go-ethereum v1.10.4/go.mod h1:nEE0TP5MtxGzOMd7egIrbPJMQBnhVU3ELNxhBglIzhg= @@ -812,7 +810,6 @@ github.com/ethereum/go-ethereum v1.10.12/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQ github.com/ethereum/go-ethereum v1.10.13/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= github.com/ethereum/go-ethereum v1.10.16/go.mod h1:Anj6cxczl+AHy63o4X9O8yWNHuN5wMpfb8MAnHkWn7Y= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.10.20/go.mod h1:LWUN82TCHGpxB3En5HVmLLzPD7YSrEUFmFfN1nKkVN0= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exoscale/egoscale v0.90.0/go.mod h1:wyXE5zrnFynMXA0jMhwQqSe24CfUhmBk2WI5wFZcq6Y= github.com/exoscale/egoscale v0.100.1 h1:iXsV1Ei7daqe/6FYSCSDyrFs1iUG1l1X9qNh2uMw6z0= @@ -941,9 +938,11 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -1007,7 +1006,6 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -1015,7 +1013,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/hanwen/go-fuse/v2 v2.3.0/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= @@ -1038,8 +1035,10 @@ github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixH github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= @@ -1150,6 +1149,7 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2 github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -1299,6 +1299,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= @@ -1314,6 +1315,7 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= @@ -1340,9 +1342,6 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1360,7 +1359,6 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -1393,7 +1391,6 @@ github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible/g github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -1416,9 +1413,7 @@ github.com/threefoldtech/zosbase v0.1.4/go.mod h1:rxc49wA04S4IsBOYe0omVO7nu7GXri github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1477,7 +1472,6 @@ github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= @@ -1486,16 +1480,24 @@ go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= @@ -1505,7 +1507,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1586,7 +1587,6 @@ golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1594,6 +1594,7 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= @@ -1603,6 +1604,8 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1619,6 +1622,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -1674,7 +1678,6 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1689,6 +1692,7 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1697,6 +1701,7 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1714,12 +1719,14 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1753,7 +1760,6 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= @@ -1797,12 +1803,15 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go. google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:qDbnxtViX5J6CvFbxeNUSzKgVlDLJ/6L+caxye9+Flo= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1822,6 +1831,7 @@ google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGO google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1840,8 +1850,10 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= diff --git a/messenger/README.md b/messenger/README.md new file mode 100644 index 000000000..36067fc62 --- /dev/null +++ b/messenger/README.md @@ -0,0 +1,331 @@ + + +# Messenger Package + +The Messenger package provides a Go SDK for building distributed messaging applications on top of the Mycelium network infrastructure. It offers a topic-based protocol registration system that enables developers to create custom server/client implementations with optional blockchain identity integration. + +## Overview + +The Messenger package serves as a high-level abstraction over the Mycelium messaging infrastructure, providing: + +- **Topic-based message routing**: Register handlers for specific message topics +- **Bidirectional communication**: Send messages and receive replies +- **Optional blockchain identity**: Integrate with ThreeFold Chain for identity management +- **JSON-RPC support**: Built-in JSON-RPC server/client implementation + +## Mycelium Infrastructure + +The Mycelium daemon provides distinct communication methods: + +1. HTTP REST server `:8989` +2. RPC server `:9090` +3. CLI `mycelium message` calling the reset server + +For more info check [mycelium docs](https://github.com/threefoldtech/mycelium/tree/master/docs) + + +### Usage Patterns + +- **Sending Messages**: Uses CLI command `mycelium message send [--topic ] [--wait] [--timeout ]` +- **Receiving Messages**: Uses CLI command `mycelium message receive` in a polling loop +- **Sending Replies**: Uses HTTP POST to `/api/v1/messages/reply/{messageId}` +- **Getting Identity**: Uses HTTP GET to `/api/v1/admin` + +## Messenger Core Component + +The Messenger serves as the main orchestration component with the following key features: + +### Topic-Based Protocol Registration + +The Messenger implements a topic-based routing system where different message handlers can be registered for specific topics: + +```go +// Register a handler for a specific topic +messenger.RegisterHandler("my-topic", func(ctx context.Context, message *Message) ([]byte, error) { + // Handle the message + return response, nil +}) +``` + +### Message Structure + +```go +type Message struct { + ID string `json:"id,omitempty"` // Unique message identifier + Topic string `json:"topic,omitempty"` // Message topic for routing + SrcIP string `json:"srcIp,omitempty"` // Source IP address + SrcPK string `json:"srcPk,omitempty"` // Source public key + DstIP string `json:"dstIp,omitempty"` // Destination IP address + DstPK string `json:"dstPk,omitempty"` // Destination public key + Payload string `json:"payload,omitempty"` // Message payload (raw string) +} +``` + +### Core Operations + +- **Send Message**: Send a message to a destination with optional reply waiting +- **Register Handler**: Register topic-specific message handlers +- **Start/Stop Receiver**: Control the message listening loop +- **Send Reply**: Reply to a received message + +## Chain Identity Management + +The package functions independently without chain identity integration by default. However, it offers optional blockchain identity features: + +### Enabling Chain Identity + +To enable chain identity management, use the `WithEnableTwinIdentity(true)` configuration option: + +```go +messenger, err := messenger.NewMessenger( + messenger.WithSubstrateManager(manager), + messenger.WithMnemonic(mnemonic), + messenger.WithEnableTwinIdentity(true), // Enable chain identity +) +``` + +### Required Configuration + +When chain identity is enabled, the following are required: +- **Substrate Manager**: Connection to ThreeFold Chain +- **Identity or Mnemonic**: Either a substrate identity or mnemonic phrase + +### Identity Lifecycle + +1. **Startup**: Automatic identity updates on chain during messenger initialization + - Retrieves Mycelium node information via HTTP API + - Updates the MyceliumTwin mapping on the blockchain + +2. **Message Processing**: Identity retrieval and context storage + - For each incoming message, retrieves the sender's twin ID from the blockchain + - Stores twin ID in the message context for handler use + - Accessible via `TwinIdContextKey` context key + +### MyceliumTwin Storage Mapping + +The chain identity feature leverages the MyceliumTwin storage mapping on the ThreeFold blockchain, which: +- Maps Mycelium public keys to Twin IDs +- Enables identity verification and authorization +- Provides a bridge between Mycelium network and blockchain identity + +## JSON-RPC Implementation + +The package includes a complete JSON-RPC server/client implementation registered under the 'rpc' topic key: + +### JSON-RPC Server + +```go +server := messenger.NewJSONRPCServer(msgr) +server.RegisterHandler("calculator.add", func(ctx context.Context, params json.RawMessage) (interface{}, error) { + // Handle RPC method + return result, nil +}) +server.Start(ctx) +``` + +### JSON-RPC Client + +```go +client := messenger.NewJSONRPCClient(msgr) +var result float64 +err := client.Call(ctx, destination, "calculator.add", []float64{10, 20}, &result) +``` +## Configuration + +### Configuration Options + +The Messenger supports various configuration options through functional options: + +```go +type MessengerOpt func(*Messenger) + +// Available options: +messenger.WithMnemonic(mnemonic) // Set mnemonic phrase +messenger.WithIdentity(identity) // Set substrate identity directly +messenger.WithEnableTwinIdentity(true) // Enable chain identity management +messenger.WithSubstrateManager(manager) // Set substrate manager +messenger.WithBinaryPath("/path/to/mycelium") // Set custom mycelium binary path +messenger.WithAPIAddress("http://localhost:8989") // Set custom API address +``` + +### Default Values + +```go +const ( + DefaultMessengerBinary = "mycelium" // Mycelium binary path + DefaultAPIAddress = "http://127.0.0.1:8989" // Mycelium HTTP API address + DefaultTimeout = 60 // Default timeout in seconds + DefaultRetryListenerInterval = 100 * time.Millisecond // Retry interval for message listener +) +``` + +# Enhanced Signature Verification System + +This document describes the enhanced signature verification system for the TFGrid Messenger, which provides cryptographic authentication of messages using twin identities stored on TFChain. + +## Overview + +The enhanced signature verification system ensures that: +- **Client** sends payload as `(message, twin_id, signature)` where signature is created using the twin's private key stored on TFChain +- **Server** verifies messages by loading the twin's public key from TFChain and validating the cryptographic signature +- **Protocol** maintains the existing JSONRPC logic over Mycelium while adding security + +## Key Components + +### 1. SignedMessage Structure + +```go +type SignedMessage struct { + TwinID uint32 `json:"twin_id"` // Twin ID from TFChain + Message string `json:"message"` // Original message content + Signature string `json:"signature"` // Hex-encoded Ed25519 signature + Timestamp int64 `json:"timestamp"` // Unix timestamp for replay protection +} +``` + +### 2. TwinKeyProvider Interface + +```go +type TwinKeyProvider interface { + // GetTwinPublicKey retrieves the Ed25519 public key for a given twin ID + GetTwinPublicKey(twinID uint32) ([]byte, error) +} +``` + +### 3. Core Functions + +#### Client-Side Functions +- `CreateSignedMessage(twinID, message, identity)` - Creates a cryptographically signed message +- `SendSecureMessage()` - Sends a signed message through the messenger + +#### Server-Side Functions +- `VerifyMessageSignature(signedMsg, keyProvider)` - Verifies signature against TFChain +- `ValidateAndExtractMessage(payload, keyProvider)` - Parses and validates signed messages +- `ParseSignedMessage(payload)` - Parses JSON payload into SignedMessage struct + +## Usage Examples + +### Server Setup + +```go +// Connect to TFChain for twin verification +manager := substrate.NewManager("ws://192.168.1.10:9944") +sub, err := manager.Substrate() +if err != nil { + log.Fatal("Failed to connect to TFChain") +} + +// Create messenger with TFChain verification +msgr, err := messenger.NewMessenger( + messenger.WithSubstrateConnection(sub), +) + +// Register JSONRPC server +server := messenger.NewJSONRPCServer(msgr) +server.RegisterHandler("calculator.add", addHandler) +server.Start(ctx) +``` + +### Client Usage + +```go +// Create identity from mnemonic +identity, err := substrate.NewIdentityFromSr25519Phrase(mnemonic) + +// Get twin ID from TFChain +twinID, err := sub.GetTwinByPubKey(identity.PublicKey()) + +// Send secure signed message +response, err := msgr.SendSecureMessage( + destination, + jsonPayload, + messenger.RPCKey, + twinID, + identity, + true, // wait for reply + 30, // timeout +) +``` + +### Message Flow + +1. **Client Side:** + ``` + Original Message → Sign with Twin Private Key → Create SignedMessage → Send via Mycelium + ``` + +2. **Server Side:** + ``` + Receive Message → Parse SignedMessage → Fetch Twin Public Key from TFChain → Verify Signature → Process if Valid + ``` + +## Security Features + +### Cryptographic Verification +- Uses **Ed25519** signatures for strong cryptographic security +- Twin public keys are retrieved from **TFChain blockchain** ensuring authenticity +- Messages are signed with twin's private key, verified against blockchain-stored public key + +### Replay Protection +- **Timestamp** field in SignedMessage provides basic replay protection +- Server can implement additional nonce-based protection if needed + +### Error Handling +- Comprehensive error messages for debugging +- Graceful fallback to unsigned messaging when TFChain is unavailable +- Clear logging of verification status + +## Examples + +### 1. Basic Signature Example +```bash +cd examples/signature_example +go run main.go +``` + +### 2. Secure JSONRPC Server +```bash +cd examples/jsonrpc/server +go run main.go +``` + +### 3. Enhanced Secure Client +```bash +export MNEMONIC="your twelve word mnemonic phrase here" +cd examples/secure_client +go run main.go +``` + +### 4. JSONRPC Client with Signature Support +```bash +cd examples/jsonrpc/client +go run main.go +``` + +## Configuration + +### Environment Variables +- `MNEMONIC` - Twin's mnemonic phrase for client authentication + +### Constants +- `chainUrl` - TFChain WebSocket endpoint (default: `ws://192.168.1.10:9944`) +- `destination` - Target Mycelium public key or IP address + +## Benefits + +1. **Strong Authentication** - Cryptographic proof of message origin +2. **Blockchain Integration** - Leverages TFChain for decentralized key management +3. **Backward Compatibility** - Graceful fallback when verification is unavailable +4. **Clean Architecture** - Well-organized, minimal, and easy to understand code +5. **Comprehensive Logging** - Clear visibility into verification process + +## Function and Variable Naming + +The enhanced system uses clear, descriptive names: +- `SignedMessage` instead of `SecureSignedRequest` +- `TwinKeyProvider` instead of `TwinPublicKeyVerifier` +- `VerifyMessageSignature` instead of `VerifySecureSignature` +- `CreateSignedMessage` instead of `CreateSecureSignedRequest` +- `ValidateAndExtractMessage` instead of `ValidateAndExtractSecureMessage` + +This naming convention better describes the actual functionality and makes the code more maintainable. diff --git a/messenger/examples/client/client b/messenger/examples/client/client new file mode 100755 index 000000000..f3e60f08b Binary files /dev/null and b/messenger/examples/client/client differ diff --git a/messenger/examples/client/main.go b/messenger/examples/client/main.go new file mode 100644 index 000000000..46a87f964 --- /dev/null +++ b/messenger/examples/client/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" + "github.com/threefoldtech/tfgrid-sdk-go/messenger" +) + +const ( + chainUrl = "wss://tfchain.dev.grid.tf" +) + +func main() { + mnemonic := os.Getenv("MNEMONIC") + + var dest string + flag.StringVar(&dest, "dest", "", "destination public key or IP address") + flag.Parse() + + log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04"}).With().Timestamp().Logger() + + manager := substrate.NewManager(chainUrl) + sub, err := manager.Substrate() + if err != nil { + log.Warn().Err(err).Msg("Failed to connect to TFChain - will send unsigned messages") + sub = nil + } + + // TODO: no need to expose messenger, just use the client directly + msgr, err := messenger.NewMessenger(messenger.WithChain(sub)) + if err != nil { + fmt.Printf("Failed to create messenger client: %v\n", err) + os.Exit(1) + } + defer msgr.Close() + + // TODO: abstracted clean api + rpcClient := messenger.NewJSONRPCClient(msgr) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // TODO: should be part of the call? maybe in the client itself? + id, err := substrate.NewIdentityFromSr25519Phrase(mnemonic) + if err != nil { + log.Error().Err(err).Msg("Failed to create identity from mnemonic") + os.Exit(1) + } + twinID, err := sub.GetTwinByPubKey(id.PublicKey()) + if err != nil { + log.Error().Err(err).Msg("Failed to get twin ID") + os.Exit(1) + } + var result float64 + err = rpcClient.Call(ctx, dest, twinID, id, "calculator.add", []float64{10, 20}, &result) + if err != nil { + log.Error().Err(err).Msg("Failed to call calculator.add") + os.Exit(1) + } + + fmt.Println(result) +} diff --git a/messenger/examples/jsonrpc/client/client b/messenger/examples/jsonrpc/client/client new file mode 100755 index 000000000..5534046bf Binary files /dev/null and b/messenger/examples/jsonrpc/client/client differ diff --git a/messenger/examples/jsonrpc/server/server b/messenger/examples/jsonrpc/server/server new file mode 100755 index 000000000..8be981339 Binary files /dev/null and b/messenger/examples/jsonrpc/server/server differ diff --git a/messenger/examples/mycelium-mesh b/messenger/examples/mycelium-mesh new file mode 100644 index 000000000..18e673648 --- /dev/null +++ b/messenger/examples/mycelium-mesh @@ -0,0 +1,99 @@ +#!/usr/bin/bash + +set -e + +NATNET=172.16.0.0/16 +NUMOFNS=2 +BRIDGE=br0 +BRIDGE_ADDR=172.16.0.1/16 +EXT_IF="wlp3s0" # change if your outbound interface differs + +function up() { + echo "[+] Creating bridge ${BRIDGE}" + sudo ip link add ${BRIDGE} type bridge || true + sudo ip addr add ${BRIDGE_ADDR} dev ${BRIDGE} 2>/dev/null || true + sudo ip link set ${BRIDGE} up + + echo "[+] Enabling IP forwarding" + sudo sysctl -w net.ipv4.ip_forward=1 + + echo "[+] Configuring NAT" + sudo nft add table ip nat 2>/dev/null || true + sudo nft add chain ip nat postrouting '{ type nat hook postrouting priority 100; }' 2>/dev/null || true + sudo nft add rule ip nat postrouting oifname "${EXT_IF}" masquerade 2>/dev/null || true + + echo "[+] Starting mycelium on host" + nohup sudo mycelium --key-file host.bin >host.out & + + for i in $(seq 1 $NUMOFNS); do + local name=n-${i} + local veth_host=out_${i} + local veth_ns=in_${i} + + echo "[+] Setting up namespace ${name}" + sudo ip netns add ${name} + sudo ip link add ${veth_ns} type veth peer name ${veth_host} + sudo ip link set ${veth_ns} netns ${name} + + sudo ip netns exec ${name} ip link set lo up + sudo ip netns exec ${name} ip link set ${veth_ns} up + sudo ip netns exec ${name} ip addr add 172.16.${i}.2/16 dev ${veth_ns} + sudo ip netns exec ${name} ip route add default via 172.16.0.1 + + sudo ip link set ${veth_host} master ${BRIDGE} + sudo ip link set ${veth_host} up + + echo "[+] Configuring DNS resolvers for ${name}" + sudo mkdir -p /etc/netns/${name} + echo "nameserver 8.8.8.8" | sudo tee /etc/netns/${name}/resolv.conf > /dev/null + echo "nameserver 1.1.1.1" | sudo tee -a /etc/netns/${name}/resolv.conf > /dev/null + + echo "[+] Starting mycelium in ${name}" + nohup sudo ip netns exec ${name} mycelium --key-file ${name}.bin --peers tcp://172.16.${i}.1:9651 >${i}.out & + done + + echo "[+] Setup complete" +} + +function down() { + echo "[+] Killing mycelium" + sudo pkill -9 mycelium || true + + for i in $(seq 1 $NUMOFNS); do + sudo ip link del out_${i} 2>/dev/null || true + sudo ip netns del n-${i} 2>/dev/null || true + sudo rm -rf /etc/netns/n-${i} 2>/dev/null || true + done + + echo "[+] Removing bridge ${BRIDGE}" + sudo ip link set ${BRIDGE} down 2>/dev/null || true + sudo ip link del ${BRIDGE} 2>/dev/null || true +} + +function show_help() { + echo "Usage: $0 [OPTION]" + echo "Manage mycelium network setup with network namespaces" + echo "" + echo "Options:" + echo " -u, --up Set up the mycelium network and namespaces" + echo " -d, --down Tear down the mycelium network and namespaces" + echo " -h, --help Show this help message" +} + +case "${1:-}" in + -u|--up) + up + ;; + -d|--down) + down + ;; + -h|--help) + show_help + ;; + *) + echo "Error: Unknown option '$1'" + show_help + exit 1 + ;; +esac + diff --git a/messenger/examples/server/main.go b/messenger/examples/server/main.go new file mode 100644 index 000000000..2a2ce0de9 --- /dev/null +++ b/messenger/examples/server/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + + substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" + "github.com/threefoldtech/tfgrid-sdk-go/messenger" +) + +type Calculator struct{} + +func (c *Calculator) Add(a, b float64) float64 { + return a + b +} + +func addHandler(ctx context.Context, calc *Calculator, params json.RawMessage) (interface{}, error) { + var args []float64 + if err := json.Unmarshal(params, &args); err != nil { + return nil, fmt.Errorf("invalid parameters: %w", err) + } + + if len(args) != 2 { + return nil, fmt.Errorf("expected 2 parameters, got %d", len(args)) + } + + // TODO: where this is coming from? + twinID, ok := ctx.Value(messenger.TwinIDContextKey).(uint32) + if !ok { + log.Warn().Msg("can't find twin id") + return nil, fmt.Errorf("can't find twin id") + } + + log.Info().Uint32("twin_id", twinID).Msg("verified request from twin") + result := calc.Add(args[0], args[1]) + return result, nil +} + +const ( + chainUrl = "wss://tfchain.dev.grid.tf" +) + +func main() { + log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04"}).With().Timestamp().Logger() + + // Get server mnemonic for signing replies + serverMnemonic := os.Getenv("SERVER_MNEMONIC") + if serverMnemonic == "" { + log.Warn().Msg("SERVER_MNEMONIC not set - server will send unsigned replies") + } + + manager := substrate.NewManager(chainUrl) + sub, err := manager.Substrate() + if err != nil { + log.Warn().Err(err).Msg("Failed to connect to TFChain - operating without signature verification") + sub = nil + } + + // Configure messenger with optional server identity + var opts []messenger.MessengerOpt + opts = append(opts, messenger.WithChain(sub)) + + // Add server identity if mnemonic is provided + if serverMnemonic != "" && sub != nil { + serverIdentity, err := substrate.NewIdentityFromSr25519Phrase(serverMnemonic) + if err != nil { + log.Error().Err(err).Msg("Failed to create server identity from mnemonic") + os.Exit(1) + } + + serverTwinID, err := sub.GetTwinByPubKey(serverIdentity.PublicKey()) + if err != nil { + log.Error().Err(err).Msg("Failed to get server twin ID") + os.Exit(1) + } + + opts = append(opts, messenger.WithServerIdentity(serverTwinID, serverIdentity)) + log.Info().Uint32("server_twin_id", serverTwinID).Msg("Server identity configured - replies will be signed") + } + + msgr, err := messenger.NewMessenger(opts...) + if err != nil { + fmt.Printf("Failed to create messenger: %v\n", err) + os.Exit(1) + } + defer msgr.Close() + + server := messenger.NewJSONRPCServer(msgr) + calc := &Calculator{} + + server.RegisterHandler("calculator.add", func(ctx context.Context, params json.RawMessage) (interface{}, error) { + return addHandler(ctx, calc, params) + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + if err := server.Start(ctx); err != nil { + fmt.Printf("Failed to start server: %v\n", err) + os.Exit(1) + } + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + + server.Stop() + fmt.Println("Server stopped.") +} diff --git a/messenger/examples/server/server b/messenger/examples/server/server new file mode 100755 index 000000000..8cab9ec31 Binary files /dev/null and b/messenger/examples/server/server differ diff --git a/messenger/go.mod b/messenger/go.mod new file mode 100644 index 000000000..468a227ad --- /dev/null +++ b/messenger/go.mod @@ -0,0 +1,32 @@ +module github.com/threefoldtech/tfgrid-sdk-go/messenger + +go 1.21 + +require ( + github.com/rs/zerolog v1.26.0 + github.com/threefoldtech/tfchain/clients/tfchain-client-go v0.0.0-20241127100051-77e684bcb1b2 +) + +require ( + github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12 // indirect + github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/deckarep/golang-set v1.8.0 // indirect + github.com/decred/base58 v1.0.4 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/ethereum/go-ethereum v1.10.20 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/gtank/merlin v0.1.1 // indirect + github.com/gtank/ristretto255 v0.1.2 // indirect + github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 // indirect + github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect + github.com/pierrec/xxHash v0.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rs/cors v1.8.2 // indirect + github.com/vedhavyas/go-subkey v1.0.3 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect +) diff --git a/messenger/go.sum b/messenger/go.sum new file mode 100644 index 000000000..2e4f3171a --- /dev/null +++ b/messenger/go.sum @@ -0,0 +1,111 @@ +github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= +github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12 h1:DCYWIBOalB0mKKfUg2HhtGgIkBbMA1fnlnkZp7fHB18= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12/go.mod h1:5g1oM4Zu3BOaLpsKQ+O8PAv2kNuq+kPcA1VzFbsSqxE= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA= +github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/go-ethereum v1.10.20 h1:75IW830ClSS40yrQC1ZCMZCt5I+zU16oqId2SiQwdQ4= +github.com/ethereum/go-ethereum v1.10.20/go.mod h1:LWUN82TCHGpxB3En5HVmLLzPD7YSrEUFmFfN1nKkVN0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= +github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 h1:4zOlv2my+vf98jT1nQt4bT/yKWUImevYPJ2H344CloE= +github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6/go.mod h1:r/8JmuR0qjuCiEhAolkfvdZgmPiHTnJaG0UXCSeR1Zo= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk= +github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU= +github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo= +github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= +github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/threefoldtech/tfchain/clients/tfchain-client-go v0.0.0-20241127100051-77e684bcb1b2 h1:VW2J36F8g/kJn4IkY0JiRFmb1gFcdjiOyltfJLJ0mYU= +github.com/threefoldtech/tfchain/clients/tfchain-client-go v0.0.0-20241127100051-77e684bcb1b2/go.mod h1:cOL5YgHUmDG5SAXrsZxFjUECRQQuAqOoqvXhZG5sEUw= +github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= +github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/vedhavyas/go-subkey v1.0.3 h1:iKR33BB/akKmcR2PMlXPBeeODjWLM90EL98OrOGs8CA= +github.com/vedhavyas/go-subkey v1.0.3/go.mod h1:CloUaFQSSTdWnINfBRFjVMkWXZANW+nd8+TI5jYcl6Y= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/messenger/jsonrpc.go b/messenger/jsonrpc.go new file mode 100644 index 000000000..c0faf4cd5 --- /dev/null +++ b/messenger/jsonrpc.go @@ -0,0 +1,201 @@ +package messenger + +import ( + "context" + "encoding/json" + "fmt" + "time" + + substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" +) + +const ( + ErrCodeParseError = -32700 + ErrCodeInvalidRequest = -32600 + ErrCodeMethodNotFound = -32601 + ErrCodeInvalidParams = -32602 + ErrCodeInternalError = -32000 + + RPCKey = "rpc" +) + +type JSONRPCRequest struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params interface{} `json:"params,omitempty"` + ID string `json:"id"` +} + +type JSONRPCResponse struct { + JSONRPC string `json:"jsonrpc"` + Result interface{} `json:"result,omitempty"` + Error *RPCError `json:"error,omitempty"` + ID string `json:"id"` +} + +type RPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +type RPCHandlerFunc func(ctx context.Context, params json.RawMessage) (interface{}, error) + +// TODO: should be abstracted in an interface demo main functions +type JSONRPCServer struct { + messenger *Messenger + handlers map[string]RPCHandlerFunc +} + +// NewJSONRPCServer creates a new JSON-RPC server with the given client +func NewJSONRPCServer(messenger *Messenger) *JSONRPCServer { + return &JSONRPCServer{ + messenger: messenger, + handlers: make(map[string]RPCHandlerFunc), + } +} + +func (s *JSONRPCServer) RegisterHandler(method string, handler RPCHandlerFunc) { + s.handlers[method] = handler +} + +func (s *JSONRPCServer) Start(ctx context.Context) error { + s.messenger.RegisterHandler(RPCKey, s.handleRPCMessage) + return s.messenger.StartReceiver(ctx) +} + +func (s *JSONRPCServer) Stop() { + s.messenger.StopReceiver() +} + +// TODO: can this function be cleaner? +func (s *JSONRPCServer) handleRPCMessage(ctx context.Context, message *Message) ([]byte, error) { + var request JSONRPCRequest + if err := json.Unmarshal([]byte(message.Payload), &request); err != nil { + response := JSONRPCResponse{ + JSONRPC: "2.0", + Error: &RPCError{ + Code: ErrCodeParseError, + Message: "Parse error", + Data: err.Error(), + }, + ID: message.ID, + } + + responseBytes, _ := json.Marshal(response) + return responseBytes, nil + } + + response := JSONRPCResponse{ + JSONRPC: "2.0", + ID: request.ID, + } + + if request.JSONRPC != "2.0" { + response.Error = &RPCError{ + Code: ErrCodeInvalidRequest, + Message: "Invalid Request", + Data: "jsonrpc version must be 2.0", + } + responseBytes, _ := json.Marshal(response) + return responseBytes, nil + } + + handler, ok := s.handlers[request.Method] + if !ok { + response.Error = &RPCError{ + Code: ErrCodeMethodNotFound, + Message: "Method not found", + Data: request.Method, + } + responseBytes, _ := json.Marshal(response) + return responseBytes, nil + } + + params := json.RawMessage("{}") + if request.Params != nil { + var err error + if params, err = json.Marshal(request.Params); err != nil { + response.Error = &RPCError{ + Code: ErrCodeInvalidParams, + Message: "Invalid params", + Data: err.Error(), + } + responseBytes, _ := json.Marshal(response) + return responseBytes, nil + } + } + + result, err := handler(ctx, params) + if err != nil { + response.Error = &RPCError{ + Code: ErrCodeInternalError, + Message: err.Error(), + } + } else { + response.Result = result + } + + responseBytes, _ := json.Marshal(response) + return responseBytes, nil +} + +// TODO: do we actually need to have client/server or we should only expose on thing +// TODO: do we need to expose messenger? or should we just expose the jsonrpc client/server +type JSONRPCClient struct { + messenger *Messenger +} + +// NewJSONRPCClient creates a new JSON-RPC client with the given client +func NewJSONRPCClient(messenger *Messenger) *JSONRPCClient { + return &JSONRPCClient{ + messenger: messenger, + } +} + +func (c *JSONRPCClient) Call(ctx context.Context, destination string, twinID uint32, identity substrate.Identity, method string, params interface{}, result interface{}) error { + // TODO: this encode/decode should be separated + request := JSONRPCRequest{ + JSONRPC: "2.0", + Method: method, + Params: params, + ID: fmt.Sprintf("request-%d", time.Now().UnixNano()), + } + + requestBytes, err := json.Marshal(request) + if err != nil { + return fmt.Errorf("failed to marshal request: %w", err) + } + + // TODO: all should be signed + msg, err := c.messenger.SendSignedMessage(destination, string(requestBytes), RPCKey, twinID, identity, true, 0) + if err != nil { + return fmt.Errorf("failed to send RPC request: %w", err) + } + + if msg == nil { + return fmt.Errorf("no response received") + } + + var response JSONRPCResponse + if err := json.Unmarshal([]byte(msg.Payload), &response); err != nil { + return fmt.Errorf("failed to parse RPC response: %w", err) + } + + if response.Error != nil { + return fmt.Errorf("RPC error: %s (code: %d)", response.Error.Message, response.Error.Code) + } + + if result != nil && response.Result != nil { + resultBytes, err := json.Marshal(response.Result) + if err != nil { + return fmt.Errorf("failed to marshal result: %w", err) + } + + if err := json.Unmarshal(resultBytes, result); err != nil { + return fmt.Errorf("failed to unmarshal result: %w", err) + } + } + + return nil +} diff --git a/messenger/messager.go b/messenger/messager.go new file mode 100644 index 000000000..ef6fefd98 --- /dev/null +++ b/messenger/messager.go @@ -0,0 +1,456 @@ +package messenger + +import ( + "context" + "encoding/json" + "fmt" + "os/exec" + "strings" + "sync" + "time" + + "github.com/rs/zerolog/log" + substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" +) + +const ( + // DefaultMessengerBinary is the default path to the Mycelium binary + DefaultMessengerBinary = "mycelium" + + // DefaultAPIAddress is the default address for the Mycelium API + DefaultAPIAddress = "http://127.0.0.1:8989" + + // DefaultTimeout is the default timeout for sending messages + DefaultTimeout = 60 // seconds + + // DefaultRetryListenerInterval is the default interval for retrying the message listener + DefaultRetryListenerInterval = 100 * time.Millisecond +) + +// Context key for twin ID +type twinIDCtx struct{} + +var TwinIDContextKey = twinIDCtx{} + +// Message represents a message structure used in the Mycelium messaging system +type Message struct { + ID string `json:"id,omitempty"` + Topic string `json:"topic,omitempty"` + SrcIP string `json:"srcIp,omitempty"` + SrcPK string `json:"srcPk,omitempty"` + DstIP string `json:"dstIp,omitempty"` + DstPK string `json:"dstPk,omitempty"` + Payload string `json:"payload,omitempty"` +} + +type MessageHandlerFunc func(ctx context.Context, message *Message) ([]byte, error) + +type Messenger struct { + BinaryPath string + APIAddress string + Timeout int + + // Substrate connection for twin verification + substrateConn *substrate.Substrate + twinKeyProvider TwinKeyProvider + + // Server identity for signing replies + serverTwinID uint32 + serverIdentity substrate.Identity + + receiveHandlers map[string]MessageHandlerFunc + stopCh chan struct{} + wg sync.WaitGroup + mutex sync.RWMutex +} + +// MessengerOpt is a function that configures a Client +type MessengerOpt func(*Messenger) + +// WithChain sets the substrate connection for blockchain verification +func WithChain(sub *substrate.Substrate) MessengerOpt { + return func(c *Messenger) { + c.substrateConn = sub + c.twinKeyProvider = NewTFChainKeyProvider(sub) + } +} + +// WithBinaryPath sets the binary path for the messenger +func WithBinaryPath(binaryPath string) MessengerOpt { + return func(c *Messenger) { + c.BinaryPath = binaryPath + } +} + +// WithAPIAddress sets the API address for the messenger +func WithAPIAddress(apiAddress string) MessengerOpt { + return func(c *Messenger) { + c.APIAddress = apiAddress + } +} + +// WithServerIdentity sets the server identity for signing replies +func WithServerIdentity(twinID uint32, identity substrate.Identity) MessengerOpt { + return func(c *Messenger) { + c.serverTwinID = twinID + c.serverIdentity = identity + } +} + +// NewMessenger creates a new mycelium message subsystem client with the given options +func NewMessenger(opts ...MessengerOpt) (*Messenger, error) { + messenger := &Messenger{ + BinaryPath: DefaultMessengerBinary, + Timeout: DefaultTimeout, + APIAddress: DefaultAPIAddress, + receiveHandlers: make(map[string]MessageHandlerFunc), + stopCh: make(chan struct{}), + } + + for _, opt := range opts { + opt(messenger) + } + + return messenger, nil +} + +// the main rpc handler for the messenger for JSONRPC messages +func (c *Messenger) RegisterHandler(topic string, handler MessageHandlerFunc) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.receiveHandlers[topic] = handler +} + +func (c *Messenger) SendMessage(destination, payload string, topic string, waitForReply bool, timeout int) (*Message, error) { + log.Debug(). + Str("destination", destination). + Msg("sending message") + args := []string{"message", "send", destination, payload} + + if waitForReply { + args = append(args, "--wait") + + if timeout > 0 { + args = append(args, "--timeout", fmt.Sprintf("%d", timeout)) + } else if c.Timeout > 0 { + args = append(args, "--timeout", fmt.Sprintf("%d", c.Timeout)) + } + } + if topic != "" { + args = append(args, "--topic", topic) + } + + cmd := exec.Command(c.BinaryPath, args...) + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("failed to send message: %s: %w", string(output), err) + } + + if !waitForReply { + return nil, nil + } + + responseStr := string(output) + responseStr = strings.TrimSpace(responseStr) + + if responseStr == "" { + return nil, nil + } + + var msg Message + if err := json.Unmarshal([]byte(responseStr), &msg); err != nil { + return nil, fmt.Errorf("failed to parse message response: %w", err) + } + + log.Debug(). + Str("source", msg.SrcPK). + Str("msg_id", msg.ID). + Any("msg", msg). + Msg("received reply") + return &msg, nil +} + +// SendSignedMessage sends a message with cryptographic signature verification +func (c *Messenger) SendSignedMessage(destination, payload string, topic string, twinID uint32, identity substrate.Identity, waitForReply bool, timeout int) (*Message, error) { + signedMsg, err := CreateSignedMessage(twinID, payload, identity) + if err != nil { + return nil, fmt.Errorf("failed to create signed message: %w", err) + } + + signedPayload, err := json.Marshal(signedMsg) + if err != nil { + return nil, fmt.Errorf("failed to marshal signed message: %w", err) + } + + return c.SendMessage(destination, string(signedPayload), topic, waitForReply, timeout) +} + +// SendReply sends a reply message to the original sender +// TODO: this should be part of the SEND +// TODO: should sign/verify as well +func (c *Messenger) SendReply(originalMessageID, destination, payload string) error { + log.Debug(). + Str("destination", destination). + Str("msg_id", originalMessageID). + Str("payload", payload). + Msg("replying to message") + + // Build command args - removed problematic single quote wrapping + args := []string{"message", "send", destination, payload, "--reply-to", originalMessageID} + + if c.Timeout > 0 { + args = append(args, "--timeout", fmt.Sprintf("%d", c.Timeout)) + } + + cmd := exec.Command(c.BinaryPath, args...) + log.Debug().Str("command", cmd.String()).Msg("executing command to send reply") + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to send reply: %s: %w", string(output), err) + } + + log.Debug().Str("output", string(output)).Msg("reply sent successfully") + return nil +} + +// SendSignedReply sends a cryptographically signed reply message +func (c *Messenger) SendSignedReply(originalMessageID, destination, payload string, twinID uint32, identity substrate.Identity) error { + log.Debug(). + Str("destination", destination). + Str("msg_id", originalMessageID). + Uint32("twin_id", twinID). + Str("payload", payload). + Msg("sending signed reply") + + // Create signed payload + signedPayload, err := CreateSignedMessage(twinID, payload, identity) + if err != nil { + return fmt.Errorf("failed to create signed reply: %w", err) + } + + // Convert signed message to JSON + signedPayloadBytes, err := json.Marshal(signedPayload) + if err != nil { + return fmt.Errorf("failed to marshal signed reply: %w", err) + } + + // Build command args for signed reply + args := []string{"message", "send", destination, string(signedPayloadBytes), "--reply-to", originalMessageID} + + if c.Timeout > 0 { + args = append(args, "--timeout", fmt.Sprintf("%d", c.Timeout)) + } + + cmd := exec.Command(c.BinaryPath, args...) + log.Debug().Str("command", cmd.String()).Msg("executing command to send signed reply") + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to send signed reply: %s: %w", string(output), err) + } + + log.Debug().Str("output", string(output)).Msg("signed reply sent successfully") + return nil +} + +func (c *Messenger) ReceiveMessage() (*Message, error) { + args := []string{"message", "receive"} + + cmd := exec.Command(c.BinaryPath, args...) + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("failed to receive message: %s: %w", string(output), err) + } + + responseStr := string(output) + responseStr = strings.TrimSpace(responseStr) + + if responseStr == "" { + return nil, nil + } + + var msg Message + if err := json.Unmarshal([]byte(responseStr), &msg); err != nil { + return nil, fmt.Errorf("failed to parse message: %w", err) + } + + log.Debug(). + Str("source", msg.SrcPK). + Str("msg_id", msg.ID). + Msg("received message") + return &msg, nil +} + +func (c *Messenger) StartReceiver(ctx context.Context) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.wg.Add(1) + go func() { + defer c.wg.Done() + c.receiveLoop(ctx) + }() + + return nil +} + +func (c *Messenger) StopReceiver() { + c.mutex.Lock() + defer c.mutex.Unlock() + + close(c.stopCh) + c.wg.Wait() + c.stopCh = make(chan struct{}) +} + +func (c *Messenger) receiveLoop(ctx context.Context) { + for { + select { + case <-c.stopCh: + return + case <-ctx.Done(): + return + default: + log.Debug().Msg("listening for message...") + msg, err := c.ReceiveMessage() + if err != nil { + // Check if this is a graceful shutdown scenario + if isGracefulShutdownError(err) || ctx.Err() != nil { + log.Info().Msg("shutting down message listener gracefully") + return + } + log.Error().Err(err).Msg("failed receiving message") + time.Sleep(DefaultRetryListenerInterval) + continue + } + + if msg == nil { + time.Sleep(DefaultRetryListenerInterval) + continue + } + + go c.processMessage(ctx, msg) + } + } +} + +// TODO: each reply with error, should follow the same pattern +func (c *Messenger) processMessage(ctx context.Context, message *Message) { + sendErrorReply := func(errorMsg string) { + // Use signed reply if server identity is configured + if c.serverIdentity != nil && c.serverTwinID != 0 { + if err := c.SendSignedReply(message.ID, message.SrcPK, errorMsg, c.serverTwinID, c.serverIdentity); err != nil { + log.Error().Err(err).Str("msg_id", message.ID). + Msg("failed to send signed error reply") + } + } else { + log.Warn().Str("msg_id", message.ID).Msg("sending unsigned error reply - server identity not configured") + if err := c.SendReply(message.ID, message.SrcPK, errorMsg); err != nil { + log.Error().Err(err).Str("msg_id", message.ID). + Msg("failed to send error reply") + } + } + } + + // Verify signature against blockchain if key provider is available + var twinID uint32 + var originalMessage string + var err error + + if c.twinKeyProvider != nil { + // Use secure blockchain-based verification + twinID, originalMessage, err = ValidateAndExtractMessage(message.Payload, c.twinKeyProvider) + if err != nil { + log.Error().Err(err).Str("msg_id", message.ID). + Msg("TFChain signature verification failed") + sendErrorReply(fmt.Sprintf("signature verification failed: %v", err)) + return + } + + log.Debug(). + Uint32("twin_id", twinID). + Str("msg_id", message.ID). + Msg("signature verification successful") + } else { + log.Warn().Str("msg_id", message.ID). + Msg("no TFChain connection available - message not verified against blockchain") + // For backward compatibility, allow unverified messages with warning + originalMessage = message.Payload + twinID = 0 // Unknown twin + + // return + } + + verifiedMessage := &Message{ + ID: message.ID, + Topic: message.Topic, + SrcIP: message.SrcIP, + SrcPK: message.SrcPK, + DstIP: message.DstIP, + DstPK: message.DstPK, + Payload: originalMessage, + } + + ctx = context.WithValue(ctx, TwinIDContextKey, twinID) + + // decide which handler group to use based on the topic + c.mutex.RLock() + handler, exists := c.receiveHandlers[message.Topic] + c.mutex.RUnlock() + + if !exists { + log.Error().Str("topic", message.Topic). + Msg("no handler registered for topic") + sendErrorReply(fmt.Sprintf("no handler registered for topic: %s", message.Topic)) + return + } + + response, err := handler(ctx, verifiedMessage) + if err != nil { + log.Error().Err(err).Str("msg_id", message.ID). + Msg("failed to process message") + sendErrorReply(fmt.Sprintf("failed to process message: %v", err)) + return + } + + if response != nil { + // Use signed reply if server identity is configured + if c.serverIdentity != nil && c.serverTwinID != 0 { + if err := c.SendSignedReply(message.ID, message.SrcPK, string(response), c.serverTwinID, c.serverIdentity); err != nil { + log.Error().Err(err).Str("msg_id", message.ID). + Msg("failed to send signed reply") + } + } else { + log.Warn().Str("msg_id", message.ID).Msg("sending unsigned reply - server identity not configured") + if err := c.SendReply(message.ID, message.SrcPK, string(response)); err != nil { + log.Error().Err(err).Str("msg_id", message.ID). + Msg("failed to send reply") + } + } + } + + log.Debug().Msg("message processed successfully") +} + +func (c *Messenger) Close() { + c.StopReceiver() + + // Close substrate connection if available + if c.substrateConn != nil { + c.substrateConn.Close() + c.substrateConn = nil + c.twinKeyProvider = nil + } +} + +// isGracefulShutdownError checks if an error is related to graceful shutdown +func isGracefulShutdownError(err error) bool { + if err == nil { + return false + } + errStr := err.Error() + return strings.Contains(errStr, "context canceled") || + strings.Contains(errStr, "context deadline exceeded") || + strings.Contains(errStr, "signal: interrupt") || + strings.Contains(errStr, "receive cancelled") || + strings.Contains(errStr, "receive interrupted") +} diff --git a/messenger/signature.go b/messenger/signature.go new file mode 100644 index 000000000..4bb7c1c74 --- /dev/null +++ b/messenger/signature.go @@ -0,0 +1,164 @@ +package messenger + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "time" + + sr25519 "github.com/ChainSafe/go-schnorrkel" + "github.com/gtank/merlin" + substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" +) + +// SignedMessage represents a cryptographically signed message with twin verification +// This structure ensures that messages are authenticated against the TFChain using SR25519 +type SignedMessage struct { + TwinID uint32 `json:"twin_id"` // Twin ID from TFChain + Message string `json:"message"` // Original message content + Signature string `json:"signature"` // Hex-encoded SR25519 signature + Timestamp int64 `json:"timestamp"` // Unix timestamp for replay protection +} + +// TwinKeyProvider defines the interface for retrieving twin public keys from blockchain +type TwinKeyProvider interface { + // GetTwinPublicKey retrieves the SR25519 public key for a given twin ID + GetTwinPublicKey(twinID uint32) ([]byte, error) +} + +// signingContext creates the signing context used by SR25519 +func signingContext(msg []byte) *merlin.Transcript { + return sr25519.NewSigningContext([]byte("substrate"), msg) +} + +// TFChainKeyProvider implements TwinKeyProvider using TFChain substrate connection +type TFChainKeyProvider struct { + substrate *substrate.Substrate +} + +// NewTFChainKeyProvider creates a new TFChain-based twin key provider +func NewTFChainKeyProvider(sub *substrate.Substrate) *TFChainKeyProvider { + return &TFChainKeyProvider{substrate: sub} +} + +// GetTwinPublicKey retrieves the SR25519 public key for a twin from TFChain +func (p *TFChainKeyProvider) GetTwinPublicKey(twinID uint32) ([]byte, error) { + twin, err := p.substrate.GetTwin(twinID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve twin %d from TFChain: %w", twinID, err) + } + + //TODO: is this right? + accountBytes := twin.Account[:] + if len(accountBytes) < 32 { // SR25519 public key size + return nil, fmt.Errorf("invalid account ID length for twin %d: expected at least %d bytes, got %d", + twinID, 32, len(accountBytes)) + } + + return accountBytes[:32], nil +} + +// VerifyMessageSignature verifies a signed message against the twin's public key from TFChain +func VerifyMessageSignature(signedMsg *SignedMessage, keyProvider TwinKeyProvider) error { + publicKeyBytes, err := keyProvider.GetTwinPublicKey(signedMsg.TwinID) + if err != nil { + return fmt.Errorf("failed to retrieve twin public key: %w", err) + } + + if len(publicKeyBytes) != 32 { // SR25519 public key size + return fmt.Errorf("invalid public key length for twin %d: expected %d bytes, got %d", + signedMsg.TwinID, 32, len(publicKeyBytes)) + } + + signatureBytes, err := hex.DecodeString(signedMsg.Signature) + if err != nil { + return fmt.Errorf("invalid signature encoding: %w", err) + } + + if len(signatureBytes) != 64 { // SR25519 signature size + return fmt.Errorf("invalid signature length: expected %d bytes, got %d", + 64, len(signatureBytes)) + } + + // Convert public key bytes to SR25519 public key + var pubKeyArray [32]byte + copy(pubKeyArray[:], publicKeyBytes) + publicKey := new(sr25519.PublicKey) + if err := publicKey.Decode(pubKeyArray); err != nil { + return fmt.Errorf("failed to decode SR25519 public key: %w", err) + } + + // Convert signature bytes to SR25519 signature + var sigArray [64]byte + copy(sigArray[:], signatureBytes) + signature := new(sr25519.Signature) + if err := signature.Decode(sigArray); err != nil { + return fmt.Errorf("failed to decode SR25519 signature: %w", err) + } + + messageBytes := []byte(signedMsg.Message) + + // Verify the signature using SR25519 + valid, err := publicKey.Verify(signature, signingContext(messageBytes)) + if err != nil { + return fmt.Errorf("SR25519 signature verification error: %w", err) + } + + if !valid { + return fmt.Errorf("cryptographic signature verification failed for twin %d", signedMsg.TwinID) + } + + return nil +} + +// ParseSignedMessage parses a JSON payload into a SignedMessage struct +func ParseSignedMessage(payload string) (*SignedMessage, error) { + var signedMsg SignedMessage + if err := json.Unmarshal([]byte(payload), &signedMsg); err != nil { + return nil, fmt.Errorf("failed to parse signed message: %w", err) + } + + if signedMsg.TwinID == 0 { + return nil, fmt.Errorf("twin_id is required and must be greater than 0") + } + if signedMsg.Message == "" { + return nil, fmt.Errorf("message content is required") + } + if signedMsg.Signature == "" { + return nil, fmt.Errorf("signature is required") + } + + return &signedMsg, nil +} + +// ValidateAndExtractMessage validates a signed message against TFChain and extracts the content +// Returns: twinID, originalMessage, error +func ValidateAndExtractMessage(payload string, keyProvider TwinKeyProvider) (uint32, string, error) { + signedMsg, err := ParseSignedMessage(payload) + if err != nil { + return 0, "", fmt.Errorf("message parsing failed: %w", err) + } + + if err := VerifyMessageSignature(signedMsg, keyProvider); err != nil { + return 0, "", fmt.Errorf("signature verification failed: %w", err) + } + + return signedMsg.TwinID, signedMsg.Message, nil +} + +// CreateSignedMessage creates a cryptographically signed message using twin's private key +func CreateSignedMessage(twinID uint32, message string, identity substrate.Identity) (*SignedMessage, error) { + signature, err := identity.Sign([]byte(message)) + if err != nil { + return nil, fmt.Errorf("failed to sign message with twin identity: %w", err) + } + + signedMsg := &SignedMessage{ + TwinID: twinID, + Message: message, + Signature: hex.EncodeToString(signature), + Timestamp: time.Now().Unix(), + } + + return signedMsg, nil +}