Skip to content
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

feat: support for multiple TLDs #180

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 45 additions & 44 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ package config
import (
"fmt"
"os"
"strings"

"github.com/kelseyhightower/envconfig"
"gopkg.in/yaml.v2"
)

type Config struct {
Logging LoggingConfig `yaml:"logging"`
Metrics MetricsConfig `yaml:"metrics"`
Dns DnsConfig `yaml:"dns"`
Debug DebugConfig `yaml:"debug"`
Indexer IndexerConfig `yaml:"indexer"`
State StateConfig `yaml:"state"`
Profile string `yaml:"profile" envconfig:"PROFILE"`
Logging LoggingConfig `yaml:"logging"`
Metrics MetricsConfig `yaml:"metrics"`
Dns DnsConfig `yaml:"dns"`
Debug DebugConfig `yaml:"debug"`
Indexer IndexerConfig `yaml:"indexer"`
State StateConfig `yaml:"state"`
Profiles []string `yaml:"profiles" envconfig:"PROFILES"`
}

type LoggingConfig struct {
Expand Down Expand Up @@ -52,11 +53,8 @@ type IndexerConfig struct {
NetworkMagic uint32 `yaml:"networkMagic" envconfig:"INDEXER_NETWORK_MAGIC"`
Address string `yaml:"address" envconfig:"INDEXER_TCP_ADDRESS"`
SocketPath string `yaml:"socketPath" envconfig:"INDEXER_SOCKET_PATH"`
ScriptAddress string `yaml:"scriptAddress" envconfig:"INDEXER_SCRIPT_ADDRESS"`
InterceptHash string `yaml:"interceptHash" envconfig:"INDEXER_INTERCEPT_HASH"`
InterceptSlot uint64 `yaml:"interceptSlot" envconfig:"INDEXER_INTERCEPT_SLOT"`
Tld string `yaml:"tld" envconfig:"INDEXER_TLD"`
PolicyId string `yaml:"policyId" envconfig:"INDEXER_POLICY_ID"`
Verify bool `yaml:"verify" envconfig:"INDEXER_VERIFY"`
}

Expand Down Expand Up @@ -90,13 +88,14 @@ var globalConfig = &Config{
ListenPort: 8081,
},
Indexer: IndexerConfig{
Network: "preprod",
Verify: true,
Verify: true,
},
State: StateConfig{
Directory: "./.state",
},
Profile: "ada-preprod",
Profiles: []string{
"ada-preprod",
},
}

func Load(configFile string) (*Config, error) {
Expand All @@ -118,42 +117,44 @@ func Load(configFile string) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("error processing environment: %s", err)
}
// Check profile
profile, ok := Profiles[globalConfig.Profile]
if !ok {
return nil, fmt.Errorf("unknown profile: %s", globalConfig.Profile)
}
// Provide default network
if globalConfig.Indexer.Network != "" {
if profile.Network != "" {
globalConfig.Indexer.Network = profile.Network
} else {
return nil, fmt.Errorf("no built-in network name for specified profile, please provide one")
// Check profiles
availableProfiles := GetAvailableProfiles()
var interceptSlot uint64
var interceptHash string
for _, profile := range globalConfig.Profiles {
foundProfile := false
for _, availableProfile := range availableProfiles {
if profile == availableProfile {
profileData := Profiles[profile]
// Provide default network
if profileData.Network != "" {
if globalConfig.Indexer.Network == "" {
globalConfig.Indexer.Network = profileData.Network
} else {
if globalConfig.Indexer.Network != profileData.Network {
return nil, fmt.Errorf("conflicting networks configured: %s and %s", globalConfig.Indexer.Network, profileData.Network)
}
}
}
// Update intercept slot/hash if earlier than any other profiles so far
if interceptSlot == 0 || profileData.InterceptSlot < interceptSlot {
interceptSlot = profileData.InterceptSlot
interceptHash = profileData.InterceptHash
}
foundProfile = true
break
}
}
}
// Provide default script address from profile
if globalConfig.Indexer.ScriptAddress == "" {
if profile.ScriptAddress != "" {
globalConfig.Indexer.ScriptAddress = profile.ScriptAddress
} else {
return nil, fmt.Errorf("no built-in script address for specified profile, please provide one")
if !foundProfile {
return nil, fmt.Errorf("unknown profile: %s: available profiles: %s", profile, strings.Join(availableProfiles, ","))
}
}
// Provide default intercept point from profile
// Provide default intercept point from profile(s)
if globalConfig.Indexer.InterceptSlot == 0 ||
globalConfig.Indexer.InterceptHash == "" {
if profile.InterceptHash != "" && profile.InterceptSlot > 0 {
globalConfig.Indexer.InterceptHash = profile.InterceptHash
globalConfig.Indexer.InterceptSlot = profile.InterceptSlot
}
}
// Provide default TLD and Policy ID from profile
if globalConfig.Indexer.Tld == "" || globalConfig.Indexer.PolicyId == "" {
if profile.Tld != "" && profile.PolicyId != "" {
globalConfig.Indexer.Tld = profile.Tld
globalConfig.Indexer.PolicyId = profile.PolicyId
} else {
return nil, fmt.Errorf("no built-in TLD and/or policy ID for specified profile, please provide one")
if interceptHash != "" && interceptSlot > 0 {
globalConfig.Indexer.InterceptHash = interceptHash
globalConfig.Indexer.InterceptSlot = interceptSlot
}
}
return globalConfig, nil
Expand Down
21 changes: 21 additions & 0 deletions internal/config/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ type Profile struct {
InterceptHash string // Chain-sync initial intercept hash
}

func GetProfiles() []Profile {
var ret []Profile
for k, profile := range Profiles {
for _, tmpProfile := range globalConfig.Profiles {
if k == tmpProfile {
ret = append(ret, profile)
break
}
}
}
return ret
}

func GetAvailableProfiles() []string {
var ret []string
for k := range Profiles {
ret = append(ret, k)
}
return ret
}

var Profiles = map[string]Profile{
// This (default) profile corresponds to the values specified in:
// https://github.com/blinklabs-io/cardano-dns/blob/main/README.md
Expand Down
157 changes: 83 additions & 74 deletions internal/indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ func (i *Indexer) Start() error {
)
i.pipeline.AddFilter(filterEvent)
// We only care about transactions on a certain address
var filterAddresses []string
for _, profile := range config.GetProfiles() {
filterAddresses = append(filterAddresses, profile.ScriptAddress)
}
filterChainsync := filter_chainsync.New(
filter_chainsync.WithAddresses([]string{cfg.Indexer.ScriptAddress}),
filter_chainsync.WithAddresses(filterAddresses),
)
i.pipeline.AddFilter(filterChainsync)
// Configure pipeline output
Expand Down Expand Up @@ -158,102 +162,107 @@ func (i *Indexer) handleEvent(evt event.Event) error {
eventTx := evt.Payload.(input_chainsync.TransactionEvent)
eventCtx := evt.Context.(input_chainsync.TransactionContext)
for _, txOutput := range eventTx.Outputs {
datum := txOutput.Datum()
if datum != nil {
var dnsDomain models.CardanoDnsDomain
if _, err := cbor.Decode(datum.Cbor(), &dnsDomain); err != nil {
logger.Warnf(
"error decoding TX (%s) output datum: %s",
eventCtx.TransactionHash,
err,
)
// Stop processing TX output if we can't parse the datum
for _, profile := range config.GetProfiles() {
if txOutput.Address().String() != profile.ScriptAddress {
continue
}
origin := string(dnsDomain.Origin)
// Convert origin to canonical form for consistency
// This mostly means adding a trailing period if it doesn't have one
domainName := dns.CanonicalName(origin)
// We want an empty value for the TLD root for convenience
if domainName == `.` {
domainName = ``
}
// Append TLD
domainName = dns.CanonicalName(
domainName + cfg.Indexer.Tld,
)
if cfg.Indexer.Verify {
// Look for asset matching domain origin and TLD policy ID
if txOutput.Assets() == nil {
datum := txOutput.Datum()
if datum != nil {
var dnsDomain models.CardanoDnsDomain
if _, err := cbor.Decode(datum.Cbor(), &dnsDomain); err != nil {
logger.Warnf(
"ignoring datum for domain %q with no matching asset",
domainName,
"error decoding TX (%s) output datum: %s",
eventCtx.TransactionHash,
err,
)
// Stop processing TX output if we can't parse the datum
continue
}
foundAsset := false
for _, policyId := range txOutput.Assets().Policies() {
for _, assetName := range txOutput.Assets().Assets(policyId) {
if policyId.String() == cfg.Indexer.PolicyId {
if string(assetName) == string(origin) {
foundAsset = true
origin := string(dnsDomain.Origin)
// Convert origin to canonical form for consistency
// This mostly means adding a trailing period if it doesn't have one
domainName := dns.CanonicalName(origin)
// We want an empty value for the TLD root for convenience
if domainName == `.` {
domainName = ``
}
// Append TLD
domainName = dns.CanonicalName(
domainName + profile.Tld,
)
if cfg.Indexer.Verify {
// Look for asset matching domain origin and TLD policy ID
if txOutput.Assets() == nil {
logger.Warnf(
"ignoring datum for domain %q with no matching asset",
domainName,
)
continue
}
foundAsset := false
for _, policyId := range txOutput.Assets().Policies() {
for _, assetName := range txOutput.Assets().Assets(policyId) {
if policyId.String() == profile.PolicyId {
if string(assetName) == string(origin) {
foundAsset = true
} else {
logger.Warnf(
"ignoring datum for domain %q with no matching asset",
domainName,
)
}
} else {
logger.Warnf(
"ignoring datum for domain %q with no matching asset",
domainName,
)
}
} else {
}
}
if !foundAsset {
continue
}
// Make sure all records are for specified origin domain
badRecordName := false
for _, record := range dnsDomain.Records {
recordName := dns.CanonicalName(
string(record.Lhs),
)
if !strings.HasSuffix(recordName, domainName) {
logger.Warnf(
"ignoring datum for domain %q with no matching asset",
"ignoring datum with record %q outside of origin domain (%s)",
recordName,
domainName,
)
badRecordName = true
break
}
}
if badRecordName {
continue
}
}
if !foundAsset {
continue
}
// Make sure all records are for specified origin domain
badRecordName := false
// Convert domain records into our storage format
tmpRecords := []state.DomainRecord{}
for _, record := range dnsDomain.Records {
recordName := dns.CanonicalName(
string(record.Lhs),
)
if !strings.HasSuffix(recordName, domainName) {
logger.Warnf(
"ignoring datum with record %q outside of origin domain (%s)",
recordName,
domainName,
)
badRecordName = true
break
tmpRecord := state.DomainRecord{
Lhs: string(record.Lhs),
Type: string(record.Type),
Rhs: string(record.Rhs),
}
if record.Ttl.HasValue() {
tmpRecord.Ttl = int(record.Ttl.Value)
}
tmpRecords = append(tmpRecords, tmpRecord)
}
if badRecordName {
continue
}
}
// Convert domain records into our storage format
tmpRecords := []state.DomainRecord{}
for _, record := range dnsDomain.Records {
tmpRecord := state.DomainRecord{
Lhs: string(record.Lhs),
Type: string(record.Type),
Rhs: string(record.Rhs),
}
if record.Ttl.HasValue() {
tmpRecord.Ttl = int(record.Ttl.Value)
if err := state.GetState().UpdateDomain(domainName, tmpRecords); err != nil {
return err
}
tmpRecords = append(tmpRecords, tmpRecord)
}
if err := state.GetState().UpdateDomain(domainName, tmpRecords); err != nil {
return err
logger.Infof(
"found updated registration for domain: %s",
domainName,
)
}
logger.Infof(
"found updated registration for domain: %s",
domainName,
)
}
}
return nil
Expand Down