From 2022b55fd46cae56cec587fc840b473bee800492 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 30 Sep 2021 20:57:44 +0200 Subject: [PATCH] node: add token bridge governance VAA support Change-Id: I731161f03590ce73145a1686eb2e62cfe19c8223 --- node/cmd/guardiand/adminserver.go | 67 +++++++++++++++++++---------- node/cmd/guardiand/admintemplate.go | 44 ++++++++++++++++--- node/cmd/guardiand/adminverify.go | 9 +++- node/pkg/vaa/governance.go | 2 +- node/pkg/vaa/payloads.go | 12 +++--- node/pkg/vaa/types_test.go | 3 +- proto/node/v1/node.proto | 31 +++++++------ 7 files changed, 116 insertions(+), 52 deletions(-) diff --git a/node/cmd/guardiand/adminserver.go b/node/cmd/guardiand/adminserver.go index 822889d19..efd393b77 100644 --- a/node/cmd/guardiand/adminserver.go +++ b/node/cmd/guardiand/adminserver.go @@ -2,25 +2,24 @@ package guardiand import ( "context" + "encoding/hex" "errors" "fmt" "github.com/certusone/wormhole/node/pkg/db" publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1" "github.com/certusone/wormhole/node/pkg/publicrpc" + ethcommon "github.com/ethereum/go-ethereum/common" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" - "math" - "net" - "os" - "time" - - ethcommon "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "math" + "net" + "os" "github.com/certusone/wormhole/node/pkg/common" nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1" @@ -36,7 +35,7 @@ type nodePrivilegedService struct { // adminGuardianSetUpdateToVAA converts a nodev1.GuardianSetUpdate message to its canonical VAA representation. // Returns an error if the data is invalid. -func adminGuardianSetUpdateToVAA(req *nodev1.GuardianSetUpdate, guardianSetIndex uint32, timestamp uint32) (*vaa.VAA, error) { +func adminGuardianSetUpdateToVAA(req *nodev1.GuardianSetUpdate, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) { if len(req.Guardians) == 0 { return nil, errors.New("empty guardian set specified") } @@ -61,22 +60,18 @@ func adminGuardianSetUpdateToVAA(req *nodev1.GuardianSetUpdate, guardianSetIndex addrs[i] = ethAddr } - v := &vaa.VAA{ - Version: vaa.SupportedVAAVersion, - GuardianSetIndex: guardianSetIndex, - Timestamp: time.Unix(int64(timestamp), 0), - Payload: vaa.BodyGuardianSetUpdate{ + v := vaa.CreateGovernanceVAA(nonce, sequence, guardianSetIndex, + vaa.BodyGuardianSetUpdate{ Keys: addrs, NewIndex: guardianSetIndex + 1, - }.Serialize(), - } + }.Serialize()) return v, nil } // adminContractUpgradeToVAA converts a nodev1.ContractUpgrade message to its canonical VAA representation. // Returns an error if the data is invalid. -func adminContractUpgradeToVAA(req *nodev1.ContractUpgrade, guardianSetIndex uint32, timestamp uint32) (*vaa.VAA, error) { +func adminContractUpgradeToVAA(req *nodev1.ContractUpgrade, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) { if len(req.NewContract) != 32 { return nil, errors.New("invalid new_contract address") } @@ -88,16 +83,40 @@ func adminContractUpgradeToVAA(req *nodev1.ContractUpgrade, guardianSetIndex uin newContractAddress := vaa.Address{} copy(newContractAddress[:], req.NewContract) - v := &vaa.VAA{ - Version: vaa.SupportedVAAVersion, - GuardianSetIndex: guardianSetIndex, - Timestamp: time.Unix(int64(timestamp), 0), - Payload: vaa.BodyContractUpgrade{ + v := vaa.CreateGovernanceVAA(nonce, sequence, guardianSetIndex, + vaa.BodyContractUpgrade{ ChainID: vaa.ChainID(req.ChainId), NewContract: newContractAddress, - }.Serialize(), + }.Serialize()) + + return v, nil +} + +// tokenBridgeRegisterChain converts a nodev1.TokenBridgeRegisterChain message to its canonical VAA representation. +// Returns an error if the data is invalid. +func tokenBridgeRegisterChain(req *nodev1.TokenBridgeRegisterChain, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) { + if req.ChainId > math.MaxUint8 { + return nil, errors.New("invalid chain_id") } + b, err := hex.DecodeString(req.EmitterAddress) + if err != nil { + return nil, errors.New("invalid emitter address encoding (expected hex)") + } + + if len(b) != 32 { + return nil, errors.New("invalid emitter address (expected 32 bytes)") + } + + emitterAddress := vaa.Address{} + copy(emitterAddress[:], req.EmitterAddress) + + v := vaa.CreateGovernanceVAA(nonce, sequence, guardianSetIndex, + vaa.BodyTokenBridgeRegisterChain{ + ChainID: vaa.ChainID(req.ChainId), + EmitterAddress: emitterAddress, + }.Serialize()) + return v, nil } @@ -110,9 +129,11 @@ func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *no ) switch payload := req.Payload.(type) { case *nodev1.InjectGovernanceVAARequest_GuardianSet: - v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, req.CurrentSetIndex, req.Timestamp) + v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, req.CurrentSetIndex, req.Nonce, req.Sequence) case *nodev1.InjectGovernanceVAARequest_ContractUpgrade: - v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, req.CurrentSetIndex, req.Timestamp) + v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, req.CurrentSetIndex, req.Nonce, req.Sequence) + case *nodev1.InjectGovernanceVAARequest_TokenBridgeRegisterChain: + v, err = tokenBridgeRegisterChain(payload.TokenBridgeRegisterChain, req.CurrentSetIndex, req.Nonce, req.Sequence) default: panic(fmt.Sprintf("unsupported VAA type: %T", payload)) } diff --git a/node/cmd/guardiand/admintemplate.go b/node/cmd/guardiand/admintemplate.go index 479dad04a..647354afa 100644 --- a/node/cmd/guardiand/admintemplate.go +++ b/node/cmd/guardiand/admintemplate.go @@ -2,6 +2,7 @@ package guardiand import ( "fmt" + "github.com/tendermint/tendermint/libs/rand" "io/ioutil" "log" @@ -22,6 +23,7 @@ func init() { TemplateCmd.AddCommand(AdminClientGuardianSetTemplateCmd) TemplateCmd.AddCommand(AdminClientContractUpgradeTemplateCmd) + TemplateCmd.AddCommand(AdminClientTokenBridgeRegisterChainCmd) } var TemplateCmd = &cobra.Command{ @@ -43,6 +45,13 @@ var AdminClientContractUpgradeTemplateCmd = &cobra.Command{ Args: cobra.ExactArgs(1), } +var AdminClientTokenBridgeRegisterChainCmd = &cobra.Command{ + Use: "token-bridge-register-chain [FILENAME]", + Short: "Generate an empty token bridge chain registration template at specified path (offline)", + Run: runTokenBridgeRegisterChainTemplate, + Args: cobra.ExactArgs(1), +} + func runGuardianSetTemplate(cmd *cobra.Command, args []string) { path := args[0] @@ -58,9 +67,8 @@ func runGuardianSetTemplate(cmd *cobra.Command, args []string) { m := &nodev1.InjectGovernanceVAARequest{ CurrentSetIndex: uint32(*templateGuardianIndex), - // Timestamp is hardcoded to make it reproducible on different devnet nodes. - // In production, a real UNIX timestamp should be used (see node.proto). - Timestamp: 1605744545, + Sequence: 1234, + Nonce: rand.Uint32(), Payload: &nodev1.InjectGovernanceVAARequest_GuardianSet{ GuardianSet: &nodev1.GuardianSetUpdate{Guardians: guardians}, }, @@ -82,9 +90,8 @@ func runContractUpgradeTemplate(cmd *cobra.Command, args []string) { m := &nodev1.InjectGovernanceVAARequest{ CurrentSetIndex: uint32(*templateGuardianIndex), - // Timestamp is hardcoded to make it reproducible on different devnet nodes. - // In production, a real UNIX timestamp should be used (see node.proto). - Timestamp: 1605744545, + Sequence: 1234, + Nonce: rand.Uint32(), Payload: &nodev1.InjectGovernanceVAARequest_ContractUpgrade{ ContractUpgrade: &nodev1.ContractUpgrade{ ChainId: 1, @@ -103,3 +110,28 @@ func runContractUpgradeTemplate(cmd *cobra.Command, args []string) { log.Fatal(err) } } +func runTokenBridgeRegisterChainTemplate(cmd *cobra.Command, args []string) { + path := args[0] + + m := &nodev1.InjectGovernanceVAARequest{ + CurrentSetIndex: uint32(*templateGuardianIndex), + Sequence: rand.Uint64(), + Nonce: rand.Uint32(), + Payload: &nodev1.InjectGovernanceVAARequest_TokenBridgeRegisterChain{ + TokenBridgeRegisterChain: &nodev1.TokenBridgeRegisterChain{ + ChainId: 5, + EmitterAddress: "0000000000000000000000000290FB167208Af455bB137780163b7B7a9a10C16", + }, + }, + } + + b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile(path, b, 0640) + if err != nil { + log.Fatal(err) + } +} diff --git a/node/cmd/guardiand/adminverify.go b/node/cmd/guardiand/adminverify.go index 1a727c510..3679feac3 100644 --- a/node/cmd/guardiand/adminverify.go +++ b/node/cmd/guardiand/adminverify.go @@ -1,6 +1,7 @@ package guardiand import ( + "fmt" "github.com/certusone/wormhole/node/pkg/vaa" "io/ioutil" "log" @@ -38,9 +39,13 @@ func runGovernanceVAAVerify(cmd *cobra.Command, args []string) { ) switch payload := msg.Payload.(type) { case *nodev1.InjectGovernanceVAARequest_GuardianSet: - v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, msg.CurrentSetIndex, msg.Timestamp) + v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, msg.CurrentSetIndex, msg.Nonce, msg.Sequence) case *nodev1.InjectGovernanceVAARequest_ContractUpgrade: - v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, msg.CurrentSetIndex, msg.Timestamp) + v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, msg.CurrentSetIndex, msg.Nonce, msg.Sequence) + case *nodev1.InjectGovernanceVAARequest_TokenBridgeRegisterChain: + v, err = tokenBridgeRegisterChain(payload.TokenBridgeRegisterChain, msg.CurrentSetIndex, msg.Nonce, msg.Sequence) + default: + panic(fmt.Sprintf("unsupported VAA type: %T", payload)) } if err != nil { log.Fatalf("invalid update: %v", err) diff --git a/node/pkg/vaa/governance.go b/node/pkg/vaa/governance.go index 8ee89e605..0ea138dd0 100644 --- a/node/pkg/vaa/governance.go +++ b/node/pkg/vaa/governance.go @@ -9,7 +9,7 @@ var governanceChain = ChainIDSolana func CreateGovernanceVAA(nonce uint32, sequence uint64, guardianSetIndex uint32, payload []byte) *VAA { vaa := &VAA{ - Version: 1, + Version: SupportedVAAVersion, GuardianSetIndex: guardianSetIndex, Signatures: nil, Timestamp: time.Unix(0, 0), diff --git a/node/pkg/vaa/payloads.go b/node/pkg/vaa/payloads.go index 3b85cefdb..3a221e9e2 100644 --- a/node/pkg/vaa/payloads.go +++ b/node/pkg/vaa/payloads.go @@ -9,6 +9,9 @@ import ( // CoreModule is the identifier of the Core module (which is used for governance messages) var CoreModule = []byte{00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x43, 0x6f, 0x72, 0x65} +// TokenBridgeModule is the identifier of the token bridge module ("TokenBridge") +var TokenBridgeModule = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65} + type ( // BodyContractUpgrade is a governance message to perform a contract upgrade of the core module BodyContractUpgrade struct { @@ -22,9 +25,8 @@ type ( NewIndex uint32 } - // BodyRegisterChain is a governance message to register a chain on the token bridge - BodyRegisterChain struct { - Header [32]byte + // BodyTokenBridgeRegisterChain is a governance message to register a chain on the token bridge + BodyTokenBridgeRegisterChain struct { ChainID ChainID EmitterAddress Address } @@ -64,11 +66,11 @@ func (b BodyGuardianSetUpdate) Serialize() []byte { return buf.Bytes() } -func (r BodyRegisterChain) Serialize() []byte { +func (r BodyTokenBridgeRegisterChain) Serialize() []byte { buf := &bytes.Buffer{} // Write token bridge header - buf.Write(r.Header[:]) + buf.Write(TokenBridgeModule) // Write action ID MustWrite(buf, binary.BigEndian, uint8(1)) // Write target chain (0 = universal) diff --git a/node/pkg/vaa/types_test.go b/node/pkg/vaa/types_test.go index 0c34dcf8d..9d3af3e0c 100644 --- a/node/pkg/vaa/types_test.go +++ b/node/pkg/vaa/types_test.go @@ -92,8 +92,7 @@ func TestBodyRegisterChain_Serialize(t *testing.T) { var headerB [32]byte copy(headerB[:], header) - msg := &BodyRegisterChain{ - Header: headerB, + msg := &BodyTokenBridgeRegisterChain{ ChainID: 8, EmitterAddress: Address{1, 2, 3, 4}, } diff --git a/proto/node/v1/node.proto b/proto/node/v1/node.proto index ed713d5c8..5e3708be1 100644 --- a/proto/node/v1/node.proto +++ b/proto/node/v1/node.proto @@ -20,23 +20,20 @@ message InjectGovernanceVAARequest { // Index of the current guardian set. uint32 current_set_index = 1; - // UNIX timestamp (s) of the VAA to be created. The timestamp is informational and will be part - // of the VAA submitted to the chain. It's part of the VAA digest and has to be identical across nodes and - // is critical for replay protection - a given event may only ever be observed with the same timestamp, - // otherwise, it may be possible to execute it multiple times. + // Sequence number. This is critical for replay protection - make sure the sequence number + // is unique for every new manually injected governance VAA. Sequences are tracked + // by emitter, and manually injected VAAs all use a single hardcoded emitter. // - // For messages, the timestamp identifies the block that the message belongs to. + // We use random sequence numbers for the manual emitter. + uint64 sequence = 2; - // For governance VAAs, guardians inject the VAA manually. Best practice is to pick a timestamp which roughly matches - // the timing of the off-chain ceremony used to achieve consensus. For guardian set updates, the actual on-chain - // guardian set creation timestamp will be set when the VAA is accepted on each chain. - // - // This is a uint32 to match the on-chain timestamp representation. This becomes a problem in 2106 (sorry). - uint32 timestamp = 2; + // Random nonce for disambiguation. Must be identical across all nodes. + uint32 nonce = 3; oneof payload{ - GuardianSetUpdate guardian_set = 3; - ContractUpgrade contract_upgrade = 4; + GuardianSetUpdate guardian_set = 10; + ContractUpgrade contract_upgrade = 11; + TokenBridgeRegisterChain token_bridge_register_chain = 12; } } @@ -68,6 +65,14 @@ message GuardianKey { bool unsafe_deterministic_key = 2; } +message TokenBridgeRegisterChain { + // ID of the chain to be registered. + uint32 chain_id = 1; + + // Hex-encoded emitter address to be registered (without leading 0x). + string emitter_address = 2; +} + // ContractUpgrade represents a Wormhole contract update to be submitted to and signed by the node. message ContractUpgrade { // ID of the chain where the Wormhole contract should be updated (uint8).