package adminrpc

import (
	"bytes"
	"context"
	"crypto/ecdsa"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"math/big"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/certusone/wormhole/node/pkg/watchers/evm/connectors"
	"github.com/holiman/uint256"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"golang.org/x/exp/slices"

	"github.com/certusone/wormhole/node/pkg/db"
	"github.com/certusone/wormhole/node/pkg/governor"
	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
	ethcommon "github.com/ethereum/go-ethereum/common"
	"github.com/mr-tron/base58"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/certusone/wormhole/node/pkg/common"
	nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1"
	"github.com/wormhole-foundation/wormhole/sdk/vaa"

	sdktypes "github.com/cosmos/cosmos-sdk/types"
)

var (
	vaaInjectionsTotal = promauto.NewCounter(
		prometheus.CounterOpts{
			Name: "wormhole_vaa_injections_total",
			Help: "Total number of injected VAA queued for broadcast",
		})
)

type nodePrivilegedService struct {
	nodev1.UnimplementedNodePrivilegedServiceServer
	db              *db.Database
	injectC         chan<- *common.MessagePublication
	obsvReqSendC    chan<- *gossipv1.ObservationRequest
	logger          *zap.Logger
	signedInC       chan<- *gossipv1.SignedVAAWithQuorum
	governor        *governor.ChainGovernor
	evmConnector    connectors.Connector
	gsCache         sync.Map
	gk              *ecdsa.PrivateKey
	guardianAddress ethcommon.Address
	rpcMap          map[string]string
}

func NewPrivService(
	db *db.Database,
	injectC chan<- *common.MessagePublication,
	obsvReqSendC chan<- *gossipv1.ObservationRequest,
	logger *zap.Logger,
	signedInC chan<- *gossipv1.SignedVAAWithQuorum,
	governor *governor.ChainGovernor,
	evmConnector connectors.Connector,
	gk *ecdsa.PrivateKey,
	guardianAddress ethcommon.Address,
	rpcMap map[string]string,

) *nodePrivilegedService {
	return &nodePrivilegedService{
		db:              db,
		injectC:         injectC,
		obsvReqSendC:    obsvReqSendC,
		logger:          logger,
		signedInC:       signedInC,
		governor:        governor,
		evmConnector:    evmConnector,
		gk:              gk,
		guardianAddress: guardianAddress,
		rpcMap:          rpcMap,
	}
}

// adminGuardianSetUpdateToVAA converts a nodev1.GuardianSetUpdate message to its canonical VAA representation.
// Returns an error if the data is invalid.
func adminGuardianSetUpdateToVAA(req *nodev1.GuardianSetUpdate, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if len(req.Guardians) == 0 {
		return nil, errors.New("empty guardian set specified")
	}

	if len(req.Guardians) > common.MaxGuardianCount {
		return nil, fmt.Errorf("too many guardians - %d, maximum is %d", len(req.Guardians), common.MaxGuardianCount)
	}

	addrs := make([]ethcommon.Address, len(req.Guardians))
	for i, g := range req.Guardians {
		if !ethcommon.IsHexAddress(g.Pubkey) {
			return nil, fmt.Errorf("invalid pubkey format at index %d (%s)", i, g.Name)
		}

		ethAddr := ethcommon.HexToAddress(g.Pubkey)
		for j, pk := range addrs {
			if pk == ethAddr {
				return nil, fmt.Errorf("duplicate pubkey at index %d (duplicate of %d): %s", i, j, g.Name)
			}
		}

		addrs[i] = ethAddr
	}

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyGuardianSetUpdate{
			Keys:     addrs,
			NewIndex: guardianSetIndex + 1,
		}.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, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	b, err := hex.DecodeString(req.NewContract)
	if err != nil {
		return nil, errors.New("invalid new contract address encoding (expected hex)")
	}

	if len(b) != 32 {
		return nil, errors.New("invalid new_contract address")
	}

	if req.ChainId > math.MaxUint16 {
		return nil, errors.New("invalid chain_id")
	}

	newContractAddress := vaa.Address{}
	copy(newContractAddress[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyContractUpgrade{
			ChainID:     vaa.ChainID(req.ChainId),
			NewContract: newContractAddress,
		}.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.BridgeRegisterChain, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.ChainId > math.MaxUint16 {
		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[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyTokenBridgeRegisterChain{
			Module:         req.Module,
			ChainID:        vaa.ChainID(req.ChainId),
			EmitterAddress: emitterAddress,
		}.Serialize())

	return v, nil
}

// recoverChainId converts a nodev1.RecoverChainId message to its canonical VAA representation.
// Returns an error if the data is invalid.
func recoverChainId(req *nodev1.RecoverChainId, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	evm_chain_id_big := big.NewInt(0)
	evm_chain_id_big, ok := evm_chain_id_big.SetString(req.EvmChainId, 10)
	if !ok {
		return nil, errors.New("invalid evm_chain_id")
	}

	// uint256 has Bytes32 method for easier serialization
	evm_chain_id, overflow := uint256.FromBig(evm_chain_id_big)
	if overflow {
		return nil, errors.New("evm_chain_id overflow")
	}

	if req.NewChainId > math.MaxUint16 {
		return nil, errors.New("invalid new_chain_id")
	}

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyRecoverChainId{
			Module:     req.Module,
			EvmChainID: evm_chain_id,
			NewChainID: vaa.ChainID(req.NewChainId),
		}.Serialize())

	return v, nil
}

// accountantModifyBalance converts a nodev1.AccountantModifyBalance message to its canonical VAA representation.
// Returns an error if the data is invalid.
func accountantModifyBalance(req *nodev1.AccountantModifyBalance, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.TargetChainId > math.MaxUint16 {
		return nil, errors.New("invalid target_chain_id")
	}
	if req.ChainId > math.MaxUint16 {
		return nil, errors.New("invalid chain_id")
	}
	if req.TokenChain > math.MaxUint16 {
		return nil, errors.New("invalid token_chain")
	}

	b, err := hex.DecodeString(req.TokenAddress)
	if err != nil {
		return nil, errors.New("invalid token address (expected hex)")
	}

	if len(b) != 32 {
		return nil, errors.New("invalid new token address (expected 32 bytes)")
	}

	if len(req.Reason) > 32 {
		return nil, errors.New("the reason should not be larger than 32 bytes")
	}

	amount_big := big.NewInt(0)
	amount_big, ok := amount_big.SetString(req.Amount, 10)
	if !ok {
		return nil, errors.New("invalid amount")
	}

	// uint256 has Bytes32 method for easier serialization
	amount, overflow := uint256.FromBig(amount_big)
	if overflow {
		return nil, errors.New("amount overflow")
	}

	tokenAdress := vaa.Address{}
	copy(tokenAdress[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyAccountantModifyBalance{
			Module:        req.Module,
			TargetChainID: vaa.ChainID(req.TargetChainId),

			Sequence:     req.Sequence,
			ChainId:      vaa.ChainID(req.ChainId),
			TokenChain:   vaa.ChainID(req.TokenChain),
			TokenAddress: tokenAdress,
			Kind:         uint8(req.Kind),
			Amount:       amount,
			Reason:       req.Reason,
		}.Serialize())

	return v, nil
}

// tokenBridgeUpgradeContract converts a nodev1.TokenBridgeRegisterChain message to its canonical VAA representation.
// Returns an error if the data is invalid.
func tokenBridgeUpgradeContract(req *nodev1.BridgeUpgradeContract, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.TargetChainId > math.MaxUint16 {
		return nil, errors.New("invalid target_chain_id")
	}

	b, err := hex.DecodeString(req.NewContract)
	if err != nil {
		return nil, errors.New("invalid new contract address (expected hex)")
	}

	if len(b) != 32 {
		return nil, errors.New("invalid new contract address (expected 32 bytes)")
	}

	newContract := vaa.Address{}
	copy(newContract[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyTokenBridgeUpgradeContract{
			Module:        req.Module,
			TargetChainID: vaa.ChainID(req.TargetChainId),
			NewContract:   newContract,
		}.Serialize())

	return v, nil
}

// wormchainStoreCode converts a nodev1.WormchainStoreCode to its canonical VAA representation
// Returns an error if the data is invalid
func wormchainStoreCode(req *nodev1.WormchainStoreCode, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	// validate the length of the hex passed in
	b, err := hex.DecodeString(req.WasmHash)
	if err != nil {
		return nil, fmt.Errorf("invalid cosmwasm bytecode hash (expected hex): %w", err)
	}

	if len(b) != 32 {
		return nil, fmt.Errorf("invalid cosmwasm bytecode hash (expected 32 bytes but received %d bytes)", len(b))
	}

	wasmHash := [32]byte{}
	copy(wasmHash[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyWormchainStoreCode{
			WasmHash: wasmHash,
		}.Serialize())

	return v, nil
}

// wormchainInstantiateContract converts a nodev1.WormchainInstantiateContract to its canonical VAA representation
// Returns an error if the data is invalid
func wormchainInstantiateContract(req *nodev1.WormchainInstantiateContract, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) { //nolint:unparam // error is always nil but kept to mirror function signature of other functions
	instantiationParams_hash := vaa.CreateInstatiateCosmwasmContractHash(req.CodeId, req.Label, []byte(req.InstantiationMsg))

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyWormchainInstantiateContract{
			InstantiationParamsHash: instantiationParams_hash,
		}.Serialize())

	return v, nil
}

// wormchainMigrateContract converts a nodev1.WormchainMigrateContract to its canonical VAA representation
func wormchainMigrateContract(req *nodev1.WormchainMigrateContract, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) { //nolint:unparam // error is always nil but kept to mirror function signature of other functions
	instantiationParams_hash := vaa.CreateMigrateCosmwasmContractHash(req.CodeId, req.Contract, []byte(req.InstantiationMsg))

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyWormchainMigrateContract{
			MigrationParamsHash: instantiationParams_hash,
		}.Serialize())

	return v, nil
}

func wormchainWasmInstantiateAllowlist(
	req *nodev1.WormchainWasmInstantiateAllowlist,
	timestamp time.Time,
	guardianSetIndex uint32,
	nonce uint32,
	sequence uint64,
) (*vaa.VAA, error) { //nolint:unparam // error is always nil but kept to mirror function signature of other functions
	decodedAddr, err := sdktypes.GetFromBech32(req.Contract, "wormhole")
	if err != nil {
		return nil, err
	}

	var action vaa.GovernanceAction
	if req.Action == nodev1.WormchainWasmInstantiateAllowlistAction_WORMCHAIN_WASM_INSTANTIATE_ALLOWLIST_ACTION_ADD {
		action = vaa.ActionAddWasmInstantiateAllowlist
	} else if req.Action == nodev1.WormchainWasmInstantiateAllowlistAction_WORMCHAIN_WASM_INSTANTIATE_ALLOWLIST_ACTION_DELETE {
		action = vaa.ActionDeleteWasmInstantiateAllowlist
	} else {
		return nil, fmt.Errorf("unrecognized wasm instantiate allowlist action")
	}

	var decodedAddr32 [32]byte
	copy(decodedAddr32[:], decodedAddr)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex, vaa.BodyWormchainWasmAllowlistInstantiate{
		ContractAddr: decodedAddr32,
		CodeId:       req.CodeId,
	}.Serialize(action))

	return v, nil
}

func gatewayScheduleUpgrade(
	req *nodev1.GatewayScheduleUpgrade,
	timestamp time.Time,
	guardianSetIndex uint32,
	nonce uint32,
	sequence uint64,
) (*vaa.VAA, error) { //nolint:unparam // error is always nil but kept to mirror function signature of other functions
	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex, vaa.BodyGatewayScheduleUpgrade{
		Name:   req.Name,
		Height: req.Height,
	}.Serialize())

	return v, nil
}

func gatewayCancelUpgrade(
	timestamp time.Time,
	guardianSetIndex uint32,
	nonce uint32,
	sequence uint64,
) (*vaa.VAA, error) { //nolint:unparam // error is always nil but kept to mirror function signature of other functions
	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.EmptyPayloadVaa(vaa.GatewayModuleStr, vaa.ActionCancelUpgrade, vaa.ChainIDWormchain),
	)

	return v, nil
}

func gatewayIbcComposabilityMwSetContract(
	req *nodev1.GatewayIbcComposabilityMwSetContract,
	timestamp time.Time,
	guardianSetIndex uint32,
	nonce uint32,
	sequence uint64,
) (*vaa.VAA, error) {
	decodedAddr, err := sdktypes.GetFromBech32(req.Contract, "wormhole")
	if err != nil {
		return nil, err
	}

	var decodedAddr32 [32]byte
	copy(decodedAddr32[:], decodedAddr)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex, vaa.BodyGatewayIbcComposabilityMwContract{
		ContractAddr: decodedAddr32,
	}.Serialize())

	return v, nil
}

// circleIntegrationUpdateWormholeFinality converts a nodev1.CircleIntegrationUpdateWormholeFinality to its canonical VAA representation
// Returns an error if the data is invalid
func circleIntegrationUpdateWormholeFinality(req *nodev1.CircleIntegrationUpdateWormholeFinality, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.TargetChainId > math.MaxUint16 {
		return nil, fmt.Errorf("invalid target chain id, must be <= %d", math.MaxUint16)
	}
	if req.Finality > math.MaxUint8 {
		return nil, fmt.Errorf("invalid finality, must be <= %d", math.MaxUint8)
	}
	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyCircleIntegrationUpdateWormholeFinality{
			TargetChainID: vaa.ChainID(req.TargetChainId),
			Finality:      uint8(req.Finality),
		}.Serialize())

	return v, nil
}

// circleIntegrationRegisterEmitterAndDomain converts a nodev1.CircleIntegrationRegisterEmitterAndDomain to its canonical VAA representation
// Returns an error if the data is invalid
func circleIntegrationRegisterEmitterAndDomain(req *nodev1.CircleIntegrationRegisterEmitterAndDomain, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.TargetChainId > math.MaxUint16 {
		return nil, fmt.Errorf("invalid target chain id, must be <= %d", math.MaxUint16)
	}
	if req.ForeignEmitterChainId > math.MaxUint16 {
		return nil, fmt.Errorf("invalid foreign emitter chain id, must be <= %d", math.MaxUint16)
	}
	b, err := hex.DecodeString(req.ForeignEmitterAddress)
	if err != nil {
		return nil, errors.New("invalid foreign emitter address encoding (expected hex)")
	}

	if len(b) != 32 {
		return nil, errors.New("invalid foreign emitter address (expected 32 bytes)")
	}

	foreignEmitterAddress := vaa.Address{}
	copy(foreignEmitterAddress[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyCircleIntegrationRegisterEmitterAndDomain{
			TargetChainID:         vaa.ChainID(req.TargetChainId),
			ForeignEmitterChainId: vaa.ChainID(req.ForeignEmitterChainId),
			ForeignEmitterAddress: foreignEmitterAddress,
			CircleDomain:          req.CircleDomain,
		}.Serialize())

	return v, nil
}

// circleIntegrationUpgradeContractImplementation converts a nodev1.CircleIntegrationUpgradeContractImplementation to its canonical VAA representation
// Returns an error if the data is invalid
func circleIntegrationUpgradeContractImplementation(req *nodev1.CircleIntegrationUpgradeContractImplementation, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.TargetChainId > math.MaxUint16 {
		return nil, fmt.Errorf("invalid target chain id, must be <= %d", math.MaxUint16)
	}
	b, err := hex.DecodeString(req.NewImplementationAddress)
	if err != nil {
		return nil, errors.New("invalid new implementation address encoding (expected hex)")
	}

	if len(b) != 32 {
		return nil, errors.New("invalid new implementation address (expected 32 bytes)")
	}

	newImplementationAddress := vaa.Address{}
	copy(newImplementationAddress[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyCircleIntegrationUpgradeContractImplementation{
			TargetChainID:            vaa.ChainID(req.TargetChainId),
			NewImplementationAddress: newImplementationAddress,
		}.Serialize())

	return v, nil
}

func ibcUpdateChannelChain(
	req *nodev1.IbcUpdateChannelChain,
	timestamp time.Time,
	guardianSetIndex uint32,
	nonce uint32,
	sequence uint64,
) (*vaa.VAA, error) {
	// validate parameters
	if req.TargetChainId > math.MaxUint16 {
		return nil, fmt.Errorf("invalid target chain id, must be <= %d", math.MaxUint16)
	}

	if req.ChainId > math.MaxUint16 {
		return nil, fmt.Errorf("invalid chain id, must be <= %d", math.MaxUint16)
	}

	if len(req.ChannelId) > 64 {
		return nil, fmt.Errorf("invalid channel ID length, must be <= 64")
	}
	channelId := vaa.LeftPadIbcChannelId(req.ChannelId)

	var module string
	if req.Module == nodev1.IbcUpdateChannelChainModule_IBC_UPDATE_CHANNEL_CHAIN_MODULE_RECEIVER {
		module = vaa.IbcReceiverModuleStr
	} else if req.Module == nodev1.IbcUpdateChannelChainModule_IBC_UPDATE_CHANNEL_CHAIN_MODULE_TRANSLATOR {
		module = vaa.IbcTranslatorModuleStr
	} else {
		return nil, fmt.Errorf("unrecognized ibc update channel chain module")
	}

	// create governance VAA
	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyIbcUpdateChannelChain{
			TargetChainId: vaa.ChainID(req.TargetChainId),
			ChannelId:     channelId,
			ChainId:       vaa.ChainID(req.ChainId),
		}.Serialize(module))

	return v, nil
}

// wormholeRelayerSetDefaultDeliveryProvider converts a nodev1.WormholeRelayerSetDefaultDeliveryProvider message to its canonical VAA representation.
// Returns an error if the data is invalid.
func wormholeRelayerSetDefaultDeliveryProvider(req *nodev1.WormholeRelayerSetDefaultDeliveryProvider, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
	if req.ChainId > math.MaxUint16 {
		return nil, errors.New("invalid target_chain_id")
	}

	b, err := hex.DecodeString(req.NewDefaultDeliveryProviderAddress)
	if err != nil {
		return nil, errors.New("invalid new default delivery provider address (expected hex)")
	}

	if len(b) != 32 {
		return nil, errors.New("invalid new default delivery provider address (expected 32 bytes)")
	}

	NewDefaultDeliveryProviderAddress := vaa.Address{}
	copy(NewDefaultDeliveryProviderAddress[:], b)

	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
		vaa.BodyWormholeRelayerSetDefaultDeliveryProvider{
			ChainID:                           vaa.ChainID(req.ChainId),
			NewDefaultDeliveryProviderAddress: NewDefaultDeliveryProviderAddress,
		}.Serialize())

	return v, nil
}

func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, timestamp time.Time) (*vaa.VAA, error) {
	var (
		v   *vaa.VAA
		err error
	)

	switch payload := message.Payload.(type) {
	case *nodev1.GovernanceMessage_GuardianSet:
		v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_ContractUpgrade:
		v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_BridgeRegisterChain:
		v, err = tokenBridgeRegisterChain(payload.BridgeRegisterChain, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_BridgeContractUpgrade:
		v, err = tokenBridgeUpgradeContract(payload.BridgeContractUpgrade, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_RecoverChainId:
		v, err = recoverChainId(payload.RecoverChainId, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_AccountantModifyBalance:
		v, err = accountantModifyBalance(payload.AccountantModifyBalance, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_WormchainStoreCode:
		v, err = wormchainStoreCode(payload.WormchainStoreCode, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_WormchainInstantiateContract:
		v, err = wormchainInstantiateContract(payload.WormchainInstantiateContract, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_WormchainMigrateContract:
		v, err = wormchainMigrateContract(payload.WormchainMigrateContract, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_WormchainWasmInstantiateAllowlist:
		v, err = wormchainWasmInstantiateAllowlist(payload.WormchainWasmInstantiateAllowlist, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_GatewayScheduleUpgrade:
		v, err = gatewayScheduleUpgrade(payload.GatewayScheduleUpgrade, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_GatewayCancelUpgrade:
		v, err = gatewayCancelUpgrade(timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_GatewayIbcComposabilityMwSetContract:
		v, err = gatewayIbcComposabilityMwSetContract(payload.GatewayIbcComposabilityMwSetContract, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_CircleIntegrationUpdateWormholeFinality:
		v, err = circleIntegrationUpdateWormholeFinality(payload.CircleIntegrationUpdateWormholeFinality, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_CircleIntegrationRegisterEmitterAndDomain:
		v, err = circleIntegrationRegisterEmitterAndDomain(payload.CircleIntegrationRegisterEmitterAndDomain, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_CircleIntegrationUpgradeContractImplementation:
		v, err = circleIntegrationUpgradeContractImplementation(payload.CircleIntegrationUpgradeContractImplementation, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_IbcUpdateChannelChain:
		v, err = ibcUpdateChannelChain(payload.IbcUpdateChannelChain, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	case *nodev1.GovernanceMessage_WormholeRelayerSetDefaultDeliveryProvider:
		v, err = wormholeRelayerSetDefaultDeliveryProvider(payload.WormholeRelayerSetDefaultDeliveryProvider, timestamp, currentSetIndex, message.Nonce, message.Sequence)
	default:
		panic(fmt.Sprintf("unsupported VAA type: %T", payload))
	}

	return v, err
}

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

	var (
		v   *vaa.VAA
		err error
	)

	timestamp := time.Unix(int64(req.Timestamp), 0)

	digests := make([][]byte, len(req.Messages))

	for i, message := range req.Messages {
		v, err = GovMsgToVaa(message, req.CurrentSetIndex, timestamp)

		if err != nil {
			return nil, status.Error(codes.InvalidArgument, err.Error())
		}

		// Generate digest of the unsigned VAA.
		digest := v.SigningDigest()

		s.logger.Info("governance VAA constructed",
			zap.Any("vaa", v),
			zap.String("digest", digest.String()),
		)

		vaaInjectionsTotal.Inc()

		s.injectC <- &common.MessagePublication{
			TxHash:           ethcommon.Hash{},
			Timestamp:        v.Timestamp,
			Nonce:            v.Nonce,
			Sequence:         v.Sequence,
			ConsistencyLevel: v.ConsistencyLevel,
			EmitterChain:     v.EmitterChain,
			EmitterAddress:   v.EmitterAddress,
			Payload:          v.Payload,
			Unreliable:       false,
		}

		digests[i] = digest.Bytes()
	}

	return &nodev1.InjectGovernanceVAAResponse{Digests: digests}, nil
}

// fetchMissing attempts to backfill a gap by fetching and storing missing signed VAAs from the network.
// Returns true if the gap was filled, false otherwise.
func (s *nodePrivilegedService) fetchMissing(
	ctx context.Context,
	nodes []string,
	c *http.Client,
	chain vaa.ChainID,
	addr string,
	seq uint64) (bool, error) {

	// shuffle the list of public RPC endpoints
	rand.Shuffle(len(nodes), func(i, j int) {
		nodes[i], nodes[j] = nodes[j], nodes[i]
	})

	ctx, cancel := context.WithTimeout(ctx, time.Second)
	defer cancel()

	for _, node := range nodes {
		req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf(
			"%s/v1/signed_vaa/%d/%s/%d", node, chain, addr, seq), nil)
		if err != nil {
			return false, fmt.Errorf("failed to create request: %w", err)
		}

		resp, err := c.Do(req)
		if err != nil {
			s.logger.Warn("failed to fetch missing VAA",
				zap.String("node", node),
				zap.String("chain", chain.String()),
				zap.String("address", addr),
				zap.Uint64("sequence", seq),
				zap.Error(err),
			)
			continue
		}

		switch resp.StatusCode {
		case http.StatusNotFound:
			resp.Body.Close()
			continue
		case http.StatusOK:
			type getVaaResp struct {
				VaaBytes string `json:"vaaBytes"`
			}
			var respBody getVaaResp
			if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
				resp.Body.Close()
				s.logger.Warn("failed to decode VAA response",
					zap.String("node", node),
					zap.String("chain", chain.String()),
					zap.String("address", addr),
					zap.Uint64("sequence", seq),
					zap.Error(err),
				)
				continue
			}

			// base64 decode the VAA bytes
			vaaBytes, err := base64.StdEncoding.DecodeString(respBody.VaaBytes)
			if err != nil {
				resp.Body.Close()
				s.logger.Warn("failed to decode VAA body",
					zap.String("node", node),
					zap.String("chain", chain.String()),
					zap.String("address", addr),
					zap.Uint64("sequence", seq),
					zap.Error(err),
				)
				continue
			}

			s.logger.Info("backfilled VAA",
				zap.Uint16("chain", uint16(chain)),
				zap.String("address", addr),
				zap.Uint64("sequence", seq),
				zap.Int("numBytes", len(vaaBytes)),
			)

			// Inject into the gossip signed VAA receive path.
			// This has the same effect as if the VAA was received from the network
			// (verifying signature, storing in local DB...).
			s.signedInC <- &gossipv1.SignedVAAWithQuorum{
				Vaa: vaaBytes,
			}

			resp.Body.Close()
			return true, nil
		default:
			resp.Body.Close()
			return false, fmt.Errorf("unexpected response status: %d", resp.StatusCode)
		}
	}

	return false, nil
}

func (s *nodePrivilegedService) FindMissingMessages(ctx context.Context, req *nodev1.FindMissingMessagesRequest) (*nodev1.FindMissingMessagesResponse, error) {
	b, err := hex.DecodeString(req.EmitterAddress)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid emitter address encoding: %v", err)
	}
	emitterAddress := vaa.Address{}
	copy(emitterAddress[:], b)

	ids, first, last, err := s.db.FindEmitterSequenceGap(db.VAAID{
		EmitterChain:   vaa.ChainID(req.EmitterChain),
		EmitterAddress: emitterAddress,
	})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "database operation failed: %v", err)
	}

	if req.RpcBackfill {
		c := &http.Client{}
		unfilled := make([]uint64, 0, len(ids))
		for _, id := range ids {
			if ok, err := s.fetchMissing(ctx, req.BackfillNodes, c, vaa.ChainID(req.EmitterChain), emitterAddress.String(), id); err != nil {
				return nil, status.Errorf(codes.Internal, "failed to backfill VAA: %v", err)
			} else if ok {
				continue
			}
			unfilled = append(unfilled, id)
		}
		ids = unfilled
	}

	resp := make([]string, len(ids))
	for i, v := range ids {
		resp[i] = fmt.Sprintf("%d/%s/%d", req.EmitterChain, emitterAddress, v)
	}
	return &nodev1.FindMissingMessagesResponse{
		MissingMessages: resp,
		FirstSequence:   first,
		LastSequence:    last,
	}, nil
}

func (s *nodePrivilegedService) SendObservationRequest(ctx context.Context, req *nodev1.SendObservationRequestRequest) (*nodev1.SendObservationRequestResponse, error) {
	if err := common.PostObservationRequest(s.obsvReqSendC, req.ObservationRequest); err != nil {
		return nil, err
	}

	s.logger.Info("sent observation request", zap.Any("request", req.ObservationRequest))
	return &nodev1.SendObservationRequestResponse{}, nil
}

func (s *nodePrivilegedService) ChainGovernorStatus(ctx context.Context, req *nodev1.ChainGovernorStatusRequest) (*nodev1.ChainGovernorStatusResponse, error) {
	if s.governor == nil {
		return nil, fmt.Errorf("chain governor is not enabled")
	}

	return &nodev1.ChainGovernorStatusResponse{
		Response: s.governor.Status(),
	}, nil
}

func (s *nodePrivilegedService) ChainGovernorReload(ctx context.Context, req *nodev1.ChainGovernorReloadRequest) (*nodev1.ChainGovernorReloadResponse, error) {
	if s.governor == nil {
		return nil, fmt.Errorf("chain governor is not enabled")
	}

	resp, err := s.governor.Reload()
	if err != nil {
		return nil, err
	}

	return &nodev1.ChainGovernorReloadResponse{
		Response: resp,
	}, nil
}

func (s *nodePrivilegedService) ChainGovernorDropPendingVAA(ctx context.Context, req *nodev1.ChainGovernorDropPendingVAARequest) (*nodev1.ChainGovernorDropPendingVAAResponse, error) {
	if s.governor == nil {
		return nil, fmt.Errorf("chain governor is not enabled")
	}

	if len(req.VaaId) == 0 {
		return nil, fmt.Errorf("the VAA id must be specified as \"chainId/emitterAddress/seqNum\"")
	}

	resp, err := s.governor.DropPendingVAA(req.VaaId)
	if err != nil {
		return nil, err
	}

	return &nodev1.ChainGovernorDropPendingVAAResponse{
		Response: resp,
	}, nil
}

func (s *nodePrivilegedService) ChainGovernorReleasePendingVAA(ctx context.Context, req *nodev1.ChainGovernorReleasePendingVAARequest) (*nodev1.ChainGovernorReleasePendingVAAResponse, error) {
	if s.governor == nil {
		return nil, fmt.Errorf("chain governor is not enabled")
	}

	if len(req.VaaId) == 0 {
		return nil, fmt.Errorf("the VAA id must be specified as \"chainId/emitterAddress/seqNum\"")
	}

	resp, err := s.governor.ReleasePendingVAA(req.VaaId)
	if err != nil {
		return nil, err
	}

	return &nodev1.ChainGovernorReleasePendingVAAResponse{
		Response: resp,
	}, nil
}

func (s *nodePrivilegedService) ChainGovernorResetReleaseTimer(ctx context.Context, req *nodev1.ChainGovernorResetReleaseTimerRequest) (*nodev1.ChainGovernorResetReleaseTimerResponse, error) {
	if s.governor == nil {
		return nil, fmt.Errorf("chain governor is not enabled")
	}

	if len(req.VaaId) == 0 {
		return nil, fmt.Errorf("the VAA id must be specified as \"chainId/emitterAddress/seqNum\"")
	}

	resp, err := s.governor.ResetReleaseTimer(req.VaaId)
	if err != nil {
		return nil, err
	}

	return &nodev1.ChainGovernorResetReleaseTimerResponse{
		Response: resp,
	}, nil
}

func (s *nodePrivilegedService) PurgePythNetVaas(ctx context.Context, req *nodev1.PurgePythNetVaasRequest) (*nodev1.PurgePythNetVaasResponse, error) {
	prefix := db.VAAID{EmitterChain: vaa.ChainIDPythNet}
	oldestTime := time.Now().Add(-time.Hour * 24 * time.Duration(req.DaysOld))
	resp, err := s.db.PurgeVaas(prefix, oldestTime, req.LogOnly)
	if err != nil {
		return nil, err
	}

	return &nodev1.PurgePythNetVaasResponse{
		Response: resp,
	}, nil
}

func (s *nodePrivilegedService) SignExistingVAA(ctx context.Context, req *nodev1.SignExistingVAARequest) (*nodev1.SignExistingVAAResponse, error) {
	v, err := vaa.Unmarshal(req.Vaa)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal VAA: %w", err)
	}

	if req.NewGuardianSetIndex <= v.GuardianSetIndex {
		return nil, errors.New("new guardian set index must be higher than provided VAA")
	}

	if s.evmConnector == nil {
		return nil, errors.New("the node needs to have an Ethereum connection configured to sign existing VAAs")
	}

	var gs *common.GuardianSet
	if cachedGs, exists := s.gsCache.Load(v.GuardianSetIndex); exists {
		var ok bool
		gs, ok = cachedGs.(*common.GuardianSet)
		if !ok {
			return nil, fmt.Errorf("internal error")
		}
	} else {
		evmGs, err := s.evmConnector.GetGuardianSet(ctx, v.GuardianSetIndex)
		if err != nil {
			return nil, fmt.Errorf("failed to load guardian set [%d]: %w", v.GuardianSetIndex, err)
		}
		gs = &common.GuardianSet{
			Keys:  evmGs.Keys,
			Index: v.GuardianSetIndex,
		}
		s.gsCache.Store(v.GuardianSetIndex, gs)
	}

	if slices.Index(gs.Keys, s.guardianAddress) != -1 {
		return nil, fmt.Errorf("local guardian is already on the old set")
	}

	// Verify VAA
	err = v.Verify(gs.Keys)
	if err != nil {
		return nil, fmt.Errorf("failed to verify existing VAA: %w", err)
	}

	if len(req.NewGuardianAddrs) > 255 {
		return nil, errors.New("new guardian set has too many guardians")
	}
	newGS := make([]ethcommon.Address, len(req.NewGuardianAddrs))
	for i, guardianString := range req.NewGuardianAddrs {
		guardianAddress := ethcommon.HexToAddress(guardianString)
		newGS[i] = guardianAddress
	}

	// Make sure there are no duplicates. Compact needs to take a sorted slice to remove all duplicates.
	newGSSorted := slices.Clone(newGS)
	slices.SortFunc(newGSSorted, func(a, b ethcommon.Address) int {
		return bytes.Compare(a[:], b[:])
	})
	newGsLen := len(newGSSorted)
	if len(slices.Compact(newGSSorted)) != newGsLen {
		return nil, fmt.Errorf("duplicate guardians in the guardian set")
	}

	localGuardianIndex := slices.Index(newGS, s.guardianAddress)
	if localGuardianIndex == -1 {
		return nil, fmt.Errorf("local guardian is not a member of the new guardian set")
	}

	newVAA := &vaa.VAA{
		Version: v.Version,
		// Set the new guardian set index
		GuardianSetIndex: req.NewGuardianSetIndex,
		// Signatures will be repopulated
		Signatures:       nil,
		Timestamp:        v.Timestamp,
		Nonce:            v.Nonce,
		Sequence:         v.Sequence,
		ConsistencyLevel: v.ConsistencyLevel,
		EmitterChain:     v.EmitterChain,
		EmitterAddress:   v.EmitterAddress,
		Payload:          v.Payload,
	}

	// Copy original VAA signatures
	for _, sig := range v.Signatures {
		signerAddress := gs.Keys[sig.Index]
		newIndex := slices.Index(newGS, signerAddress)
		// Guardian is not part of the new set
		if newIndex == -1 {
			continue
		}
		newVAA.Signatures = append(newVAA.Signatures, &vaa.Signature{
			Index:     uint8(newIndex),
			Signature: sig.Signature,
		})
	}

	// Add our own signature only if the new guardian set would reach quorum
	if vaa.CalculateQuorum(len(newGS)) > len(newVAA.Signatures)+1 {
		return nil, errors.New("cannot reach quorum on new guardian set with the local signature")
	}

	// Add local signature
	newVAA.AddSignature(s.gk, uint8(localGuardianIndex))

	// Sort VAA signatures by guardian ID
	slices.SortFunc(newVAA.Signatures, func(a, b *vaa.Signature) int {
		if a.Index < b.Index {
			return -1
		} else if a.Index > b.Index {
			return 1
		}
		return 0
	})

	newVAABytes, err := newVAA.Marshal()
	if err != nil {
		return nil, fmt.Errorf("failed to marshal new VAA: %w", err)
	}

	return &nodev1.SignExistingVAAResponse{Vaa: newVAABytes}, nil
}

func (s *nodePrivilegedService) DumpRPCs(ctx context.Context, req *nodev1.DumpRPCsRequest) (*nodev1.DumpRPCsResponse, error) {
	return &nodev1.DumpRPCsResponse{
		Response: s.rpcMap,
	}, nil
}

func (s *nodePrivilegedService) GetAndObserveMissingVAAs(ctx context.Context, req *nodev1.GetAndObserveMissingVAAsRequest) (*nodev1.GetAndObserveMissingVAAsResponse, error) {
	// Get URL and API key from the command line
	url := req.GetUrl()
	apiKey := req.GetApiKey()

	// Create the body of the request
	jsonBody := []byte(`{"apiKey": "` + apiKey + `"}`)
	jsonBodyReader := bytes.NewReader(jsonBody)

	// Create the actual request
	httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, url, jsonBodyReader)
	if err != nil {
		fmt.Printf("GetAndObserveMissingVAAs: could not create request: %s\n", err)
		return nil, err
	}

	httpRequest.Header.Set("Content-Type", "application/json")

	client := http.Client{
		Timeout: 30 * time.Second,
	}

	// Call the cloud function to get the missing VAAs
	results, err := client.Do(httpRequest)
	if err != nil {
		fmt.Printf("GetAndObserveMissingVAAs: error making http request: %s\n", err)
		return nil, err
	}

	// Collect the results
	resBody, err := io.ReadAll(results.Body)
	if err != nil {
		fmt.Printf("GetAndObserveMissingVAAs: could not read response body: %s\n", err)
		return nil, err
	}
	fmt.Printf("client: response body: %s\n", resBody)
	type MissingVAA struct {
		Chain  int    `json:"chain"`
		VaaKey string `json:"vaaKey"`
		Txhash string `json:"txhash"`
	}
	var missingVAAs []MissingVAA
	err = json.Unmarshal(resBody, &missingVAAs)
	if err != nil {
		fmt.Printf("GetAndObserveMissingVAAs: could not unmarshal response body: %s\n", err)
		return nil, err
	}

	MAX_VAAS_TO_PROCESS := 25
	// Only do a max of 25 at a time so as to not overload the node
	numVaas := len(missingVAAs)
	processingLen := numVaas
	if processingLen > MAX_VAAS_TO_PROCESS {
		processingLen = MAX_VAAS_TO_PROCESS
	}

	// Start injecting the VAAs
	obsCounter := 0
	errCounter := 0
	errMsgs := "Messages: "
	for i := 0; i < processingLen; i++ {
		missingVAA := missingVAAs[i]
		// First check to see if this VAA has already been signed
		// Convert vaaKey to VAAID
		splits := strings.Split(missingVAA.VaaKey, "/")
		chainID, err := strconv.Atoi(splits[0])
		if err != nil {
			errMsgs += fmt.Sprintf("\nerror converting chainID [%s] to int", missingVAA.VaaKey)
			errCounter++
			continue
		}
		sequence, err := strconv.ParseUint(splits[2], 10, 64)
		if err != nil {
			errMsgs += fmt.Sprintf("\nerror converting sequence %s to uint64", splits[2])
			errCounter++
			continue
		}
		vaaKey := db.VAAID{EmitterChain: vaa.ChainID(chainID), EmitterAddress: vaa.Address([]byte(splits[1])), Sequence: sequence}
		hasVaa, err := s.db.HasVAA(vaaKey)
		if err != nil || hasVaa {
			errMsgs += fmt.Sprintf("\nerror checking for VAA %s", missingVAA.VaaKey)
			errCounter++
			continue
		}
		var obsvReq gossipv1.ObservationRequest
		obsvReq.ChainId = uint32(missingVAA.Chain)
		obsvReq.TxHash, err = hex.DecodeString(strings.TrimPrefix(missingVAA.Txhash, "0x"))
		if err != nil {
			obsvReq.TxHash, err = base58.Decode(missingVAA.Txhash)
			if err != nil {
				errMsgs += "Invalid transaction hash (neither hex nor base58)"
				errCounter++
				continue
			}
		}
		errMsgs += fmt.Sprintf("\nAttempting to observe %s", missingVAA.Txhash)
		// Call the following function to send the observation request
		if err := common.PostObservationRequest(s.obsvReqSendC, &obsvReq); err != nil {
			errMsgs += fmt.Sprintf("\nPostObservationRequest error %s", err.Error())
			errCounter++
			continue
		}
		obsCounter++
	}
	response := "There were no missing VAAs to recover."
	if processingLen > 0 {
		response = fmt.Sprintf("Successfully injected %d of %d VAAs. %d errors were encountered.", obsCounter, processingLen, errCounter)
		if numVaas > MAX_VAAS_TO_PROCESS {
			response += fmt.Sprintf("\nOnly %d of the %d missing VAAs were processed.  Run the command again to process more.", MAX_VAAS_TO_PROCESS, numVaas)
		}
	}
	response += "\n" + errMsgs
	return &nodev1.GetAndObserveMissingVAAsResponse{
		Response: response,
	}, nil
}
