2022-06-23 06:43:29 -07:00
|
|
|
package blockhash
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
|
|
"github.com/gagliardetto/solana-go/rpc"
|
2022-10-29 04:30:26 -07:00
|
|
|
envv1 "go.firedancer.io/radiance/proto/env/v1"
|
2022-06-23 06:43:29 -07:00
|
|
|
"k8s.io/klog/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Tracker struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
byNode map[string]struct {
|
|
|
|
Blockhash solana.Hash
|
|
|
|
HighestValidSlot uint64
|
|
|
|
}
|
|
|
|
nodes []*envv1.RPCNode
|
|
|
|
|
|
|
|
c map[string]*rpc.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(nodes []*envv1.RPCNode) *Tracker {
|
|
|
|
t := &Tracker{
|
|
|
|
byNode: make(map[string]struct {
|
|
|
|
Blockhash solana.Hash
|
|
|
|
HighestValidSlot uint64
|
|
|
|
}),
|
|
|
|
c: make(map[string]*rpc.Client),
|
|
|
|
nodes: nodes,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
t.c[node.Name] = rpc.New(node.Http)
|
|
|
|
}
|
|
|
|
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tracker) MostPopular() solana.Hash {
|
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
|
|
|
// Return the most frequently occurring value of t.byNode
|
|
|
|
var mostPopular solana.Hash
|
|
|
|
var mostPopularCount int
|
|
|
|
for _, h := range t.byNode {
|
|
|
|
count := 0
|
|
|
|
for _, h2 := range t.byNode {
|
|
|
|
if h.Blockhash.Equals(h2.Blockhash) {
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if count > mostPopularCount {
|
|
|
|
mostPopular = h.Blockhash
|
|
|
|
mostPopularCount = count
|
|
|
|
}
|
|
|
|
|
|
|
|
klog.V(2).Infof("%s: %d", h, count)
|
|
|
|
}
|
|
|
|
return mostPopular
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tracker) MostRecent() solana.Hash {
|
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
|
|
|
// Return the blockhash which has the highest valid slot
|
|
|
|
var mostRecent solana.Hash
|
|
|
|
var highestValidSlot uint64
|
|
|
|
for _, h := range t.byNode {
|
|
|
|
if h.HighestValidSlot > highestValidSlot {
|
|
|
|
highestValidSlot = h.HighestValidSlot
|
|
|
|
mostRecent = h.Blockhash
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mostRecent
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tracker) Run(ctx context.Context, interval time.Duration) {
|
|
|
|
t.update(ctx)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case <-time.After(interval):
|
|
|
|
t.update(ctx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tracker) update(ctx context.Context) {
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
for _, node := range t.nodes {
|
|
|
|
node := node
|
|
|
|
go func() {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
|
|
|
|
defer cancel()
|
|
|
|
klog.V(1).Infof("Fetching blockhash from %s", node.Http)
|
|
|
|
h, err := t.c[node.Name].GetLatestBlockhash(ctx, rpc.CommitmentConfirmed)
|
|
|
|
if err != nil {
|
|
|
|
klog.Errorf("%s: failed to request blockhash: %v", node.Name, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
klog.V(1).Infof("%s: fetched blockhash %d -> %s in %v",
|
|
|
|
node.Name, h.Value.LastValidBlockHeight, h.Value.Blockhash, time.Since(now))
|
|
|
|
t.mu.Lock()
|
|
|
|
t.byNode[node.Name] = struct {
|
|
|
|
Blockhash solana.Hash
|
|
|
|
HighestValidSlot uint64
|
|
|
|
}{
|
|
|
|
Blockhash: h.Value.Blockhash,
|
|
|
|
HighestValidSlot: h.Value.LastValidBlockHeight,
|
|
|
|
}
|
|
|
|
t.mu.Unlock()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|