Skip to content
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
12 changes: 11 additions & 1 deletion cmd/adder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,17 @@ var (
programName string = "adder"
cfg = config.GetConfig()
rootCmd = &cobra.Command{
Use: programName,
Use: programName,
Short: "Cardano blockchain event streaming tool",
Long: `Adder tails the Cardano blockchain and emits structured events for blocks,
transactions, rollbacks, and governance actions. It uses a plugin-based
pipeline architecture with configurable inputs, filters, and outputs.

Input plugins: chainsync (default), mempool
Output plugins: log (default), webhook, telegram, push, notify
Filters: address, asset, policy, pool, drep, event type

Events are also available via the /events WebSocket/SSE API endpoint.`,
SilenceUsage: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
4 changes: 2 additions & 2 deletions filter/cardano/cardano_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,8 @@ func TestWithDRepIds(t *testing.T) {
t.Run("skips invalid input without crashing", func(t *testing.T) {
cs := New(WithDRepIds([]string{"invalid_bech32", "", "xyz"}))

// Should not crash, filter should still be set
assert.True(t, cs.filterSet.hasDRepFilter)
// Should not crash, filter should not be enabled with only invalid IDs
assert.False(t, cs.filterSet.hasDRepFilter)
})
}

Expand Down
61 changes: 32 additions & 29 deletions filter/cardano/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,41 +158,44 @@ func WithDRepIds(drepIds []string) CardanoOptionFunc {

if strings.HasPrefix(drepId, "drep") {
// bech32 format (drep1xxx or drep_script1xxx)
c.filterSet.dreps.bech32DRepIds[drepId] = struct{}{}
// Try to decode and compute hex representation
if _, data, err := bech32.Decode(drepId); err == nil {
if decoded, err := bech32.ConvertBits(data, 5, 8, false); err == nil {
hexId := hex.EncodeToString(decoded)
c.filterSet.dreps.hexDRepIds[hexId] = struct{}{}
c.filterSet.dreps.hexToBech32[hexId] = drepId
// Pre-compute byte slice for direct comparison
c.filterSet.dreps.bytesDRepIds[hexId] = decoded // Store as byte string for O(1) lookup without encoding
c.filterSet.dreps.bytesLookup[string(decoded)] = struct{}{}
}
// Only store after successful decode
_, data, err := bech32.Decode(drepId)
if err != nil {
continue
}
decoded, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
continue
}
c.filterSet.dreps.bech32DRepIds[drepId] = struct{}{}
hexId := hex.EncodeToString(decoded)
c.filterSet.dreps.hexDRepIds[hexId] = struct{}{}
c.filterSet.dreps.hexToBech32[hexId] = drepId
c.filterSet.dreps.bytesDRepIds[hexId] = decoded
c.filterSet.dreps.bytesLookup[string(decoded)] = struct{}{}
} else {
// Assume hex format - store hex
// Hex format - only store after successful decode
hexBytes, err := hex.DecodeString(drepId)
if err != nil {
continue
}
c.filterSet.dreps.hexDRepIds[drepId] = struct{}{}
// Compute both bech32 variants (drep and drep_script)
if hexBytes, err := hex.DecodeString(drepId); err == nil {
// Pre-compute byte slice for direct comparison
c.filterSet.dreps.bytesDRepIds[drepId] = hexBytes
// Store as byte string for O(1) lookup without encoding
c.filterSet.dreps.bytesLookup[string(hexBytes)] = struct{}{}
if convData, err := bech32.ConvertBits(hexBytes, 8, 5, true); err == nil {
// Store as key hash version
if encoded, err := bech32.Encode("drep", convData); err == nil {
c.filterSet.dreps.bech32DRepIds[encoded] = struct{}{}
c.filterSet.dreps.hexToBech32[drepId] = encoded
}
// Also store as script hash version
if encoded, err := bech32.Encode("drep_script", convData); err == nil {
c.filterSet.dreps.bech32DRepIds[encoded] = struct{}{}
}
c.filterSet.dreps.bytesDRepIds[drepId] = hexBytes
c.filterSet.dreps.bytesLookup[string(hexBytes)] = struct{}{}
if convData, err := bech32.ConvertBits(hexBytes, 8, 5, true); err == nil {
if encoded, err := bech32.Encode("drep", convData); err == nil {
c.filterSet.dreps.bech32DRepIds[encoded] = struct{}{}
c.filterSet.dreps.hexToBech32[drepId] = encoded
}
if encoded, err := bech32.Encode("drep_script", convData); err == nil {
c.filterSet.dreps.bech32DRepIds[encoded] = struct{}{}
}
}
}
}
c.filterSet.hasDRepFilter = true
// Only enable filter if at least one valid ID was stored
if len(c.filterSet.dreps.hexDRepIds) > 0 || len(c.filterSet.dreps.bech32DRepIds) > 0 {
c.filterSet.hasDRepFilter = true
}
}
}
27 changes: 26 additions & 1 deletion filter/cardano/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ var cmdlineOptions struct {
asset string
policyId string
poolId string
drepId string
}

func init() {
plugin.Register(
plugin.PluginEntry{
Type: plugin.PluginTypeFilter,
Name: "cardano",
Description: "filters Cardano blockchain events by address, asset, policy, or pool",
Description: "filters Cardano blockchain events by address, asset, policy, pool, or DRep",
NewFromOptionsFunc: NewFromCmdlineOptions,
Options: []plugin.PluginOption{
{
Expand Down Expand Up @@ -68,6 +69,14 @@ func init() {
Dest: &(cmdlineOptions.poolId),
CustomFlag: "pool",
},
{
Name: "drep",
Type: plugin.PluginOptionTypeString,
Description: "specifies DRep ID(s) to filter on (comma-separated, hex or bech32)",
DefaultValue: "",
Dest: &(cmdlineOptions.drepId),
CustomFlag: "drep",
},
},
},
)
Expand Down Expand Up @@ -111,6 +120,22 @@ func NewFromCmdlineOptions() plugin.Plugin {
),
)
}
if cmdlineOptions.drepId != "" {
rawIds := strings.Split(cmdlineOptions.drepId, ",")
var cleanIds []string
for _, id := range rawIds {
id = strings.TrimSpace(strings.ToLower(id))
if id != "" {
cleanIds = append(cleanIds, id)
}
}
if len(cleanIds) > 0 {
pluginOptions = append(
pluginOptions,
WithDRepIds(cleanIds),
)
}
}
p := New(pluginOptions...)
return p
}
30 changes: 30 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,36 @@ func (c *Config) BindFlags(fs *pflag.FlagSet) error {
DefaultOutputPlugin,
"output plugin to use, 'list' to show available",
)
fs.StringVar(
&c.Api.ListenAddress,
"api-address",
c.Api.ListenAddress,
"API listen address",
)
fs.UintVar(
&c.Api.ListenPort,
"api-port",
c.Api.ListenPort,
"API listen port",
)
fs.StringVar(
&c.Logging.Level,
"logging-level",
c.Logging.Level,
"logging level (debug, info, warn, error)",
)
fs.StringVar(
&c.Debug.ListenAddress,
"debug-address",
c.Debug.ListenAddress,
"debug listener address",
)
fs.UintVar(
&c.Debug.ListenPort,
"debug-port",
c.Debug.ListenPort,
"debug listener port (0 to disable)",
)
return plugin.PopulateCmdlineOptions(fs)
}

Expand Down
Loading