Skip to content

Commit 13b9ce9

Browse files
diorwavekrasiroot
authored
reporter: sign via SignTx allowlist instead of blind SignRaw (#36)
* Add remote signer keyring to reporter client via --remote-signer-addr flag * Add mTLS support for remote signer in reporter client * reporter: sign via SignTx allowlist instead of blind SignRaw * update golangci-lint error * update golang lint error * reporter: address review - SignTx for all ops, drop else, defer on init, generic Dockerfile * reporter: broadcast returns on the first accepting RPC so a slow endpoint cannot block the caller --------- Co-authored-by: krasi <root@ns1028910.ip-40-160-21.us> Co-authored-by: root <root@vps-cd0829ae.vps.ovh.us>
1 parent d20d496 commit 13b9ce9

13 files changed

Lines changed: 979 additions & 58 deletions

File tree

Dockerfile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# syntax=docker/dockerfile:1
2+
# Dockerfile for the reporterd standalone binary.
3+
# Single module — the reporter does not depend on layer-monitor (the dispute
4+
# monitor is intentionally not included). It DOES depend on the
5+
# bridge-remote-signer/api module (mTLS + SignTx types), which is vendored into
6+
# ./vendor-api in the build context and wired in via the relative replace in go.mod.
7+
8+
### Build stage
9+
FROM golang:1.24-bookworm AS builder
10+
11+
WORKDIR /src
12+
13+
ENV GOTOOLCHAIN=auto
14+
15+
COPY . /src/
16+
17+
RUN --mount=type=cache,target=/go/pkg/mod \
18+
--mount=type=cache,target=/root/.cache/go-build \
19+
go build -o /tmp/reporterd ./cmd
20+
21+
### Runtime stage
22+
FROM debian:bookworm-slim
23+
24+
RUN apt-get update && apt-get install -y --no-install-recommends \
25+
ca-certificates \
26+
wget \
27+
&& rm -rf /var/lib/apt/lists/*
28+
29+
WORKDIR /app
30+
COPY --from=builder /tmp/reporterd /usr/local/bin/reporterd
31+
32+
ENTRYPOINT ["/usr/local/bin/reporterd"]

cmd/create-reporter/main.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Command create-reporter submits a MsgCreateReporter for the operator account,
2+
// signing it through the remote signer (mTLS + scope-checked SignTx) — same path
3+
// as the reporter/unjail/create-validator commands, so no local private key is
4+
// required. MsgCreateReporter must be on the signer's SignTx allowlist.
5+
// Creating a reporter auto-self-selects the creator's own delegation, so the
6+
// validator's self-bond becomes the reporter's stake.
7+
package main
8+
9+
import (
10+
"context"
11+
"flag"
12+
"fmt"
13+
"os"
14+
"time"
15+
16+
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
17+
rsclient "github.com/tellor-io/layer-daemons/reporter/client"
18+
// sets the tellor bech32 address prefix via init()
19+
_ "github.com/tellor-io/layer/app/config"
20+
reportertypes "github.com/tellor-io/layer/x/reporter/types"
21+
22+
"cosmossdk.io/math"
23+
24+
cosmosclient "github.com/cosmos/cosmos-sdk/client"
25+
"github.com/cosmos/cosmos-sdk/client/tx"
26+
"github.com/cosmos/cosmos-sdk/types/tx/signing"
27+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
28+
)
29+
30+
func must(what string, err error) {
31+
if err != nil {
32+
fmt.Fprintf(os.Stderr, "ERROR (%s): %v\n", what, err)
33+
os.Exit(1)
34+
}
35+
}
36+
37+
func main() {
38+
os.Exit(run())
39+
}
40+
41+
func run() int {
42+
signerAddr := flag.String("remote-signer-addr", "", "remote signer gRPC address (host:port)")
43+
ca := flag.String("remote-signer-ca-cert", "", "CA cert path")
44+
cert := flag.String("remote-signer-client-cert", "", "client cert path")
45+
key := flag.String("remote-signer-client-key", "", "client key path")
46+
node := flag.String("node", "tcp://127.0.0.1:26657", "CometBFT RPC endpoint")
47+
chainID := flag.String("chain-id", "tellor-1", "chain id")
48+
gasPrices := flag.String("gas-prices", "0.000025loya", "gas prices")
49+
gas := flag.Uint64("gas", 400000, "gas limit")
50+
commRate := flag.String("commission-rate", "0.0", "reporter commission rate")
51+
minTokens := flag.String("min-tokens-required", "1000000", "min tokens a selector needs to join (loya)")
52+
moniker := flag.String("moniker", "max-profit-tellor", "reporter moniker")
53+
dryRun := flag.Bool("dry-run", false, "build and sign but do not broadcast")
54+
flag.Parse()
55+
56+
ctx := context.Background()
57+
58+
ec := rsclient.CreateEncodingConfig()
59+
reportertypes.RegisterInterfaces(ec.InterfaceRegistry)
60+
61+
kr, fromAddr, conn, err := rsclient.NewRemoteSignerKeyringTx(ctx, "reporter", *signerAddr, *ca, *cert, *key)
62+
must("dial remote signer", err)
63+
defer conn.Close()
64+
65+
rpcClient, err := rpchttp.New(*node, "/websocket")
66+
must("create rpc client", err)
67+
68+
clientCtx := cosmosclient.Context{}.
69+
WithCodec(ec.Codec).
70+
WithInterfaceRegistry(ec.InterfaceRegistry).
71+
WithTxConfig(ec.TxConfig).
72+
WithChainID(*chainID).
73+
WithKeyring(kr).
74+
WithFromName("reporter").
75+
WithFrom("reporter").
76+
WithFromAddress(fromAddr).
77+
WithClient(rpcClient).
78+
WithBroadcastMode("sync").
79+
WithAccountRetriever(authtypes.AccountRetriever{}).
80+
WithSkipConfirmation(true)
81+
82+
minTok, ok := math.NewIntFromString(*minTokens)
83+
if !ok {
84+
must("parse min-tokens-required", fmt.Errorf("invalid %q", *minTokens))
85+
}
86+
87+
msg := &reportertypes.MsgCreateReporter{
88+
ReporterAddress: fromAddr.String(),
89+
CommissionRate: math.LegacyMustNewDecFromStr(*commRate),
90+
MinTokensRequired: minTok,
91+
Moniker: *moniker,
92+
}
93+
94+
fmt.Println("reporter account:", fromAddr.String())
95+
fmt.Println("commission: ", *commRate)
96+
fmt.Println("min-tokens: ", *minTokens, "loya")
97+
fmt.Println("moniker: ", *moniker)
98+
99+
txf := tx.Factory{}.
100+
WithChainID(*chainID).
101+
WithKeybase(kr).
102+
WithTxConfig(ec.TxConfig).
103+
WithAccountRetriever(clientCtx.AccountRetriever).
104+
WithGas(*gas).
105+
WithGasPrices(*gasPrices).
106+
WithSignMode(signing.SignMode_SIGN_MODE_DIRECT).
107+
WithSequence(0).
108+
WithUnordered(true).
109+
WithTimeoutTimestamp(time.Now().Add(60 * time.Second))
110+
111+
txf, err = txf.Prepare(clientCtx)
112+
must("prepare tx factory", err)
113+
114+
txb, err := txf.BuildUnsignedTx(msg)
115+
must("build unsigned tx", err)
116+
117+
must("sign tx", tx.Sign(ctx, txf, "reporter", txb, true))
118+
119+
txBytes, err := ec.TxConfig.TxEncoder()(txb.GetTx())
120+
must("encode tx", err)
121+
122+
if *dryRun {
123+
fmt.Printf("dry-run OK: signed create-reporter tx built (%d bytes), not broadcasting\n", len(txBytes))
124+
return 0
125+
}
126+
127+
res, err := clientCtx.BroadcastTx(txBytes)
128+
must("broadcast tx", err)
129+
fmt.Printf("broadcast: code=%d txhash=%s\n", res.Code, res.TxHash)
130+
if res.RawLog != "" {
131+
fmt.Println("rawlog:", res.RawLog)
132+
}
133+
if res.Code != 0 {
134+
return 2
135+
}
136+
return 0
137+
}

cmd/create-validator/main.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Command create-validator submits a MsgCreateValidator for the operator account,
2+
// signing it through the remote signer (mTLS + scope-checked SignTx) — the same path
3+
// the reporter and cmd/unjail use — so no local private key is required.
4+
// MsgCreateValidator must be on the signer's SignTx allowlist. The consensus pubkey is
5+
// the validator's ed25519 key (held by the remote signer / presented by the node).
6+
package main
7+
8+
import (
9+
"context"
10+
"encoding/base64"
11+
"flag"
12+
"fmt"
13+
"os"
14+
"time"
15+
16+
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
17+
rsclient "github.com/tellor-io/layer-daemons/reporter/client"
18+
// sets the tellor bech32 address prefix via init()
19+
_ "github.com/tellor-io/layer/app/config"
20+
21+
"cosmossdk.io/math"
22+
23+
cosmosclient "github.com/cosmos/cosmos-sdk/client"
24+
"github.com/cosmos/cosmos-sdk/client/tx"
25+
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
26+
sdk "github.com/cosmos/cosmos-sdk/types"
27+
"github.com/cosmos/cosmos-sdk/types/tx/signing"
28+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
29+
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
30+
)
31+
32+
func must(what string, err error) {
33+
if err != nil {
34+
fmt.Fprintf(os.Stderr, "ERROR (%s): %v\n", what, err)
35+
os.Exit(1)
36+
}
37+
}
38+
39+
func main() {
40+
os.Exit(run())
41+
}
42+
43+
func run() int {
44+
signerAddr := flag.String("remote-signer-addr", "", "remote signer gRPC address (host:port)")
45+
ca := flag.String("remote-signer-ca-cert", "", "CA cert path")
46+
cert := flag.String("remote-signer-client-cert", "", "client cert path")
47+
key := flag.String("remote-signer-client-key", "", "client key path")
48+
node := flag.String("node", "tcp://127.0.0.1:26657", "CometBFT RPC endpoint")
49+
chainID := flag.String("chain-id", "tellor-1", "chain id")
50+
gasPrices := flag.String("gas-prices", "0.000025loya", "gas prices")
51+
gas := flag.Uint64("gas", 600000, "gas limit")
52+
moniker := flag.String("moniker", "", "validator moniker")
53+
pubkeyB64 := flag.String("consensus-pubkey", "", "ed25519 consensus pubkey (base64)")
54+
amount := flag.String("amount", "", "self-bond amount in loya (e.g. 19000000)")
55+
commRate := flag.String("commission-rate", "0.10", "commission rate")
56+
commMax := flag.String("commission-max-rate", "0.20", "commission max rate")
57+
commMaxChange := flag.String("commission-max-change-rate", "0.01", "commission max change rate")
58+
minSelf := flag.String("min-self-delegation", "1", "min self delegation (loya)")
59+
dryRun := flag.Bool("dry-run", false, "build and sign but do not broadcast")
60+
flag.Parse()
61+
62+
ctx := context.Background()
63+
64+
ec := rsclient.CreateEncodingConfig()
65+
stakingtypes.RegisterInterfaces(ec.InterfaceRegistry)
66+
67+
kr, fromAddr, conn, err := rsclient.NewRemoteSignerKeyringTx(ctx, "reporter", *signerAddr, *ca, *cert, *key)
68+
must("dial remote signer", err)
69+
defer conn.Close()
70+
71+
rpcClient, err := rpchttp.New(*node, "/websocket")
72+
must("create rpc client", err)
73+
74+
clientCtx := cosmosclient.Context{}.
75+
WithCodec(ec.Codec).
76+
WithInterfaceRegistry(ec.InterfaceRegistry).
77+
WithTxConfig(ec.TxConfig).
78+
WithChainID(*chainID).
79+
WithKeyring(kr).
80+
WithFromName("reporter").
81+
WithFrom("reporter").
82+
WithFromAddress(fromAddr).
83+
WithClient(rpcClient).
84+
WithBroadcastMode("sync").
85+
WithAccountRetriever(authtypes.AccountRetriever{}).
86+
WithSkipConfirmation(true)
87+
88+
valAddr := sdk.ValAddress(fromAddr)
89+
90+
pkBytes, err := base64.StdEncoding.DecodeString(*pubkeyB64)
91+
must("decode consensus pubkey", err)
92+
if len(pkBytes) != 32 {
93+
must("consensus pubkey length", fmt.Errorf("expected 32 bytes, got %d", len(pkBytes)))
94+
}
95+
consPub := &ed25519.PubKey{Key: pkBytes}
96+
97+
amt, ok := math.NewIntFromString(*amount)
98+
if !ok {
99+
must("parse amount", fmt.Errorf("invalid amount %q", *amount))
100+
}
101+
selfDel := sdk.NewCoin("loya", amt)
102+
103+
minSelfInt, ok := math.NewIntFromString(*minSelf)
104+
if !ok {
105+
must("parse min-self-delegation", fmt.Errorf("invalid %q", *minSelf))
106+
}
107+
108+
desc := stakingtypes.NewDescription(*moniker, "", "", "", "")
109+
comm := stakingtypes.NewCommissionRates(
110+
math.LegacyMustNewDecFromStr(*commRate),
111+
math.LegacyMustNewDecFromStr(*commMax),
112+
math.LegacyMustNewDecFromStr(*commMaxChange),
113+
)
114+
115+
msg, err := stakingtypes.NewMsgCreateValidator(valAddr.String(), consPub, selfDel, desc, comm, minSelfInt)
116+
must("build MsgCreateValidator", err)
117+
118+
fmt.Println("operator account: ", fromAddr.String())
119+
fmt.Println("validator: ", valAddr.String())
120+
fmt.Println("moniker: ", *moniker)
121+
fmt.Println("self-bond: ", selfDel.String())
122+
fmt.Println("commission: ", *commRate, "/", *commMax, "/", *commMaxChange)
123+
fmt.Println("min-self-deleg: ", *minSelf, "loya")
124+
fmt.Println("consensus pubkey: ", *pubkeyB64)
125+
126+
txf := tx.Factory{}.
127+
WithChainID(*chainID).
128+
WithKeybase(kr).
129+
WithTxConfig(ec.TxConfig).
130+
WithAccountRetriever(clientCtx.AccountRetriever).
131+
WithGas(*gas).
132+
WithGasPrices(*gasPrices).
133+
WithSignMode(signing.SignMode_SIGN_MODE_DIRECT).
134+
WithSequence(0).
135+
WithUnordered(true).
136+
WithTimeoutTimestamp(time.Now().Add(60 * time.Second))
137+
138+
txf, err = txf.Prepare(clientCtx)
139+
must("prepare tx factory", err)
140+
141+
txb, err := txf.BuildUnsignedTx(msg)
142+
must("build unsigned tx", err)
143+
144+
must("sign tx", tx.Sign(ctx, txf, "reporter", txb, true))
145+
146+
txBytes, err := ec.TxConfig.TxEncoder()(txb.GetTx())
147+
must("encode tx", err)
148+
149+
if *dryRun {
150+
fmt.Printf("dry-run OK: signed create-validator tx built (%d bytes), not broadcasting\n", len(txBytes))
151+
return 0
152+
}
153+
154+
res, err := clientCtx.BroadcastTx(txBytes)
155+
must("broadcast tx", err)
156+
fmt.Printf("broadcast: code=%d txhash=%s\n", res.Code, res.TxHash)
157+
if res.RawLog != "" {
158+
fmt.Println("rawlog:", res.RawLog)
159+
}
160+
if res.Code != 0 {
161+
return 2
162+
}
163+
return 0
164+
}

0 commit comments

Comments
 (0)