Files

181 lines
3.6 KiB
Go
Raw Permalink Normal View History

2025-12-26 11:34:45 +08:00
package shreder
2025-12-26 10:57:37 +08:00
import (
"context"
2026-01-05 12:45:32 +08:00
"fmt"
2026-01-07 21:15:54 +08:00
"runtime"
"time"
2025-12-26 10:57:37 +08:00
2026-01-05 12:45:32 +08:00
"github.com/gagliardetto/solana-go/rpc"
2026-01-07 21:15:54 +08:00
"github.com/panjf2000/ants/v2"
2025-12-26 10:57:37 +08:00
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
2025-12-30 11:03:11 +08:00
type Client struct {
2026-01-08 11:57:57 +08:00
enableBlockStats bool
enableParseStats bool
conn *grpc.ClientConn
client ShrederServiceClient
tableLoader *AddressTables
subscription map[string]*SubscribeRequestFilterTransactions
2026-01-07 21:15:54 +08:00
pool *ants.Pool
lastSlot uint64
lastSlotTime time.Time
2025-12-26 10:57:37 +08:00
}
2026-01-08 11:57:57 +08:00
type ClientOpts struct {
blockStats bool
showTableLoaded bool
logParseStats bool
}
type ClientOption func(*ClientOpts)
func ShowTableLoaded(enable bool) ClientOption {
return func(opts *ClientOpts) {
opts.showTableLoaded = enable
}
}
func BlocksStats(enable bool) ClientOption {
return func(opts *ClientOpts) {
opts.blockStats = enable
}
}
func LogParsedStats(enable bool) ClientOption {
return func(opts *ClientOpts) {
opts.logParseStats = enable
}
}
2025-12-26 11:34:45 +08:00
func NewShrederClient(
url string,
2026-01-05 12:45:32 +08:00
rpcClient *rpc.Client,
2025-12-30 11:03:11 +08:00
subscription map[string]*SubscribeRequestFilterTransactions,
2026-01-08 11:57:57 +08:00
options ...ClientOption,
2025-12-30 11:03:11 +08:00
) (*Client, func(), error) {
2026-01-05 12:45:32 +08:00
if rpcClient == nil {
return nil, func() {}, fmt.Errorf("rpc client is nil")
}
2025-12-26 10:57:37 +08:00
conn, err := grpc.NewClient(url, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, func() {}, err
}
2026-01-07 21:15:54 +08:00
poolSize := runtime.NumCPU()*2 + 2
logger.Info("creating shreder client", "url", url, "pool_size", poolSize)
pool, err := ants.NewPool(poolSize, ants.WithNonblocking(false))
if err != nil {
return nil, func() {}, err
}
2026-01-08 11:57:57 +08:00
o := &ClientOpts{
blockStats: false,
showTableLoaded: true,
logParseStats: false,
}
for _, option := range options {
option(o)
}
2025-12-30 11:03:11 +08:00
s := &Client{
2025-12-26 11:34:45 +08:00
conn: conn,
2025-12-30 11:03:11 +08:00
client: NewShrederServiceClient(conn),
2025-12-26 11:34:45 +08:00
subscription: subscription,
2026-01-08 11:57:57 +08:00
tableLoader: NewAddressTables(rpcClient, o.showTableLoaded),
2026-01-07 21:15:54 +08:00
pool: pool,
2026-01-08 11:57:57 +08:00
enableBlockStats: o.blockStats,
enableParseStats: o.logParseStats,
2025-12-26 10:57:37 +08:00
}
return s, func() {
s.Wait()
}, nil
}
2025-12-30 11:03:11 +08:00
func (c *Client) Wait() {
2026-01-05 12:45:32 +08:00
logger.Debug("waiting for shreder client to stop")
2025-12-26 10:57:37 +08:00
2026-01-07 21:15:54 +08:00
if c.pool != nil {
c.pool.Release()
}
2025-12-26 10:57:37 +08:00
err := c.conn.Close()
if err != nil {
2026-01-05 12:45:32 +08:00
logger.Error("failed to close connection: ", "err", err)
2025-12-26 10:57:37 +08:00
}
2026-01-05 12:45:32 +08:00
logger.Debug("shreder client stopped")
2025-12-26 10:57:37 +08:00
}
2025-12-30 11:03:11 +08:00
func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error {
2025-12-26 10:57:37 +08:00
stream, err := c.client.SubscribeTransactions(ctx)
if err != nil {
return err
}
2026-01-07 21:15:54 +08:00
logger.Debug("subscribing to transactions")
2025-12-30 11:03:11 +08:00
err = stream.Send(&SubscribeTransactionsRequest{
2025-12-26 11:34:45 +08:00
Transactions: c.subscription,
2025-12-26 10:57:37 +08:00
})
if err != nil {
return err
}
2026-01-19 09:35:47 +08:00
// reboot the pool
c.pool.Reboot()
2025-12-26 10:57:37 +08:00
for {
2026-01-19 09:35:47 +08:00
var response *SubscribeTransactionsResponse
response, err = stream.Recv()
2025-12-26 10:57:37 +08:00
if err != nil {
2026-01-19 09:35:47 +08:00
break
2025-12-26 10:57:37 +08:00
}
2026-01-08 11:57:57 +08:00
if c.enableBlockStats {
2026-01-07 21:15:54 +08:00
slot := response.Transaction.Slot
now := time.Now()
if c.lastSlotTime.IsZero() || slot > c.lastSlot {
if !c.lastSlotTime.IsZero() {
logger.Info("block processed", "running", c.pool.Running(), "slot", slot, "prev_slot", c.lastSlot, "delta_ms", now.Sub(c.lastSlotTime).Milliseconds())
}
c.lastSlot = slot
c.lastSlotTime = now
}
2025-12-26 10:57:37 +08:00
}
2026-01-07 21:15:54 +08:00
txData := response.Transaction
err = c.pool.Submit(func() {
2026-01-08 11:57:57 +08:00
txBatch := ParseTransaction(txData, c.tableLoader, c.enableParseStats)
2026-01-07 21:15:54 +08:00
if len(txBatch) == 0 {
return
}
2025-12-26 10:57:37 +08:00
2026-01-07 21:15:54 +08:00
for _, tx := range txBatch {
tx.Source = "shreder"
}
select {
case <-ctx.Done():
return
case txCh <- txBatch:
}
})
if err != nil {
2026-01-19 09:35:47 +08:00
break
2025-12-26 10:57:37 +08:00
}
}
2026-01-19 09:35:47 +08:00
// sync waiting for all tasks to complete
c.pool.Release()
return err
2025-12-26 10:57:37 +08:00
}