node: add token bridge governance VAA support

Change-Id: I731161f03590ce73145a1686eb2e62cfe19c8223
This commit is contained in:
Leo 2021-09-30 20:57:44 +02:00 committed by Leopold Schabel
parent 7998d04554
commit 2022b55fd4
7 changed files with 116 additions and 52 deletions

View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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),

View File

@ -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)

View File

@ -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},
}

View File

@ -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).