Преглед изворни кода

Implement upgradeability (#151)

* Implement contract upgrade VAA action

* naming and (╯°□°)╯︵ ┻━┻

* Carefully unflip table and replace broken cutlery ┬─┬ノ( ◕◡◕ ノ)

* fix and automate upgradeability

* document contract upgrade call

* Update comments

* Exhaustiveness check in VAA payload switch

* Fix typo

Co-authored-by: Leo <leo@certus.one>
Hendrik Hofstadt пре 4 година
родитељ
комит
efa03ef73c

+ 8 - 9
bridge/cmd/guardiand/adminclient.go

@@ -26,8 +26,7 @@ func init() {
 	}
 
 	AdminCmd.AddCommand(AdminClientInjectGuardianSetUpdateCmd)
-	AdminCmd.AddCommand(AdminClientGuardianSetTemplateCmd)
-	AdminCmd.AddCommand(AdminClientGuardianSetVerifyCmd)
+	AdminCmd.AddCommand(AdminClientGovernanceVAAVerifyCmd)
 }
 
 var AdminCmd = &cobra.Command{
@@ -36,9 +35,9 @@ var AdminCmd = &cobra.Command{
 }
 
 var AdminClientInjectGuardianSetUpdateCmd = &cobra.Command{
-	Use:   "guardian-set-update-inject",
-	Short: "Inject and sign a guardian set update from a prototxt file (see docs!)",
-	Run:   runInjectGuardianSetUpdate,
+	Use:   "governance-vaa-inject",
+	Short: "Inject and sign a governance VAA from a prototxt file (see docs!)",
+	Run:   runInjectGovernanceVAA,
 	Args:  cobra.ExactArgs(1),
 }
 
@@ -53,7 +52,7 @@ func getAdminClient(ctx context.Context, addr string) (*grpc.ClientConn, error,
 	return conn, err, c
 }
 
-func runInjectGuardianSetUpdate(cmd *cobra.Command, args []string) {
+func runInjectGovernanceVAA(cmd *cobra.Command, args []string) {
 	path := args[0]
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
@@ -66,15 +65,15 @@ func runInjectGuardianSetUpdate(cmd *cobra.Command, args []string) {
 		log.Fatalf("failed to read file: %v", err)
 	}
 
-	var msg nodev1.GuardianSetUpdate
+	var msg nodev1.InjectGovernanceVAARequest
 	err = prototext.Unmarshal(b, &msg)
 	if err != nil {
 		log.Fatalf("failed to deserialize: %v", err)
 	}
 
-	resp, err := c.SubmitGuardianSetVAA(ctx, &nodev1.SubmitGuardianSetVAARequest{GuardianSet: &msg})
+	resp, err := c.InjectGovernanceVAA(ctx, &msg)
 	if err != nil {
-		log.Fatalf("failed to submit guardian set update: %v", err)
+		log.Fatalf("failed to submit governance VAA: %v", err)
 	}
 
 	log.Printf("VAA successfully injected with digest %s", hexutils.BytesToHex(resp.Digest))

+ 48 - 9
bridge/cmd/guardiand/adminserver.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"math"
 	"net"
 	"os"
 	"time"
@@ -28,7 +29,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) (*vaa.VAA, error) {
+func adminGuardianSetUpdateToVAA(req *nodev1.GuardianSetUpdate, guardianSetIndex uint32, timestamp uint32) (*vaa.VAA, error) {
 	if len(req.Guardians) == 0 {
 		return nil, errors.New("empty guardian set specified")
 	}
@@ -48,21 +49,59 @@ func adminGuardianSetUpdateToVAA(req *nodev1.GuardianSetUpdate) (*vaa.VAA, error
 
 	v := &vaa.VAA{
 		Version:          vaa.SupportedVAAVersion,
-		GuardianSetIndex: req.CurrentSetIndex,
-		Timestamp:        time.Unix(int64(req.Timestamp), 0),
+		GuardianSetIndex: guardianSetIndex,
+		Timestamp:        time.Unix(int64(timestamp), 0),
 		Payload: &vaa.BodyGuardianSetUpdate{
 			Keys:     addrs,
-			NewIndex: req.CurrentSetIndex + 1,
+			NewIndex: guardianSetIndex + 1,
 		},
 	}
 
 	return v, nil
 }
 
-func (s *nodePrivilegedService) SubmitGuardianSetVAA(ctx context.Context, req *nodev1.SubmitGuardianSetVAARequest) (*nodev1.SubmitGuardianSetVAAResponse, error) {
-	s.logger.Info("guardian set injected via admin socket", zap.String("request", req.String()))
+// 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) {
+	if len(req.NewContract) != 32 {
+		return nil, errors.New("invalid new_contract address")
+	}
+
+	if req.ChainId > math.MaxUint8 {
+		return nil, errors.New("invalid chain_id")
+	}
+
+	newContractAddress := vaa.Address{}
+	copy(newContractAddress[:], req.NewContract)
+
+	v := &vaa.VAA{
+		Version:          vaa.SupportedVAAVersion,
+		GuardianSetIndex: guardianSetIndex,
+		Timestamp:        time.Unix(int64(timestamp), 0),
+		Payload: &vaa.BodyContractUpgrade{
+			ChainID:     uint8(req.ChainId),
+			NewContract: newContractAddress,
+		},
+	}
+
+	return v, nil
+}
+
+func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *nodev1.InjectGovernanceVAARequest) (*nodev1.InjectGovernanceVAAResponse, error) {
+	s.logger.Info("governance VAA injected via admin socket", zap.String("request", req.String()))
 
-	v, err := adminGuardianSetUpdateToVAA(req.GuardianSet)
+	var (
+		v   *vaa.VAA
+		err error
+	)
+	switch payload := req.Payload.(type) {
+	case *nodev1.InjectGovernanceVAARequest_GuardianSet:
+		v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, req.CurrentSetIndex, req.Timestamp)
+	case *nodev1.InjectGovernanceVAARequest_ContractUpgrade:
+		v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, req.CurrentSetIndex, req.Timestamp)
+	default:
+		panic(fmt.Sprintf("unsupported VAA type: %T", payload))
+	}
 	if err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
@@ -73,14 +112,14 @@ func (s *nodePrivilegedService) SubmitGuardianSetVAA(ctx context.Context, req *n
 		panic(err)
 	}
 
-	s.logger.Info("guardian set VAA constructed",
+	s.logger.Info("governance VAA constructed",
 		zap.Any("vaa", v),
 		zap.String("digest", digest.String()),
 	)
 
 	s.injectC <- v
 
-	return &nodev1.SubmitGuardianSetVAAResponse{Digest: digest.Bytes()}, nil
+	return &nodev1.InjectGovernanceVAAResponse{Digest: digest.Bytes()}, nil
 }
 
 func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<- *vaa.VAA) (supervisor.Runnable, error) {

+ 52 - 8
bridge/cmd/guardiand/admintemplate.go

@@ -13,27 +13,42 @@ import (
 	nodev1 "github.com/certusone/wormhole/bridge/pkg/proto/node/v1"
 )
 
-var templateNumGuardians *int
+var setUpdateNumGuardians *int
 var templateGuardianIndex *int
 
 func init() {
-	templateNumGuardians = AdminClientGuardianSetTemplateCmd.Flags().Int("num", 1, "Number of devnet guardians in example file")
-	templateGuardianIndex = AdminClientGuardianSetTemplateCmd.Flags().Int("idx", 0, "Default current guardian set index")
+	templateGuardianIndex = TemplateCmd.PersistentFlags().Int("idx", 0, "Default current guardian set index")
+	setUpdateNumGuardians = AdminClientGuardianSetTemplateCmd.Flags().Int("num", 1, "Number of devnet guardians in example file")
+
+	TemplateCmd.AddCommand(AdminClientGuardianSetTemplateCmd)
+	TemplateCmd.AddCommand(AdminClientContractUpgradeTemplateCmd)
+}
+
+var TemplateCmd = &cobra.Command{
+	Use:   "template",
+	Short: "Guardian governance VAA template commands ",
 }
 
 var AdminClientGuardianSetTemplateCmd = &cobra.Command{
-	Use:   "guardian-set-update-template",
+	Use:   "guardian-set-update",
 	Short: "Generate an empty guardian set template at specified path (offline)",
 	Run:   runGuardianSetTemplate,
 	Args:  cobra.ExactArgs(1),
 }
 
+var AdminClientContractUpgradeTemplateCmd = &cobra.Command{
+	Use:   "contract-upgrade",
+	Short: "Generate an empty contract upgrade template at specified path (offline)",
+	Run:   runContractUpgradeTemplate,
+	Args:  cobra.ExactArgs(1),
+}
+
 func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
 	path := args[0]
 
 	// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
-	guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *templateNumGuardians)
-	for i := 0; i < *templateNumGuardians; i++ {
+	guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
+	for i := 0; i < *setUpdateNumGuardians; i++ {
 		k := devnet.DeterministicEcdsaKeyByIndex(crypto.S256(), uint64(i))
 		guardians[i] = &nodev1.GuardianSetUpdate_Guardian{
 			Pubkey: crypto.PubkeyToAddress(k.PublicKey).Hex(),
@@ -41,12 +56,41 @@ func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
 		}
 	}
 
-	m := &nodev1.GuardianSetUpdate{
+	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,
+		Payload: &nodev1.InjectGovernanceVAARequest_GuardianSet{
+			GuardianSet: &nodev1.GuardianSetUpdate{Guardians: guardians},
+		},
+	}
+
+	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)
+	}
+}
+
+func runContractUpgradeTemplate(cmd *cobra.Command, args []string) {
+	path := args[0]
+
+	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,
-		Guardians: guardians,
+		Payload: &nodev1.InjectGovernanceVAARequest_ContractUpgrade{
+			ContractUpgrade: &nodev1.ContractUpgrade{
+				ChainId:     1,
+				NewContract: make([]byte, 32),
+			},
+		},
 	}
 
 	b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m)

+ 16 - 7
bridge/cmd/guardiand/adminverify.go

@@ -1,6 +1,7 @@
 package guardiand
 
 import (
+	"github.com/certusone/wormhole/bridge/pkg/vaa"
 	"io/ioutil"
 	"log"
 
@@ -11,14 +12,14 @@ import (
 	nodev1 "github.com/certusone/wormhole/bridge/pkg/proto/node/v1"
 )
 
-var AdminClientGuardianSetVerifyCmd = &cobra.Command{
-	Use:   "guardian-set-update-verify",
-	Short: "Verify guardian set update in prototxt format (offline)",
-	Run:   runGuardianSetVerify,
+var AdminClientGovernanceVAAVerifyCmd = &cobra.Command{
+	Use:   "governance-vaa-verify",
+	Short: "Verify governance vaa in prototxt format (offline)",
+	Run:   runGovernanceVAAVerify,
 	Args:  cobra.ExactArgs(1),
 }
 
-func runGuardianSetVerify(cmd *cobra.Command, args []string) {
+func runGovernanceVAAVerify(cmd *cobra.Command, args []string) {
 	path := args[0]
 
 	b, err := ioutil.ReadFile(path)
@@ -26,13 +27,21 @@ func runGuardianSetVerify(cmd *cobra.Command, args []string) {
 		log.Fatalf("failed to read file: %v", err)
 	}
 
-	var msg nodev1.GuardianSetUpdate
+	var msg nodev1.InjectGovernanceVAARequest
 	err = prototext.Unmarshal(b, &msg)
 	if err != nil {
 		log.Fatalf("failed to deserialize: %v", err)
 	}
 
-	v, err := adminGuardianSetUpdateToVAA(&msg)
+	var (
+		v *vaa.VAA
+	)
+	switch payload := msg.Payload.(type) {
+	case *nodev1.InjectGovernanceVAARequest_GuardianSet:
+		v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, msg.CurrentSetIndex, msg.Timestamp)
+	case *nodev1.InjectGovernanceVAARequest_ContractUpgrade:
+		v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, msg.CurrentSetIndex, msg.Timestamp)
+	}
 	if err != nil {
 		log.Fatalf("invalid update: %v", err)
 	}

+ 1 - 0
bridge/cmd/root.go

@@ -36,6 +36,7 @@ func init() {
 	rootCmd.AddCommand(guardiand.BridgeCmd)
 	rootCmd.AddCommand(guardiand.KeygenCmd)
 	rootCmd.AddCommand(guardiand.AdminCmd)
+	rootCmd.AddCommand(guardiand.TemplateCmd)
 }
 
 // initConfig reads in config file and ENV variables if set.

+ 11 - 0
bridge/pkg/processor/observation.go

@@ -172,6 +172,17 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
 				// A guardian set update is broadcast to every chain that we talk to.
 				p.devnetVAASubmission(ctx, signed, hash)
 				p.terraVAASubmission(ctx, signed, hash)
+			case *vaa.BodyContractUpgrade:
+				switch t.ChainID {
+				case vaa.ChainIDSolana:
+				// Already submitted to Solana.
+				default:
+					p.logger.Error("unsupported target chain for contract upgrade",
+						zap.String("digest", hash),
+						zap.Any("vaa", signed),
+						zap.String("bytes", hex.EncodeToString(vaaBytes)),
+						zap.Uint8("target_chain", t.ChainID))
+				}
 			default:
 				panic(fmt.Sprintf("unknown VAA payload type: %+v", v))
 			}

+ 37 - 0
bridge/pkg/vaa/structs.go

@@ -85,6 +85,13 @@ type (
 		// NewIndex is the index of the new guardian set
 		NewIndex uint32
 	}
+
+	BodyContractUpgrade struct {
+		// ChainID is the chain on which the contract should be upgraded
+		ChainID uint8
+		// NewContract is the address of the account containing the new contract.
+		NewContract Address
+	}
 )
 
 func (a Address) String() string {
@@ -106,6 +113,7 @@ func (c ChainID) String() string {
 
 const (
 	ActionGuardianSetUpdate Action = 0x01
+	ActionContractUpgrade   Action = 0x02
 	ActionTransfer          Action = 0x10
 
 	// ChainIDSolana is the ChainID of Solana
@@ -182,6 +190,8 @@ func Unmarshal(data []byte) (*VAA, error) {
 		v.Payload, err = parseBodyGuardianSetUpdate(payloadReader)
 	case ActionTransfer:
 		v.Payload, err = parseBodyTransfer(payloadReader)
+	case ActionContractUpgrade:
+		v.Payload, err = parseBodyContractUpgrade(payloadReader)
 	default:
 		return nil, fmt.Errorf("unknown action: %d", action)
 	}
@@ -403,6 +413,33 @@ func (v *BodyGuardianSetUpdate) serialize() ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
+func parseBodyContractUpgrade(r io.Reader) (*BodyContractUpgrade, error) {
+	b := &BodyContractUpgrade{}
+
+	if err := binary.Read(r, binary.BigEndian, &b.ChainID); err != nil {
+		return nil, fmt.Errorf("failed to read chain id: %w", err)
+	}
+
+	if n, err := r.Read(b.NewContract[:]); err != nil || n != 32 {
+		return nil, fmt.Errorf("failed to read new contract address: %w", err)
+	}
+
+	return b, nil
+}
+
+func (v *BodyContractUpgrade) getActionID() Action {
+	return ActionContractUpgrade
+}
+
+func (v *BodyContractUpgrade) serialize() ([]byte, error) {
+	buf := new(bytes.Buffer)
+
+	MustWrite(buf, binary.BigEndian, v.ChainID)
+	buf.Write(v.NewContract[:])
+
+	return buf.Bytes(), nil
+}
+
 // MustWrite calls binary.Write and panics on errors
 func MustWrite(w io.Writer, order binary.ByteOrder, data interface{}) {
 	if err := binary.Write(w, order, data); err != nil {

+ 18 - 0
bridge/pkg/vaa/types_test.go

@@ -61,6 +61,24 @@ func TestSerializeDeserialize(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "ContractUpgrade",
+			vaa: &VAA{
+				Version:          1,
+				GuardianSetIndex: 9,
+				Signatures: []*Signature{
+					{
+						Index:     1,
+						Signature: [65]byte{},
+					},
+				},
+				Timestamp: time.Unix(2837, 0),
+				Payload: &BodyContractUpgrade{
+					ChainID:     ChainIDEthereum,
+					NewContract: Address{1, 3, 4, 5, 2, 3},
+				},
+			},
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {

+ 14 - 0
docs/protocol.md

@@ -146,6 +146,20 @@ uint8 len(keys)
 The `new_index` must be monotonically increasing and is manually specified here to fix a potential guardian_set index 
 desynchronization between the any of the chains in the system.
 
+##### Contract upgrade
+
+ID: `0x02`
+
+Payload:
+
+```
+uint8 chain_id
+[32]uint8 new_contract
+```
+
+`chain_id` specifies the chain on which the contract should be updated. `new_contract` is the address of the updated
+contract.
+
 ##### Transfer
 
 ID: `0x10`

+ 8 - 0
docs/solana_program.md

@@ -154,6 +154,14 @@ followed by:
 | ----- | ------------     | ------------------- | ------ | --------- | ----- | ------- |
 | 9     | guardian_set_new | GuardianSet         |        | ✅        | ✅    | ✅      |
 
+##### Contract upgrade
+
+| Index | Name               | Type              | signer | writeable | empty | derived |
+| ----- | ------------------ | ----------------- | ------ | --------- | ----- | ------- |
+| 9     | new_contract       | Account           |        |           | ✅    |         |
+| 10    | program_data       | Account           |        | ✅        | ✅    | ✅      |
+| 11    | upgradeable_loader | UpgradeableLoader |        |           |       |         |
+
 ##### Transfer: Ethereum (native) -> Solana (wrapped)
 
 | Index | Name          | Type         | signer | writeable | empty | derived |

+ 54 - 42
proto/node/v1/node.proto

@@ -9,58 +9,70 @@ import "google/api/annotations.proto";
 // NodePrivileged exposes an administrative API. It runs on a UNIX socket and is authenticated
 // using Linux filesystem permissions.
 service NodePrivileged {
-    // SubmitGuardianSetVAA injects a guardian set change VAA into the guardian node.
-    // The node will inject the VAA into the aggregator and sign/broadcast the VAA signature.
-    //
-    // A consensus majority of nodes on the network will have to inject the VAA within the
-    // VAA timeout window for it to reach consensus.
-    //
-    rpc SubmitGuardianSetVAA (SubmitGuardianSetVAARequest) returns (SubmitGuardianSetVAAResponse);
+  // InjectGovernanceVAA injects a governance VAA into the guardian node.
+  // The node will inject the VAA into the aggregator and sign/broadcast the VAA signature.
+  //
+  // A consensus majority of nodes on the network will have to inject the VAA within the
+  // VAA timeout window for it to reach consensus.
+  //
+  rpc InjectGovernanceVAA (InjectGovernanceVAARequest) returns (InjectGovernanceVAAResponse);
 }
 
-// GuardianSet represents a new guardian set to be submitted to and signed by the node.
-// During the genesis procedure, this data structure will be assembled using off-chain collaborative tooling
-// like GitHub using a human-readable encoding, so readability is a concern.
-message GuardianSetUpdate {
-    // Index of the current guardian set to be replaced.
-    uint32 current_set_index = 1;
+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.
+  //
+  // For lockups, the timestamp identifies the block that the lockup belongs to.
 
-    // 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.
-    //
-    // For lockups, the timestamp identifies the block that the lockup belongs to. For guardian set updates,
-    // we create the VAA manually. Best practice is to pick a timestamp which roughly matches the expected
-    // genesis ceremony data.
-    //
-    // 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;
+  // 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;
 
-    // List of guardian set members.
-    message Guardian {
-        // Guardian key pubkey. Stored as hex string with 0x prefix for human readability -
-        // this is the canonical Ethereum representation.
-        string pubkey = 1;
-        // Optional descriptive name. Not stored on any chain, purely informational.
-        string name = 2;
-    };
-    repeated Guardian guardians = 3;
+  oneof payload{
+    GuardianSetUpdate guardian_set = 3;
+    ContractUpgrade contract_upgrade = 4;
+  }
 }
 
-message SubmitGuardianSetVAARequest {
-    GuardianSetUpdate guardian_set = 1;
+message InjectGovernanceVAAResponse {
+  // Canonical digest of the submitted VAA.
+  bytes digest = 1;
 }
 
-message SubmitGuardianSetVAAResponse {
-    // Canonical digest of the submitted VAA.
-    bytes digest = 1;
+// GuardianSet represents a new guardian set to be submitted to and signed by the node.
+// During the genesis procedure, this data structure will be assembled using off-chain collaborative tooling
+// like GitHub using a human-readable encoding, so readability is a concern.
+message GuardianSetUpdate {
+  // List of guardian set members.
+  message Guardian {
+    // Guardian key pubkey. Stored as hex string with 0x prefix for human readability -
+    // this is the canonical Ethereum representation.
+    string pubkey = 1;
+    // Optional descriptive name. Not stored on any chain, purely informational.
+    string name = 2;
+  };
+  repeated Guardian guardians = 3;
 }
 
 // GuardianKey specifies the on-disk format for a node's guardian key.
 message GuardianKey {
-    // data is the binary representation of the secp256k1 private key.
-    bytes data = 1;
+  // data is the binary representation of the secp256k1 private key.
+  bytes data = 1;
+}
+
+// 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).
+  uint32 chain_id = 1;
+
+  // Address of the new program/contract.
+  bytes new_contract = 2;
 }

+ 3 - 3
scripts/test-injection.sh

@@ -9,10 +9,10 @@ path=/tmp/new-guardianset.prototxt
 sock=/tmp/admin.sock
 
 # Create a no-op update that sets the same 1-node guardian set again.
-kubectl exec guardian-${node} -c guardiand -- /guardiand admin guardian-set-update-template --num=1 --idx=${idx} $path
+kubectl exec -n wormhole guardian-${node} -c guardiand -- /guardiand template guardian-set-update --num=1 --idx=${idx} $path
 
 # Verify and print resulting result. The digest incorporates the current time and is NOT deterministic.
-kubectl exec guardian-${node} -c guardiand -- /guardiand admin guardian-set-update-verify $path
+kubectl exec -n wormhole guardian-${node} -c guardiand -- /guardiand admin governance-vaa-verify $path
 
 # Submit to node
-kubectl exec guardian-${node} -c guardiand -- /guardiand admin guardian-set-update-inject --socket $sock $path
+kubectl exec -n wormhole guardian-${node} -c guardiand -- /guardiand admin governance-vaa-inject --socket $sock $path

+ 159 - 98
solana/Cargo.lock

@@ -330,6 +330,12 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
 
+[[package]]
+name = "bytes"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
+
 [[package]]
 name = "bzip2"
 version = "0.3.3"
@@ -823,9 +829,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.7.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
 dependencies = [
  "atty",
  "humantime",
@@ -1245,12 +1251,9 @@ checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
 
 [[package]]
 name = "humantime"
-version = "1.3.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
-dependencies = [
- "quick-error",
-]
+checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
 
 [[package]]
 name = "hyper"
@@ -1563,13 +1566,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
 
 [[package]]
-name = "memmap"
-version = "0.7.0"
+name = "memmap2"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a"
 dependencies = [
  "libc",
- "winapi 0.3.9",
 ]
 
 [[package]]
@@ -1629,12 +1631,25 @@ dependencies = [
  "kernel32-sys",
  "libc",
  "log",
- "miow",
+ "miow 0.2.2",
  "net2",
  "slab",
  "winapi 0.2.8",
 ]
 
+[[package]]
+name = "mio"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
+dependencies = [
+ "libc",
+ "log",
+ "miow 0.3.6",
+ "ntapi",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "mio-uds"
 version = "0.6.8"
@@ -1643,7 +1658,7 @@ checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
 dependencies = [
  "iovec",
  "libc",
- "mio",
+ "mio 0.6.23",
 ]
 
 [[package]]
@@ -1658,6 +1673,16 @@ dependencies = [
  "ws2_32-sys",
 ]
 
+[[package]]
+name = "miow"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
+dependencies = [
+ "socket2",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "multimap"
 version = "0.8.2"
@@ -1684,9 +1709,9 @@ dependencies = [
 
 [[package]]
 name = "net2"
-version = "0.2.36"
+version = "0.2.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
 dependencies = [
  "cfg-if 0.1.10",
  "libc",
@@ -1695,15 +1720,23 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.17.0"
+version = "0.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
+checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
 dependencies = [
  "bitflags",
  "cc",
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "libc",
- "void",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -2179,12 +2212,6 @@ dependencies = [
  "prost",
 ]
 
-[[package]]
-name = "quick-error"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-
 [[package]]
 name = "quote"
 version = "0.6.13"
@@ -2653,6 +2680,15 @@ dependencies = [
  "opaque-debug 0.3.0",
 ]
 
+[[package]]
+name = "signal-hook-registry"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "signature"
 version = "1.2.2"
@@ -2694,9 +2730,9 @@ dependencies = [
 
 [[package]]
 name = "solana-account-decoder"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29fa579ccae25e66c5b9f3e058b16b19efc823e653ee491bbcda1f06a440de7a"
+checksum = "469a64e65f2788c0fcd68124fce6a490c139a37ee159d855fe45bde9404be96e"
 dependencies = [
  "Inflector",
  "base64 0.12.3",
@@ -2718,9 +2754,9 @@ dependencies = [
 
 [[package]]
 name = "solana-clap-utils"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90e908b0267c02e3898fb5769109dddbc936f14aadcedd3d5925d1a4d9b120f9"
+checksum = "5bc41382ae2e834acce6b26e361e0ba79acec3b6633e36189e1cbcd16e3d9edf"
 dependencies = [
  "chrono",
  "clap",
@@ -2734,9 +2770,9 @@ dependencies = [
 
 [[package]]
 name = "solana-cli-config"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f0f69ef61a624038c6c2e0109f08c827633a04a4d21272e1e02e3bd70961008"
+checksum = "ef627ff85612a66af08384dfbe006948c74143cee6a0b80311a94e5e0ffe8c2b"
 dependencies = [
  "dirs-next",
  "lazy_static",
@@ -2748,9 +2784,9 @@ dependencies = [
 
 [[package]]
 name = "solana-client"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7253ee3b369812df3c30c9076ac2d5f042e09d7cb9c04e2928ebf5df26ad4ab2"
+checksum = "3fbe10a0b09201e4d272848644720d75d237dfe92536701b973ecb6a042d8363"
 dependencies = [
  "base64 0.13.0",
  "bincode",
@@ -2759,6 +2795,7 @@ dependencies = [
  "indicatif",
  "jsonrpc-core",
  "log",
+ "net2",
  "rayon",
  "reqwest",
  "semver 0.11.0",
@@ -2779,9 +2816,9 @@ dependencies = [
 
 [[package]]
 name = "solana-config-program"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c5c599f4049337d8c89c08811b3044203d083038e4e4be724843088211f3158"
+checksum = "d0dbb6f55cfd7eefb7d739c3a5b8ff2bba53825f002b7a3fc0531c2a335092a5"
 dependencies = [
  "bincode",
  "chrono",
@@ -2793,9 +2830,9 @@ dependencies = [
 
 [[package]]
 name = "solana-crate-features"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc73ada8cddc62edf58dc09ef3cf4551259d0ca1c7ece5f102757279ec2afa95"
+checksum = "a694ee719e16262801bdaeacda065e21352b7607cd7327b180172adca99864dc"
 dependencies = [
  "backtrace",
  "bytes 0.4.12",
@@ -2817,13 +2854,12 @@ dependencies = [
 
 [[package]]
 name = "solana-faucet"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16cae7630ea7accaae2e6f538f400bcac312271cf4ab882a92875570447650b0"
+checksum = "ca85d93e05c4270f15c4ce929895bafa42a6d6f368d6d70ada93fbd51add69f8"
 dependencies = [
  "bincode",
  "byteorder",
- "bytes 0.4.12",
  "clap",
  "log",
  "serde",
@@ -2834,21 +2870,20 @@ dependencies = [
  "solana-metrics",
  "solana-sdk",
  "solana-version",
- "tokio 0.1.22",
- "tokio-codec",
+ "tokio 0.3.6",
 ]
 
 [[package]]
 name = "solana-frozen-abi"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5ab6ad3dda6a3d95d19603eeedc65689d8125eafb3e23c6a1e01ab8a6ba60c"
+checksum = "2cc29ffc04200709ae0ece6a6a4a78d97043537e0cf70fa36a0cec200140e893"
 dependencies = [
  "bs58",
  "bv",
  "generic-array 0.14.4",
  "log",
- "memmap",
+ "memmap2",
  "rustc_version",
  "serde",
  "serde_derive",
@@ -2860,9 +2895,9 @@ dependencies = [
 
 [[package]]
 name = "solana-frozen-abi-macro"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffaa09aa938a67501479ed8a785071c6993f72c34e43f680db3ea7a2dadad9e7"
+checksum = "9351c5355512afadaa64225d0653b050c768a7d5938429bff8a2e7c17143429f"
 dependencies = [
  "lazy_static",
  "proc-macro2 1.0.24",
@@ -2873,9 +2908,9 @@ dependencies = [
 
 [[package]]
 name = "solana-logger"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d949157d0b23eaf5758b427d90741d2a90751c4e3dfee028f5726ab8b36e769"
+checksum = "188a6e6d7e001336d00ece746ecbd4b5e5ff1a12397c456934d831288f99b23d"
 dependencies = [
  "env_logger",
  "lazy_static",
@@ -2884,9 +2919,9 @@ dependencies = [
 
 [[package]]
 name = "solana-measure"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76c197e92932f6498371df4a52e972403b0a3dfd3eac101b2844774b43292d89"
+checksum = "cef4e2d6ffd08f35e6268b1bcffe63b37a6f9a53d91644757d607b8d3ba4251e"
 dependencies = [
  "jemalloc-ctl",
  "jemallocator",
@@ -2897,9 +2932,9 @@ dependencies = [
 
 [[package]]
 name = "solana-metrics"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ed2f570bd9d87a991c58800bd94149c34243f14c6b3dfc39fbca602e13db70a"
+checksum = "4c02403e4f6fcb97e149854c42a0a7e81af041e5228544ad375cd75fb82a859d"
 dependencies = [
  "env_logger",
  "gethostname",
@@ -2911,12 +2946,11 @@ dependencies = [
 
 [[package]]
 name = "solana-net-utils"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fea0a40ca7c0f5be26d1a203ac1ef44920f7c845ffdcf922e2aa597662e13958"
+checksum = "5468c5a5590aef624a668d264cb64f009d0d81ad8c7d609a57dbcb4328fcb2ef"
 dependencies = [
  "bincode",
- "bytes 0.4.12",
  "clap",
  "log",
  "nix",
@@ -2927,15 +2961,15 @@ dependencies = [
  "solana-clap-utils",
  "solana-logger",
  "solana-version",
- "tokio 0.1.22",
+ "tokio 0.3.6",
  "url",
 ]
 
 [[package]]
 name = "solana-program"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9c6cb16e8aa986bc0d2af8ec50628f7451bef9dac89924adf48302bd4fc755"
+checksum = "6c16bdd751d5549716a610f87b8ab1ca13ceaf66dfc9f325440894a72eadb74e"
 dependencies = [
  "bincode",
  "bs58",
@@ -2963,9 +2997,9 @@ dependencies = [
 
 [[package]]
 name = "solana-rayon-threadlimit"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de7367bfa559a08c2023d62f780ae01fddf865b974d7fd449bbeb1399641764d"
+checksum = "72de795cac4d5e9c9c083177bfcbe343c461c1ce5dcffbecddd2d0b7f09e04fa"
 dependencies = [
  "lazy_static",
  "num_cpus",
@@ -2973,9 +3007,9 @@ dependencies = [
 
 [[package]]
 name = "solana-remote-wallet"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fde6cc84dfcc15c34eedba0675ca51cbaa8b83ca70233c5751459af869568e1"
+checksum = "46f7717eca656d386dd233c60d6c63c3d5e16fa64dd3bca1a7f1b27007cf354e"
 dependencies = [
  "base32",
  "console 0.11.3",
@@ -2993,9 +3027,9 @@ dependencies = [
 
 [[package]]
 name = "solana-runtime"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d1364382b1ea6dd516c53455c817eaa5cd47bf7565cd00953586002731dd8fd"
+checksum = "b817d921c53245e3fcc78ac605fb61ae0ada81346fda7dec41a7681770610ca5"
 dependencies = [
  "bincode",
  "blake3",
@@ -3013,7 +3047,7 @@ dependencies = [
  "libc",
  "libloading",
  "log",
- "memmap",
+ "memmap2",
  "num-derive 0.3.3",
  "num-traits",
  "num_cpus",
@@ -3044,9 +3078,9 @@ dependencies = [
 
 [[package]]
 name = "solana-sdk"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85c38a02d501422070cd6a4d561b4ab1408e311c5a0b5af3a7bb01246da14f66"
+checksum = "280fa9cef4fdb9154fea538ed1efdc74215b3734a4ad611ddb1393269efac1bb"
 dependencies = [
  "assert_matches",
  "bincode",
@@ -3063,7 +3097,7 @@ dependencies = [
  "lazy_static",
  "libsecp256k1",
  "log",
- "memmap",
+ "memmap2",
  "num-derive 0.3.3",
  "num-traits",
  "pbkdf2",
@@ -3088,9 +3122,9 @@ dependencies = [
 
 [[package]]
 name = "solana-sdk-macro"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "475a680cd175f2e256452e007c6f8622d3a1ab97ab36d26303b35576e24f340c"
+checksum = "11489c157e78f60ad9d4a96dc4d1955ef6936ef606f6ce0273689fcdb90391c2"
 dependencies = [
  "bs58",
  "proc-macro2 1.0.24",
@@ -3101,9 +3135,9 @@ dependencies = [
 
 [[package]]
 name = "solana-secp256k1-program"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64f76265997062d8cb18b8055ab200294087ee0bb2de3cf65864eb964f39b2c8"
+checksum = "3eb1617ea1dec65e59ab94eb6bdcf15e831270ae41317f4e9bee926693f74f30"
 dependencies = [
  "bincode",
  "digest 0.9.0",
@@ -3116,9 +3150,9 @@ dependencies = [
 
 [[package]]
 name = "solana-stake-program"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fcb16ef12c9ed669308074bcc0549d5b36f29597fb701530f108088d438b3cd"
+checksum = "a691ac55bb8cb4ded6c192ab117b545e3090e92c13560b02318b02ed423eaef6"
 dependencies = [
  "bincode",
  "log",
@@ -3138,9 +3172,9 @@ dependencies = [
 
 [[package]]
 name = "solana-transaction-status"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2f69cb54e6854d4799529c22c21c1d53abaf2ebfc7b2c2fe1ce84b2afb9c620"
+checksum = "ede0f19e5bec9b0d9a85c4eba2f453547064b124b4849ddfe6e7212a94aa75b0"
 dependencies = [
  "Inflector",
  "base64 0.12.3",
@@ -3162,9 +3196,9 @@ dependencies = [
 
 [[package]]
 name = "solana-version"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6995a2a6734a108b4a80497a391e86c8d234d94b02ceec0cebc9ba132874502"
+checksum = "94bc42af0fb331572e8161e099f4b1c50cec8f12c3d64d61ff04c0719199aebb"
 dependencies = [
  "log",
  "rustc_version",
@@ -3178,9 +3212,9 @@ dependencies = [
 
 [[package]]
 name = "solana-vote-program"
-version = "1.4.14"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b42b3fd7b76dd8c98eb13f92677b1803d0bc1ac06af45e10f1aee46b8c19a8a"
+checksum = "c9a3c8a54916bb189a80dee075bae93950198ac8b51cde6d2e7e8860b8a05250"
 dependencies = [
  "bincode",
  "log",
@@ -3205,18 +3239,18 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
 [[package]]
 name = "spl-memo"
-version = "2.0.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99775feb54f735a6826ea0af500c1f78f7a5974d6b17f1ac586cd114e2da7d80"
+checksum = "fb2b771f6146dec14ef5fbf498f9374652c54badc3befc8c40c1d426dd45d720"
 dependencies = [
  "solana-program",
 ]
 
 [[package]]
 name = "spl-token"
-version = "3.0.0"
+version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f77fa0b41cbc82d1d7c8f2d914b49e9a1a7b6e32af952d03383fb989c42bc89"
+checksum = "a9774eebb62ff1ff2f5eca112413e476143925a2f5a43cee98fc5d3a6c0eec5c"
 dependencies = [
  "arrayref",
  "num-derive 0.3.3",
@@ -3447,7 +3481,7 @@ checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
 dependencies = [
  "bytes 0.4.12",
  "futures 0.1.30",
- "mio",
+ "mio 0.6.23",
  "num_cpus",
  "tokio-codec",
  "tokio-current-thread",
@@ -3476,12 +3510,34 @@ dependencies = [
  "lazy_static",
  "libc",
  "memchr",
- "mio",
+ "mio 0.6.23",
  "mio-uds",
  "num_cpus",
  "pin-project-lite 0.1.11",
  "slab",
- "tokio-macros",
+ "tokio-macros 0.2.6",
+]
+
+[[package]]
+name = "tokio"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "720ba21c25078711bf456d607987d95bce90f7c3bea5abe1db587862e7a1e87c"
+dependencies = [
+ "autocfg",
+ "bytes 0.6.0",
+ "futures-core",
+ "libc",
+ "memchr",
+ "mio 0.7.7",
+ "num_cpus",
+ "once_cell",
+ "parking_lot 0.11.1",
+ "pin-project-lite 0.2.0",
+ "signal-hook-registry",
+ "slab",
+ "tokio-macros 0.3.2",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -3548,6 +3604,17 @@ dependencies = [
  "syn 1.0.53",
 ]
 
+[[package]]
+name = "tokio-macros"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46dfffa59fc3c8aad216ed61bdc2c263d2b9d87a9c8ac9de0c11a813e51b6db7"
+dependencies = [
+ "proc-macro2 1.0.24",
+ "quote 1.0.7",
+ "syn 1.0.53",
+]
+
 [[package]]
 name = "tokio-reactor"
 version = "0.1.12"
@@ -3558,7 +3625,7 @@ dependencies = [
  "futures 0.1.30",
  "lazy_static",
  "log",
- "mio",
+ "mio 0.6.23",
  "num_cpus",
  "parking_lot 0.9.0",
  "slab",
@@ -3598,7 +3665,7 @@ dependencies = [
  "bytes 0.4.12",
  "futures 0.1.30",
  "iovec",
- "mio",
+ "mio 0.6.23",
  "tokio-io",
  "tokio-reactor",
 ]
@@ -3641,7 +3708,7 @@ dependencies = [
  "bytes 0.4.12",
  "futures 0.1.30",
  "log",
- "mio",
+ "mio 0.6.23",
  "tokio-codec",
  "tokio-io",
  "tokio-reactor",
@@ -3658,7 +3725,7 @@ dependencies = [
  "iovec",
  "libc",
  "log",
- "mio",
+ "mio 0.6.23",
  "mio-uds",
  "tokio-codec",
  "tokio-io",
@@ -4114,12 +4181,6 @@ version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
 
-[[package]]
-name = "void"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
-
 [[package]]
 name = "walkdir"
 version = "2.3.1"

+ 5 - 5
solana/agent/Cargo.toml

@@ -9,10 +9,10 @@ tonic = "0.3.0"
 tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
 prost = "0.6"
 prost-types = "0.6"
-solana-sdk = { version = "1.4.7" }
-solana-client = { version = "1.4.7" }
-solana-faucet = "1.4.7"
-spl-token =  "=3.0.0"
+solana-sdk = { version = "1.4.20" }
+solana-client = { version = "1.4.20" }
+solana-faucet = "1.4.20"
+spl-token =  "=3.0.1"
 wormhole-bridge = { path = "../bridge" }
 primitive-types = { version = "0.7.2" }
 hex = "0.4.2"
@@ -21,7 +21,7 @@ tungstenite = "0.11.1"
 serde = "1.0.103"
 url = "2.1.1"
 serde_bytes = "0.11.5"
-log = "0.4.8"
+log = "0.4.11"
 serde_derive = "1.0.103"
 serde_json = "1.0.57"
 bs58 = "0.3.1"

+ 2 - 2
solana/bridge/Cargo.toml

@@ -17,8 +17,8 @@ program = ["spl-token/no-entrypoint"]
 num-derive = "0.2"
 num-traits = "0.2"
 remove_dir_all = "=0.5.0"
-solana-program = "1.4.7"
-spl-token = { version = "=3.0.0" }
+solana-program = "1.4.20"
+spl-token = { version = "=3.0.1" }
 thiserror = "1.0"
 byteorder = "1.3.4"
 zerocopy = "0.3.0"

+ 3 - 0
solana/bridge/src/error.rs

@@ -112,6 +112,9 @@ pub enum Error {
     /// Invalid Sysvar
     #[error("InvalidSysvar")]
     InvalidSysvar,
+    /// Invalid Chain
+    #[error("InvalidChain")]
+    InvalidChain,
 }
 
 impl From<Error> for ProgramError {

+ 1 - 0
solana/bridge/src/error_program.rs

@@ -42,6 +42,7 @@ impl PrintProgramError for Error {
             Error::InsufficientFees => msg!("Error: InsufficientFees"),
             Error::InvalidOwner => msg!("Error: InvalidOwner"),
             Error::InvalidSysvar => msg!("Error: InvalidSysvar"),
+            Error::InvalidChain => msg!("Error: InvalidChain"),
         }
     }
 }

+ 6 - 0
solana/bridge/src/instruction.rs

@@ -425,6 +425,12 @@ pub fn post_vaa(
                 Bridge::derive_guardian_set_id(program_id, &bridge_key, u.new_index)?;
             accounts.push(AccountMeta::new(guardian_set_key, false));
         }
+        VAABody::UpgradeContract(u) => {
+            accounts.push(AccountMeta::new(u.buffer, false));
+            let (programdata_address, _) = Pubkey::find_program_address(&[program_id.as_ref()], &solana_program::bpf_loader_upgradeable::id());
+            accounts.push(AccountMeta::new(programdata_address, false));
+            accounts.push(AccountMeta::new_readonly(solana_program::bpf_loader_upgradeable::id(), false));
+        }
         VAABody::Transfer(t) => {
             if t.source_chain == CHAIN_ID_SOLANA {
                 // Solana (any) -> Ethereum (any)

+ 33 - 0
solana/bridge/src/processor.rs

@@ -36,6 +36,7 @@ use solana_program::program_pack::Pack;
 use std::borrow::BorrowMut;
 use std::ops::Add;
 use solana_program::fee_calculator::FeeCalculator;
+use crate::vaa::BodyContractUpgrade;
 
 /// SigInfo contains metadata about signers in a VerifySignature ix
 struct SigInfo {
@@ -772,6 +773,19 @@ impl Bridge {
                     )
                 }
             }
+            VAABody::UpgradeContract(v) => {
+                if v.chain_id == CHAIN_ID_SOLANA {
+                    evict_signatures = true;
+                    Self::process_vaa_upgrade(
+                        program_id,
+                        accounts,
+                        bridge_info,
+                        v,
+                    )
+                } else {
+                    return Err(Error::InvalidChain.into());
+                }
+            }
         }?;
 
         // Check and create claim
@@ -1001,6 +1015,25 @@ impl Bridge {
         Ok(())
     }
 
+    /// Processes a VAA contract upgrade
+    pub fn process_vaa_upgrade(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        bridge_info: &AccountInfo,
+        b: &BodyContractUpgrade,
+    ) -> ProgramResult {
+        // Invoke upgrade
+        let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
+            program_id,
+            &b.buffer,
+            bridge_info.key,
+            bridge_info.key,
+        );
+        Self::invoke_as_bridge(program_id, &upgrade_ix, accounts);
+
+        Ok(())
+    }
+
     /// Creates a new wrapped asset
     pub fn process_create_wrapped(
         program_id: &Pubkey,

+ 90 - 0
solana/bridge/src/vaa.rs

@@ -6,6 +6,7 @@ use sha3::Digest;
 use solana_program::program_error::ProgramError;
 
 use crate::{error::Error, state::AssetMeta};
+use solana_program::pubkey::Pubkey;
 
 pub type ForeignAddress = [u8; 32];
 
@@ -129,12 +130,14 @@ impl VAA {
 pub enum VAABody {
     UpdateGuardianSet(BodyUpdateGuardianSet),
     Transfer(BodyTransfer),
+    UpgradeContract(BodyContractUpgrade),
 }
 
 impl VAABody {
     fn action_id(&self) -> u8 {
         match self {
             VAABody::UpdateGuardianSet(_) => 0x01,
+            VAABody::UpgradeContract(_) => 0x02,
             VAABody::Transfer(_) => 0x10,
         }
     }
@@ -147,6 +150,7 @@ impl VAABody {
             0x01 => {
                 VAABody::UpdateGuardianSet(BodyUpdateGuardianSet::deserialize(&mut payload_data)?)
             }
+            0x02 => VAABody::UpgradeContract(BodyContractUpgrade::deserialize(&mut payload_data)?),
             0x10 => VAABody::Transfer(BodyTransfer::deserialize(&mut payload_data)?),
             _ => {
                 return Err(Error::InvalidVAAAction);
@@ -160,6 +164,7 @@ impl VAABody {
         match self {
             VAABody::Transfer(b) => b.serialize(),
             VAABody::UpdateGuardianSet(b) => b.serialize(),
+            VAABody::UpgradeContract(b) => b.serialize(),
         }
     }
 }
@@ -181,6 +186,34 @@ pub struct BodyTransfer {
     pub amount: U256,
 }
 
+#[derive(Clone, Debug, PartialEq)]
+pub struct BodyContractUpgrade {
+    pub chain_id: u8,
+    pub buffer: Pubkey,
+}
+
+impl BodyContractUpgrade {
+    fn deserialize(data: &mut Cursor<&Vec<u8>>) -> Result<BodyContractUpgrade, Error> {
+        let chain_id = data.read_u8()?;
+        let mut key: [u8; 32] = [0; 32];
+        data.read(&mut key[..])?;
+
+        Ok(BodyContractUpgrade {
+            chain_id,
+            buffer: Pubkey::new(&key[..]),
+        })
+    }
+
+    fn serialize(&self) -> Result<Vec<u8>, Error> {
+        let mut v: Cursor<Vec<u8>> = Cursor::new(Vec::new());
+        v.write_u8(self.chain_id)?;
+        v.write(&self.buffer.to_bytes());
+
+        Ok(v.into_inner())
+    }
+}
+
+
 impl BodyUpdateGuardianSet {
     fn deserialize(data: &mut Cursor<&Vec<u8>>) -> Result<BodyUpdateGuardianSet, Error> {
         let new_index = data.read_u32::<BigEndian>()?;
@@ -273,6 +306,8 @@ mod tests {
         state::AssetMeta,
         vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA},
     };
+    use crate::vaa::BodyContractUpgrade;
+    use solana_program::pubkey::Pubkey;
 
     #[test]
     fn serialize_deserialize_vaa_transfer() {
@@ -329,6 +364,29 @@ mod tests {
         assert_eq!(vaa, parsed_vaa)
     }
 
+    #[test]
+    fn serialize_deserialize_vaa_contract_upgrade() {
+        let vaa = VAA {
+            version: 8,
+            guardian_set_index: 3,
+            signatures: vec![Signature {
+                index: 1,
+                r: [2; 32],
+                s: [2; 32],
+                v: 7,
+            }],
+            timestamp: 83,
+            payload: Some(VAABody::UpgradeContract(BodyContractUpgrade {
+                chain_id: 3,
+                buffer: Pubkey::new_unique(),
+            })),
+        };
+
+        let data = vaa.serialize().unwrap();
+        let parsed_vaa = VAA::deserialize(data.as_slice()).unwrap();
+        assert_eq!(vaa, parsed_vaa)
+    }
+
     #[test]
     fn parse_given_guardian_set_update() {
         let vaa = VAA {
@@ -411,4 +469,36 @@ mod tests {
         let rec_data = parsed_vaa.serialize().unwrap();
         assert_eq!(data, rec_data);
     }
+
+    #[test]
+    fn parse_given_contract_upgrade() {
+        let vaa = VAA {
+            version: 1,
+            guardian_set_index: 2,
+            signatures: vec![Signature {
+                index: 0,
+                r: [
+                    72, 156, 56, 20, 222, 146, 161, 112, 22, 97, 69, 59, 188, 199, 130, 240, 89,
+                    249, 241, 79, 96, 27, 235, 10, 99, 16, 56, 80, 232, 188, 235, 11
+                ],
+                s: [
+                    65, 19, 144, 42, 104, 122, 52, 0, 126, 7, 43, 127, 120, 85, 5, 21, 216, 207,
+                    78, 73, 213, 207, 142, 103, 211, 192, 100, 90, 27, 98, 176, 98
+                ],
+                v: 1,
+            }],
+            timestamp: 4000,
+            payload: Some(VAABody::UpgradeContract(BodyContractUpgrade {
+                chain_id: 2,
+                buffer: Pubkey::new(&[146, 115, 122, 21, 4, 243, 179, 223, 140, 147, 203, 133, 198, 74, 72, 96, 187,
+                    39, 14, 38, 2, 107, 110, 55, 240, 149, 53, 106, 64, 111, 106, 244]),
+            })),
+        };
+        let data = hex::decode("01000000020100489c3814de92a1701661453bbcc782f059f9f14f601beb0a63103850e8bceb0b4113902a687a34007e072b7f78550515d8cf4e49d5cf8e67d3c0645a1b62b0620100000fa0020292737a1504f3b3df8c93cb85c64a4860bb270e26026b6e37f095356a406f6af4").unwrap();
+        let parsed_vaa = VAA::deserialize(data.as_slice()).unwrap();
+        assert_eq!(vaa, parsed_vaa);
+
+        let rec_data = parsed_vaa.serialize().unwrap();
+        assert_eq!(data, rec_data);
+    }
 }

+ 8 - 8
solana/cli/Cargo.toml

@@ -8,14 +8,14 @@ edition = "2018"
 
 [dependencies]
 clap = "2.33.0"
-solana-clap-utils = { version = "1.4.7" }
-solana-cli-config = { version = "1.4.7" }
-solana-logger = { version = "1.4.7" }
-solana-sdk = { version = "1.4.7" }
-solana-client = { version = "1.4.7" }
-solana-faucet = "1.4.7"
-solana-account-decoder = { version = "1.4.7" }
-spl-token = "=3.0.0"
+solana-clap-utils = { version = "1.4.20" }
+solana-cli-config = { version = "1.4.20" }
+solana-logger = { version = "1.4.20" }
+solana-sdk = { version = "1.4.20" }
+solana-client = { version = "1.4.20" }
+solana-faucet = "1.4.20"
+solana-account-decoder = { version = "1.4.20" }
+spl-token = "=3.0.1"
 wormhole-bridge = { path = "../bridge" }
 primitive-types = { version = "0.7.2" }
 hex = "0.4.2"

+ 1 - 1
third_party/solana/Dockerfile

@@ -11,7 +11,7 @@ RUN rustup component add rustfmt
 WORKDIR /usr/src/solana
 
 RUN git clone https://github.com/solana-labs/solana --branch master && \
-  cd solana && git checkout v1.4.12
+  cd solana && git checkout v1.4.20
 
 ADD *.patch .