diff --git a/messages/message_manager.go b/messages/message_manager.go index 00251353..ef3df535 100644 --- a/messages/message_manager.go +++ b/messages/message_manager.go @@ -26,7 +26,7 @@ type MessageManager interface { ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) // SendMessage sends the signed message to the destination chain. The payload parsed according to // the VM rules is also passed in, since MessageManager does not assume any particular VM - SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error + SendMessage(signedMessage *warp.Message, parsedVMPayload []byte, destinationChainID ids.ID) error } // NewMessageManager constructs a MessageManager for a particular message protocol, defined by the message protocol address and config diff --git a/messages/mocks/mock_message_manager.go b/messages/mocks/mock_message_manager.go index eba2032f..25997eb9 100644 --- a/messages/mocks/mock_message_manager.go +++ b/messages/mocks/mock_message_manager.go @@ -37,17 +37,17 @@ func (m *MockMessageManager) EXPECT() *MockMessageManagerMockRecorder { } // SendMessage mocks base method. -func (m *MockMessageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { +func (m *MockMessageManager) SendMessage(signedMessage *warp.Message, parsedVMPayload []byte, destinationChainID ids.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendMessage", signedMessage, parsedVmPayload, destinationChainID) + ret := m.ctrl.Call(m, "SendMessage", signedMessage, parsedVMPayload, destinationChainID) ret0, _ := ret[0].(error) return ret0 } // SendMessage indicates an expected call of SendMessage. -func (mr *MockMessageManagerMockRecorder) SendMessage(signedMessage, parsedVmPayload, destinationChainID interface{}) *gomock.Call { +func (mr *MockMessageManagerMockRecorder) SendMessage(signedMessage, parsedVMPayload, destinationChainID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockMessageManager)(nil).SendMessage), signedMessage, parsedVmPayload, destinationChainID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockMessageManager)(nil).SendMessage), signedMessage, parsedVMPayload, destinationChainID) } // ShouldSendMessage mocks base method. diff --git a/messages/teleporter/message_manager.go b/messages/teleporter/message_manager.go index 08ccd92c..2131eed7 100644 --- a/messages/teleporter/message_manager.go +++ b/messages/teleporter/message_manager.go @@ -209,7 +209,7 @@ func (m *messageManager) messageDelivered( // SendMessage extracts the gasLimit and packs the call data to call the receiveCrossChainMessage method of the Teleporter contract, // and dispatches transaction construction and broadcast to the destination client -func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { +func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVMPayload []byte, destinationChainID ids.ID) error { teleporterMessage, ok := m.teleporterMessageCache.Get(signedMessage.ID()) if !ok { m.logger.Debug( @@ -218,7 +218,7 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayloa zap.String("warpMessageID", signedMessage.ID().String()), ) var err error - teleporterMessage, err = teleportermessenger.UnpackTeleporterMessage(parsedVmPayload) + teleporterMessage, err = teleportermessenger.UnpackTeleporterMessage(parsedVMPayload) if err != nil { m.logger.Error( "Failed unpacking teleporter message.", diff --git a/peers/external_handler.go b/peers/external_handler.go index e048143b..8325d748 100644 --- a/peers/external_handler.go +++ b/peers/external_handler.go @@ -18,7 +18,7 @@ import ( "go.uber.org/zap" ) -var _ router.ExternalHandler = (*RelayerExternalHandler)(nil) +var _ router.ExternalHandler = &RelayerExternalHandler{} // Note: all of the external handler's methods are called on peer goroutines. It // is possible for multiple concurrent calls to happen with different NodeIDs. diff --git a/relayer/canonical_validator_client.go b/relayer/canonical_validator_client.go index 5a2e369d..b8eb337c 100644 --- a/relayer/canonical_validator_client.go +++ b/relayer/canonical_validator_client.go @@ -8,17 +8,23 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm" + "go.uber.org/zap" ) +var _ validators.State = &CanonicalValidatorClient{} + // CanonicalValidatorClient wraps platformvm.Client and implements validators.State type CanonicalValidatorClient struct { client platformvm.Client + logger logging.Logger } -func NewCanonicalValidatorClient(client platformvm.Client) *CanonicalValidatorClient { +func NewCanonicalValidatorClient(logger logging.Logger, client platformvm.Client) *CanonicalValidatorClient { return &CanonicalValidatorClient{ client: client, + logger: logger, } } @@ -34,10 +40,86 @@ func (v *CanonicalValidatorClient) GetSubnetID(ctx context.Context, chainID ids. return v.client.ValidatedBy(ctx, chainID) } +// Gets the current validator set of the given subnet ID, including the validators' BLS public +// keys. The implementation currently makes two RPC requests, one to get the subnet validators, +// and another to get their BLS public keys. This is necessary in order to enable the use of +// the public APIs (which don't support "GetValidatorsAt") because BLS keys are currently only +// associated with primary network validation periods. If ACP-13 is implementated in the future +// (https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/13-subnet-only-validators.md), it +// may become possible to reduce this to a single RPC request that returns both the subnet validators +// as well as their BLS public keys. +func (v *CanonicalValidatorClient) getCurrentValidatorSet( + ctx context.Context, + subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + // Get the current subnet validators. These validators are not expected to include + // BLS signing information given that addPermissionlessValidatorTx is only used to + // add primary network validators. + subnetVdrs, err := v.client.GetCurrentValidators(ctx, subnetID, nil) + if err != nil { + return nil, err + } + + // Look up the primary network validators of the NodeIDs validating the subnet + // in order to get their BLS keys. + res := make(map[ids.NodeID]*validators.GetValidatorOutput, len(subnetVdrs)) + subnetNodeIDs := make([]ids.NodeID, 0, len(subnetVdrs)) + for _, subnetVdr := range subnetVdrs { + subnetNodeIDs = append(subnetNodeIDs, subnetVdr.NodeID) + res[subnetVdr.NodeID] = &validators.GetValidatorOutput{ + NodeID: subnetVdr.NodeID, + Weight: subnetVdr.Weight, + } + } + primaryVdrs, err := v.client.GetCurrentValidators(ctx, ids.Empty, subnetNodeIDs) + if err != nil { + return nil, err + } + + // Set the BLS keys of the result. + for _, primaryVdr := range primaryVdrs { + // We expect all of the primary network validators to already be in `res` because + // we filtered the request to node IDs that were identified as validators of the + // specific subnet ID. + vdr, ok := res[primaryVdr.NodeID] + if !ok { + v.logger.Warn( + "Unexpected primary network validator returned by getCurrentValidators request", + zap.String("subnetID", subnetID.String()), + zap.String("nodeID", primaryVdr.NodeID.String())) + continue + } + + // Validators that do not have a BLS public key registered on the P-chain are still + // included in the result because they affect the stake weight of the subnet validators. + // Such validators will not be queried for BLS signatures of warp messages. As long as + // sufficient stake percentage of subnet validators have registered BLS public keys, + // messages can still be successfully relayed. + if primaryVdr.Signer != nil { + vdr.PublicKey = primaryVdr.Signer.Key() + } + } + + return res, nil +} + +// Gets the validator set of the given subnet at the given P-chain block height. +// Attempts to use the "getValidatorsAt" API first. If not available, falls back +// to use "getCurrentValidators", ignoring the specified P-chain block height. func (v *CanonicalValidatorClient) GetValidatorSet( ctx context.Context, height uint64, subnetID ids.ID, ) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return v.client.GetValidatorsAt(ctx, subnetID, height) + // First, attempt to use the "getValidatorsAt" RPC method. This method may not be available on + // all API nodes, in which case we can fall back to using "getCurrentValidators" if needed. + res, err := v.client.GetValidatorsAt(ctx, subnetID, height) + if err != nil { + v.logger.Debug( + "P-chain RPC to getValidatorAt returned error. Falling back to getCurrentValidators", + zap.String("subnetID", subnetID.String()), + zap.Uint64("pChainHeight", height), + zap.Error(err)) + return v.getCurrentValidatorSet(ctx, subnetID) + } + return res, nil } diff --git a/relayer/relayer.go b/relayer/relayer.go index aaab95a0..0b4742a4 100644 --- a/relayer/relayer.go +++ b/relayer/relayer.go @@ -105,7 +105,7 @@ func NewRelayer( ) r := Relayer{ pChainClient: pChainClient, - canonicalValidatorClient: NewCanonicalValidatorClient(pChainClient), + canonicalValidatorClient: NewCanonicalValidatorClient(logger, pChainClient), currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision network: network, sourceSubnetID: subnetID, diff --git a/vms/evm/contract_message.go b/vms/evm/contract_message.go index d4441352..a6ffb183 100644 --- a/vms/evm/contract_message.go +++ b/vms/evm/contract_message.go @@ -31,14 +31,6 @@ func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.W ) return nil, err } - err = unsignedMsg.Initialize() - if err != nil { - m.logger.Error( - "Failed initializing unsigned message", - zap.Error(err), - ) - return nil, err - } warpPayload, err := warpPayload.ParseAddressedPayload(unsignedMsg.Payload) if err != nil {