-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configuration support fully specified source to destination mapping #59
Changes from 14 commits
9ab0d98
618a185
fddb357
8b5eacd
62d4fac
4c0d019
4240369
199b3e7
6f56a22
724178f
5b6a211
89ab862
836bce8
c22b7bd
be3f30f
b3318d6
5e75b5f
6973bdb
c80f97b
947d5d7
9ae1307
5b5decb
50dd208
c05368e
6c8a869
77fe281
cb82c07
50548ac
468fa3f
03b790d
5630533
87634cb
0e5eee6
8498771
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,9 @@ import ( | |
"github.com/spf13/viper" | ||
) | ||
|
||
// global config singleton | ||
var globalConfig Config | ||
|
||
const ( | ||
relayerPrivateKeyBytes = 32 | ||
accountPrivateKeyEnvVarName = "ACCOUNT_PRIVATE_KEY" | ||
|
@@ -37,15 +40,19 @@ type MessageProtocolConfig struct { | |
Settings map[string]interface{} `mapstructure:"settings" json:"settings"` | ||
} | ||
type SourceSubnet struct { | ||
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"` | ||
ChainID string `mapstructure:"chain-id" json:"chain-id"` | ||
VM string `mapstructure:"vm" json:"vm"` | ||
APINodeHost string `mapstructure:"api-node-host" json:"api-node-host"` | ||
APINodePort uint32 `mapstructure:"api-node-port" json:"api-node-port"` | ||
EncryptConnection bool `mapstructure:"encrypt-connection" json:"encrypt-connection"` | ||
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"` | ||
WSEndpoint string `mapstructure:"ws-endpoint" json:"ws-endpoint"` | ||
MessageContracts map[string]MessageProtocolConfig `mapstructure:"message-contracts" json:"message-contracts"` | ||
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"` | ||
ChainID string `mapstructure:"chain-id" json:"chain-id"` | ||
VM string `mapstructure:"vm" json:"vm"` | ||
APINodeHost string `mapstructure:"api-node-host" json:"api-node-host"` | ||
APINodePort uint32 `mapstructure:"api-node-port" json:"api-node-port"` | ||
EncryptConnection bool `mapstructure:"encrypt-connection" json:"encrypt-connection"` | ||
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"` | ||
WSEndpoint string `mapstructure:"ws-endpoint" json:"ws-endpoint"` | ||
MessageContracts map[string]MessageProtocolConfig `mapstructure:"message-contracts" json:"message-contracts"` | ||
SupportedDestinations []string `mapstructure:"supported-destinations" json:"allowed-destinations"` | ||
cam-schultz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// convenience field | ||
cam-schultz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
supportedDestinationsMap map[ids.ID]bool | ||
} | ||
|
||
type DestinationSubnet struct { | ||
|
@@ -152,6 +159,8 @@ func BuildConfig(v *viper.Viper) (Config, bool, error) { | |
} | ||
cfg.PChainAPIURL = pChainapiUrl | ||
|
||
globalConfig = cfg | ||
|
||
return cfg, optionOverwritten, nil | ||
} | ||
|
||
|
@@ -165,17 +174,8 @@ func (c *Config) Validate() error { | |
if _, err := url.ParseRequestURI(c.PChainAPIURL); err != nil { | ||
return err | ||
} | ||
sourceChains := set.NewSet[string](len(c.SourceSubnets)) | ||
for _, s := range c.SourceSubnets { | ||
if err := s.Validate(); err != nil { | ||
return err | ||
} | ||
if sourceChains.Contains(s.ChainID) { | ||
return fmt.Errorf("configured source subnets must have unique chain IDs") | ||
} | ||
sourceChains.Add(s.ChainID) | ||
} | ||
|
||
// Validate the destination chains | ||
destinationChains := set.NewSet[string](len(c.DestinationSubnets)) | ||
for _, s := range c.DestinationSubnets { | ||
if err := s.Validate(); err != nil { | ||
|
@@ -187,27 +187,31 @@ func (c *Config) Validate() error { | |
destinationChains.Add(s.ChainID) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// GetSourceIDs returns the Subnet and Chain IDs of all subnets configured as a source | ||
func (cfg *Config) GetSourceIDs() ([]ids.ID, []ids.ID, error) { | ||
var sourceSubnetIDs []ids.ID | ||
var sourceChainIDs []ids.ID | ||
for _, s := range cfg.SourceSubnets { | ||
subnetID, err := ids.FromString(s.SubnetID) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("invalid subnetID in configuration. error: %v", err) | ||
// Validate the source chains, and validate that the allowed destinations are configured as destinations | ||
sourceChains := set.NewSet[string](len(c.SourceSubnets)) | ||
for _, s := range c.SourceSubnets { | ||
if err := s.Validate(); err != nil { | ||
return err | ||
} | ||
sourceSubnetIDs = append(sourceSubnetIDs, subnetID) | ||
if sourceChains.Contains(s.ChainID) { | ||
return fmt.Errorf("configured source subnets must have unique chain IDs") | ||
} | ||
sourceChains.Add(s.ChainID) | ||
|
||
chainID, err := ids.FromString(s.ChainID) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("invalid subnetID in configuration. error: %v", err) | ||
for _, blockchainID := range s.SupportedDestinations { | ||
cam-schultz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if !destinationChains.Contains(blockchainID) { | ||
return fmt.Errorf("configured source subnet %s has a supported destination blockchain ID %s that is not configured as a destination blockchain", | ||
s.SubnetID, | ||
blockchainID) | ||
} | ||
} | ||
sourceChainIDs = append(sourceChainIDs, chainID) | ||
} | ||
return sourceSubnetIDs, sourceChainIDs, nil | ||
|
||
return nil | ||
} | ||
|
||
func (s *SourceSubnet) GetSupportedDestinations() map[ids.ID]bool { | ||
return s.supportedDestinationsMap | ||
} | ||
|
||
func (s *SourceSubnet) Validate() error { | ||
|
@@ -244,6 +248,23 @@ func (s *SourceSubnet) Validate() error { | |
} | ||
} | ||
|
||
// Validate the allowed destinations | ||
gwen917 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for _, chainIDs := range s.SupportedDestinations { | ||
if _, err := ids.FromString(chainIDs); err != nil { | ||
return fmt.Errorf("invalid chainID in source subnet configuration. Provided ID: %s", chainIDs) | ||
} | ||
} | ||
cam-schultz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Store the allowed destinations for future use | ||
s.supportedDestinationsMap = make(map[ids.ID]bool) | ||
for _, chainIDStr := range s.SupportedDestinations { | ||
chainID, err := ids.FromString(chainIDStr) | ||
if err != nil { | ||
return fmt.Errorf("invalid chainID in configuration. error: %v", err) | ||
} | ||
s.supportedDestinationsMap[chainID] = true | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
@@ -373,3 +394,27 @@ func (s *DestinationSubnet) GetRelayerAccountInfo() (*ecdsa.PrivateKey, common.A | |
pkBytes = append(pkBytes, pk.PublicKey.Y.Bytes()...) | ||
return pk, common.BytesToAddress(crypto.Keccak256(pkBytes)), nil | ||
} | ||
|
||
// | ||
// Global config getters | ||
// | ||
|
||
// GetSourceIDs returns the Subnet and Chain IDs of all subnets configured as a source | ||
func GetSourceIDs() ([]ids.ID, []ids.ID, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why did we move this to using a global config variable vs a receiver function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After it's constructed, the configuration is a static singleton, so this pattern makes access more straightforward. The alternative is passing the option from the config down the call stack to where it's used, unnecessarily complicating interfaces along the way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a preference, though not a strong preference, to stay away from the global variables and to instead pass in the configs, similar to previously how we had global var for destination clients. This also helps prevent in the future from accidentally ovewriting values in the global config There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, I agree we should pass the config down the callstack rather than have a global variable. I've made this change. |
||
var sourceSubnetIDs []ids.ID | ||
var sourceChainIDs []ids.ID | ||
for _, s := range globalConfig.SourceSubnets { | ||
subnetID, err := ids.FromString(s.SubnetID) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("invalid subnetID in configuration. error: %v", err) | ||
} | ||
sourceSubnetIDs = append(sourceSubnetIDs, subnetID) | ||
|
||
chainID, err := ids.FromString(s.ChainID) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("invalid subnetID in configuration. error: %v", err) | ||
} | ||
sourceChainIDs = append(sourceChainIDs, chainID) | ||
} | ||
return sourceSubnetIDs, sourceChainIDs, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ type messageManager struct { | |
// The cache is keyed by the Warp message ID, NOT the Teleporter message ID | ||
teleporterMessageCache *cache.LRU[ids.ID, *TeleporterMessage] | ||
destinationClients map[ids.ID]vms.DestinationClient | ||
supportedDestinationss map[ids.ID]bool | ||
cam-schultz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
logger logging.Logger | ||
} | ||
|
@@ -43,6 +44,7 @@ func NewMessageManager( | |
messageProtocolAddress common.Hash, | ||
messageProtocolConfig config.MessageProtocolConfig, | ||
destinationClients map[ids.ID]vms.DestinationClient, | ||
supportedDestinationss map[ids.ID]bool, | ||
) (*messageManager, error) { | ||
// Marshal the map and unmarshal into the Teleporter config | ||
data, err := json.Marshal(messageProtocolConfig.Settings) | ||
|
@@ -71,6 +73,7 @@ func NewMessageManager( | |
teleporterMessageCache: teleporterMessageCache, | ||
destinationClients: destinationClients, | ||
logger: logger, | ||
supportedDestinationss: supportedDestinationss, | ||
}, nil | ||
} | ||
|
||
|
@@ -105,6 +108,22 @@ func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageI | |
if !ok { | ||
return false, fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID.String()) | ||
} | ||
|
||
// If supportedDestinationss is empty, then all destinations are allowed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems opposite than the expected behavior (one normally assumes that if the allow list is empty, then nothing is allowed). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is similar to how the "allowedRelayerAddresses" field is handled in The expected use case here is that if the |
||
// If supportedDestinationss is not empty, then only the allowed destinations are allowed | ||
if len(m.supportedDestinationss) > 0 { | ||
if allowed, exist := m.supportedDestinationss[destinationChainID]; !exist || !allowed { | ||
m.logger.Info( | ||
"Relayer not configured to relay between source and destination", | ||
zap.String("sourceChainID", warpMessageInfo.WarpUnsignedMessage.SourceChainID.String()), | ||
zap.String("destinationChainID", destinationChainID.String()), | ||
zap.String("warpMessageID", warpMessageInfo.WarpUnsignedMessage.ID().String()), | ||
zap.String("teleporterMessageID", teleporterMessage.MessageID.String()), | ||
) | ||
return false, nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case above where the destination chain ID does not have a destination client, we return false and a non-nil error. I noticed this case is different in that we return @cam-schultz could you confirm this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that case should not be an error. With the upcoming subnet-evm changes (as well as anycasting support in the future), I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking about it some more, we can clean up this implementation by using the configured allowed destinations as a filter in on the list of destination clients in
This would move logic for deciding which destinations are and are not valid to the relayer constructor which ones once, rather than the message manager There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would agree that we should move the destination client filtering logic to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved 👍 |
||
} | ||
} | ||
|
||
senderAddress := destinationClient.SenderAddress() | ||
if !isAllowedRelayer(teleporterMessage.AllowedRelayerAddresses, senderAddress) { | ||
m.logger.Info( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this new README section 🙌