Skip to content

Commit 5b8a684

Browse files
authored
Merge pull request #9 from blinklabs-io/feat/indexer-initial
feat: initial indexer
2 parents b8f1b5b + 2454055 commit 5b8a684

File tree

5 files changed

+183
-28
lines changed

5 files changed

+183
-28
lines changed

cmd/chnsd/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/blinklabs-io/chnsd/internal/config"
1717
"github.com/blinklabs-io/chnsd/internal/dns"
18+
"github.com/blinklabs-io/chnsd/internal/indexer"
1819
"github.com/blinklabs-io/chnsd/internal/logging"
1920
)
2021

@@ -65,6 +66,11 @@ func main() {
6566
}()
6667
}
6768

69+
// Start indexer
70+
if err := indexer.GetIndexer().Start(); err != nil {
71+
logger.Fatalf("failed to start indexer: %s", err)
72+
}
73+
6874
// Start DNS listener
6975
logger.Infof("starting DNS listener on %s:%d", cfg.Dns.ListenAddress, cfg.Dns.ListenPort)
7076
if err := dns.Start(); err != nil {

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module github.com/blinklabs-io/chnsd
33
go 1.19
44

55
require (
6-
github.com/blinklabs-io/gouroboros v0.47.0
6+
github.com/blinklabs-io/gouroboros v0.48.0
7+
github.com/blinklabs-io/snek v0.4.0
78
github.com/kelseyhightower/envconfig v1.4.0
89
github.com/miekg/dns v1.1.55
910
go.uber.org/zap v1.24.0
@@ -17,9 +18,9 @@ require (
1718
github.com/x448/float16 v0.8.4 // indirect
1819
go.uber.org/atomic v1.7.0 // indirect
1920
go.uber.org/multierr v1.6.0 // indirect
20-
golang.org/x/crypto v0.10.0 // indirect
21+
golang.org/x/crypto v0.11.0 // indirect
2122
golang.org/x/mod v0.9.0 // indirect
2223
golang.org/x/net v0.10.0 // indirect
23-
golang.org/x/sys v0.9.0 // indirect
24+
golang.org/x/sys v0.10.0 // indirect
2425
golang.org/x/tools v0.7.0 // indirect
2526
)

go.sum

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
2-
github.com/blinklabs-io/gouroboros v0.47.0 h1:N8SoWoT6h6m+w60OU5qNGNyAHj/MxJzSZaw6cnR1Dc8=
3-
github.com/blinklabs-io/gouroboros v0.47.0/go.mod h1:J3Zf9Fx65LoLldIZB4+dZZpsUsGfwXMQQKQOhNBNlwY=
2+
github.com/blinklabs-io/gouroboros v0.48.0 h1:A3mqNX1C8QtW/CoIoF3iyXfKPrKyfyWfbjzdnkwj6Jw=
3+
github.com/blinklabs-io/gouroboros v0.48.0/go.mod h1:WBghLJF3Im3hXjMd8BFk929tfuSjcUZdn2zE2xtZJEo=
4+
github.com/blinklabs-io/snek v0.4.0 h1:IAuA4HBg2agK2XmOtvs9siJJJWwNCoI5fAl3UuMdIRg=
5+
github.com/blinklabs-io/snek v0.4.0/go.mod h1:3jwdMN/IsrpjsdZT4yqxtZVMt/2kXq/NaWvcS/lD42A=
46
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
68
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -28,15 +30,15 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
2830
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
2931
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
3032
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
31-
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
32-
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
33+
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
34+
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
3335
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
3436
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
3537
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
3638
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
3739
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
38-
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
39-
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
40+
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
41+
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4042
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
4143
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
4244
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

internal/config/config.go

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,34 @@ import (
44
"fmt"
55
"os"
66

7-
ouroboros "github.com/blinklabs-io/gouroboros"
87
"github.com/kelseyhightower/envconfig"
98
"gopkg.in/yaml.v2"
109
)
1110

11+
// Per-network script address for Handshake
12+
var networkScriptAddresses = map[string]string{
13+
"preprod": "addr_test1wqhlsl9dsny9d2hdc9uyx4ktj0ty0s8kxev4y9futq4qt4s5anczn",
14+
}
15+
16+
// Per-network intercept points for starting the chain-sync
17+
// We start the sync somewhere near where we expect the first data to appear to save time
18+
// during the initial sync
19+
var networkInterceptPoints = map[string]struct {
20+
Hash string
21+
Slot uint64
22+
}{
23+
"preprod": {
24+
Hash: "f5366caf6cc87383a33fece0968c3c8c3b25ec496829ab3ba324f7dce5a89c5d",
25+
Slot: 29852950,
26+
},
27+
}
28+
1229
type Config struct {
1330
Logging LoggingConfig `yaml:"logging"`
1431
Metrics MetricsConfig `yaml:"metrics"`
1532
Dns DnsConfig `yaml:"dns"`
1633
Debug DebugConfig `yaml:"debug"`
17-
Node NodeConfig `yaml:"node"`
34+
Indexer IndexerConfig `yaml:"indexer"`
1835
}
1936

2037
type LoggingConfig struct {
@@ -37,12 +54,14 @@ type MetricsConfig struct {
3754
ListenPort uint `yaml:"port" envconfig:"METRICS_LISTEN_PORT"`
3855
}
3956

40-
type NodeConfig struct {
41-
Network string `yaml:"network" envconfig:"CARDANO_NETWORK"`
42-
NetworkMagic uint32 `yaml:"networkMagic" envconfig:"CARDANO_NODE_NETWORK_MAGIC"`
43-
Address string `yaml:"address" envconfig:"CARDANO_NODE_SOCKET_TCP_HOST"`
44-
Port uint `yaml:"port" envconfig:"CARDANO_NODE_SOCKET_TCP_PORT"`
45-
SocketPath string `yaml:"socketPath" envconfig:"CARDANO_NODE_SOCKET_PATH"`
57+
type IndexerConfig struct {
58+
Network string `yaml:"network" envconfig:"INDEXER_NETWORK"`
59+
NetworkMagic uint32 `yaml:"networkMagic" envconfig:"INDEXER_NETWORK_MAGIC"`
60+
Address string `yaml:"address" envconfig:"INDEXER_TCP_ADDRESS"`
61+
SocketPath string `yaml:"socketPath" envconfig:"INDEXER_SOCKET_PATH"`
62+
ScriptAddress string `yaml:"scriptAddress" envconfig:"INDEXER_SCRIPT_ADDRESS"`
63+
InterceptHash string `yaml:"interceptHash" envconfig:"INDEXER_INTERCEPT_HASH"`
64+
InterceptSlot uint64 `yaml:"interceptSlot" envconfig:"INDEXER_INTERCEPT_SLOT"`
4665
}
4766

4867
// Singleton config instance with default values
@@ -63,9 +82,8 @@ var globalConfig = &Config{
6382
ListenAddress: "",
6483
ListenPort: 8081,
6584
},
66-
Node: NodeConfig{
67-
Network: "mainnet",
68-
SocketPath: "/node-ipc/node.socket",
85+
Indexer: IndexerConfig{
86+
Network: "preprod",
6987
},
7088
}
7189

@@ -88,18 +106,21 @@ func Load(configFile string) (*Config, error) {
88106
if err != nil {
89107
return nil, fmt.Errorf("error processing environment: %s", err)
90108
}
91-
// Populate network magic value from network name
92-
if globalConfig.Node.Network != "" {
93-
network := ouroboros.NetworkByName(globalConfig.Node.Network)
94-
if network == ouroboros.NetworkInvalid {
95-
return nil, fmt.Errorf("unknown network: %s", globalConfig.Node.Network)
96-
}
97-
globalConfig.Node.NetworkMagic = network.NetworkMagic
109+
// Provide default script address for named network
110+
if scriptAddress, ok := networkScriptAddresses[globalConfig.Indexer.Network]; ok {
111+
globalConfig.Indexer.ScriptAddress = scriptAddress
112+
} else {
113+
return nil, fmt.Errorf("no built-in script address for specified network, please provide one")
114+
}
115+
// Provide default intercept point for named network
116+
if interceptPoint, ok := networkInterceptPoints[globalConfig.Indexer.Network]; ok {
117+
globalConfig.Indexer.InterceptHash = interceptPoint.Hash
118+
globalConfig.Indexer.InterceptSlot = interceptPoint.Slot
98119
}
99120
return globalConfig, nil
100121
}
101122

102-
// Config returns the global config instance
123+
// GetConfig returns the global config instance
103124
func GetConfig() *Config {
104125
return globalConfig
105126
}

internal/indexer/indexer.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package indexer
2+
3+
import (
4+
"encoding/hex"
5+
6+
"github.com/blinklabs-io/chnsd/internal/config"
7+
"github.com/blinklabs-io/chnsd/internal/logging"
8+
9+
"github.com/blinklabs-io/gouroboros/cbor"
10+
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
11+
"github.com/blinklabs-io/snek/event"
12+
filter_chainsync "github.com/blinklabs-io/snek/filter/chainsync"
13+
filter_event "github.com/blinklabs-io/snek/filter/event"
14+
input_chainsync "github.com/blinklabs-io/snek/input/chainsync"
15+
output_embedded "github.com/blinklabs-io/snek/output/embedded"
16+
"github.com/blinklabs-io/snek/pipeline"
17+
)
18+
19+
type Indexer struct {
20+
pipeline *pipeline.Pipeline
21+
}
22+
23+
// Singleton indexer instance
24+
var globalIndexer = &Indexer{}
25+
26+
func (i *Indexer) Start() error {
27+
cfg := config.GetConfig()
28+
logger := logging.GetLogger()
29+
// Create pipeline
30+
i.pipeline = pipeline.New()
31+
// Configure pipeline input
32+
inputOpts := []input_chainsync.ChainSyncOptionFunc{
33+
//input_chainsync.WithIntersectTip(true),
34+
}
35+
if cfg.Indexer.NetworkMagic > 0 {
36+
inputOpts = append(
37+
inputOpts,
38+
input_chainsync.WithNetworkMagic(cfg.Indexer.NetworkMagic),
39+
)
40+
} else {
41+
inputOpts = append(
42+
inputOpts,
43+
input_chainsync.WithNetwork(cfg.Indexer.Network),
44+
)
45+
}
46+
if cfg.Indexer.InterceptHash != "" && cfg.Indexer.InterceptSlot > 0 {
47+
hashBytes, err := hex.DecodeString(cfg.Indexer.InterceptHash)
48+
if err != nil {
49+
return err
50+
}
51+
inputOpts = append(
52+
inputOpts,
53+
input_chainsync.WithIntersectPoints(
54+
[]ocommon.Point{
55+
{
56+
Hash: hashBytes,
57+
Slot: cfg.Indexer.InterceptSlot,
58+
},
59+
},
60+
),
61+
)
62+
}
63+
input := input_chainsync.New(
64+
inputOpts...,
65+
)
66+
i.pipeline.AddInput(input)
67+
// Configure pipeline filters
68+
// We only care about transaction events
69+
filterEvent := filter_event.New(
70+
filter_event.WithType("chainsync.transaction"),
71+
)
72+
i.pipeline.AddFilter(filterEvent)
73+
// We only care about transactions on a certain address
74+
filterChainsync := filter_chainsync.New(
75+
filter_chainsync.WithAddress(cfg.Indexer.ScriptAddress),
76+
)
77+
i.pipeline.AddFilter(filterChainsync)
78+
// Configure pipeline output
79+
output := output_embedded.New(
80+
output_embedded.WithCallbackFunc(i.handleEvent),
81+
)
82+
i.pipeline.AddOutput(output)
83+
// Start pipeline
84+
if err := i.pipeline.Start(); err != nil {
85+
logger.Fatalf("failed to start pipeline: %s\n", err)
86+
}
87+
// Start error handler
88+
go func() {
89+
err, ok := <-i.pipeline.ErrorChan()
90+
if ok {
91+
logger.Fatalf("pipeline failed: %s\n", err)
92+
}
93+
}()
94+
return nil
95+
}
96+
97+
func (i *Indexer) handleEvent(evt event.Event) error {
98+
logger := logging.GetLogger()
99+
eventTx := evt.Payload.(input_chainsync.TransactionEvent)
100+
for _, txOutput := range eventTx.Outputs {
101+
datum := txOutput.Datum()
102+
if datum != nil {
103+
if _, err := datum.Decode(); err != nil {
104+
logger.Warnf("error decoding TX (%s) output datum: %s", eventTx.TransactionHash, err)
105+
return err
106+
}
107+
datumFields := datum.Value().(cbor.Constructor).Fields()
108+
domainName := string(datumFields[0].(cbor.ByteString).Bytes())
109+
nsRecords := []string{}
110+
for _, record := range datumFields[1].([]any) {
111+
nsRecords = append(
112+
nsRecords,
113+
string(record.(cbor.ByteString).Bytes()),
114+
)
115+
}
116+
logger.Infof("found domain %s with NS records: %v", domainName, nsRecords)
117+
}
118+
}
119+
return nil
120+
}
121+
122+
// GetIndexer returns the global indexer instance
123+
func GetIndexer() *Indexer {
124+
return globalIndexer
125+
}

0 commit comments

Comments
 (0)