Skip to content

Commit 2407685

Browse files
committed
feat: initial Handshake indexer support
Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 700c4f1 commit 2407685

File tree

6 files changed

+159
-26
lines changed

6 files changed

+159
-26
lines changed

internal/config/config.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ type MetricsConfig struct {
5050
}
5151

5252
type IndexerConfig struct {
53-
Network string `yaml:"network" envconfig:"INDEXER_NETWORK"`
54-
NetworkMagic uint32 `yaml:"networkMagic" envconfig:"INDEXER_NETWORK_MAGIC"`
55-
Address string `yaml:"address" envconfig:"INDEXER_TCP_ADDRESS"`
56-
SocketPath string `yaml:"socketPath" envconfig:"INDEXER_SOCKET_PATH"`
57-
InterceptHash string `yaml:"interceptHash" envconfig:"INDEXER_INTERCEPT_HASH"`
58-
InterceptSlot uint64 `yaml:"interceptSlot" envconfig:"INDEXER_INTERCEPT_SLOT"`
59-
Verify bool `yaml:"verify" envconfig:"INDEXER_VERIFY"`
53+
Network string `yaml:"network" envconfig:"INDEXER_NETWORK"`
54+
NetworkMagic uint32 `yaml:"networkMagic" envconfig:"INDEXER_NETWORK_MAGIC"`
55+
Address string `yaml:"address" envconfig:"INDEXER_TCP_ADDRESS"`
56+
SocketPath string `yaml:"socketPath" envconfig:"INDEXER_SOCKET_PATH"`
57+
InterceptHash string `yaml:"interceptHash" envconfig:"INDEXER_INTERCEPT_HASH"`
58+
InterceptSlot uint64 `yaml:"interceptSlot" envconfig:"INDEXER_INTERCEPT_SLOT"`
59+
Verify bool `yaml:"verify" envconfig:"INDEXER_VERIFY"`
60+
HandshakeAddress string `yaml:"handshakeAddress" envconfig:"INDEXER_HANDSHAKE_ADDRESS"`
6061
}
6162

6263
type StateConfig struct {

internal/handshake/covenant.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package handshake
99
import (
1010
"encoding/binary"
1111
"errors"
12+
"fmt"
1213
"io"
1314
)
1415

@@ -66,13 +67,13 @@ func (c *GenericCovenant) Covenant() Covenant {
6667
case CovenantTypeRegister:
6768
ret, err := NewRegisterCovenantFromGeneric(c)
6869
if err != nil {
69-
panic("can't convert generic covenant to Register")
70+
panic(fmt.Sprintf("can't convert generic covenant to Register: %s", err))
7071
}
7172
return ret
7273
case CovenantTypeUpdate:
7374
ret, err := NewUpdateCovenantFromGeneric(c)
7475
if err != nil {
75-
panic("can't convert generic covenant to Update")
76+
panic(fmt.Sprintf("can't convert generic covenant to Update: %s", err))
7677
}
7778
return ret
7879
}
@@ -120,7 +121,6 @@ type UpdateCovenant struct {
120121
NameHash []byte
121122
Height uint32
122123
ResourceData DomainResourceData
123-
BlockHash []byte
124124
}
125125

126126
func (UpdateCovenant) isCovenant() {}
@@ -131,16 +131,14 @@ func NewUpdateCovenantFromGeneric(
131131
if gc.Type != CovenantTypeUpdate {
132132
return nil, errors.New("wrong covenant type")
133133
}
134-
if len(gc.Items) != 4 {
134+
if len(gc.Items) != 3 {
135135
return nil, errors.New("incorrect items length")
136136
}
137137
ret := &UpdateCovenant{
138-
NameHash: make([]byte, len(gc.Items[0])),
139-
BlockHash: make([]byte, len(gc.Items[3])),
138+
NameHash: make([]byte, len(gc.Items[0])),
140139
}
141140
// Copy hashes
142141
copy(ret.NameHash, gc.Items[0])
143-
copy(ret.BlockHash, gc.Items[3])
144142
// Decode height from bytes
145143
ret.Height = binary.LittleEndian.Uint32(gc.Items[1])
146144
// Decode resource data

internal/handshake/covenant_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ func TestCovenantUpdateFromGeneric(t *testing.T) {
113113
decodeHex(
114114
"0002036e73310a69727677696c6c69616d002ce706b701c00202036e7332c00636d688f601c01a00d5580d0114402ed0125506f35ba249265f39b988d7028a28c300d5580d02200c6c45064c26b529b4ac074dff5de60a99d6025d5b0d7f32c2b8c7d40ec8b3de00d5580d043071cb0417852b08b965413f3b871b033996159d121a585e35111a335d4cfb79b67e49a99c3829f6a1f42e100f7f33d7d9",
115115
),
116-
decodeHex(
117-
"0000000000000000153c62dbcabb762c254fb4104ab7cdd779926b79b34601fc",
118-
),
119116
},
120117
}
121118
expectedCovenant := &handshake.UpdateCovenant{
@@ -166,9 +163,6 @@ func TestCovenantUpdateFromGeneric(t *testing.T) {
166163
},
167164
},
168165
},
169-
BlockHash: decodeHex(
170-
"0000000000000000153c62dbcabb762c254fb4104ab7cdd779926b79b34601fc",
171-
),
172166
}
173167
tmpCovenant, err := handshake.NewUpdateCovenantFromGeneric(
174168
testGenericCovenant,

internal/handshake/peer.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@ func (p *Peer) Close() error {
9999
return err
100100
}
101101
p.conn = nil
102+
// Close done channel to signify shutdown internally
102103
close(p.doneCh)
104+
// Close error channel to signify shutdown to consumer
105+
close(p.errorCh)
103106
return nil
104107
}
105108

@@ -187,6 +190,12 @@ func (p *Peer) recvLoop() {
187190
}
188191
}()
189192
if err != nil {
193+
// Don't return an async error if we're already shutting down
194+
select {
195+
case <-p.doneCh:
196+
return
197+
default:
198+
}
190199
p.errorCh <- err
191200
_ = p.Close()
192201
}
@@ -473,6 +482,12 @@ func (p *Peer) Sync(locator [][32]byte, syncFunc SyncFunc) error {
473482
}
474483
}()
475484
if err != nil {
485+
// Don't return an async error if we're already shutting down
486+
select {
487+
case <-p.doneCh:
488+
return
489+
default:
490+
}
476491
p.errorCh <- err
477492
_ = p.Close()
478493
}

internal/indexer/handshake.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file or at
5+
// https://opensource.org/licenses/MIT.
6+
7+
package indexer
8+
9+
import (
10+
"fmt"
11+
"log/slog"
12+
"time"
13+
14+
"github.com/blinklabs-io/cdnsd/internal/config"
15+
"github.com/blinklabs-io/cdnsd/internal/handshake"
16+
)
17+
18+
type handshakeState struct {
19+
peer *handshake.Peer
20+
peerAddress string
21+
peerBackoffDelay time.Duration
22+
blockHeight int
23+
}
24+
25+
func (i *Indexer) startHandshake() error {
26+
cfg := config.GetConfig()
27+
if cfg.Indexer.HandshakeAddress == "" {
28+
return nil
29+
}
30+
i.handshakeState.peerAddress = cfg.Indexer.HandshakeAddress
31+
if err := i.handshakeConnectPeer(); err != nil {
32+
return err
33+
}
34+
return nil
35+
}
36+
37+
func (i *Indexer) handshakeConnectPeer() error {
38+
slog.Info("connecting to Handshake peer", "address", i.handshakeState.peerAddress)
39+
p, err := handshake.NewPeer(nil, handshake.NetworkMainnet)
40+
if err != nil {
41+
return err
42+
}
43+
i.handshakeState.peer = p
44+
if err := i.handshakeState.peer.Connect(i.handshakeState.peerAddress); err != nil {
45+
return err
46+
}
47+
// Async error handler
48+
go func() {
49+
err := <-i.handshakeState.peer.ErrorChan()
50+
if err != nil {
51+
slog.Error(
52+
"peer disconnected",
53+
"error",
54+
err,
55+
)
56+
}
57+
// Try reconnecting to peer until we are successful
58+
for {
59+
err = i.handshakeConnectPeer()
60+
if err == nil {
61+
i.handshakeState.peerBackoffDelay = 0
62+
return
63+
}
64+
if i.handshakeState.peerBackoffDelay == 0 {
65+
// Set initial backoff delay
66+
i.handshakeState.peerBackoffDelay = 1 * time.Second
67+
} else {
68+
// Double backoff delay
69+
i.handshakeState.peerBackoffDelay *= 2
70+
}
71+
// Don't delay longer than 2m
72+
if i.handshakeState.peerBackoffDelay > 120*time.Second {
73+
i.handshakeState.peerBackoffDelay = 120 * time.Second
74+
}
75+
slog.Error(
76+
"connection to Handshake peer failed",
77+
"error",
78+
err,
79+
"delay",
80+
i.handshakeState.peerBackoffDelay.String(),
81+
)
82+
time.Sleep(i.handshakeState.peerBackoffDelay)
83+
}
84+
}()
85+
// Start sync
86+
if err := i.handshakeState.peer.Sync(nil, i.handshakeHandleSync); err != nil {
87+
return err
88+
}
89+
return nil
90+
}
91+
92+
func (i *Indexer) handshakeHandleSync(block *handshake.Block) error {
93+
i.handshakeState.blockHeight++
94+
slog.Debug(
95+
"synced Handshake block",
96+
"height", i.handshakeState.blockHeight,
97+
"hash", fmt.Sprintf("%x", block.Hash()),
98+
"prevHash", fmt.Sprintf("%x", block.Header.PrevBlock),
99+
)
100+
// Process transactions
101+
for _, tx := range block.Transactions {
102+
// Process outputs
103+
for _, output := range tx.Outputs {
104+
cov := output.Covenant.Covenant()
105+
switch c := cov.(type) {
106+
case *handshake.RegisterCovenant:
107+
slog.Debug("Handshake domain registration", "resdata", c.ResourceData)
108+
case *handshake.UpdateCovenant:
109+
slog.Debug("Handshake domain update", "resdata", c.ResourceData)
110+
}
111+
}
112+
}
113+
return nil
114+
}

internal/indexer/indexer.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,13 @@ type Domain struct {
5555
}
5656

5757
type Indexer struct {
58-
pipeline *pipeline.Pipeline
59-
domains map[string]Domain
60-
tipReached bool
61-
syncLogTimer *time.Timer
62-
syncStatus input_chainsync.ChainSyncStatus
63-
watched []watchedAddr
58+
pipeline *pipeline.Pipeline
59+
domains map[string]Domain
60+
tipReached bool
61+
syncLogTimer *time.Timer
62+
syncStatus input_chainsync.ChainSyncStatus
63+
watched []watchedAddr
64+
handshakeState handshakeState
6465
}
6566

6667
type watchedAddr struct {
@@ -76,6 +77,16 @@ var globalIndexer = &Indexer{
7677
}
7778

7879
func (i *Indexer) Start() error {
80+
if err := i.startCardano(); err != nil {
81+
return err
82+
}
83+
if err := i.startHandshake(); err != nil {
84+
return err
85+
}
86+
return nil
87+
}
88+
89+
func (i *Indexer) startCardano() error {
7990
// Build watched addresses from enabled profiles
8091
cfg := config.GetConfig()
8192
for _, profile := range config.GetProfiles() {

0 commit comments

Comments
 (0)