-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Functions] Allowlist support (#9635)
- Loading branch information
1 parent
a328b09
commit ac85db1
Showing
8 changed files
with
332 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package functions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/big" | ||
"sync/atomic" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/pkg/errors" | ||
|
||
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/ocr2dr_oracle" | ||
"github.com/smartcontractkit/chainlink/v2/core/logger" | ||
) | ||
|
||
// OnchainAllowlist maintains an allowlist of addresses fetched from the blockchain (EVM-only). | ||
// Use UpdateFromContract() for a one-time update or UpdatePeriodically() for periodic updates. | ||
// All methods are thread-safe. | ||
// | ||
//go:generate mockery --quiet --name OnchainAllowlist --output ./mocks/ --case=underscore | ||
type OnchainAllowlist interface { | ||
Allow(common.Address) bool | ||
UpdateFromContract(ctx context.Context) error | ||
UpdatePeriodically(ctx context.Context, updateFrequency time.Duration, updateTimeout time.Duration) | ||
} | ||
|
||
type onchainAllowlist struct { | ||
allowlist atomic.Pointer[map[common.Address]struct{}] | ||
client evmclient.Client | ||
contract *ocr2dr_oracle.OCR2DROracle | ||
blockConfirmations *big.Int | ||
lggr logger.Logger | ||
} | ||
|
||
func NewOnchainAllowlist(client evmclient.Client, contractAddress common.Address, blockConfirmations int64, lggr logger.Logger) (OnchainAllowlist, error) { | ||
if client == nil { | ||
return nil, errors.New("client is nil") | ||
} | ||
if lggr == nil { | ||
return nil, errors.New("logger is nil") | ||
} | ||
contract, err := ocr2dr_oracle.NewOCR2DROracle(contractAddress, client) | ||
if err != nil { | ||
return nil, fmt.Errorf("unexpected error during NewOCR2DROracle: %s", err) | ||
} | ||
allowlist := &onchainAllowlist{ | ||
client: client, | ||
contract: contract, | ||
blockConfirmations: big.NewInt(blockConfirmations), | ||
lggr: lggr.Named("OnchainAllowlist"), | ||
} | ||
emptyMap := make(map[common.Address]struct{}) | ||
allowlist.allowlist.Store(&emptyMap) | ||
return allowlist, nil | ||
} | ||
|
||
func (a *onchainAllowlist) Allow(address common.Address) bool { | ||
allowlist := *a.allowlist.Load() | ||
_, ok := allowlist[address] | ||
return ok | ||
} | ||
|
||
func (a *onchainAllowlist) UpdateFromContract(ctx context.Context) error { | ||
latestBlockHeight, err := a.client.LatestBlockHeight(ctx) | ||
if err != nil { | ||
return errors.Wrap(err, "error calling LatestBlockHeight") | ||
} | ||
if latestBlockHeight == nil { | ||
return errors.New("LatestBlockHeight returned nil") | ||
} | ||
blockNum := big.NewInt(0).Sub(latestBlockHeight, a.blockConfirmations) | ||
addrList, err := a.contract.GetAuthorizedSenders(&bind.CallOpts{ | ||
Pending: false, | ||
BlockNumber: blockNum, | ||
Context: ctx, | ||
}) | ||
if err != nil { | ||
return errors.Wrap(err, "error calling GetAuthorizedSenders") | ||
} | ||
newAllowlist := make(map[common.Address]struct{}) | ||
for _, addr := range addrList { | ||
newAllowlist[addr] = struct{}{} | ||
} | ||
a.allowlist.Store(&newAllowlist) | ||
a.lggr.Infow("allowlist updated successfully", "len", len(addrList), "blockNumber", blockNum) | ||
return nil | ||
} | ||
|
||
func (a *onchainAllowlist) UpdatePeriodically(ctx context.Context, updateFrequency time.Duration, updateTimeout time.Duration) { | ||
ticker := time.NewTicker(updateFrequency) | ||
defer ticker.Stop() | ||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case <-ticker.C: | ||
timeoutCtx, cancel := context.WithTimeout(ctx, updateTimeout) | ||
err := a.UpdateFromContract(timeoutCtx) | ||
if err != nil { | ||
a.lggr.Errorw("error calling UpdateFromContract", "err", err) | ||
} | ||
cancel() | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
core/services/gateway/handlers/functions/allowlist_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package functions_test | ||
|
||
import ( | ||
"context" | ||
"encoding/hex" | ||
"math/big" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" | ||
"github.com/smartcontractkit/chainlink/v2/core/logger" | ||
"github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" | ||
) | ||
|
||
const ( | ||
addr1 = "9ed925d8206a4f88a2f643b28b3035b315753cd6" | ||
addr2 = "ea6721ac65bced841b8ec3fc5fedea6141a0ade4" | ||
addr3 = "84689acc87ff22841b8ec378300da5e141a99911" | ||
) | ||
|
||
func sampleEncodedAllowlist(t *testing.T) []byte { | ||
abiEncodedAddresses := | ||
"0000000000000000000000000000000000000000000000000000000000000020" + | ||
"0000000000000000000000000000000000000000000000000000000000000002" + | ||
"000000000000000000000000" + addr1 + | ||
"000000000000000000000000" + addr2 | ||
rawData, err := hex.DecodeString(abiEncodedAddresses) | ||
require.NoError(t, err) | ||
return rawData | ||
} | ||
|
||
func TestAllowlist_UpdateAndCheck(t *testing.T) { | ||
t.Parallel() | ||
|
||
client := mocks.NewClient(t) | ||
client.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(42), nil) | ||
client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(sampleEncodedAllowlist(t), nil) | ||
allowlist, err := functions.NewOnchainAllowlist(client, common.Address{}, 1, logger.TestLogger(t)) | ||
require.NoError(t, err) | ||
|
||
require.NoError(t, allowlist.UpdateFromContract(context.Background())) | ||
require.False(t, allowlist.Allow(common.Address{})) | ||
require.True(t, allowlist.Allow(common.HexToAddress(addr1))) | ||
require.True(t, allowlist.Allow(common.HexToAddress(addr2))) | ||
require.False(t, allowlist.Allow(common.HexToAddress(addr3))) | ||
} | ||
|
||
func TestAllowlist_UpdatePeriodically(t *testing.T) { | ||
t.Parallel() | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
client := mocks.NewClient(t) | ||
client.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(42), nil) | ||
client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { | ||
cancel() | ||
}).Return(sampleEncodedAllowlist(t), nil) | ||
allowlist, err := functions.NewOnchainAllowlist(client, common.Address{}, 1, logger.TestLogger(t)) | ||
require.NoError(t, err) | ||
|
||
allowlist.UpdatePeriodically(ctx, time.Millisecond*10, time.Second*1) | ||
require.True(t, allowlist.Allow(common.HexToAddress(addr1))) | ||
require.False(t, allowlist.Allow(common.HexToAddress(addr3))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.