Skip to content

Commit ac45576

Browse files
committed
feat(node): Manager Service and Dogecoin signing
1 parent 9d5d5da commit ac45576

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+7972
-1214
lines changed

Tiltfile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ config.define_bool("ibc_relayer", False, "Enable IBC relayer between cosmos chai
7474
config.define_bool("redis", False, "Enable a redis instance")
7575
config.define_bool("generic_relayer", False, "Enable the generic relayer off-chain component")
7676
config.define_bool("query_server", False, "Enable cross-chain query server")
77+
config.define_bool("manager_service", False, "Enable manager service for UTXO chains (Dogecoin)")
7778

7879
cfg = config.parse()
7980
num_guardians = int(cfg.get("num", "1"))
@@ -100,6 +101,7 @@ btc = cfg.get("btc", False)
100101
redis = cfg.get('redis', ci)
101102
generic_relayer = cfg.get("generic_relayer", ci)
102103
query_server = cfg.get("query_server", ci)
104+
manager_service = cfg.get("manager_service", False)
103105

104106
if ci:
105107
guardiand_loglevel = cfg.get("guardiand_loglevel", "warn")
@@ -407,6 +409,13 @@ def build_node_yaml():
407409
"http://wormchain:1317"
408410
]
409411

412+
if manager_service:
413+
container["command"] += [
414+
"--managerServiceEnabled",
415+
"--dogecoinManagerSignerUri",
416+
"file:///tmp/bridge.key"
417+
]
418+
410419
# Wrap the command with a shell script for per-guardian configuration
411420
if require_per_guardian_config:
412421
original_command = container["command"]
@@ -1115,4 +1124,4 @@ if query_server:
11151124
],
11161125
labels = ["query-server"],
11171126
trigger_mode = trigger_mode
1118-
)
1127+
)

cspell-custom-words.txt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,16 @@ behaviour
1919
Berachain
2020
bigset
2121
Bigtable
22-
Blackhole
2322
blackhole
23+
Blackhole
2424
blackholed
25+
Blackholed
26+
blackholing
2527
borsh
2628
bscscan
2729
BUILDKIT
2830
bytecodes
2931
callstack
30-
Blackhole
31-
Blackholed
32-
blackholing
33-
blackholed
3432
ccqlistener
3533
CCTP
3634
celestia
@@ -56,6 +54,7 @@ Cyfrin
5654
datagram
5755
denoms
5856
devnet
57+
Dogecoin
5958
dymension
6059
Dymension
6160
ethcrypto
@@ -117,6 +116,7 @@ lamports
117116
lastrun
118117
libp
119118
Linea
119+
Litecoin
120120
localnet
121121
localterra
122122
lockfiles
@@ -138,9 +138,13 @@ Neodyme
138138
nhooyr
139139
obsv
140140
Obsv
141+
OP_CHECKMULTISIG
142+
OP_CHECKSIG
143+
OP_EQUALVERIFY
141144
optimisation
142145
optin
143146
Optin
147+
P2WPKH
144148
parachain
145149
pdas
146150
permissioned
@@ -157,6 +161,7 @@ protobuf
157161
protos
158162
prototxt
159163
pubkey
164+
pubkeys
160165
publicrpc
161166
pushbytes
162167
pushint
@@ -169,13 +174,15 @@ readyz
169174
regen
170175
reinit
171176
reobservation
172-
reobservations
173177
Reobservation
178+
reobservations
174179
Reobservations
175180
reobserved
176181
repoint
182+
runtimes
177183
rustup
178184
satoshi
185+
satoshis
179186
secp
180187
SECQ
181188
SECG
@@ -221,12 +228,15 @@ Unichain
221228
unmarshal
222229
unnormalize
223230
untampered
231+
upserted
224232
utest
233+
UTXO
225234
uusd
226235
uvarint
227236
varint
228237
varints
229238
vimdiff
239+
vout
230240
vphash
231241
wasmhooks
232242
wasms
@@ -240,8 +250,9 @@ wormchaind
240250
Wormholescan
241251
wormscan
242252
wormscanurl
253+
XFER
243254
xlayer
244255
xpla
245-
XFER
246256
XPLA
257+
XRPL
247258
Zellic
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Dogecoin (65) Testnet Token Bridge,01000000000100332d786f5f8daec0ec99d08c7df7777d57cf45605b1667d868bd0b07a8f816f4723bd5578381173c5ef2ed4b6f2224726439ea4a7227cf8b2742b143bf3f8fed01000000008d53cf9f000100000000000000000000000000000000000000000000000000000000000000041e1a7bad048d464a200000000000000000000000000000000044656c6567617465644d616e6167657201000000410000000101050702349de56ca5dd06db8660419d6f150662e0f04febdbf6512d7cfe78c23b51491c035163bfd9518b0a536a17f330a1589fe21d7404b51f525a0a990a65a701952ebb036d40b0b85bca49e41f05a26950578bb13a424507ce34a80f83d3cf601e25818b0307681002ae28b9399e828d0f46d54c31d5d6ff187b3bdddc6615987a466455f50375abc8955c8a8c875ee1febd157132adcc1b992d69a946e83485b8360e23a277030212d206546216917a75533ed6c975f8f794ba0d8a7fb84dedf65ebb20e64841037ff483369b52bd87a73f23413dd8fcace71de7f7823c5c9120f1e9cfe5733a88

node/cmd/guardiand/admintemplate.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ var coreBridgeSetMessageFeeMessageFee *string
8383
var delegatedGuardiansConfigJson *string
8484
var delegatedGuardiansConfigId *string
8585

86+
var delegatedManagerChainId *string
87+
var delegatedManagerSetIndex *string
88+
var delegatedManagerSet *string
89+
var delegatedManagerThreshold *string
90+
var delegatedManagerNumKeys *string
91+
var delegatedManagerPublicKeys *string
92+
8693
func init() {
8794
governanceFlagSet := pflag.NewFlagSet("governance", pflag.ExitOnError)
8895
chainID = governanceFlagSet.String("chain-id", "", "Chain ID")
@@ -236,6 +243,17 @@ func init() {
236243
// solana call command
237244
AdminClientGeneralPurposeGovernanceSolanaCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
238245
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceSolanaCallCmd)
246+
247+
// flags for the delegated-manager-set-update command
248+
delegatedManagerFlagSet := pflag.NewFlagSet("delegated-manager", pflag.ExitOnError)
249+
delegatedManagerChainId = delegatedManagerFlagSet.String("manager-chain-id", "", "Wormhole Chain ID for the manager chain (e.g., 65 for Dogecoin)")
250+
delegatedManagerSetIndex = delegatedManagerFlagSet.String("manager-set-index", "", "Index of the new manager set (must be current + 1)")
251+
delegatedManagerSet = delegatedManagerFlagSet.String("manager-set", "", "Hex-encoded manager set bytes (without leading 0x). Alternative to --threshold/--num-keys/--public-keys")
252+
delegatedManagerThreshold = delegatedManagerFlagSet.String("threshold", "", "Number of required signatures (M) for secp256k1 multisig")
253+
delegatedManagerNumKeys = delegatedManagerFlagSet.String("num-keys", "", "Total number of public keys (N) for secp256k1 multisig")
254+
delegatedManagerPublicKeys = delegatedManagerFlagSet.String("public-keys", "", "Comma-separated list of compressed secp256k1 public keys (33 bytes each, hex-encoded)")
255+
AdminClientDelegatedManagerSetUpdateCmd.Flags().AddFlagSet(delegatedManagerFlagSet)
256+
TemplateCmd.AddCommand(AdminClientDelegatedManagerSetUpdateCmd)
239257
}
240258

241259
var TemplateCmd = &cobra.Command{
@@ -387,6 +405,12 @@ var AdminClientGeneralPurposeGovernanceSolanaCallCmd = &cobra.Command{
387405
Run: runGeneralPurposeGovernanceSolanaCallTemplate,
388406
}
389407

408+
var AdminClientDelegatedManagerSetUpdateCmd = &cobra.Command{
409+
Use: "delegated-manager-set-update",
410+
Short: "Generate a DelegatedManager manager set update governance VAA template",
411+
Run: runDelegatedManagerSetUpdateTemplate,
412+
}
413+
390414
func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
391415
// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
392416
guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
@@ -1309,6 +1333,96 @@ func runGeneralPurposeGovernanceSolanaCallTemplate(cmd *cobra.Command, args []st
13091333
fmt.Print(string(b))
13101334
}
13111335

1336+
func runDelegatedManagerSetUpdateTemplate(cmd *cobra.Command, args []string) {
1337+
if *delegatedManagerChainId == "" {
1338+
log.Fatal("--manager-chain-id must be specified")
1339+
}
1340+
managerChainId, err := parseChainID(*delegatedManagerChainId)
1341+
if err != nil {
1342+
log.Fatal("failed to parse manager-chain-id: ", err)
1343+
}
1344+
1345+
if *delegatedManagerSetIndex == "" {
1346+
log.Fatal("--manager-set-index must be specified")
1347+
}
1348+
managerSetIndex, err := strconv.ParseUint(*delegatedManagerSetIndex, 10, 32)
1349+
if err != nil {
1350+
log.Fatal("failed to parse manager-set-index as uint32: ", err)
1351+
}
1352+
1353+
var managerSet string
1354+
if *delegatedManagerSet != "" {
1355+
// Use raw manager set bytes if provided
1356+
managerSet = strings.TrimPrefix(*delegatedManagerSet, "0x")
1357+
// Validate it's valid hex
1358+
if _, err := hex.DecodeString(managerSet); err != nil {
1359+
log.Fatal("invalid manager-set (expected hex): ", err)
1360+
}
1361+
} else if *delegatedManagerThreshold != "" && *delegatedManagerNumKeys != "" && *delegatedManagerPublicKeys != "" {
1362+
// Build secp256k1 multisig manager set from components
1363+
threshold, err := strconv.ParseUint(*delegatedManagerThreshold, 10, 8)
1364+
if err != nil {
1365+
log.Fatal("failed to parse threshold as uint8: ", err)
1366+
}
1367+
numKeys, err := strconv.ParseUint(*delegatedManagerNumKeys, 10, 8)
1368+
if err != nil {
1369+
log.Fatal("failed to parse num-keys as uint8: ", err)
1370+
}
1371+
publicKeyStrs := strings.Split(*delegatedManagerPublicKeys, ",")
1372+
1373+
// Parse public keys into the format expected by vaa.Secp256k1MultisigManagerSet
1374+
publicKeys := make([][vaa.CompressedSecp256k1PublicKeyLength]byte, len(publicKeyStrs))
1375+
for i, pkStr := range publicKeyStrs {
1376+
pkHex := strings.TrimPrefix(strings.TrimSpace(pkStr), "0x")
1377+
pkBytes, err := hex.DecodeString(pkHex)
1378+
if err != nil {
1379+
log.Fatalf("public key %d is not valid hex: %v", i, err)
1380+
}
1381+
if len(pkBytes) != vaa.CompressedSecp256k1PublicKeyLength {
1382+
log.Fatalf("public key %d has invalid length: expected %d bytes, got %d", i, vaa.CompressedSecp256k1PublicKeyLength, len(pkBytes))
1383+
}
1384+
copy(publicKeys[i][:], pkBytes)
1385+
}
1386+
1387+
managerSetStruct := vaa.Secp256k1MultisigManagerSet{
1388+
M: uint8(threshold),
1389+
N: uint8(numKeys),
1390+
PublicKeys: publicKeys,
1391+
}
1392+
managerSetBytes, err := managerSetStruct.Serialize()
1393+
if err != nil {
1394+
log.Fatal("failed to serialize manager set: ", err)
1395+
}
1396+
managerSet = hex.EncodeToString(managerSetBytes)
1397+
} else {
1398+
log.Fatal("Either --manager-set or (--threshold, --num-keys, --public-keys) must be provided")
1399+
}
1400+
1401+
seq, nonce := randSeqNonce()
1402+
m := &nodev1.InjectGovernanceVAARequest{
1403+
CurrentSetIndex: uint32(*templateGuardianIndex), // #nosec G115 -- This will never overflow
1404+
Messages: []*nodev1.GovernanceMessage{
1405+
{
1406+
Sequence: seq,
1407+
Nonce: nonce,
1408+
Payload: &nodev1.GovernanceMessage_DelegatedManagerSetUpdate{
1409+
DelegatedManagerSetUpdate: &nodev1.DelegatedManagerSetUpdate{
1410+
ManagerChainId: uint32(managerChainId),
1411+
ManagerSetIndex: uint32(managerSetIndex),
1412+
ManagerSet: managerSet,
1413+
},
1414+
},
1415+
},
1416+
},
1417+
}
1418+
1419+
b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m)
1420+
if err != nil {
1421+
log.Fatal("failed to marshal request: ", err)
1422+
}
1423+
fmt.Print(string(b))
1424+
}
1425+
13121426
// parseAddress parses either a hex-encoded address and returns
13131427
// a left-padded 32 byte hex string.
13141428
func parseAddress(s string) (string, error) {

node/cmd/guardiand/node.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"syscall"
1515
"time"
1616

17+
"github.com/btcsuite/btcd/btcec/v2"
1718
"github.com/certusone/wormhole/node/pkg/guardiansigner"
1819
"github.com/certusone/wormhole/node/pkg/watchers"
1920
"github.com/certusone/wormhole/node/pkg/watchers/ibc"
@@ -309,6 +310,9 @@ var (
309310
// featureFlags are additional static flags that should be published in P2P heartbeats.
310311
featureFlags []string
311312
notaryEnabled *bool
313+
314+
managerServiceEnabled *bool
315+
dogecoinManagerSignerUri *string
312316
)
313317

314318
func init() {
@@ -555,6 +559,9 @@ func init() {
555559
transferVerifierEnabledChainIDs = NodeCmd.Flags().UintSlice("transferVerifierEnabledChainIDs", make([]uint, 0), "Transfer Verifier will be enabled for these chain IDs (comma-separated)")
556560

557561
notaryEnabled = NodeCmd.Flags().Bool("notaryEnabled", false, "Run the notary")
562+
563+
managerServiceEnabled = NodeCmd.Flags().Bool("managerServiceEnabled", false, "Run the manager service")
564+
dogecoinManagerSignerUri = NodeCmd.Flags().String("dogecoinManagerSignerUri", "", "Dogecoin manager signer URI")
558565
}
559566

560567
var (
@@ -1975,12 +1982,36 @@ func runNode(cmd *cobra.Command, args []string) {
19751982
guardianAddrAsBytes = ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(rootCtx)).Bytes()
19761983
}
19771984

1985+
// Initialize manager signers map
1986+
managerSigners := make(map[vaa.ChainID]guardiansigner.GuardianSigner)
1987+
if *dogecoinManagerSignerUri != "" {
1988+
// Ensure the manager signer is not the same as the guardian signer in non-devnet environments
1989+
if env != common.UnsafeDevNet && *dogecoinManagerSignerUri == *guardianSignerUri {
1990+
logger.Fatal("dogecoinManagerSignerUri must be different from guardianSignerUri in non-devnet environments")
1991+
}
1992+
dogecoinSigner, err := guardiansigner.NewGuardianSignerFromUriWithPurpose(rootCtx, *dogecoinManagerSignerUri, env == common.UnsafeDevNet, "manager-dogecoin")
1993+
if err != nil {
1994+
logger.Fatal("failed to create dogecoin manager signer", zap.Error(err))
1995+
}
1996+
managerSigners[vaa.ChainIDDogecoin] = dogecoinSigner
1997+
1998+
// Log the 33-byte compressed public key for use in P2SH multisig
1999+
pubKey := dogecoinSigner.PublicKey(rootCtx)
2000+
btcecPubKey, err := btcec.ParsePubKey(ethcrypto.FromECDSAPub(&pubKey))
2001+
if err != nil {
2002+
logger.Fatal("failed to parse dogecoin public key", zap.Error(err))
2003+
}
2004+
compressedPubKey := btcecPubKey.SerializeCompressed()
2005+
logger.Info("initialized dogecoin manager signer", zap.String("compressed_public_key", fmt.Sprintf("%x", compressedPubKey)))
2006+
}
2007+
19782008
guardianOptions := []*node.GuardianOption{
19792009
node.GuardianOptionDatabase(db),
19802010
node.GuardianOptionWatchers(watcherConfigs, ibcWatcherConfig),
19812011
node.GuardianOptionAccountant(*accountantWS, *accountantContract, *accountantCheckEnabled, accountantWormchainConn, *accountantNttContract, accountantNttWormchainConn),
19822012
node.GuardianOptionGovernor(*chainGovernorEnabled, *governorFlowCancelEnabled, *coinGeckoApiKey),
19832013
node.GuardianOptionNotary(*notaryEnabled),
2014+
node.GuardianOptionManagerService(*managerServiceEnabled, managerSigners, *ethRPC),
19842015
node.GuardianOptionGatewayRelayer(*gatewayRelayerContract, gatewayRelayerWormchainConn),
19852016
node.GuardianOptionQueryHandler(*ccqEnabled, *ccqAllowedRequesters),
19862017
node.GuardianOptionAdminService(*adminSocketPath, ethRPC, ethContract, rpcMap),

node/go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ require (
4747
github.com/aws/aws-sdk-go-v2/config v1.28.1
4848
github.com/aws/aws-sdk-go-v2/service/kms v1.37.3
4949
github.com/blendle/zapdriver v1.3.1
50+
github.com/btcsuite/btcd v0.22.1
51+
github.com/btcsuite/btcd/btcec/v2 v2.3.2
52+
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
5053
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
5154
github.com/cosmos/cosmos-sdk v0.45.11
5255
github.com/fsnotify/fsnotify v1.6.0
@@ -95,8 +98,7 @@ require (
9598
github.com/benbjohnson/clock v1.3.5 // indirect
9699
github.com/beorn7/perks v1.0.1 // indirect
97100
github.com/bgentry/speakeasy v0.1.0 // indirect
98-
github.com/btcsuite/btcd v0.22.1 // indirect
99-
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
101+
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
100102
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee // indirect
101103
github.com/cespare/xxhash v1.1.0 // indirect
102104
github.com/cespare/xxhash/v2 v2.3.0 // indirect

node/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf
763763
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
764764
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
765765
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
766+
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
766767
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
767768
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
768769
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=

0 commit comments

Comments
 (0)