Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BCI-887] Solana HeadTracker Integration #521

Draft
wants to merge 47 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fe3a520
Added solana hash and hash test
yongkangc Jun 9, 2023
15a48ba
Added Head
yongkangc Jun 9, 2023
64f3e54
Added Hash Helper for tests
yongkangc Jun 9, 2023
87c113c
Added Head
yongkangc Jun 9, 2023
69005ea
Added Head and Head test for solana
yongkangc Jun 10, 2023
f067f03
added mocks
yongkangc Jun 12, 2023
4ee8456
Updated Solana Headtracker Config
yongkangc Jun 12, 2023
23a5366
Added subscription and client interfaces
yongkangc Jun 13, 2023
2da21a2
Updated config to include polling interval
yongkangc Jun 15, 2023
ed7192a
removed HeadByHash from client interface - not needed
yongkangc Jun 15, 2023
fc2586e
Added subscription concrete type + tests
yongkangc Jun 15, 2023
7fd9084
Updated helper for client
yongkangc Jun 15, 2023
f2cd914
Added Client + SubscribeNewHead test for polling
yongkangc Jun 15, 2023
784b827
go mod tidy
yongkangc Jun 15, 2023
fda3ff0
updated mock file
yongkangc Jun 15, 2023
e395c80
Added test for Client HeadByNumber
yongkangc Jun 15, 2023
8ee9449
cleanup
yongkangc Jun 15, 2023
c0734ae
moved interfaces over for headtracker services
yongkangc Jun 17, 2023
623d63b
update go mod
yongkangc Jun 17, 2023
7d3e7eb
go mod tidy
yongkangc Jun 21, 2023
a57978d
Added InMemory HeadSaver + Tests
yongkangc Jun 22, 2023
c3f524e
use chainlink-relay, removed common folder from solana
yongkangc Jun 22, 2023
bb012ed
update go mod dependency for chainlink-relay
yongkangc Jun 22, 2023
607e7b6
improve package naming
yongkangc Jun 22, 2023
832b102
Added head broadcaster
yongkangc Jun 22, 2023
a48307c
upgrade go mod
yongkangc Jun 23, 2023
7b38910
refactored tests
yongkangc Jun 23, 2023
ecad253
added head_broadcaster_test
yongkangc Jun 23, 2023
50c96cc
added headtracker
yongkangc Jun 23, 2023
5d3b2b8
fixed head broadcaster test
yongkangc Jun 23, 2023
4942db8
Nit changes to New Headtracker components
yongkangc Jun 23, 2023
24500a5
Added HeadListener
yongkangc Jun 23, 2023
3571cc7
Added Headlistener test
yongkangc Jun 23, 2023
9e3eec8
fixed head listener test
yongkangc Jun 23, 2023
7f20a24
Reduced Verbosity of Headtracker
yongkangc Jul 10, 2023
d1aa0d4
cleanup headlistener test
yongkangc Jul 10, 2023
e6cd1df
Headtracker Test added
yongkangc Jul 10, 2023
fc4652c
fixed headsaver
yongkangc Jul 11, 2023
eee34ae
fixed TestHeadTracker_Save_InsertsAndTrimsTable
yongkangc Jul 11, 2023
dcadf49
Added Mock chain for testing
yongkangc Jul 11, 2023
5f1f859
test passing for TestHeadTracker_SwitchesToLongestChainWithHeadSampli…
yongkangc Jul 11, 2023
ca79b23
Fixed Get Parent Hash method for head
yongkangc Jul 12, 2023
d4362f1
backfill fixes to headsaver
yongkangc Jul 12, 2023
3e814e6
backfill test passing
yongkangc Jul 12, 2023
46d01bb
All headtracker test passing
yongkangc Jul 12, 2023
9e9aff2
cleanup
yongkangc Jul 13, 2023
b66e00e
cleanup of TODO
yongkangc Jul 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 21 additions & 18 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ require (
github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27
github.com/gagliardetto/treeout v0.1.4
github.com/google/uuid v1.3.0
github.com/onsi/gomega v1.24.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.14.0
github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230525161650-dce1bc58b504
github.com/smartcontractkit/libocr v0.0.0-20230525150148-a75f6e244bb3
github.com/stretchr/testify v1.8.2
go.uber.org/multierr v1.8.0
github.com/prometheus/client_golang v1.15.0
github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230623025050-a286a91d29e6
github.com/smartcontractkit/libocr v0.0.0-20230606215712-82b910bef5c1
github.com/stretchr/testify v1.8.4
github.com/test-go/testify v1.1.4
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
golang.org/x/sync v0.1.0
Expand All @@ -26,16 +28,16 @@ require (
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
github.com/benbjohnson/clock v1.1.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/confluentinc/confluent-kafka-go v1.9.2 // indirect
github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand All @@ -45,17 +47,16 @@ require (
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/onsi/gomega v1.24.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/riferrei/srclient v0.5.4 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
Expand All @@ -64,11 +65,13 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.4.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
191 changes: 44 additions & 147 deletions go.sum

Large diffs are not rendered by default.

258 changes: 258 additions & 0 deletions pkg/internal/cltest/cltest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package cltest

import (
"context"
"fmt"
"math/big"
"sync/atomic"
"testing"
"time"

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"

clientmocks "github.com/smartcontractkit/chainlink-relay/pkg/headtracker/types/mocks"
commontypes "github.com/smartcontractkit/chainlink-relay/pkg/types"
"github.com/smartcontractkit/chainlink-solana/pkg/internal/utils"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/headtracker/types"
)

// TODO: write tests for this package

// Chain Specific Test utils

// Head returns a new Head with the given block height
func Head(val interface{}) *types.Head {
time := solana.UnixTimeSeconds(0)
chainId := types.Mainnet
blockHeight := getBlockHeight(val)
parentSlot := getParentSlot(blockHeight)
block := getBlock(blockHeight, parentSlot, time)
h := createHead(val, blockHeight, block, chainId)
return h
}

func getBlockHeight(val interface{}) uint64 {
switch t := val.(type) {
case int:
return uint64(t)
case uint64:
return t
case int64:
return uint64(t)
case *big.Int:
return t.Uint64()
default:
panic(fmt.Sprintf("Could not convert %v of type %T to Head", val, val))
}
}

func getParentSlot(blockHeight uint64) uint64 {
if blockHeight > 1 {
return blockHeight - 1
}
return 0
}

func getBlock(blockHeight, parentSlot uint64, time solana.UnixTimeSeconds) rpc.GetBlockResult {
return rpc.GetBlockResult{
Blockhash: utils.NewSolanaHash(),
PreviousBlockhash: utils.NewSolanaHash(),
ParentSlot: parentSlot,
Transactions: nil,
Rewards: nil,
BlockTime: &time,
BlockHeight: &blockHeight,
}
}

func createHead(val interface{}, blockHeight uint64, block rpc.GetBlockResult, chainId types.ChainID) *types.Head {
switch t := val.(type) {
case int, uint64:
return types.NewHead(int64(blockHeight), block, nil, chainId)
case int64:
return types.NewHead(t, block, nil, chainId)
case *big.Int:
return types.NewHead(t.Int64(), block, nil, chainId)
default:
panic(fmt.Sprintf("Could not convert %v of type %T to Head", val, val))
}
}

// Blocks - a helper logic to construct a range of linked heads
// and an ability to fork and create logs from them
type Blocks struct {
t *testing.T
Hashes []types.Hash
mHashes map[int64]types.Hash
Heads map[int64]*types.Head
}

func (b *Blocks) Head(number uint64) *types.Head {
return b.Heads[int64(number)]
}

func NewBlocks(t *testing.T, numHashes int) *Blocks {
hashes := make([]types.Hash, 0)
heads := make(map[int64]*types.Head)
for i := int64(0); i < int64(numHashes); i++ {
hash := utils.NewHash()
hashes = append(hashes, hash)

heads[i] = Head(i)
if i > 0 {
parent := heads[i-1]
heads[i].Parent = parent
heads[i].Block.PreviousBlockhash = parent.BlockHash().Hash
}
}

hashesMap := make(map[int64]types.Hash)
for i := 0; i < len(hashes); i++ {
hashesMap[int64(i)] = hashes[i]
}

return &Blocks{
t: t,
Hashes: hashes,
mHashes: hashesMap,
Heads: heads,
}
}

func (b *Blocks) NewHead(number uint64) *types.Head {
parentNumber := number - 1
parent, ok := b.Heads[int64(parentNumber)]
if !ok {
b.t.Fatalf("Can't find parent block at index: %v", parentNumber)
}

head := Head(number)
head.Parent = parent
head.Block.PreviousBlockhash = parent.BlockHash().Hash

return head
}

func (b *Blocks) ForkAt(t *testing.T, blockNum int64, numHashes int) *Blocks {
forked := NewBlocks(t, len(b.Heads)+numHashes)
if _, exists := forked.Heads[blockNum]; !exists {
t.Fatalf("Not enough length for block num: %v", blockNum)
}

for i := int64(0); i < blockNum; i++ {
forked.Heads[i] = b.Heads[i]
}

forked.Heads[blockNum].Block.PreviousBlockhash = b.Heads[blockNum].Block.PreviousBlockhash
forked.Heads[blockNum].Parent = b.Heads[blockNum].Parent
return forked
}

// HeadBuffer - stores heads in sequence, with increasing timestamps
type HeadBuffer struct {
t *testing.T
Heads []*types.Head
}

func NewHeadBuffer(t *testing.T) *HeadBuffer {
return &HeadBuffer{
t: t,
Heads: make([]*types.Head, 0),
}
}

func (hb *HeadBuffer) Append(head *types.Head) {
// Create a copy of the head, so that we can modify it
cloned := &types.Head{
Slot: head.Slot,
Block: head.Block,
Parent: head.Parent,
ID: head.ID,
}
hb.Heads = append(hb.Heads, cloned)
}

// MockHeadTrackable allows you to mock HeadTrackable
type MockHeadTrackable struct {
onNewHeadCount atomic.Int32
}

// OnNewLongestChain increases the OnNewLongestChainCount count by one
func (m *MockHeadTrackable) OnNewLongestChain(context.Context, *types.Head) {
m.onNewHeadCount.Add(1)
}

// OnNewLongestChainCount returns the count of new heads, safely.
func (m *MockHeadTrackable) OnNewLongestChainCount() int32 {
return m.onNewHeadCount.Load()
}

type Awaiter chan struct{}

func NewAwaiter() Awaiter { return make(Awaiter) }

func (a Awaiter) ItHappened() { close(a) }

func (a Awaiter) AssertHappened(t *testing.T, expected bool) {
t.Helper()
select {
case <-a:
if !expected {
t.Fatal("It happened")
}
default:
if expected {
t.Fatal("It didn't happen")
}
}
}

func (a Awaiter) AwaitOrFail(t testing.TB, durationParams ...time.Duration) {
t.Helper()

duration := 10 * time.Second
if len(durationParams) > 0 {
duration = durationParams[0]
}

select {
case <-a:
case <-time.After(duration):
t.Fatal("Timed out waiting for Awaiter to get ItHappened")
}
}

func NewClientMock(t *testing.T) *clientmocks.Client[
*types.Head,
commontypes.Subscription,
types.ChainID,
types.Hash] {
return clientmocks.NewClient[*types.Head, commontypes.Subscription,
types.ChainID, types.Hash](t)
}

func NewClientMockWithDefaultChain(t *testing.T) *clientmocks.Client[
*types.Head,
commontypes.Subscription,
types.ChainID,
types.Hash] {
c := NewClientMock(t)
c.On("ConfiguredChainID").Return(types.Mainnet).Maybe()
c.On("IsL2").Return(false).Maybe()
return c
}

func ConfigureBlockResult() rpc.GetBlockResult {
result := rpc.GetBlockResult{
Blockhash: utils.NewSolanaHash(),
PreviousBlockhash: utils.NewSolanaHash(),
ParentSlot: 0,
Transactions: []rpc.TransactionWithMeta{},
Signatures: []solana.Signature{},
Rewards: []rpc.BlockReward{},
BlockTime: nil,
BlockHeight: nil,
}
return result
}