Skip to content
Open
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
47 changes: 47 additions & 0 deletions common/logpoller/mocks/log_poller.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions core/cmd/blocks_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"bytes"
"encoding/json"
stderrors "errors"
"fmt"
"net/url"
Expand All @@ -21,7 +22,7 @@
Action: s.ReplayFromBlock,
Flags: []cli.Flag{
cli.IntFlag{
Name: "block-number",

Check warning on line 25 in core/cmd/blocks_commands.go

View check run for this annotation

CL-sonarqube-production / SonarQube Code Analysis

Define a constant instead of duplicating this literal "block-number" 4 times.

[S1192] String literals should not be duplicated See more on https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=23099&issues=256c5324-6f96-4dae-a5be-470c09842eec&open=256c5324-6f96-4dae-a5be-470c09842eec
Usage: "Block number to replay from",
Required: true,
},
Expand All @@ -31,7 +32,7 @@
Required: true,
},
cli.StringFlag{
Name: "chain-id",

Check warning on line 35 in core/cmd/blocks_commands.go

View check run for this annotation

CL-sonarqube-production / SonarQube Code Analysis

Define a constant instead of duplicating this literal "chain-id" 6 times.

[S1192] String literals should not be duplicated See more on https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=23099&issues=6510c80f-8099-422c-af5a-b4e5065d7335&open=6510c80f-8099-422c-af5a-b4e5065d7335
Usage: "Chain ID of the blockchain",
Required: true,
},
Expand All @@ -53,6 +54,28 @@
},
},
},
{
Name: "lp-skip-to-block",
Usage: `Reposition LogPoller to start processing from the given finalized block number. Note: LogPoller does not guarantee that finalized blocks in the DB and specified block belong to the same chain. LogPoller will remove all unfinalized logs before saving new checkpoint.`,
Action: s.LPSkipToBlock,
Flags: []cli.Flag{
cli.Int64Flag{
Name: "block-number",
Usage: "Block number to skip to",
Required: true,
},
cli.StringFlag{
Name: "family",
Usage: "Chain family for specified chain-id (currently only evm is supported)",
Required: true,
},
cli.StringFlag{
Name: "chain-id",
Usage: "Chain ID of the blockchain",
Required: true,
},
},
},
}
}

Expand Down Expand Up @@ -140,3 +163,46 @@

return s.renderAPIResponse(resp, &LCAPresenter{}, "Last Common Ancestor")
}

// LPSkipToBlock repositions LogPoller to start processing from the given block number.
func (s *Shell) LPSkipToBlock(c *cli.Context) (err error) {
blockNumber := c.Int64("block-number")
if blockNumber < 2 {
return s.errorOut(errors.New("Must pass a value >= 2 in '--block-number' parameter"))
}

if !c.IsSet("family") {
return s.errorOut(errors.New("Must set '--family' parameter to specify chain family type"))
}

if !c.IsSet("chain-id") {
return s.errorOut(errors.New("Must set '--chain-id' parameter"))
}

request, err := json.Marshal(web.LPSkipToBlockRequest{
BlockNumber: blockNumber,
Family: c.String("family"),
ChainID: c.String("chain-id"),
})
if err != nil {
return s.errorOut(err)
}

resp, err := s.HTTP.Post(s.ctx(), "/v2/lp_skip_to_block", bytes.NewReader(request))
if err != nil {
return s.errorOut(errors.Wrap(err, "failed to send request to reposition LogPoller"))
}

defer func() {
if cerr := resp.Body.Close(); cerr != nil {
err = stderrors.Join(err, cerr)
}
}()

_, err = s.parseResponse(resp)
if err != nil {
return s.errorOut(err)
}
fmt.Println("Log poller will start processing from the new block on next tick")
return nil
}
37 changes: 36 additions & 1 deletion core/cmd/blocks_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func Test_ReplayFromBlock(t *testing.T) {
flagSetApplyFromAction(client.ReplayFromBlock, set, "")

t.Run("invalid args", func(t *testing.T) {
t.Parallel()
// Incorrect block number
require.NoError(t, set.Set("block-number", "0"))
c := cli.NewContext(nil, set, nil)
Expand All @@ -45,6 +46,7 @@ func Test_ReplayFromBlock(t *testing.T) {
})

t.Run("evm replay", func(t *testing.T) {
t.Parallel()
require.NoError(t, set.Set("block-number", "1"))
require.NoError(t, set.Set("chain-id", "5"))
require.NoError(t, set.Set("family", "evm"))
Expand All @@ -59,7 +61,7 @@ func Test_FindLCA(t *testing.T) {
// ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil)
app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) {
c.EVM[0].ChainID = (*sqlutil.Big)(big.NewInt(5))
c.EVM[0].Enabled = ptr(true)
c.EVM[0].Enabled = new(true)
})

client, _ := app.NewShellAndRenderer()
Expand All @@ -77,3 +79,36 @@ func Test_FindLCA(t *testing.T) {
c = cli.NewContext(nil, set, nil)
require.ErrorContains(t, client.FindLCA(c), "FindLCA is only available if LogPoller is enabled")
}

func Test_LPSkipToBlock(t *testing.T) {
t.Parallel()

app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) {
c.Feature.LogPoller = new(true)
c.EVM[0].ChainID = (*sqlutil.Big)(big.NewInt(5))
c.EVM[0].Enabled = new(true)
})

client, _ := app.NewShellAndRenderer()

set := flag.NewFlagSet("test", 0)
flagSetApplyFromAction(client.LPSkipToBlock, set, "")

t.Run("invalid args", func(t *testing.T) {
t.Parallel()
require.NoError(t, set.Set("block-number", "1"))
c := cli.NewContext(nil, set, nil)
require.ErrorContains(t, client.LPSkipToBlock(c), "Must pass a value >= 2")

require.NoError(t, set.Set("block-number", "100"))
require.NoError(t, set.Set("chain-id", "123"))
require.NoError(t, set.Set("family", "evm"))
c = cli.NewContext(nil, set, nil)
require.ErrorContains(t, client.LPSkipToBlock(c), "relayer does not exist")

require.NoError(t, set.Set("chain-id", "5"))
require.NoError(t, set.Set("family", "solana"))
c = cli.NewContext(nil, set, nil)
require.ErrorContains(t, client.LPSkipToBlock(c), "only evm is supported")
})
}
49 changes: 49 additions & 0 deletions core/internal/mocks/application.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ require (
github.com/smartcontractkit/chain-selectors v1.0.104
github.com/smartcontractkit/chainlink-automation v0.8.1
github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260624154507-ea7ff77a0ddb
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260713194119-2689c5708c8b
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260714113618-bd55996215f7
github.com/smartcontractkit/chainlink-common/keystore v1.2.0
github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260707105132-2da5d31f22fc
github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54
github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260623170329-4577ef4ba0ae
github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260713161947-559439dd0d3f
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260713161920-de075095648b
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260623200841-e0322b819f62
github.com/smartcontractkit/chainlink-protos/job-distributor v0.20.1-0.20260701185448-696c075849ea
Expand Down Expand Up @@ -517,7 +517,7 @@ require (
github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260630073003-fb8da7229930 // indirect
github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260513123719-d347eaf314e1 // indirect
github.com/smartcontractkit/chainlink-sui v0.0.0-20260713221039-69796c8a78ae // indirect
github.com/smartcontractkit/chainlink-sui/codec v0.0.0-20260713202800-ac352a2c68f0 // indirect
github.com/smartcontractkit/chainlink-sui/codec v0.0.0-20260713221039-69796c8a78ae // indirect
github.com/smartcontractkit/chainlink-testing-framework/framework/components/chiprouter v1.0.4 // indirect
github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake v0.15.0 // indirect
github.com/smartcontractkit/chainlink-testing-framework/lib v1.54.9 // indirect
Expand Down
12 changes: 6 additions & 6 deletions core/scripts/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions core/services/chainlink/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ type Application interface {
FindLCA(ctx context.Context, chainID *big.Int) (*logpoller.Block, error)
// DeleteLogPollerDataAfter - delete LogPoller state starting from the specified block
DeleteLogPollerDataAfter(ctx context.Context, chainID *big.Int, start int64) error
// LPSkipToBlock repositions the LogPoller to start processing from the given block number.
LPSkipToBlock(ctx context.Context, chainFamily string, chainID string, blockNumber int64) error
}

// ChainlinkApplication contains fields for the JobSubscriber, Scheduler,
Expand Down Expand Up @@ -1265,6 +1267,7 @@ func (app *ChainlinkApplication) FindLCA(ctx context.Context, chainID *big.Int)
if err != nil {
return nil, err
}

if !app.Config.Feature().LogPoller() {
return nil, errors.New("FindLCA is only available if LogPoller is enabled")
}
Expand All @@ -1281,6 +1284,36 @@ func (app *ChainlinkApplication) FindLCA(ctx context.Context, chainID *big.Int)
return lca, nil
}

// LPSkipToBlock repositions the LogPoller to start processing from the given block number.
func (app *ChainlinkApplication) LPSkipToBlock(ctx context.Context, chainFamily string, chainID string, blockNumber int64) error {
if chainFamily != relay.NetworkEVM {
return fmt.Errorf("LPSkipToBlock is only supported for %s chain family", relay.NetworkEVM)
}
if !app.Config.Feature().LogPoller() {
return errors.New("LPSkipToBlock is only available if LogPoller is enabled")
}
if blockNumber < 2 {
return fmt.Errorf("invalid skip block number %d, must be >= 2", blockNumber)
}

relayer, err := app.GetRelayers().Get(commontypes.RelayID{
Network: chainFamily,
ChainID: chainID,
})
if err != nil {
return err
}
evmService, err := relayer.EVM()
if err != nil {
return err
}

if err = evmService.LPSkipToBlock(ctx, blockNumber); err != nil {
return fmt.Errorf("failed to skip log poller to block %d: %w", blockNumber, err)
}
return nil
}

// DeleteLogPollerDataAfter - delete LogPoller state starting from the specified block
func (app *ChainlinkApplication) DeleteLogPollerDataAfter(ctx context.Context, chainID *big.Int, start int64) error {
chain, err := app.GetRelayers().LegacyEVMChains().Get(chainID.String())
Expand Down
Loading
Loading