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
15 changes: 15 additions & 0 deletions core/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"github.com/ethereum/go-ethereum/core/oracle"
"github.com/ethereum/go-ethereum/core/types"
)

Expand Down Expand Up @@ -52,3 +53,17 @@ type ChainHeadEvent struct {
}

type HighestVerifiedBlockEvent struct{ Header *types.Header }

// OracleTxInfo pairs a transaction with its oracle identification metadata.
type OracleTxInfo struct {
Tx *types.Transaction
Info *oracle.OracleInfo
}

// NewOracleTxsEvent is posted when oracle-related transactions enter the transaction pool.
type NewOracleTxsEvent struct{ Txs []OracleTxInfo }

// NewHighGasTxsEvent is posted when high-gas-cost transactions enter the transaction pool.
// A transaction qualifies when gasFeeCap*gasLimit exceeds the configured threshold,
// its gasLimit is below the configured cap, and its To address is not whitelisted.
type NewHighGasTxsEvent struct{ Txs []*types.Transaction }
94 changes: 94 additions & 0 deletions core/oracle/chainlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2026 The bsc Authors
// This file is part of the bsc library.
//
// The bsc library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The bsc library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the bsc library. If not, see <http://www.gnu.org/licenses/>.

package oracle

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

var (
// Chainlink transmit method selectors.
chainlinkSelectors = [2][4]byte{
{0x6f, 0xad, 0xcf, 0x72},
{0xb6, 0x4f, 0xa9, 0xe6},
}

// Chainlink forwarder contract addresses.
chainlinkTargets = map[common.Address]struct{}{
common.HexToAddress("0x080f02795ba9003404Aceaf102f14443647DBe08"): {},
common.HexToAddress("0x1877d26Dabef3b5869aAA788EbFB39F1d6C477A0"): {},
common.HexToAddress("0x1b66fBd3a0c4aa37705E7091EF40554E124A25F6"): {},
common.HexToAddress("0x2c14565cDDC53F3a60D27E8C345809a31D934642"): {},
common.HexToAddress("0x328e4BDcf3096C36D9CB2d989Da6995b461140ac"): {},
common.HexToAddress("0x342682197497FC9b860e914636847F1a9AD8b037"): {},
common.HexToAddress("0x347dF906a432D7964Ce2D854B51D9166B6A46CcC"): {},
common.HexToAddress("0x3A642243e38beA40322aa9A7e59c26Aa27Cc6F8D"): {},
common.HexToAddress("0x42b950F1Cf4855a54f6d0bd6981687Eb4d5d6a18"): {},
common.HexToAddress("0x5b4813eE2D4366D73C89D7870aFa82BF2FBB3E71"): {},
common.HexToAddress("0x5d6173E05c3Af359DBb13ee12Af726E34AAeDAD2"): {},
common.HexToAddress("0x64F4d51eE8AE9671c5d1cE58dc15a653A71efC90"): {},
common.HexToAddress("0x7511839dFfaf432A5DbA567bc1Aa77115EfEf882"): {},
common.HexToAddress("0x870A3D3BBB5554f1D04967a6b76534989020B203"): {},
common.HexToAddress("0x8bDB5E089E9285037E6874D8F3160b5548611380"): {},
common.HexToAddress("0x9A7032C6EDCBbf164E201ca9dF5894BC35416e6A"): {},
common.HexToAddress("0x9dFCA5435036ad0069ECa7C8e15b0c5c44548952"): {},
common.HexToAddress("0xD979873cFAf9a655F44006Fe01B148f1ABbc47Cc"): {},
common.HexToAddress("0xF72bb66E324B5188c6178c6401Ee982FA30bE096"): {},
common.HexToAddress("0xF87C2E9f08c04f805d15C5E90273227639151860"): {},
common.HexToAddress("0xa13814fE399ea85e0541B4FdD2b38efa149c8Ee5"): {},
common.HexToAddress("0xaaC815bcd3d1eCEACfC51608c4EaF194eEfd14f1"): {},
common.HexToAddress("0xc3bC83292fcc9c646E61f37126839449736d64FD"): {},
common.HexToAddress("0xc7F79eA9c7FF17968eEC60b4CC9880905cFdb2f4"): {},
common.HexToAddress("0xdB1A1C98F4A23c8C78B7B2c44Cd9678Ff286A0A1"): {},
}
)

// ChainlinkIdentifier identifies Chainlink oracle transactions.
type ChainlinkIdentifier struct{}

// NewChainlinkIdentifier creates a new Chainlink oracle identifier.
func NewChainlinkIdentifier() *ChainlinkIdentifier {
return &ChainlinkIdentifier{}
}

// Type returns the oracle type for Chainlink.
func (c *ChainlinkIdentifier) Type() OracleType {
return OracleChainlink
}

// Identify checks whether the transaction is a Chainlink oracle transaction.
func (c *ChainlinkIdentifier) Identify(tx *types.Transaction) *OracleInfo {
// Check method selector (4-byte comparison, very cheap).
data := tx.Data()
if len(data) < 4 {
return nil
}
sel := [4]byte(data[:4])
if sel != chainlinkSelectors[0] && sel != chainlinkSelectors[1] {
return nil
}
// Check target address against the known set.
to := tx.To()
if to == nil {
return nil
}
if _, ok := chainlinkTargets[*to]; !ok {
return nil
}
return &OracleInfo{Type: OracleChainlink}
}
149 changes: 149 additions & 0 deletions core/oracle/chainlink_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2026 The bsc Authors
// This file is part of the bsc library.
//
// The bsc library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The bsc library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the bsc library. If not, see <http://www.gnu.org/licenses/>.
package oracle

import (
"context"
"fmt"
"math/big"
"os"
"sort"
"strconv"
"testing"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)

const (
chainlinkFactoryBSC = "0x297Bc37BCE59112D245cCfC22f54079466733401"
)

// TestScanChainlinkForwarders scans AuthorizedForwarderCreated events from the
// Chainlink OperatorFactory and prints unique forwarder addresses.
//
// Env vars:
// - BSC_RPC_URL: required RPC endpoint (e.g., https://bsc-dataseed.binance.org)
// - FROM_BLOCK: optional start block (default 0)
// - TO_BLOCK: optional end block (default latest)
// - BATCH_SIZE: optional batch size (default 50000)
func TestScanChainlinkForwarders(t *testing.T) {
rpcURL := "http://127.0.0.1:8544"

fromBlock := uint64(40299701) // deployed at block 40299701
toBlock := uint64(44844259) // known latest tx height
batchSize := uint64(50000)

client, err := ethclient.Dial(rpcURL)
if err != nil {
t.Fatalf("dial rpc: %v", err)
}
defer client.Close()

if toBlock == 0 {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
latest, err := client.BlockNumber(ctx)
if err != nil {
t.Fatalf("get latest block: %v", err)
}
toBlock = latest
}

factoryAddr := common.HexToAddress(chainlinkFactoryBSC)
eventSig := crypto.Keccak256Hash([]byte("AuthorizedForwarderCreated(address,address,address)"))

forwarders := map[common.Address]struct{}{}
var logsCount int

for start := fromBlock; start <= toBlock; start += batchSize {
end := start + batchSize - 1
if end > toBlock {
end = toBlock
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
query := ethereum.FilterQuery{
FromBlock: bigInt(start),
ToBlock: bigInt(end),
Addresses: []common.Address{factoryAddr},
Topics: [][]common.Hash{{eventSig}},
}
logs, err := client.FilterLogs(ctx, query)
cancel()
if err != nil {
t.Fatalf("filter logs [%d-%d]: %v", start, end, err)
}

for _, log := range logs {
logsCount++
fwd, owner, sender, ok := decodeForwarderEvent(log)
if !ok {
continue
}
forwarders[fwd] = struct{}{}
t.Logf("forwarder=%s owner=%s sender=%s block=%d tx=%s",
fwd.Hex(), owner.Hex(), sender.Hex(), log.BlockNumber, log.TxHash.Hex())
}
}

list := make([]string, 0, len(forwarders))
for addr := range forwarders {
list = append(list, addr.Hex())
}
sort.Strings(list)

t.Logf("total logs=%d unique forwarders=%d", logsCount, len(list))
for _, addr := range list {
fmt.Println(addr)
}
}

func decodeForwarderEvent(log types.Log) (common.Address, common.Address, common.Address, bool) {
// Topics: [sig, forwarder, owner, sender]
if len(log.Topics) < 4 {
return common.Address{}, common.Address{}, common.Address{}, false
}
return topicToAddress(log.Topics[1]),
topicToAddress(log.Topics[2]),
topicToAddress(log.Topics[3]),
true
}

func topicToAddress(topic common.Hash) common.Address {
b := topic.Bytes()
return common.BytesToAddress(b[12:])
}

func parseUintEnv(t *testing.T, key string, def uint64) uint64 {
v := os.Getenv(key)
if v == "" {
return def
}
n, err := strconv.ParseUint(v, 10, 64)
if err != nil {
t.Fatalf("invalid %s=%q: %v", key, v, err)
}
return n
}

func bigInt(v uint64) *big.Int {
return new(big.Int).SetUint64(v)
}
47 changes: 47 additions & 0 deletions core/oracle/identifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2026 The bsc Authors
// This file is part of the bsc library.
//
// The bsc library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The bsc library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the bsc library. If not, see <http://www.gnu.org/licenses/>.

// Package oracle provides an extensible framework for identifying oracle-related
// transactions in the transaction pool (e.g., Chainlink, Redstone price feed updates).
package oracle

import "github.com/ethereum/go-ethereum/core/types"

// OracleType represents the type of oracle provider.
type OracleType string

const (
OracleChainlink OracleType = "chainlink"
OracleRedstone OracleType = "redstone"
)

// OracleInfo contains metadata about an identified oracle transaction.
type OracleInfo struct {
Type OracleType
// Extensible: future fields for decoded price data, feed address, etc.
}

// Identifier defines the interface for oracle transaction identification.
// Implementations should check whether a transaction is related to a specific
// oracle provider and return metadata if it matches.
type Identifier interface {
// Type returns the oracle type this identifier handles.
Type() OracleType

// Identify checks whether the given transaction is an oracle transaction.
// Returns non-nil OracleInfo if the transaction matches, nil otherwise.
Identify(tx *types.Transaction) *OracleInfo
}
58 changes: 58 additions & 0 deletions core/oracle/redstone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2026 The bsc Authors
// This file is part of the bsc library.
//
// The bsc library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The bsc library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the bsc library. If not, see <http://www.gnu.org/licenses/>.

package oracle

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

var (
// Redstone updateDataFeedsValues method selector.
redstoneSelector = [4]byte{0xb7, 0xa1, 0x62, 0x51}

// Redstone PriceFeedAdapter contract address.
redstoneTarget = common.HexToAddress("0x97c19d3Ae8e4d74e25EF3AFf3a277fB614ed76D4")
)

// RedstoneIdentifier identifies Redstone oracle transactions.
type RedstoneIdentifier struct{}

// NewRedstoneIdentifier creates a new Redstone oracle identifier.
func NewRedstoneIdentifier() *RedstoneIdentifier {
return &RedstoneIdentifier{}
}

// Type returns the oracle type for Redstone.
func (r *RedstoneIdentifier) Type() OracleType {
return OracleRedstone
}

// Identify checks whether the transaction is a Redstone oracle transaction.
func (r *RedstoneIdentifier) Identify(tx *types.Transaction) *OracleInfo {
// Check single target address first (cheapest filter for single-target oracle).
to := tx.To()
if to == nil || *to != redstoneTarget {
return nil
}
// Check method selector.
data := tx.Data()
if len(data) < 4 || [4]byte(data[:4]) != redstoneSelector {
return nil
}
return &OracleInfo{Type: OracleRedstone}
}
Loading
Loading