Skip to content

Commit 75dc65f

Browse files
jkawanJenita
andauthored
feat(filter): check StakeDeregistrationCertificate and StakeDelegationCertificate StakeCredential (#390)
Signed-off-by: Jenita <[email protected]> Signed-off-by: Jenita <[email protected]> Co-authored-by: Jenita <[email protected]>
1 parent 63b29e7 commit 75dc65f

File tree

2 files changed

+145
-31
lines changed

2 files changed

+145
-31
lines changed

filter/chainsync/chainsync.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/blinklabs-io/adder/input/chainsync"
2323
"github.com/blinklabs-io/adder/plugin"
2424
"github.com/blinklabs-io/gouroboros/ledger"
25+
"github.com/blinklabs-io/gouroboros/ledger/common"
2526
"github.com/btcsuite/btcd/btcutil/bech32"
2627
)
2728

@@ -124,6 +125,36 @@ func (c *ChainSync) Start() error {
124125
}
125126
}
126127
}
128+
129+
if !foundMatch && isStakeAddress {
130+
for _, certificate := range v.Certificates {
131+
var credBytes []byte
132+
switch cert := certificate.(type) {
133+
case *common.StakeDelegationCertificate:
134+
hash := cert.StakeCredential.Hash()
135+
credBytes = hash[:]
136+
case *common.StakeDeregistrationCertificate:
137+
hash := cert.StakeDeregistration.Hash()
138+
credBytes = hash[:]
139+
default:
140+
continue
141+
}
142+
143+
convData, err := bech32.ConvertBits(credBytes, 8, 5, true)
144+
if err != nil {
145+
continue
146+
}
147+
encoded, err := bech32.Encode("stake", convData)
148+
if err != nil {
149+
continue
150+
}
151+
if encoded == filterAddress {
152+
foundMatch = true
153+
break
154+
}
155+
}
156+
}
157+
127158
if foundMatch {
128159
filterMatched = true
129160
break

filter/chainsync/chainsync_test.go

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
66
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
7+
// http://www.apache.org/licenses/LICENSE-2.0
88
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
1514
package chainsync
1615

1716
import (
@@ -191,41 +190,125 @@ func TestChainSync_OutputChan(t *testing.T) {
191190
}
192191
}
193192

194-
func TestFilterByAddress(t *testing.T) {
195-
// Setup ChainSync with address filter
196-
cs := New(WithAddresses([]string{"addr_test1qqjwq357"}))
193+
// Mock certificate implementations
194+
type mockStakeDelegationCert struct {
195+
common.StakeDelegationCertificate
196+
cborData []byte
197+
}
197198

198-
// Create a mock address using the methods
199-
mockAddr := common.Address{}
199+
func (m *mockStakeDelegationCert) Cbor() []byte { return m.cborData }
200200

201-
// Mock transaction event
202-
output := MockOutput{
203-
address: mockAddr,
204-
amount: 1000000,
205-
assets: nil,
206-
datum: nil,
207-
}
208-
evt := event.Event{
209-
Payload: chainsync.TransactionEvent{
210-
Outputs: []ledger.TransactionOutput{output},
211-
ResolvedInputs: []ledger.TransactionOutput{output},
212-
},
201+
type mockStakeDeregistrationCert struct {
202+
common.StakeDeregistrationCertificate
203+
cborData []byte
204+
}
205+
206+
func (m *mockStakeDeregistrationCert) Cbor() []byte { return m.cborData }
207+
208+
func mockStakeCredentialValue(credType uint, hashBytes []byte) common.StakeCredential {
209+
return common.StakeCredential{
210+
StructAsArray: cbor.StructAsArray{},
211+
DecodeStoreCbor: cbor.DecodeStoreCbor{},
212+
CredType: credType,
213+
Credential: hashBytes,
213214
}
215+
}
214216

215-
// Start the filter
216-
err := cs.Start()
217-
assert.NoError(t, err, "ChainSync filter should start without error")
218-
defer cs.Stop()
217+
func mockStakeCredentialPtr(credType uint, hashBytes []byte) *common.StakeCredential {
218+
cred := mockStakeCredentialValue(credType, hashBytes)
219+
return &cred
220+
}
219221

220-
// Send event to input channel
221-
cs.InputChan() <- evt
222+
func mockAddress(addrStr string) common.Address {
223+
return common.Address{}
224+
}
222225

223-
// Wait for the event to be processed
224-
select {
225-
case filteredEvt := <-cs.OutputChan():
226-
assert.Equal(t, evt, filteredEvt, "Filtered event should match the input event")
227-
case <-time.After(10 * time.Second):
228-
t.Fatal("Test timed out waiting for filtered event")
226+
func TestFilterByAddress(t *testing.T) {
227+
tests := []struct {
228+
name string
229+
filterAddress string
230+
outputAddr common.Address
231+
cert ledger.Certificate
232+
shouldMatch bool
233+
}{
234+
{
235+
name: "Basic address match",
236+
filterAddress: "addr_test1qqjwq357",
237+
outputAddr: mockAddress("addr_test1qqjwq357"),
238+
shouldMatch: true,
239+
},
240+
241+
{
242+
name: "StakeDelegationCertificate match",
243+
filterAddress: "stake1276l2v4nvtm6mpr7s6cu3ezneh6vrunlw0jahq9fxy6v6e37p04",
244+
outputAddr: mockAddress("addr_doesnt_match"),
245+
cert: &common.StakeDelegationCertificate{
246+
StakeCredential: mockStakeCredentialPtr(0, []byte{1, 2, 3}),
247+
},
248+
shouldMatch: true,
249+
},
250+
{
251+
name: "StakeDeregistrationCertificate match",
252+
filterAddress: "stake1276l2v4nvtm6mpr7s6cu3ezneh6vrunlw0jahq9fxy6v6e37p04",
253+
outputAddr: mockAddress("addr_doesnt_match"),
254+
cert: &common.StakeDeregistrationCertificate{
255+
StakeDeregistration: mockStakeCredentialValue(0, []byte{1, 2, 3}),
256+
},
257+
shouldMatch: true,
258+
},
259+
{
260+
name: "No match",
261+
filterAddress: "stake_test1uzw2x9z6y3q4y5z6x7y8z9",
262+
outputAddr: mockAddress("addr_doesnt_match"),
263+
shouldMatch: false,
264+
},
265+
}
266+
267+
for _, tt := range tests {
268+
t.Run(tt.name, func(t *testing.T) {
269+
// Create chainsync instance with address filter
270+
cs := New(WithAddresses([]string{tt.filterAddress}))
271+
272+
output := MockOutput{
273+
address: tt.outputAddr,
274+
amount: 1000000,
275+
assets: nil,
276+
datum: nil,
277+
}
278+
279+
txEvent := chainsync.TransactionEvent{
280+
Outputs: []ledger.TransactionOutput{output},
281+
ResolvedInputs: []ledger.TransactionOutput{output},
282+
}
283+
284+
if tt.cert != nil {
285+
txEvent.Certificates = []ledger.Certificate{tt.cert}
286+
}
287+
288+
evt := event.Event{Payload: txEvent}
289+
290+
err := cs.Start()
291+
assert.NoError(t, err)
292+
defer cs.Stop()
293+
294+
cs.InputChan() <- evt
295+
296+
if tt.shouldMatch {
297+
select {
298+
case filteredEvt := <-cs.OutputChan():
299+
assert.Equal(t, evt, filteredEvt)
300+
case <-time.After(1 * time.Second):
301+
t.Error("Expected event to pass filter but it didn't")
302+
}
303+
} else {
304+
select {
305+
case <-cs.OutputChan():
306+
t.Error("Expected event to be filtered out but it passed through")
307+
case <-time.After(100 * time.Millisecond):
308+
// Expected no event
309+
}
310+
}
311+
})
229312
}
230313
}
231314

0 commit comments

Comments
 (0)