diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 30534873134..6ba88247220 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -71,6 +71,17 @@ jobs: run: go build -o chainlink.test . - name: Setup DB run: ./chainlink.test local db preparetest + - name: Install LOOP Plugins + run: | + pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-feeds) + go install ./cmd/chainlink-feeds + popd + pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-solana) + go install ./pkg/solana/cmd/chainlink-solana + popd + pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-starknet/relayer) + go install ./pkg/chainlink/cmd/chainlink-starknet + popd - name: Increase Race Timeout if: github.event.schedule != '' run: | diff --git a/.github/workflows/live-testnet-tests.yml b/.github/workflows/live-testnet-tests.yml index 9cad07f0916..45207fad3ce 100644 --- a/.github/workflows/live-testnet-tests.yml +++ b/.github/workflows/live-testnet-tests.yml @@ -974,4 +974,4 @@ jobs: if: always() uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@ea889b3133bd7f16ab19ba4ba130de5d9162c669 # v2.3.4 with: - test_directory: "./" \ No newline at end of file + test_directory: "./" diff --git a/common/client/node_selector_priority_level_test.go b/common/client/node_selector_priority_level_test.go index ac84645e91c..15a7a7ac60b 100644 --- a/common/client/node_selector_priority_level_test.go +++ b/common/client/node_selector_priority_level_test.go @@ -17,72 +17,75 @@ func TestPriorityLevelNodeSelector(t *testing.T) { t.Parallel() type nodeClient NodeClient[types.ID, Head] - var nodes []Node[types.ID, Head, nodeClient] - n1 := newMockNode[types.ID, Head, nodeClient](t) - n1.On("State").Return(nodeStateAlive) - n1.On("Order").Return(int32(1)) - - n2 := newMockNode[types.ID, Head, nodeClient](t) - n2.On("State").Return(nodeStateAlive) - n2.On("Order").Return(int32(1)) - - n3 := newMockNode[types.ID, Head, nodeClient](t) - n3.On("State").Return(nodeStateAlive) - n3.On("Order").Return(int32(1)) - - nodes = append(nodes, n1, n2, n3) - selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) - assert.Same(t, nodes[0], selector.Select()) - assert.Same(t, nodes[1], selector.Select()) - assert.Same(t, nodes[2], selector.Select()) - assert.Same(t, nodes[0], selector.Select()) - assert.Same(t, nodes[1], selector.Select()) - assert.Same(t, nodes[2], selector.Select()) -} - -func TestPriorityLevelNodeSelector_None(t *testing.T) { - t.Parallel() - - type nodeClient NodeClient[types.ID, Head] - var nodes []Node[types.ID, Head, nodeClient] - - for i := 0; i < 3; i++ { - node := newMockNode[types.ID, Head, nodeClient](t) - if i == 0 { - // first node is out of sync - node.On("State").Return(nodeStateOutOfSync) - node.On("Order").Return(int32(1)) - } else { - // others are unreachable - node.On("State").Return(nodeStateUnreachable) - node.On("Order").Return(int32(1)) - } - nodes = append(nodes, node) + type testNode struct { + order int32 + state nodeState + } + type testCase struct { + name string + nodes []testNode + expect []int // indexes of the nodes expected to be returned by Select } - selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) - assert.Nil(t, selector.Select()) -} - -func TestPriorityLevelNodeSelector_DifferentOrder(t *testing.T) { - t.Parallel() - - type nodeClient NodeClient[types.ID, Head] - var nodes []Node[types.ID, Head, nodeClient] - n1 := newMockNode[types.ID, Head, nodeClient](t) - n1.On("State").Return(nodeStateAlive) - n1.On("Order").Return(int32(1)) - - n2 := newMockNode[types.ID, Head, nodeClient](t) - n2.On("State").Return(nodeStateAlive) - n2.On("Order").Return(int32(2)) - - n3 := newMockNode[types.ID, Head, nodeClient](t) - n3.On("State").Return(nodeStateAlive) - n3.On("Order").Return(int32(3)) + testCases := []testCase{ + { + name: "TwoNodesSameOrder: Highest Allowed Order", + nodes: []testNode{ + {order: 1, state: nodeStateAlive}, + {order: 1, state: nodeStateAlive}, + }, + expect: []int{0, 1, 0, 1, 0, 1}, + }, + { + name: "TwoNodesSameOrder: Lowest Allowed Order", + nodes: []testNode{ + {order: 100, state: nodeStateAlive}, + {order: 100, state: nodeStateAlive}, + }, + expect: []int{0, 1, 0, 1, 0, 1}, + }, + { + name: "NoneAvailable", + nodes: []testNode{ + {order: 1, state: nodeStateOutOfSync}, + {order: 1, state: nodeStateUnreachable}, + {order: 1, state: nodeStateUnreachable}, + }, + expect: []int{}, // no nodes should be selected + }, + { + name: "DifferentOrder", + nodes: []testNode{ + {order: 1, state: nodeStateAlive}, + {order: 2, state: nodeStateAlive}, + {order: 3, state: nodeStateAlive}, + }, + expect: []int{0, 0}, // only the highest order node should be selected + }, + } - nodes = append(nodes, n1, n2, n3) - selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) - assert.Same(t, nodes[0], selector.Select()) - assert.Same(t, nodes[0], selector.Select()) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var nodes []Node[types.ID, Head, nodeClient] + for _, tn := range tc.nodes { + node := newMockNode[types.ID, Head, nodeClient](t) + node.On("State").Return(tn.state) + node.On("Order").Return(tn.order) + nodes = append(nodes, node) + } + + selector := newNodeSelector(NodeSelectionModePriorityLevel, nodes) + for _, idx := range tc.expect { + if idx >= len(nodes) { + t.Fatalf("Invalid node index %d in test case '%s'", idx, tc.name) + } + assert.Same(t, nodes[idx], selector.Select()) + } + + // Check for nil selection if expected slice is empty + if len(tc.expect) == 0 { + assert.Nil(t, selector.Select()) + } + }) + } } diff --git a/contracts/gas-snapshots/functions.gas-snapshot b/contracts/gas-snapshots/functions.gas-snapshot index d0893969bff..39efc0d10ea 100644 --- a/contracts/gas-snapshots/functions.gas-snapshot +++ b/contracts/gas-snapshots/functions.gas-snapshot @@ -1,12 +1,12 @@ -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14579333) -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14579311) -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14579327) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14590747) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14590724) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14590696) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14590647) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14590636) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14590680) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14805978) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14805956) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14805972) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14817392) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14817369) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14817341) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14817292) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14817281) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14817325) FunctionsBilling_Constructor:test_Constructor_Success() (gas: 14812) FunctionsBilling_DeleteCommitment:test_DeleteCommitment_RevertIfNotRouter() (gas: 13282) FunctionsBilling_DeleteCommitment:test_DeleteCommitment_Success() (gas: 15897) @@ -28,7 +28,7 @@ FunctionsBilling_UpdateConfig:test_UpdateConfig_Success() (gas: 38251) FunctionsBilling__DisperseFeePool:test__DisperseFeePool_RevertIfNotSet() (gas: 8810) FunctionsBilling__FulfillAndBill:test__FulfillAndBill_RevertIfInvalidCommitment() (gas: 13302) FunctionsBilling__FulfillAndBill:test__FulfillAndBill_Success() (gas: 180763) -FunctionsBilling__StartBilling:test__FulfillAndBill_HasUniqueGlobalRequestId() (gas: 398312) +FunctionsBilling__StartBilling:test__FulfillAndBill_HasUniqueGlobalRequestId() (gas: 398400) FunctionsClient_Constructor:test_Constructor_Success() (gas: 7573) FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 497786) FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 198990) @@ -61,7 +61,7 @@ FunctionsRouter_Fulfill:test_Fulfill_RevertIfNotCommittedCoordinator() (gas: 280 FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 151496) FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 321059) FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 334680) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2509962) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2510006) FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 540441) FunctionsRouter_GetAdminFee:test_GetAdminFee_Success() (gas: 17983) FunctionsRouter_GetAllowListId:test_GetAllowListId_Success() (gas: 12904) @@ -82,17 +82,17 @@ FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfLengt FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfNotNewContract() (gas: 19048) FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_RevertIfNotOwner() (gas: 23392) FunctionsRouter_ProposeContractsUpdate:test_ProposeContractsUpdate_Success() (gas: 118479) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfConsumerNotAllowed() (gas: 59347) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfConsumerNotAllowed() (gas: 59391) FunctionsRouter_SendRequest:test_SendRequest_RevertIfDuplicateRequestId() (gas: 193436) FunctionsRouter_SendRequest:test_SendRequest_RevertIfEmptyData() (gas: 29426) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfIncorrectDonId() (gas: 57925) -FunctionsRouter_SendRequest:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 186932) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfIncorrectDonId() (gas: 57904) +FunctionsRouter_SendRequest:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 187020) FunctionsRouter_SendRequest:test_SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 50947) FunctionsRouter_SendRequest:test_SendRequest_RevertIfInvalidDonId() (gas: 25082) FunctionsRouter_SendRequest:test_SendRequest_RevertIfNoSubscription() (gas: 29132) FunctionsRouter_SendRequest:test_SendRequest_RevertIfPaused() (gas: 34291) FunctionsRouter_SendRequest:test_SendRequest_Success() (gas: 286243) -FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfConsumerNotAllowed() (gas: 65843) +FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfConsumerNotAllowed() (gas: 65887) FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfEmptyData() (gas: 36012) FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfIncorrectDonId() (gas: 29896) FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfInvalidCallbackGasLimit() (gas: 57533) @@ -100,7 +100,7 @@ FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfInvalid FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfNoSubscription() (gas: 35717) FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_RevertIfPaused() (gas: 40810) FunctionsRouter_SendRequestToProposed:test_SendRequestToProposed_Success() (gas: 292812) -FunctionsRouter_SendRequestToProposed:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 193424) +FunctionsRouter_SendRequestToProposed:test_SendRequest_RevertIfInsufficientSubscriptionBalance() (gas: 193512) FunctionsRouter_SetAllowListId:test_SetAllowListId_Success() (gas: 30688) FunctionsRouter_SetAllowListId:test_UpdateConfig_RevertIfNotOwner() (gas: 13403) FunctionsRouter_Unpause:test_Unpause_RevertIfNotOwner() (gas: 13293) @@ -109,30 +109,30 @@ FunctionsRouter_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 24437) FunctionsRouter_UpdateConfig:test_UpdateConfig_Success() (gas: 60676) FunctionsRouter_UpdateContracts:test_UpdateContracts_RevertIfNotOwner() (gas: 13336) FunctionsRouter_UpdateContracts:test_UpdateContracts_Success() (gas: 38732) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 60326) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfPaused() (gas: 60987) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderBecomesBlocked() (gas: 94677) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderIsNotNewOwner() (gas: 62693) -FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_Success() (gas: 215197) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumers() (gas: 137893) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumersAfterConfigUpdate() (gas: 164837) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 60414) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfPaused() (gas: 61031) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderBecomesBlocked() (gas: 139404) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_RevertIfSenderIsNotNewOwner() (gas: 62781) +FunctionsSubscriptions_AcceptSubscriptionOwnerTransfer:test_AcceptSubscriptionOwnerTransfer_Success() (gas: 215285) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumers() (gas: 138025) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfMaximumConsumersAfterConfigUpdate() (gas: 164969) FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNoSubscription() (gas: 12946) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotAllowedSender() (gas: 57809) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotSubscriptionOwner() (gas: 87177) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotAllowedSender() (gas: 102448) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfNotSubscriptionOwner() (gas: 87199) FunctionsSubscriptions_AddConsumer:test_AddConsumer_RevertIfPaused() (gas: 18094) -FunctionsSubscriptions_AddConsumer:test_AddConsumer_Success() (gas: 95480) +FunctionsSubscriptions_AddConsumer:test_AddConsumer_Success() (gas: 95524) FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNoSubscription() (gas: 15041) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotAllowedSender() (gas: 57885) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotSubscriptionOwner() (gas: 89287) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotAllowedSender() (gas: 102524) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotSubscriptionOwner() (gas: 89309) FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPaused() (gas: 20148) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPendingRequests() (gas: 194325) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitAllBalanceAsDeposit() (gas: 114506) -FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitSomeBalanceAsDeposit() (gas: 125832) -FunctionsSubscriptions_CancelSubscription_ReceiveDeposit:test_CancelSubscription_SuccessRecieveDeposit() (gas: 74973) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPendingRequests() (gas: 194369) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitAllBalanceAsDeposit() (gas: 114541) +FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_SuccessForfeitSomeBalanceAsDeposit() (gas: 125867) +FunctionsSubscriptions_CancelSubscription_ReceiveDeposit:test_CancelSubscription_SuccessRecieveDeposit() (gas: 75017) FunctionsSubscriptions_Constructor:test_Constructor_Success() (gas: 7654) -FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfNotAllowedSender() (gas: 28660) +FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfNotAllowedSender() (gas: 28704) FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfPaused() (gas: 17994) -FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_Success() (gas: 351726) +FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_Success() (gas: 351858) FunctionsSubscriptions_GetConsumer:test_GetConsumer_Success() (gas: 16226) FunctionsSubscriptions_GetFlags:test_GetFlags_SuccessInvalidSubscription() (gas: 13101) FunctionsSubscriptions_GetFlags:test_GetFlags_SuccessValidSubscription() (gas: 40903) @@ -168,23 +168,23 @@ FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_SuccessPaysRecipient() ( FunctionsSubscriptions_OwnerWithdraw:test_OwnerWithdraw_SuccessSetsBalanceToZero() (gas: 37790) FunctionsSubscriptions_PendingRequestExists:test_PendingRequestExists_SuccessFalseIfNoPendingRequests() (gas: 14981) FunctionsSubscriptions_PendingRequestExists:test_PendingRequestExists_SuccessTrueIfPendingRequests() (gas: 176494) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfEmptyNewOwner() (gas: 27611) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfInvalidNewOwner() (gas: 57709) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfEmptyNewOwner() (gas: 27655) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfInvalidNewOwner() (gas: 57797) FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNoSubscription() (gas: 15001) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 75131) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotAllowedSender() (gas: 119770) FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfNotSubscriptionOwner() (gas: 17960) FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_RevertIfPaused() (gas: 20128) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_Success() (gas: 68196) -FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_SuccessChangeProposedOwner() (gas: 82749) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_Success() (gas: 68240) +FunctionsSubscriptions_ProposeSubscriptionOwnerTransfer:test_ProposeSubscriptionOwnerTransfer_SuccessChangeProposedOwner() (gas: 82837) FunctionsSubscriptions_RecoverFunds:test_OwnerCancelSubscription_RevertIfNotOwner() (gas: 15554) FunctionsSubscriptions_RecoverFunds:test_RecoverFunds_Success() (gas: 41111) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfInvalidConsumer() (gas: 30260) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfInvalidConsumer() (gas: 30304) FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNoSubscription() (gas: 15019) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotAllowedSender() (gas: 57800) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotSubscriptionOwner() (gas: 87223) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotAllowedSender() (gas: 102439) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfNotSubscriptionOwner() (gas: 87245) FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPaused() (gas: 18049) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPendingRequests() (gas: 191858) -FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_Success() (gas: 41979) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_RevertIfPendingRequests() (gas: 191902) +FunctionsSubscriptions_RemoveConsumer:test_RemoveConsumer_Success() (gas: 42023) FunctionsSubscriptions_SetFlags:test_SetFlags_RevertIfNoSubscription() (gas: 12891) FunctionsSubscriptions_SetFlags:test_SetFlags_RevertIfNotOwner() (gas: 15684) FunctionsSubscriptions_SetFlags:test_SetFlags_Success() (gas: 35594) @@ -192,34 +192,44 @@ FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertIfPaused() (ga FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertIfTimeoutNotExceeded() (gas: 25261) FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertInvalidRequest() (gas: 28242) FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_Success() (gas: 57754) -FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfNotAllowedSender() (gas: 26390) +FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfNotAllowedSender() (gas: 26434) FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfPaused() (gas: 15759) -FunctionsSubscriptions_createSubscription:test_CreateSubscription_Success() (gas: 152576) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:testAcceptTermsOfService_InvalidSigner_vuln() (gas: 94830) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfAcceptorIsNotSender() (gas: 25837) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfBlockedSender() (gas: 44348) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfInvalidSigner() (gas: 23597) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientContractIsNotSender() (gas: 1866530) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientIsNotSender() (gas: 26003) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForContract() (gas: 1946562) -FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForSelf() (gas: 103411) -FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_RevertIfNotOwner() (gas: 15469) -FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_Success() (gas: 51794) -FunctionsTermsOfServiceAllowList_Constructor:test_Constructor_Success() (gas: 12187) -FunctionsTermsOfServiceAllowList_GetAllAllowedSenders:test_GetAllAllowedSenders_Success() (gas: 19243) -FunctionsTermsOfServiceAllowList_GetConfig:test_GetConfig_Success() (gas: 15773) -FunctionsTermsOfServiceAllowList_GetMessage:test_GetMessage_Success() (gas: 11583) -FunctionsTermsOfServiceAllowList_HasAccess:test_HasAccess_FalseWhenEnabled() (gas: 15925) -FunctionsTermsOfServiceAllowList_HasAccess:test_HasAccess_TrueWhenDisabled() (gas: 23430) -FunctionsTermsOfServiceAllowList_IsBlockedSender:test_IsBlockedSender_SuccessFalse() (gas: 15354) -FunctionsTermsOfServiceAllowList_IsBlockedSender:test_IsBlockedSender_SuccessTrue() (gas: 41957) -FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_RevertIfNotOwner() (gas: 13525) -FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_Success() (gas: 95199) -FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 13727) +FunctionsSubscriptions_createSubscription:test_CreateSubscription_Success() (gas: 152708) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:testAcceptTermsOfService_InvalidSigner_vuln() (gas: 94864) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfAcceptorIsNotSender() (gas: 25859) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfBlockedSender() (gas: 88990) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfInvalidSigner() (gas: 23619) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientContractIsNotSender() (gas: 1866552) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientIsNotSender() (gas: 26025) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForContract() (gas: 1946628) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForSelf() (gas: 103533) +FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_RevertIfNotOwner() (gas: 15491) +FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_Success() (gas: 96662) +FunctionsTermsOfServiceAllowList_Constructor:test_Constructor_Success() (gas: 12253) +FunctionsTermsOfServiceAllowList_GetAllAllowedSenders:test_GetAllAllowedSenders_Success() (gas: 19199) +FunctionsTermsOfServiceAllowList_GetAllowedSendersCount:test_GetAllowedSendersCount_Success() (gas: 12995) +FunctionsTermsOfServiceAllowList_GetAllowedSendersInRange:test_GetAllowedSendersInRange_RevertIfAllowedSendersIsEmpty() (gas: 12184656) +FunctionsTermsOfServiceAllowList_GetAllowedSendersInRange:test_GetAllowedSendersInRange_RevertIfEndIsAfterLastAllowedSender() (gas: 16571) +FunctionsTermsOfServiceAllowList_GetAllowedSendersInRange:test_GetAllowedSendersInRange_RevertIfStartIsAfterEnd() (gas: 13301) +FunctionsTermsOfServiceAllowList_GetAllowedSendersInRange:test_GetAllowedSendersInRange_Success() (gas: 20448) +FunctionsTermsOfServiceAllowList_GetBlockedSendersCount:test_GetBlockedSendersCount_Success() (gas: 12931) +FunctionsTermsOfServiceAllowList_GetBlockedSendersInRange:test_GetBlockedSendersInRange_RevertIfAllowedSendersIsEmpty() (gas: 12184660) +FunctionsTermsOfServiceAllowList_GetBlockedSendersInRange:test_GetBlockedSendersInRange_RevertIfEndIsAfterLastAllowedSender() (gas: 16549) +FunctionsTermsOfServiceAllowList_GetBlockedSendersInRange:test_GetBlockedSendersInRange_RevertIfStartIsAfterEnd() (gas: 13367) +FunctionsTermsOfServiceAllowList_GetBlockedSendersInRange:test_GetBlockedSendersInRange_Success() (gas: 18493) +FunctionsTermsOfServiceAllowList_GetConfig:test_GetConfig_Success() (gas: 15751) +FunctionsTermsOfServiceAllowList_GetMessage:test_GetMessage_Success() (gas: 11593) +FunctionsTermsOfServiceAllowList_HasAccess:test_HasAccess_FalseWhenEnabled() (gas: 15969) +FunctionsTermsOfServiceAllowList_HasAccess:test_HasAccess_TrueWhenDisabled() (gas: 23496) +FunctionsTermsOfServiceAllowList_IsBlockedSender:test_IsBlockedSender_SuccessFalse() (gas: 15445) +FunctionsTermsOfServiceAllowList_IsBlockedSender:test_IsBlockedSender_SuccessTrue() (gas: 86643) +FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_RevertIfNotOwner() (gas: 13502) +FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_Success() (gas: 96216) +FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 13749) FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_Success() (gas: 22073) -Gas_AcceptTermsOfService:test_AcceptTermsOfService_Gas() (gas: 84690) -Gas_AddConsumer:test_AddConsumer_Gas() (gas: 79087) -Gas_CreateSubscription:test_CreateSubscription_Gas() (gas: 73375) +Gas_AcceptTermsOfService:test_AcceptTermsOfService_Gas() (gas: 84702) +Gas_AddConsumer:test_AddConsumer_Gas() (gas: 79131) +Gas_CreateSubscription:test_CreateSubscription_Gas() (gas: 73419) Gas_FundSubscription:test_FundSubscription_Gas() (gas: 38546) Gas_SendRequest:test_SendRequest_MaximumGas() (gas: 979631) Gas_SendRequest:test_SendRequest_MinimumGas() (gas: 157578) \ No newline at end of file diff --git a/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol b/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol index 3bb7c7b04cc..3d93a3fef04 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/accessControl/TermsOfServiceAllowList.sol @@ -20,7 +20,7 @@ contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, string public constant override typeAndVersion = "Functions Terms of Service Allow List v1.1.0"; EnumerableSet.AddressSet private s_allowedSenders; - mapping(address => bool) private s_blockedSenders; + EnumerableSet.AddressSet private s_blockedSenders; event AddedAccess(address user); event BlockedAccess(address user); @@ -29,6 +29,7 @@ contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, error InvalidSignature(); error InvalidUsage(); error RecipientIsBlocked(); + error InvalidCalldata(); TermsOfServiceAllowListConfig private s_config; @@ -70,7 +71,7 @@ contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, /// @inheritdoc ITermsOfServiceAllowList function acceptTermsOfService(address acceptor, address recipient, bytes32 r, bytes32 s, uint8 v) external override { - if (s_blockedSenders[recipient]) { + if (s_blockedSenders.contains(recipient)) { revert RecipientIsBlocked(); } @@ -101,6 +102,32 @@ contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, return s_allowedSenders.values(); } + /// @inheritdoc ITermsOfServiceAllowList + function getAllowedSendersCount() external view override returns (uint64) { + return uint64(s_allowedSenders.length()); + } + + /// @inheritdoc ITermsOfServiceAllowList + function getAllowedSendersInRange( + uint64 allowedSenderIdxStart, + uint64 allowedSenderIdxEnd + ) external view override returns (address[] memory allowedSenders) { + if ( + allowedSenderIdxStart > allowedSenderIdxEnd || + allowedSenderIdxEnd >= s_allowedSenders.length() || + s_allowedSenders.length() == 0 + ) { + revert InvalidCalldata(); + } + + allowedSenders = new address[]((allowedSenderIdxEnd - allowedSenderIdxStart) + 1); + for (uint256 i = 0; i <= allowedSenderIdxEnd - allowedSenderIdxStart; ++i) { + allowedSenders[i] = s_allowedSenders.at(uint256(allowedSenderIdxStart + i)); + } + + return allowedSenders; + } + /// @inheritdoc IAccessController function hasAccess(address user, bytes calldata /* data */) external view override returns (bool) { if (!s_config.enabled) { @@ -118,19 +145,45 @@ contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController, if (!s_config.enabled) { return false; } - return s_blockedSenders[sender]; + return s_blockedSenders.contains(sender); } /// @inheritdoc ITermsOfServiceAllowList function blockSender(address sender) external override onlyOwner { s_allowedSenders.remove(sender); - s_blockedSenders[sender] = true; + s_blockedSenders.add(sender); emit BlockedAccess(sender); } /// @inheritdoc ITermsOfServiceAllowList function unblockSender(address sender) external override onlyOwner { - s_blockedSenders[sender] = false; + s_blockedSenders.remove(sender); emit UnblockedAccess(sender); } + + /// @inheritdoc ITermsOfServiceAllowList + function getBlockedSendersCount() external view override returns (uint64) { + return uint64(s_blockedSenders.length()); + } + + /// @inheritdoc ITermsOfServiceAllowList + function getBlockedSendersInRange( + uint64 blockedSenderIdxStart, + uint64 blockedSenderIdxEnd + ) external view override returns (address[] memory blockedSenders) { + if ( + blockedSenderIdxStart > blockedSenderIdxEnd || + blockedSenderIdxEnd >= s_blockedSenders.length() || + s_blockedSenders.length() == 0 + ) { + revert InvalidCalldata(); + } + + blockedSenders = new address[]((blockedSenderIdxEnd - blockedSenderIdxStart) + 1); + for (uint256 i = 0; i <= blockedSenderIdxEnd - blockedSenderIdxStart; ++i) { + blockedSenders[i] = s_blockedSenders.at(uint256(blockedSenderIdxStart + i)); + } + + return blockedSenders; + } } diff --git a/contracts/src/v0.8/functions/dev/v1_X/accessControl/interfaces/ITermsOfServiceAllowList.sol b/contracts/src/v0.8/functions/dev/v1_X/accessControl/interfaces/ITermsOfServiceAllowList.sol index 209d25c0ab3..65db9c42b69 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/accessControl/interfaces/ITermsOfServiceAllowList.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/accessControl/interfaces/ITermsOfServiceAllowList.sol @@ -22,6 +22,22 @@ interface ITermsOfServiceAllowList { /// @return addresses - all allowed addresses function getAllAllowedSenders() external view returns (address[] memory); + /// @notice Get details about the total number of allowed senders + /// @return count - total number of allowed senders in the system + function getAllowedSendersCount() external view returns (uint64); + + /// @notice Retrieve a list of allowed senders using an inclusive range + /// @dev WARNING: getAllowedSendersInRange uses EnumerableSet .length() and .at() methods to iterate over the list + /// without the need for an extra mapping. These method can not guarantee the ordering when new elements are added. + /// Evaluate if eventual consistency will satisfy your usecase before using it. + /// @param allowedSenderIdxStart - index of the allowed sender to start the range at + /// @param allowedSenderIdxEnd - index of the allowed sender to end the range at + /// @return allowedSenders - allowed addresses in the range provided + function getAllowedSendersInRange( + uint64 allowedSenderIdxStart, + uint64 allowedSenderIdxEnd + ) external view returns (address[] memory allowedSenders); + /// @notice Allows access to the sender based on acceptance of the Terms of Service /// @param acceptor - The wallet address that has accepted the Terms of Service on the UI /// @param recipient - The recipient address that the acceptor is taking responsibility for @@ -37,6 +53,22 @@ interface ITermsOfServiceAllowList { /// @notice Re-allows a previously blocked sender to accept the Terms of Service /// @param sender - Address of the sender to unblock function unblockSender(address sender) external; + + /// @notice Get details about the total number of blocked senders + /// @return count - total number of blocked senders in the system + function getBlockedSendersCount() external view returns (uint64); + + /// @notice Retrieve a list of blocked senders using an inclusive range + /// @dev WARNING: getBlockedSendersInRange uses EnumerableSet .length() and .at() methods to iterate over the list + /// without the need for an extra mapping. These method can not guarantee the ordering when new elements are added. + /// Evaluate if eventual consistency will satisfy your usecase before using it. + /// @param blockedSenderIdxStart - index of the blocked sender to start the range at + /// @param blockedSenderIdxEnd - index of the blocked sender to end the range at + /// @return blockedSenders - blocked addresses in the range provided + function getBlockedSendersInRange( + uint64 blockedSenderIdxStart, + uint64 blockedSenderIdxEnd + ) external view returns (address[] memory blockedSenders); } // ================================================================ diff --git a/contracts/src/v0.8/functions/tests/v1_X/FunctionsTermsOfServiceAllowList.t.sol b/contracts/src/v0.8/functions/tests/v1_X/FunctionsTermsOfServiceAllowList.t.sol index 08c981dd4f9..e121f7b881d 100644 --- a/contracts/src/v0.8/functions/tests/v1_X/FunctionsTermsOfServiceAllowList.t.sol +++ b/contracts/src/v0.8/functions/tests/v1_X/FunctionsTermsOfServiceAllowList.t.sol @@ -247,6 +247,69 @@ contract FunctionsTermsOfServiceAllowList_GetAllAllowedSenders is FunctionsOwner } } +/// @notice #getAllowedSendersCount +contract FunctionsTermsOfServiceAllowList_GetAllowedSendersCount is FunctionsOwnerAcceptTermsOfServiceSetup { + function test_GetAllowedSendersCount_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + uint96 allowedSendersCount = s_termsOfServiceAllowList.getAllowedSendersCount(); + // One allowed sender was made during setup + assertEq(allowedSendersCount, 1); + } +} + +/// @notice #getAllowedSendersInRange +contract FunctionsTermsOfServiceAllowList_GetAllowedSendersInRange is FunctionsOwnerAcceptTermsOfServiceSetup { + function test_GetAllowedSendersInRange_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + address[] memory expectedSenders = new address[](1); + expectedSenders[0] = OWNER_ADDRESS; + + assertEq(s_termsOfServiceAllowList.getAllowedSendersInRange(0, 0), expectedSenders); + } + + function test_GetAllowedSendersInRange_RevertIfAllowedSendersIsEmpty() public { + // setup a new empty s_termsOfServiceAllowList + FunctionsRoutesSetup.setUp(); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + uint64 AllowedSendersCount = s_termsOfServiceAllowList.getAllowedSendersCount(); + uint64 expected = 0; + assertEq(AllowedSendersCount, expected); + + vm.expectRevert(TermsOfServiceAllowList.InvalidCalldata.selector); + s_termsOfServiceAllowList.getAllowedSendersInRange(0, 0); + } + + function test_GetAllowedSendersInRange_RevertIfStartIsAfterEnd() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert(TermsOfServiceAllowList.InvalidCalldata.selector); + + s_termsOfServiceAllowList.getAllowedSendersInRange(1, 0); + } + + function test_GetAllowedSendersInRange_RevertIfEndIsAfterLastAllowedSender() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + uint64 AllowedSendersCount = s_termsOfServiceAllowList.getAllowedSendersCount(); + vm.expectRevert(TermsOfServiceAllowList.InvalidCalldata.selector); + s_termsOfServiceAllowList.getAllowedSendersInRange(1, AllowedSendersCount + 1); + } +} + /// @notice #hasAccess contract FunctionsTermsOfServiceAllowList_HasAccess is FunctionsRoutesSetup { function test_HasAccess_FalseWhenEnabled() public { @@ -373,3 +436,78 @@ contract FunctionsTermsOfServiceAllowList_UnblockSender is FunctionsRoutesSetup s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, STRANGER_ADDRESS, r, s, v); } } + +/// @notice #getBlockedSendersCount +contract FunctionsTermsOfServiceAllowList_GetBlockedSendersCount is FunctionsRoutesSetup { + function setUp() public virtual override { + FunctionsRoutesSetup.setUp(); + + s_termsOfServiceAllowList.blockSender(STRANGER_ADDRESS); + } + + function test_GetBlockedSendersCount_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + uint96 blockedSendersCount = s_termsOfServiceAllowList.getBlockedSendersCount(); + // One blocked sender was made during setup + assertEq(blockedSendersCount, 1); + } +} + +/// @notice #getBlockedSendersInRange +contract FunctionsTermsOfServiceAllowList_GetBlockedSendersInRange is FunctionsRoutesSetup { + function setUp() public virtual override { + FunctionsRoutesSetup.setUp(); + + s_termsOfServiceAllowList.blockSender(STRANGER_ADDRESS); + } + + function test_GetBlockedSendersInRange_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + address[] memory expectedBlockedSenders = new address[](1); + expectedBlockedSenders[0] = STRANGER_ADDRESS; + + assertEq(s_termsOfServiceAllowList.getBlockedSendersInRange(0, 0), expectedBlockedSenders); + } + + function test_GetBlockedSendersInRange_RevertIfAllowedSendersIsEmpty() public { + // setup a new empty s_termsOfServiceBlockList + FunctionsRoutesSetup.setUp(); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + uint64 BlockedSendersCount = s_termsOfServiceAllowList.getBlockedSendersCount(); + uint64 expected = 0; + assertEq(BlockedSendersCount, expected); + + vm.expectRevert(TermsOfServiceAllowList.InvalidCalldata.selector); + s_termsOfServiceAllowList.getBlockedSendersInRange(0, 0); + } + + function test_GetBlockedSendersInRange_RevertIfStartIsAfterEnd() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert(TermsOfServiceAllowList.InvalidCalldata.selector); + + s_termsOfServiceAllowList.getBlockedSendersInRange(1, 0); + } + + function test_GetBlockedSendersInRange_RevertIfEndIsAfterLastAllowedSender() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + uint64 BlockedSendersCount = s_termsOfServiceAllowList.getBlockedSendersCount(); + vm.expectRevert(TermsOfServiceAllowList.InvalidCalldata.selector); + s_termsOfServiceAllowList.getBlockedSendersInRange(1, BlockedSendersCount + 1); + } +} diff --git a/core/cmd/shell_test.go b/core/cmd/shell_test.go index ec9606e0ac6..f265d5f4787 100644 --- a/core/cmd/shell_test.go +++ b/core/cmd/shell_test.go @@ -380,6 +380,17 @@ func TestSetupSolanaRelayer(t *testing.T) { } }) + t2Config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Solana = solana.TOMLConfigs{ + &solana.TOMLConfig{ + ChainID: ptr[string]("solana-id-1"), + Enabled: ptr(true), + Chain: solcfg.Chain{}, + Nodes: []*solcfg.Node{}, + }, + } + }) + rf := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: reg, @@ -435,6 +446,23 @@ func TestSetupSolanaRelayer(t *testing.T) { _, err := rf.NewSolana(ks, duplicateConfig.SolanaConfigs()) require.Error(t, err) }) + + t.Run("plugin env parsing fails", func(t *testing.T) { + t.Setenv("CL_SOLANA_CMD", "phony_solana_cmd") + t.Setenv("CL_SOLANA_ENV", "fake_path") + + _, err := rf.NewSolana(ks, t2Config.SolanaConfigs()) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to parse Solana env file") + }) + + t.Run("plugin already registered", func(t *testing.T) { + t.Setenv("CL_SOLANA_CMD", "phony_solana_cmd") + + _, err := rf.NewSolana(ks, tConfig.SolanaConfigs()) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to create Solana LOOP command") + }) } func TestSetupStarkNetRelayer(t *testing.T) { @@ -465,6 +493,17 @@ func TestSetupStarkNetRelayer(t *testing.T) { }, } }) + + t2Config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Starknet = stkcfg.TOMLConfigs{ + &stkcfg.TOMLConfig{ + ChainID: ptr[string]("starknet-id-3"), + Enabled: ptr(true), + Chain: stkcfg.Chain{}, + Nodes: []*config.Node{}, + }, + } + }) rf := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: reg, @@ -520,6 +559,23 @@ func TestSetupStarkNetRelayer(t *testing.T) { _, err := rf.NewStarkNet(ks, duplicateConfig.StarknetConfigs()) require.Error(t, err) }) + + t.Run("plugin env parsing fails", func(t *testing.T) { + t.Setenv("CL_STARKNET_CMD", "phony_starknet_cmd") + t.Setenv("CL_STARKNET_ENV", "fake_path") + + _, err := rf.NewStarkNet(ks, t2Config.StarknetConfigs()) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to parse Starknet env file") + }) + + t.Run("plugin already registered", func(t *testing.T) { + t.Setenv("CL_STARKNET_CMD", "phony_starknet_cmd") + + _, err := rf.NewStarkNet(ks, tConfig.StarknetConfigs()) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to create StarkNet LOOP command") + }) } // flagSetApplyFromAction applies the flags from action to the flagSet. diff --git a/core/config/env/env.go b/core/config/env/env.go index 37ae131ddf1..f22310a6cf8 100644 --- a/core/config/env/env.go +++ b/core/config/env/env.go @@ -1,6 +1,7 @@ package env import ( + "fmt" "os" "strings" @@ -8,12 +9,24 @@ import ( ) var ( - Config = Var("CL_CONFIG") + Config = Var("CL_CONFIG") + DatabaseAllowSimplePasswords = Var("CL_DATABASE_ALLOW_SIMPLE_PASSWORDS") + DatabaseURL = Secret("CL_DATABASE_URL") + DatabaseBackupURL = Secret("CL_DATABASE_BACKUP_URL") + PasswordKeystore = Secret("CL_PASSWORD_KEYSTORE") + PasswordVRF = Secret("CL_PASSWORD_VRF") + PyroscopeAuthToken = Secret("CL_PYROSCOPE_AUTH_TOKEN") + PrometheusAuthToken = Secret("CL_PROMETHEUS_AUTH_TOKEN") + ThresholdKeyShare = Secret("CL_THRESHOLD_KEY_SHARE") + // Migrations env vars + EVMChainIDNotNullMigration0195 = "CL_EVM_CHAINID_NOT_NULL_MIGRATION_0195" +) - // LOOPP commands and vars - MedianPluginCmd = Var("CL_MEDIAN_CMD") - SolanaPluginCmd = Var("CL_SOLANA_CMD") - StarknetPluginCmd = Var("CL_STARKNET_CMD") +// LOOPP commands and vars +var ( + MedianPlugin = NewPlugin("median") + SolanaPlugin = NewPlugin("solana") + StarknetPlugin = NewPlugin("starknet") // PrometheusDiscoveryHostName is the externally accessible hostname // published by the node in the `/discovery` endpoint. Generally, it is expected to match // the public hostname of node. @@ -22,24 +35,13 @@ var ( // In house we observed that the resolved value of os.Hostname was not accessible to // outside of the given pod PrometheusDiscoveryHostName = Var("CL_PROMETHEUS_DISCOVERY_HOSTNAME") - // EnvLooopHostName is the hostname used for HTTP communication between the + // LOOPPHostName is the hostname used for HTTP communication between the // node and LOOPps. In most cases this does not need to be set explicitly. LOOPPHostName = Var("CL_LOOPP_HOSTNAME") // Work around for Solana LOOPPs configured with zero values. MinOCR2MaxDurationQuery = Var("CL_MIN_OCR2_MAX_DURATION_QUERY") // PipelineOvertime is an undocumented escape hatch for overriding the default padding in pipeline executions. PipelineOvertime = Var("CL_PIPELINE_OVERTIME") - - DatabaseAllowSimplePasswords = Var("CL_DATABASE_ALLOW_SIMPLE_PASSWORDS") - DatabaseURL = Secret("CL_DATABASE_URL") - DatabaseBackupURL = Secret("CL_DATABASE_BACKUP_URL") - PasswordKeystore = Secret("CL_PASSWORD_KEYSTORE") - PasswordVRF = Secret("CL_PASSWORD_VRF") - PyroscopeAuthToken = Secret("CL_PYROSCOPE_AUTH_TOKEN") - PrometheusAuthToken = Secret("CL_PROMETHEUS_AUTH_TOKEN") - ThresholdKeyShare = Secret("CL_THRESHOLD_KEY_SHARE") - // Migrations env vars - EVMChainIDNotNullMigration0195 = "CL_EVM_CHAINID_NOT_NULL_MIGRATION_0195" ) type Var string @@ -54,3 +56,16 @@ func (e Var) IsTrue() bool { return strings.ToLower(e.Get()) == "true" } type Secret string func (e Secret) Get() models.Secret { return models.Secret(os.Getenv(string(e))) } + +type Plugin struct { + Cmd Var + Env Var +} + +func NewPlugin(kind string) Plugin { + kind = strings.ToUpper(kind) + return Plugin{ + Cmd: Var(fmt.Sprintf("CL_%s_CMD", kind)), + Env: Var(fmt.Sprintf("CL_%s_ENV", kind)), + } +} diff --git a/core/config/env/env_test.go b/core/config/env/env_test.go new file mode 100644 index 00000000000..b6638758d6f --- /dev/null +++ b/core/config/env/env_test.go @@ -0,0 +1,24 @@ +package env + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewPlugin(t *testing.T) { + for _, tt := range []struct { + name string + kind string + exp Plugin + }{ + {"lower", "foo", Plugin{Cmd: "CL_FOO_CMD", Env: "CL_FOO_ENV"}}, + {"upper", "BAR", Plugin{Cmd: "CL_BAR_CMD", Env: "CL_BAR_ENV"}}, + {"mixed", "Baz", Plugin{Cmd: "CL_BAZ_CMD", Env: "CL_BAZ_ENV"}}, + } { + t.Run(tt.name, func(t *testing.T) { + got := NewPlugin(tt.kind) + require.Equal(t, tt.exp, got) + }) + } +} diff --git a/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go b/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go index b0c4894f14c..ff15cc75d41 100644 --- a/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go +++ b/core/gethwrappers/functions/generated/functions_allow_list/functions_allow_list.go @@ -36,8 +36,8 @@ type TermsOfServiceAllowListConfig struct { } var TermsOfServiceAllowListMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"config\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidUsage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RecipientIsBlocked\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"AddedAccess\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"BlockedAccess\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"UnblockedAccess\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"acceptor\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"}],\"name\":\"acceptTermsOfService\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"blockSender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllAllowedSenders\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"acceptor\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"getMessage\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"hasAccess\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"isBlockedSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"unblockSender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60806040523480156200001157600080fd5b50604051620012c9380380620012c9833981016040819052620000349162000269565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000d9565b505050620000d2816200018460201b60201c565b50620002ea565b336001600160a01b03821603620001335760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6200018e6200020b565b805160058054602080850180516001600160a81b0319909316941515610100600160a81b03198116959095176101006001600160a01b039485160217909355604080519485529251909116908301527f0d22b8a99f411b3dd338c961284f608489ca0dab9cdad17366a343c361bcf80a910160405180910390a150565b6000546001600160a01b03163314620002675760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000082565b565b6000604082840312156200027c57600080fd5b604080519081016001600160401b0381118282101715620002ad57634e487b7160e01b600052604160045260246000fd5b60405282518015158114620002c157600080fd5b815260208301516001600160a01b0381168114620002de57600080fd5b60208201529392505050565b610fcf80620002fa6000396000f3fe608060405234801561001057600080fd5b50600436106100df5760003560e01c806382184c7b1161008c578063a39b06e311610066578063a39b06e3146101b8578063a5e1d61d146101d9578063c3f909d4146101ec578063f2fde38b1461024b57600080fd5b806382184c7b1461016a57806389f9a2c41461017d5780638da5cb5b1461019057600080fd5b80636b14daf8116100bd5780636b14daf81461012a57806379ba50971461014d578063817ef62e1461015557600080fd5b8063181f5a77146100e45780633908c4d41461010257806347663acb14610117575b600080fd5b6100ec61025e565b6040516100f99190610c54565b60405180910390f35b610115610110366004610ce9565b61027a565b005b610115610125366004610d4a565b6104f5565b61013d610138366004610d65565b610580565b60405190151581526020016100f9565b6101156105aa565b61015d6106ac565b6040516100f99190610de8565b610115610178366004610d4a565b6106bd565b61011561018b366004610e42565b610750565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100f9565b6101cb6101c6366004610ecb565b61080b565b6040519081526020016100f9565b61013d6101e7366004610d4a565b610869565b60408051808201825260008082526020918201528151808301835260055460ff8116151580835273ffffffffffffffffffffffffffffffffffffffff6101009092048216928401928352845190815291511691810191909152016100f9565b610115610259366004610d4a565b6108aa565b6040518060600160405280602c8152602001610f97602c913981565b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602052604090205460ff16156102da576040517f62b7a34d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006102e6868661080b565b6040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c810191909152605c01604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815282825280516020918201206005546000855291840180845281905260ff8616928401929092526060830187905260808301869052909250610100900473ffffffffffffffffffffffffffffffffffffffff169060019060a0016020604051602081039080840390855afa1580156103c0573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff1614610417576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff861614158061045c57503373ffffffffffffffffffffffffffffffffffffffff87161480159061045c5750333b155b15610493576040517f381cfcbd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61049e6002866108be565b156104ed5760405173ffffffffffffffffffffffffffffffffffffffff861681527f87286ad1f399c8e82bf0c4ef4fcdc570ea2e1e92176e5c848b6413545b885db49060200160405180910390a15b505050505050565b6104fd6108e0565b73ffffffffffffffffffffffffffffffffffffffff811660008181526004602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905590519182527f28bbd0761309a99e8fb5e5d02ada0b7b2db2e5357531ff5dbfc205c3f5b6592b91015b60405180910390a150565b60055460009060ff16610595575060016105a3565b6105a0600285610963565b90505b9392505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610630576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60606106b86002610992565b905090565b6106c56108e0565b6106d060028261099f565b5073ffffffffffffffffffffffffffffffffffffffff811660008181526004602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590519182527f337cd0f3f594112b6d830afb510072d3b08556b446514f73b8109162fd1151e19101610575565b6107586108e0565b805160058054602080850180517fffffffffffffffffffffff0000000000000000000000000000000000000000009093169415157fffffffffffffffffffffff0000000000000000000000000000000000000000ff81169590951761010073ffffffffffffffffffffffffffffffffffffffff9485160217909355604080519485529251909116908301527f0d22b8a99f411b3dd338c961284f608489ca0dab9cdad17366a343c361bcf80a9101610575565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606084811b8216602084015283901b1660348201526000906048016040516020818303038152906040528051906020012090505b92915050565b60055460009060ff1661087e57506000919050565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526004602052604090205460ff1690565b6108b26108e0565b6108bb816109c1565b50565b60006105a38373ffffffffffffffffffffffffffffffffffffffff8416610ab6565b60005473ffffffffffffffffffffffffffffffffffffffff163314610961576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610627565b565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260018301602052604081205415156105a3565b606060006105a383610b05565b60006105a38373ffffffffffffffffffffffffffffffffffffffff8416610b61565b3373ffffffffffffffffffffffffffffffffffffffff821603610a40576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610627565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000818152600183016020526040812054610afd57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610863565b506000610863565b606081600001805480602002602001604051908101604052809291908181526020018280548015610b5557602002820191906000526020600020905b815481526020019060010190808311610b41575b50505050509050919050565b60008181526001830160205260408120548015610c4a576000610b85600183610efe565b8554909150600090610b9990600190610efe565b9050818114610bfe576000866000018281548110610bb957610bb9610f38565b9060005260206000200154905080876000018481548110610bdc57610bdc610f38565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080610c0f57610c0f610f67565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610863565b6000915050610863565b600060208083528351808285015260005b81811015610c8157858101830151858201604001528201610c65565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610ce457600080fd5b919050565b600080600080600060a08688031215610d0157600080fd5b610d0a86610cc0565b9450610d1860208701610cc0565b93506040860135925060608601359150608086013560ff81168114610d3c57600080fd5b809150509295509295909350565b600060208284031215610d5c57600080fd5b6105a382610cc0565b600080600060408486031215610d7a57600080fd5b610d8384610cc0565b9250602084013567ffffffffffffffff80821115610da057600080fd5b818601915086601f830112610db457600080fd5b813581811115610dc357600080fd5b876020828501011115610dd557600080fd5b6020830194508093505050509250925092565b6020808252825182820181905260009190848201906040850190845b81811015610e3657835173ffffffffffffffffffffffffffffffffffffffff1683529284019291840191600101610e04565b50909695505050505050565b600060408284031215610e5457600080fd5b6040516040810181811067ffffffffffffffff82111715610e9e577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405282358015158114610eb157600080fd5b8152610ebf60208401610cc0565b60208201529392505050565b60008060408385031215610ede57600080fd5b610ee783610cc0565b9150610ef560208401610cc0565b90509250929050565b81810381811115610863577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfe46756e6374696f6e73205465726d73206f66205365727669636520416c6c6f77204c6973742076312e312e30a164736f6c6343000813000a", + ABI: "[{\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"config\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidUsage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RecipientIsBlocked\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"AddedAccess\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"BlockedAccess\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"UnblockedAccess\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"acceptor\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"}],\"name\":\"acceptTermsOfService\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"blockSender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllAllowedSenders\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllowedSendersCount\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"allowedSenderIdxStart\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"allowedSenderIdxEnd\",\"type\":\"uint64\"}],\"name\":\"getAllowedSendersInRange\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"allowedSenders\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBlockedSendersCount\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"blockedSenderIdxStart\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"blockedSenderIdxEnd\",\"type\":\"uint64\"}],\"name\":\"getBlockedSendersInRange\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"blockedSenders\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"acceptor\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"getMessage\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"hasAccess\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"isBlockedSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"unblockSender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"signerPublicKey\",\"type\":\"address\"}],\"internalType\":\"structTermsOfServiceAllowListConfig\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60806040523480156200001157600080fd5b506040516200173438038062001734833981016040819052620000349162000269565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000d9565b505050620000d2816200018460201b60201c565b50620002ea565b336001600160a01b03821603620001335760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6200018e6200020b565b805160068054602080850180516001600160a81b0319909316941515610100600160a81b03198116959095176101006001600160a01b039485160217909355604080519485529251909116908301527f0d22b8a99f411b3dd338c961284f608489ca0dab9cdad17366a343c361bcf80a910160405180910390a150565b6000546001600160a01b03163314620002675760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000082565b565b6000604082840312156200027c57600080fd5b604080519081016001600160401b0381118282101715620002ad57634e487b7160e01b600052604160045260246000fd5b60405282518015158114620002c157600080fd5b815260208301516001600160a01b0381168114620002de57600080fd5b60208201529392505050565b61143a80620002fa6000396000f3fe608060405234801561001057600080fd5b506004361061011b5760003560e01c8063817ef62e116100b2578063a39b06e311610081578063c3f909d411610066578063c3f909d4146102c3578063cc7ebf4914610322578063f2fde38b1461032a57600080fd5b8063a39b06e314610237578063a5e1d61d146102b057600080fd5b8063817ef62e146101e157806382184c7b146101e957806389f9a2c4146101fc5780638da5cb5b1461020f57600080fd5b80633908c4d4116100ee5780633908c4d41461018e57806347663acb146101a35780636b14daf8146101b657806379ba5097146101d957600080fd5b806301a05958146101205780630a8c9c2414610146578063181f5a771461016657806320229a861461017b575b600080fd5b61012861033d565b60405167ffffffffffffffff90911681526020015b60405180910390f35b610159610154366004610fd6565b61034e565b60405161013d9190611009565b61016e6104bc565b60405161013d9190611063565b610159610189366004610fd6565b6104d8565b6101a161019c3660046110f3565b61063e565b005b6101a16101b1366004611154565b6108e9565b6101c96101c436600461116f565b61094a565b604051901515815260200161013d565b6101a1610974565b610159610a76565b6101a16101f7366004611154565b610a82565b6101a161020a366004611221565b610ae8565b60005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161013d565b6102a26102453660046112aa565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606084811b8216602084015283901b16603482015260009060480160405160208183030381529060405280519060200120905092915050565b60405190815260200161013d565b6101c96102be366004611154565b610ba3565b60408051808201825260008082526020918201528151808301835260065460ff8116151580835273ffffffffffffffffffffffffffffffffffffffff61010090920482169284019283528451908152915116918101919091520161013d565b610128610bc3565b6101a1610338366004611154565b610bcf565b60006103496004610be3565b905090565b60608167ffffffffffffffff168367ffffffffffffffff16118061038557506103776002610be3565b8267ffffffffffffffff1610155b8061039757506103956002610be3565b155b156103ce576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6103d88383611303565b6103e3906001611324565b67ffffffffffffffff1667ffffffffffffffff811115610405576104056111f2565b60405190808252806020026020018201604052801561042e578160200160208202803683370190505b50905060005b61043e8484611303565b67ffffffffffffffff1681116104b45761046d6104658267ffffffffffffffff8716611345565b600290610bed565b82828151811061047f5761047f611358565b73ffffffffffffffffffffffffffffffffffffffff909216602092830291909101909101526104ad81611387565b9050610434565b505b92915050565b6040518060600160405280602c8152602001611402602c913981565b60608167ffffffffffffffff168367ffffffffffffffff16118061050f57506105016004610be3565b8267ffffffffffffffff1610155b80610521575061051f6004610be3565b155b15610558576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6105628383611303565b61056d906001611324565b67ffffffffffffffff1667ffffffffffffffff81111561058f5761058f6111f2565b6040519080825280602002602001820160405280156105b8578160200160208202803683370190505b50905060005b6105c88484611303565b67ffffffffffffffff1681116104b4576105f76105ef8267ffffffffffffffff8716611345565b600490610bed565b82828151811061060957610609611358565b73ffffffffffffffffffffffffffffffffffffffff9092166020928302919091019091015261063781611387565b90506105be565b610649600485610bf9565b15610680576040517f62b7a34d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051606087811b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009081166020808501919091529188901b16603483015282516028818403018152604890920190925280519101206000906040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c810191909152605c01604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815282825280516020918201206006546000855291840180845281905260ff8616928401929092526060830187905260808301869052909250610100900473ffffffffffffffffffffffffffffffffffffffff169060019060a0016020604051602081039080840390855afa1580156107b4573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff161461080b576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff861614158061085057503373ffffffffffffffffffffffffffffffffffffffff8716148015906108505750333b155b15610887576040517f381cfcbd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610892600286610c28565b156108e15760405173ffffffffffffffffffffffffffffffffffffffff861681527f87286ad1f399c8e82bf0c4ef4fcdc570ea2e1e92176e5c848b6413545b885db49060200160405180910390a15b505050505050565b6108f1610c4a565b6108fc600482610ccd565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527f28bbd0761309a99e8fb5e5d02ada0b7b2db2e5357531ff5dbfc205c3f5b6592b906020015b60405180910390a150565b60065460009060ff1661095f5750600161096d565b61096a600285610bf9565b90505b9392505050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146109fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60606103496002610cef565b610a8a610c4a565b610a95600282610ccd565b50610aa1600482610c28565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527f337cd0f3f594112b6d830afb510072d3b08556b446514f73b8109162fd1151e19060200161093f565b610af0610c4a565b805160068054602080850180517fffffffffffffffffffffff0000000000000000000000000000000000000000009093169415157fffffffffffffffffffffff0000000000000000000000000000000000000000ff81169590951761010073ffffffffffffffffffffffffffffffffffffffff9485160217909355604080519485529251909116908301527f0d22b8a99f411b3dd338c961284f608489ca0dab9cdad17366a343c361bcf80a910161093f565b60065460009060ff16610bb857506000919050565b6104b6600483610bf9565b60006103496002610be3565b610bd7610c4a565b610be081610cfc565b50565b60006104b6825490565b600061096d8383610df1565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600183016020526040812054151561096d565b600061096d8373ffffffffffffffffffffffffffffffffffffffff8416610e1b565b60005473ffffffffffffffffffffffffffffffffffffffff163314610ccb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016109f1565b565b600061096d8373ffffffffffffffffffffffffffffffffffffffff8416610e6a565b6060600061096d83610f5d565b3373ffffffffffffffffffffffffffffffffffffffff821603610d7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016109f1565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000826000018281548110610e0857610e08611358565b9060005260206000200154905092915050565b6000818152600183016020526040812054610e62575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556104b6565b5060006104b6565b60008181526001830160205260408120548015610f53576000610e8e6001836113bf565b8554909150600090610ea2906001906113bf565b9050818114610f07576000866000018281548110610ec257610ec2611358565b9060005260206000200154905080876000018481548110610ee557610ee5611358565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080610f1857610f186113d2565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506104b6565b60009150506104b6565b606081600001805480602002602001604051908101604052809291908181526020018280548015610fad57602002820191906000526020600020905b815481526020019060010190808311610f99575b50505050509050919050565b803567ffffffffffffffff81168114610fd157600080fd5b919050565b60008060408385031215610fe957600080fd5b610ff283610fb9565b915061100060208401610fb9565b90509250929050565b6020808252825182820181905260009190848201906040850190845b8181101561105757835173ffffffffffffffffffffffffffffffffffffffff1683529284019291840191600101611025565b50909695505050505050565b600060208083528351808285015260005b8181101561109057858101830151858201604001528201611074565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610fd157600080fd5b600080600080600060a0868803121561110b57600080fd5b611114866110cf565b9450611122602087016110cf565b93506040860135925060608601359150608086013560ff8116811461114657600080fd5b809150509295509295909350565b60006020828403121561116657600080fd5b61096d826110cf565b60008060006040848603121561118457600080fd5b61118d846110cf565b9250602084013567ffffffffffffffff808211156111aa57600080fd5b818601915086601f8301126111be57600080fd5b8135818111156111cd57600080fd5b8760208285010111156111df57600080fd5b6020830194508093505050509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006040828403121561123357600080fd5b6040516040810181811067ffffffffffffffff8211171561127d577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040528235801515811461129057600080fd5b815261129e602084016110cf565b60208201529392505050565b600080604083850312156112bd57600080fd5b6112c6836110cf565b9150611000602084016110cf565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b67ffffffffffffffff8281168282160390808211156104b4576104b46112d4565b67ffffffffffffffff8181168382160190808211156104b4576104b46112d4565b808201808211156104b6576104b66112d4565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036113b8576113b86112d4565b5060010190565b818103818111156104b6576104b66112d4565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfe46756e6374696f6e73205465726d73206f66205365727669636520416c6c6f77204c6973742076312e312e30a164736f6c6343000813000a", } var TermsOfServiceAllowListABI = TermsOfServiceAllowListMetaData.ABI @@ -198,6 +198,94 @@ func (_TermsOfServiceAllowList *TermsOfServiceAllowListCallerSession) GetAllAllo return _TermsOfServiceAllowList.Contract.GetAllAllowedSenders(&_TermsOfServiceAllowList.CallOpts) } +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCaller) GetAllowedSendersCount(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _TermsOfServiceAllowList.contract.Call(opts, &out, "getAllowedSendersCount") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListSession) GetAllowedSendersCount() (uint64, error) { + return _TermsOfServiceAllowList.Contract.GetAllowedSendersCount(&_TermsOfServiceAllowList.CallOpts) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCallerSession) GetAllowedSendersCount() (uint64, error) { + return _TermsOfServiceAllowList.Contract.GetAllowedSendersCount(&_TermsOfServiceAllowList.CallOpts) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCaller) GetAllowedSendersInRange(opts *bind.CallOpts, allowedSenderIdxStart uint64, allowedSenderIdxEnd uint64) ([]common.Address, error) { + var out []interface{} + err := _TermsOfServiceAllowList.contract.Call(opts, &out, "getAllowedSendersInRange", allowedSenderIdxStart, allowedSenderIdxEnd) + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListSession) GetAllowedSendersInRange(allowedSenderIdxStart uint64, allowedSenderIdxEnd uint64) ([]common.Address, error) { + return _TermsOfServiceAllowList.Contract.GetAllowedSendersInRange(&_TermsOfServiceAllowList.CallOpts, allowedSenderIdxStart, allowedSenderIdxEnd) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCallerSession) GetAllowedSendersInRange(allowedSenderIdxStart uint64, allowedSenderIdxEnd uint64) ([]common.Address, error) { + return _TermsOfServiceAllowList.Contract.GetAllowedSendersInRange(&_TermsOfServiceAllowList.CallOpts, allowedSenderIdxStart, allowedSenderIdxEnd) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCaller) GetBlockedSendersCount(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _TermsOfServiceAllowList.contract.Call(opts, &out, "getBlockedSendersCount") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListSession) GetBlockedSendersCount() (uint64, error) { + return _TermsOfServiceAllowList.Contract.GetBlockedSendersCount(&_TermsOfServiceAllowList.CallOpts) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCallerSession) GetBlockedSendersCount() (uint64, error) { + return _TermsOfServiceAllowList.Contract.GetBlockedSendersCount(&_TermsOfServiceAllowList.CallOpts) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCaller) GetBlockedSendersInRange(opts *bind.CallOpts, blockedSenderIdxStart uint64, blockedSenderIdxEnd uint64) ([]common.Address, error) { + var out []interface{} + err := _TermsOfServiceAllowList.contract.Call(opts, &out, "getBlockedSendersInRange", blockedSenderIdxStart, blockedSenderIdxEnd) + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListSession) GetBlockedSendersInRange(blockedSenderIdxStart uint64, blockedSenderIdxEnd uint64) ([]common.Address, error) { + return _TermsOfServiceAllowList.Contract.GetBlockedSendersInRange(&_TermsOfServiceAllowList.CallOpts, blockedSenderIdxStart, blockedSenderIdxEnd) +} + +func (_TermsOfServiceAllowList *TermsOfServiceAllowListCallerSession) GetBlockedSendersInRange(blockedSenderIdxStart uint64, blockedSenderIdxEnd uint64) ([]common.Address, error) { + return _TermsOfServiceAllowList.Contract.GetBlockedSendersInRange(&_TermsOfServiceAllowList.CallOpts, blockedSenderIdxStart, blockedSenderIdxEnd) +} + func (_TermsOfServiceAllowList *TermsOfServiceAllowListCaller) GetConfig(opts *bind.CallOpts) (TermsOfServiceAllowListConfig, error) { var out []interface{} err := _TermsOfServiceAllowList.contract.Call(opts, &out, "getConfig") @@ -1193,6 +1281,14 @@ func (_TermsOfServiceAllowList *TermsOfServiceAllowList) Address() common.Addres type TermsOfServiceAllowListInterface interface { GetAllAllowedSenders(opts *bind.CallOpts) ([]common.Address, error) + GetAllowedSendersCount(opts *bind.CallOpts) (uint64, error) + + GetAllowedSendersInRange(opts *bind.CallOpts, allowedSenderIdxStart uint64, allowedSenderIdxEnd uint64) ([]common.Address, error) + + GetBlockedSendersCount(opts *bind.CallOpts) (uint64, error) + + GetBlockedSendersInRange(opts *bind.CallOpts, blockedSenderIdxStart uint64, blockedSenderIdxEnd uint64) ([]common.Address, error) + GetConfig(opts *bind.CallOpts) (TermsOfServiceAllowListConfig, error) GetMessage(opts *bind.CallOpts, acceptor common.Address, recipient common.Address) ([32]byte, error) diff --git a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 132f38507ad..16bb718e7c1 100644 --- a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,6 +1,6 @@ GETH_VERSION: 1.13.8 functions: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRequest.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRequest.bin 3c972870b0afeb6d73a29ebb182f24956a2cebb127b21c4f867d1ecf19a762db -functions_allow_list: ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServiceAllowList.abi ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServiceAllowList.bin 6beec092fbb3b619dfe69f1ad23392b0bbaf00327b335e4080f921c7122a57e4 +functions_allow_list: ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServiceAllowList.abi ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServiceAllowList.bin 586696a0cacc0e5112bdd6c99535748a3fbf08c9319360aee868831d38a96d7b functions_billing_registry_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.bin 50deeb883bd9c3729702be335c0388f9d8553bab4be5e26ecacac496a89e2b77 functions_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.bin 2368f537a04489c720a46733f8596c4fc88a31062ecfa966d05f25dd98608aca functions_client_example: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.bin abf32e69f268f40e8530eb8d8e96bf310b798a4c0049a58022d9d2fb527b601b diff --git a/core/internal/features/ocr2/features_ocr2_plugin_test.go b/core/internal/features/ocr2/features_ocr2_plugin_test.go new file mode 100644 index 00000000000..96a9f32e957 --- /dev/null +++ b/core/internal/features/ocr2/features_ocr2_plugin_test.go @@ -0,0 +1,14 @@ +//go:build integration + +package ocr2_test + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/v2/core/config/env" +) + +func TestIntegration_OCR2_plugins(t *testing.T) { + t.Setenv(string(env.MedianPlugin.Cmd), "chainlink-feeds") + testIntegration_OCR2(t) +} diff --git a/core/internal/features/ocr2/features_ocr2_test.go b/core/internal/features/ocr2/features_ocr2_test.go index 002f1d192ae..938b7aa2a66 100644 --- a/core/internal/features/ocr2/features_ocr2_test.go +++ b/core/internal/features/ocr2/features_ocr2_test.go @@ -189,7 +189,10 @@ func setupNodeOCR2( func TestIntegration_OCR2(t *testing.T) { t.Parallel() + testIntegration_OCR2(t) +} +func testIntegration_OCR2(t *testing.T) { for _, test := range []struct { name string chainReaderAndCodec bool @@ -199,8 +202,6 @@ func TestIntegration_OCR2(t *testing.T) { } { test := test t.Run(test.name, func(t *testing.T) { - t.Parallel() - owner, b, ocrContractAddress, ocrContract := setupOCR2Contracts(t) lggr := logger.TestLogger(t) diff --git a/core/scripts/chaincli/DEBUGGING.md b/core/scripts/chaincli/DEBUGGING.md index 703261fcb54..6466a40fc31 100644 --- a/core/scripts/chaincli/DEBUGGING.md +++ b/core/scripts/chaincli/DEBUGGING.md @@ -41,10 +41,10 @@ For detailed transaction simulation logs, set up Tenderly credentials. Refer to Execute the following command based on your upkeep type: -- For conditional upkeep: +- For conditional upkeep, if a block number is given we use that block, otherwise we use the latest block: ```bash - go run main.go keeper debug UPKEEP_ID + go run main.go keeper debug UPKEEP_ID [OPTIONAL BLOCK_NUMBER] ``` - For log trigger upkeep: diff --git a/core/scripts/chaincli/handler/debug.go b/core/scripts/chaincli/handler/debug.go index 2c97adace26..8b06937fc2c 100644 --- a/core/scripts/chaincli/handler/debug.go +++ b/core/scripts/chaincli/handler/debug.go @@ -68,8 +68,10 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { } chainID := chainIDBig.Int64() - var triggerCallOpts *bind.CallOpts // use latest block for conditionals, but use block from tx for log triggers - latestCallOpts := &bind.CallOpts{Context: ctx} // always use latest block + // Log triggers: always use block from tx + // Conditional: use latest block if no block number is provided, otherwise use block from user input + var triggerCallOpts *bind.CallOpts // use a certain block + latestCallOpts := &bind.CallOpts{Context: ctx} // use the latest block // connect to registry contract registryAddress := gethcommon.HexToAddress(k.cfg.RegistryAddress) @@ -139,8 +141,21 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { // check upkeep if triggerType == ConditionTrigger { message("upkeep identified as conditional trigger") + + if len(args) > 1 { + // if a block number is provided, use that block for both checkUpkeep and simulatePerformUpkeep + blockNum, err = strconv.ParseUint(args[1], 10, 64) + if err != nil { + failCheckArgs("unable to parse block number", err) + } + triggerCallOpts = &bind.CallOpts{Context: ctx, BlockNumber: new(big.Int).SetUint64(blockNum)} + } else { + // if no block number is provided, use latest block for both checkUpkeep and simulatePerformUpkeep + triggerCallOpts = latestCallOpts + } + var tmpCheckResult iregistry21.CheckUpkeep0 - tmpCheckResult, err = keeperRegistry21.CheckUpkeep0(latestCallOpts, upkeepID) + tmpCheckResult, err = keeperRegistry21.CheckUpkeep0(triggerCallOpts, upkeepID) if err != nil { failUnknown("failed to check upkeep: ", err) } @@ -251,11 +266,12 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { resolveIneligible(fmt.Sprintf("invalid trigger type: %d", triggerType)) } upkeepNeeded, performData = checkResult.UpkeepNeeded, checkResult.PerformData - // handle streams lookup + if checkResult.UpkeepFailureReason != 0 { message(fmt.Sprintf("checkUpkeep failed with UpkeepFailureReason %s", getCheckUpkeepFailureReason(checkResult.UpkeepFailureReason))) } + // handle data streams lookup if checkResult.UpkeepFailureReason == uint8(encoding.UpkeepFailureReasonTargetCheckReverted) { mc := &types2.MercuryCredentials{LegacyURL: k.cfg.DataStreamsLegacyURL, URL: k.cfg.DataStreamsURL, Username: k.cfg.DataStreamsID, Password: k.cfg.DataStreamsKey} mercuryConfig := evm21.NewMercuryConfig(mc, core.StreamsCompatibleABI) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index fea7da60757..c533d486f60 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -166,6 +166,8 @@ require ( github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect + github.com/hashicorp/consul/sdk v0.14.1 // indirect + github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-plugin v1.5.2 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index bd13587c679..b956198b8f2 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -694,6 +694,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= +github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc= github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= @@ -711,8 +713,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index 49582e2d52a..c42ca77dc39 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -116,7 +116,7 @@ func (r *RelayerFactory) NewSolana(ks keystore.Solana, chainCfgs solana.TOMLConf lggr := solLggr.Named(relayID.ChainID) - if cmdName := env.SolanaPluginCmd.Get(); cmdName != "" { + if cmdName := env.SolanaPlugin.Cmd.Get(); cmdName != "" { // setup the solana relayer to be a LOOP cfgTOML, err := toml.Marshal(struct { @@ -126,10 +126,14 @@ func (r *RelayerFactory) NewSolana(ks keystore.Solana, chainCfgs solana.TOMLConf if err != nil { return nil, fmt.Errorf("failed to marshal Solana configs: %w", err) } - + envVars, err := plugins.ParseEnvFile(env.SolanaPlugin.Env.Get()) + if err != nil { + return nil, fmt.Errorf("failed to parse Solana env file: %w", err) + } solCmdFn, err := plugins.NewCmdFactory(r.Register, plugins.CmdConfig{ ID: relayID.Name(), Cmd: cmdName, + Env: envVars, }) if err != nil { return nil, fmt.Errorf("failed to create Solana LOOP command: %w", err) @@ -187,7 +191,7 @@ func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOML lggr := starkLggr.Named(relayID.ChainID) - if cmdName := env.StarknetPluginCmd.Get(); cmdName != "" { + if cmdName := env.StarknetPlugin.Cmd.Get(); cmdName != "" { // setup the starknet relayer to be a LOOP cfgTOML, err := toml.Marshal(struct { Starknet config.TOMLConfig @@ -196,9 +200,14 @@ func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOML return nil, fmt.Errorf("failed to marshal StarkNet configs: %w", err) } + envVars, err := plugins.ParseEnvFile(env.StarknetPlugin.Env.Get()) + if err != nil { + return nil, fmt.Errorf("failed to parse Starknet env file: %w", err) + } starknetCmdFn, err := plugins.NewCmdFactory(r.Register, plugins.CmdConfig{ ID: relayID.Name(), Cmd: cmdName, + Env: envVars, }) if err != nil { return nil, fmt.Errorf("failed to create StarkNet LOOP command: %w", err) diff --git a/core/services/functions/connector_handler.go b/core/services/functions/connector_handler.go index 18df644c876..c8c522e6a62 100644 --- a/core/services/functions/connector_handler.go +++ b/core/services/functions/connector_handler.go @@ -26,6 +26,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + fallow "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" + fsub "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" "github.com/smartcontractkit/chainlink/v2/core/services/s4" ) @@ -37,9 +39,9 @@ type functionsConnectorHandler struct { signerKey *ecdsa.PrivateKey nodeAddress string storage s4.Storage - allowlist functions.OnchainAllowlist + allowlist fallow.OnchainAllowlist rateLimiter *hc.RateLimiter - subscriptions functions.OnchainSubscriptions + subscriptions fsub.OnchainSubscriptions minimumBalance assets.Link listener FunctionsListener offchainTransmitter OffchainTransmitter @@ -72,7 +74,7 @@ func InternalId(sender []byte, requestId []byte) RequestID { return RequestID(crypto.Keccak256Hash(append(sender, requestId...)).Bytes()) } -func NewFunctionsConnectorHandler(pluginConfig *config.PluginConfig, signerKey *ecdsa.PrivateKey, storage s4.Storage, allowlist functions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions functions.OnchainSubscriptions, listener FunctionsListener, offchainTransmitter OffchainTransmitter, lggr logger.Logger) (*functionsConnectorHandler, error) { +func NewFunctionsConnectorHandler(pluginConfig *config.PluginConfig, signerKey *ecdsa.PrivateKey, storage s4.Storage, allowlist fallow.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions fsub.OnchainSubscriptions, listener FunctionsListener, offchainTransmitter OffchainTransmitter, lggr logger.Logger) (*functionsConnectorHandler, error) { if signerKey == nil || storage == nil || allowlist == nil || rateLimiter == nil || subscriptions == nil || listener == nil || offchainTransmitter == nil { return nil, fmt.Errorf("all dependencies must be non-nil") } diff --git a/core/services/functions/connector_handler_test.go b/core/services/functions/connector_handler_test.go index 9a5a9042693..aadc84ba96a 100644 --- a/core/services/functions/connector_handler_test.go +++ b/core/services/functions/connector_handler_test.go @@ -23,7 +23,8 @@ import ( gwconnector "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" gcmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector/mocks" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" - gfmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/mocks" + fallowMocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist/mocks" + fsubMocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" "github.com/smartcontractkit/chainlink/v2/core/services/s4" s4mocks "github.com/smartcontractkit/chainlink/v2/core/services/s4/mocks" @@ -66,9 +67,9 @@ func TestFunctionsConnectorHandler(t *testing.T) { privateKey, addr := testutils.NewPrivateKeyAndAddress(t) storage := s4mocks.NewStorage(t) connector := gcmocks.NewGatewayConnector(t) - allowlist := gfmocks.NewOnchainAllowlist(t) + allowlist := fallowMocks.NewOnchainAllowlist(t) rateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) - subscriptions := gfmocks.NewOnchainSubscriptions(t) + subscriptions := fsubMocks.NewOnchainSubscriptions(t) reportCh := make(chan *functions.OffchainResponse) offchainTransmitter := sfmocks.NewOffchainTransmitter(t) offchainTransmitter.On("ReportChannel", mock.Anything).Return(reportCh) diff --git a/core/services/gateway/handlers/functions/allowlist.go b/core/services/gateway/handlers/functions/allowlist/allowlist.go similarity index 51% rename from core/services/gateway/handlers/functions/allowlist.go rename to core/services/gateway/handlers/functions/allowlist/allowlist.go index 0a18d6e6d87..20dc92ced70 100644 --- a/core/services/gateway/handlers/functions/allowlist.go +++ b/core/services/gateway/handlers/functions/allowlist/allowlist.go @@ -1,4 +1,4 @@ -package functions +package allowlist import ( "context" @@ -22,14 +22,26 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/utils" ) +const ( + defaultStoredAllowlistBatchSize = 1000 + defaultOnchainAllowlistBatchSize = 100 + defaultFetchingDelayInRangeSec = 1 +) + type OnchainAllowlistConfig struct { // ContractAddress is required ContractAddress common.Address `json:"contractAddress"` ContractVersion uint32 `json:"contractVersion"` BlockConfirmations uint `json:"blockConfirmations"` // UpdateFrequencySec can be zero to disable periodic updates - UpdateFrequencySec uint `json:"updateFrequencySec"` - UpdateTimeoutSec uint `json:"updateTimeoutSec"` + UpdateFrequencySec uint `json:"updateFrequencySec"` + UpdateTimeoutSec uint `json:"updateTimeoutSec"` + StoredAllowlistBatchSize uint `json:"storedAllowlistBatchSize"` + OnchainAllowlistBatchSize uint `json:"onchainAllowlistBatchSize"` + // StoreAllowedSendersEnabled is a feature flag that enables storing in db a copy of the allowlist. + StoreAllowedSendersEnabled bool `json:"storeAllowedSendersEnabled"` + // FetchingDelayInRangeSec prevents RPC client being rate limited when fetching the allowlist in ranges. + FetchingDelayInRangeSec uint `json:"fetchingDelayInRangeSec"` } // OnchainAllowlist maintains an allowlist of addresses fetched from the blockchain (EVM-only). @@ -50,6 +62,7 @@ type onchainAllowlist struct { config OnchainAllowlistConfig allowlist atomic.Pointer[map[common.Address]struct{}] + orm ORM client evmclient.Client contractV1 *functions_router.FunctionsRouter blockConfirmations *big.Int @@ -58,7 +71,7 @@ type onchainAllowlist struct { stopCh services.StopChan } -func NewOnchainAllowlist(client evmclient.Client, config OnchainAllowlistConfig, lggr logger.Logger) (OnchainAllowlist, error) { +func NewOnchainAllowlist(client evmclient.Client, config OnchainAllowlistConfig, orm ORM, lggr logger.Logger) (OnchainAllowlist, error) { if client == nil { return nil, errors.New("client is nil") } @@ -68,12 +81,33 @@ func NewOnchainAllowlist(client evmclient.Client, config OnchainAllowlistConfig, if config.ContractVersion != 1 { return nil, fmt.Errorf("unsupported contract version %d", config.ContractVersion) } + + if config.StoredAllowlistBatchSize == 0 { + lggr.Info("StoredAllowlistBatchSize not specified, using default size: ", defaultStoredAllowlistBatchSize) + config.StoredAllowlistBatchSize = defaultStoredAllowlistBatchSize + } + + if config.OnchainAllowlistBatchSize == 0 { + lggr.Info("OnchainAllowlistBatchSize not specified, using default size: ", defaultOnchainAllowlistBatchSize) + config.OnchainAllowlistBatchSize = defaultOnchainAllowlistBatchSize + } + + if config.FetchingDelayInRangeSec == 0 { + lggr.Info("FetchingDelayInRangeSec not specified, using default delay: ", defaultFetchingDelayInRangeSec) + config.FetchingDelayInRangeSec = defaultFetchingDelayInRangeSec + } + + if config.UpdateFrequencySec != 0 && config.FetchingDelayInRangeSec >= config.UpdateFrequencySec { + return nil, fmt.Errorf("to avoid updates overlapping FetchingDelayInRangeSec:%d should be less than UpdateFrequencySec:%d", config.FetchingDelayInRangeSec, config.UpdateFrequencySec) + } + contractV1, err := functions_router.NewFunctionsRouter(config.ContractAddress, client) if err != nil { return nil, fmt.Errorf("unexpected error during functions_router.NewFunctionsRouter: %s", err) } allowlist := &onchainAllowlist{ config: config, + orm: orm, client: client, contractV1: contractV1, blockConfirmations: big.NewInt(int64(config.BlockConfirmations)), @@ -93,6 +127,8 @@ func (a *onchainAllowlist) Start(ctx context.Context) error { return nil } + a.loadStoredAllowedSenderList() + updateOnce := func() { timeoutCtx, cancel := utils.ContextFromChanWithTimeout(a.stopCh, time.Duration(a.config.UpdateTimeoutSec)*time.Second) if err := a.UpdateFromContract(timeoutCtx); err != nil { @@ -172,17 +208,113 @@ func (a *onchainAllowlist) updateFromContractV1(ctx context.Context, blockNum *b if err != nil { return errors.Wrap(err, "unexpected error during functions_allow_list.NewTermsOfServiceAllowList") } - addrList, err := tosContract.GetAllAllowedSenders(&bind.CallOpts{ + + var allowedSenderList []common.Address + if !a.config.StoreAllowedSendersEnabled { + allowedSenderList, err = tosContract.GetAllAllowedSenders(&bind.CallOpts{ + Pending: false, + BlockNumber: blockNum, + Context: ctx, + }) + if err != nil { + return errors.Wrap(err, "error calling GetAllAllowedSenders") + } + } else { + err = a.syncBlockedSenders(ctx, tosContract, blockNum) + if err != nil { + return errors.Wrap(err, "failed to sync the stored allowed and blocked senders") + } + + allowedSenderList, err = a.getAllowedSendersBatched(ctx, tosContract, blockNum) + if err != nil { + return errors.Wrap(err, "failed to get allowed senders in rage") + } + } + + a.update(allowedSenderList) + return nil +} + +func (a *onchainAllowlist) getAllowedSendersBatched(ctx context.Context, tosContract *functions_allow_list.TermsOfServiceAllowList, blockNum *big.Int) ([]common.Address, error) { + allowedSenderList := make([]common.Address, 0) + count, err := tosContract.GetAllowedSendersCount(&bind.CallOpts{ Pending: false, BlockNumber: blockNum, Context: ctx, }) if err != nil { - return errors.Wrap(err, "error calling GetAllAllowedSenders") + return nil, errors.Wrap(err, "unexpected error during functions_allow_list.GetAllowedSendersCount") } - a.update(addrList) + + throttleTicker := time.NewTicker(time.Duration(a.config.FetchingDelayInRangeSec) * time.Second) + for idxStart := uint64(0); idxStart < count; idxStart += uint64(a.config.OnchainAllowlistBatchSize) { + <-throttleTicker.C + + idxEnd := idxStart + uint64(a.config.OnchainAllowlistBatchSize) + if idxEnd >= count { + idxEnd = count - 1 + } + + allowedSendersBatch, err := tosContract.GetAllowedSendersInRange(&bind.CallOpts{ + Pending: false, + BlockNumber: blockNum, + Context: ctx, + }, idxStart, idxEnd) + if err != nil { + return nil, errors.Wrap(err, "error calling GetAllowedSendersInRange") + } + + allowedSenderList = append(allowedSenderList, allowedSendersBatch...) + err = a.orm.CreateAllowedSenders(allowedSendersBatch) + if err != nil { + a.lggr.Errorf("failed to update stored allowedSenderList: %w", err) + } + } + throttleTicker.Stop() + + return allowedSenderList, nil +} + +// syncBlockedSenders fetches the list of blocked addresses from the contract in batches +// and removes the addresses from the functions_allowlist table if present +func (a *onchainAllowlist) syncBlockedSenders(ctx context.Context, tosContract *functions_allow_list.TermsOfServiceAllowList, blockNum *big.Int) error { + count, err := tosContract.GetBlockedSendersCount(&bind.CallOpts{ + Pending: false, + BlockNumber: blockNum, + Context: ctx, + }) + if err != nil { + return errors.Wrap(err, "unexpected error during functions_allow_list.GetBlockedSendersCount") + } + + throttleTicker := time.NewTicker(time.Duration(a.config.FetchingDelayInRangeSec) * time.Second) + for idxStart := uint64(0); idxStart < count; idxStart += uint64(a.config.OnchainAllowlistBatchSize) { + <-throttleTicker.C + + idxEnd := idxStart + uint64(a.config.OnchainAllowlistBatchSize) + if idxEnd >= count { + idxEnd = count - 1 + } + + blockedSendersBatch, err := tosContract.GetBlockedSendersInRange(&bind.CallOpts{ + Pending: false, + BlockNumber: blockNum, + Context: ctx, + }, idxStart, idxEnd) + if err != nil { + return errors.Wrap(err, "error calling GetAllowedSendersInRange") + } + + err = a.orm.DeleteAllowedSenders(blockedSendersBatch) + if err != nil { + a.lggr.Errorf("failed to delete blocked address from allowed list in storage: %w", err) + } + } + throttleTicker.Stop() + return nil } + func (a *onchainAllowlist) update(addrList []common.Address) { newAllowlist := make(map[common.Address]struct{}) for _, addr := range addrList { @@ -191,3 +323,24 @@ func (a *onchainAllowlist) update(addrList []common.Address) { a.allowlist.Store(&newAllowlist) a.lggr.Infow("allowlist updated successfully", "len", len(addrList)) } + +func (a *onchainAllowlist) loadStoredAllowedSenderList() { + allowedList := make([]common.Address, 0) + offset := uint(0) + for { + asBatch, err := a.orm.GetAllowedSenders(offset, a.config.StoredAllowlistBatchSize) + if err != nil { + a.lggr.Errorf("failed to get stored allowed senders: %w", err) + break + } + + allowedList = append(allowedList, asBatch...) + + if len(asBatch) < int(a.config.StoredAllowlistBatchSize) { + break + } + offset += a.config.StoredAllowlistBatchSize + } + + a.update(allowedList) +} diff --git a/core/services/gateway/handlers/functions/allowlist/allowlist_test.go b/core/services/gateway/handlers/functions/allowlist/allowlist_test.go new file mode 100644 index 00000000000..e8cbca80b94 --- /dev/null +++ b/core/services/gateway/handlers/functions/allowlist/allowlist_test.go @@ -0,0 +1,184 @@ +package allowlist_test + +import ( + "context" + "encoding/hex" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + "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/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" + amocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist/mocks" +) + +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) + config := allowlist.OnchainAllowlistConfig{ + ContractVersion: 1, + ContractAddress: common.Address{}, + BlockConfirmations: 1, + } + + orm := amocks.NewORM(t) + allowlist, err := allowlist.NewOnchainAllowlist(client, config, orm, logger.TestLogger(t)) + require.NoError(t, err) + + err = allowlist.Start(testutils.Context(t)) + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, allowlist.Close()) + }) + + require.NoError(t, allowlist.UpdateFromContract(testutils.Context(t))) + 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_UnsupportedVersion(t *testing.T) { + t.Parallel() + + client := mocks.NewClient(t) + config := allowlist.OnchainAllowlistConfig{ + ContractVersion: 0, + ContractAddress: common.Address{}, + BlockConfirmations: 1, + } + + orm := amocks.NewORM(t) + _, err := allowlist.NewOnchainAllowlist(client, config, orm, logger.TestLogger(t)) + require.Error(t, err) +} + +func TestAllowlist_UpdatePeriodically(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(testutils.Context(t)) + 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) + config := allowlist.OnchainAllowlistConfig{ + ContractAddress: common.Address{}, + ContractVersion: 1, + BlockConfirmations: 1, + UpdateFrequencySec: 2, + UpdateTimeoutSec: 1, + } + + orm := amocks.NewORM(t) + orm.On("GetAllowedSenders", uint(0), uint(1000)).Return([]common.Address{}, nil) + + allowlist, err := allowlist.NewOnchainAllowlist(client, config, orm, logger.TestLogger(t)) + require.NoError(t, err) + + err = allowlist.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, allowlist.Close()) + }) + + gomega.NewGomegaWithT(t).Eventually(func() bool { + return allowlist.Allow(common.HexToAddress(addr1)) && !allowlist.Allow(common.HexToAddress(addr3)) + }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) +} +func TestAllowlist_UpdateFromContract(t *testing.T) { + t.Parallel() + + t.Run("OK-iterate_over_list_of_allowed_senders", func(t *testing.T) { + ctx, cancel := context.WithCancel(testutils.Context(t)) + 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) + config := allowlist.OnchainAllowlistConfig{ + ContractAddress: common.HexToAddress(addr3), + ContractVersion: 1, + BlockConfirmations: 1, + UpdateFrequencySec: 2, + UpdateTimeoutSec: 1, + StoredAllowlistBatchSize: 2, + OnchainAllowlistBatchSize: 16, + StoreAllowedSendersEnabled: true, + FetchingDelayInRangeSec: 0, + } + + orm := amocks.NewORM(t) + orm.On("DeleteAllowedSenders", []common.Address{common.HexToAddress(addr1), common.HexToAddress(addr2)}).Times(2).Return(nil) + orm.On("CreateAllowedSenders", []common.Address{common.HexToAddress(addr1), common.HexToAddress(addr2)}).Times(2).Return(nil) + + allowlist, err := allowlist.NewOnchainAllowlist(client, config, orm, logger.TestLogger(t)) + require.NoError(t, err) + + err = allowlist.UpdateFromContract(ctx) + require.NoError(t, err) + + gomega.NewGomegaWithT(t).Eventually(func() bool { + return allowlist.Allow(common.HexToAddress(addr1)) && !allowlist.Allow(common.HexToAddress(addr3)) + }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) + }) + + t.Run("OK-fetch_complete_list_of_allowed_senders_without_storing", func(t *testing.T) { + ctx, cancel := context.WithCancel(testutils.Context(t)) + 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) + config := allowlist.OnchainAllowlistConfig{ + ContractAddress: common.HexToAddress(addr3), + ContractVersion: 1, + BlockConfirmations: 1, + UpdateFrequencySec: 2, + UpdateTimeoutSec: 1, + StoredAllowlistBatchSize: 2, + OnchainAllowlistBatchSize: 16, + StoreAllowedSendersEnabled: false, + FetchingDelayInRangeSec: 0, + } + + orm := amocks.NewORM(t) + allowlist, err := allowlist.NewOnchainAllowlist(client, config, orm, logger.TestLogger(t)) + require.NoError(t, err) + + err = allowlist.UpdateFromContract(ctx) + require.NoError(t, err) + + gomega.NewGomegaWithT(t).Eventually(func() bool { + return allowlist.Allow(common.HexToAddress(addr1)) && !allowlist.Allow(common.HexToAddress(addr3)) + }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) + }) +} diff --git a/core/services/gateway/handlers/functions/mocks/onchain_allowlist.go b/core/services/gateway/handlers/functions/allowlist/mocks/onchain_allowlist.go similarity index 100% rename from core/services/gateway/handlers/functions/mocks/onchain_allowlist.go rename to core/services/gateway/handlers/functions/allowlist/mocks/onchain_allowlist.go diff --git a/core/services/gateway/handlers/functions/allowlist/mocks/orm.go b/core/services/gateway/handlers/functions/allowlist/mocks/orm.go new file mode 100644 index 00000000000..c2ba27c3a24 --- /dev/null +++ b/core/services/gateway/handlers/functions/allowlist/mocks/orm.go @@ -0,0 +1,116 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + common "github.com/ethereum/go-ethereum/common" + mock "github.com/stretchr/testify/mock" + + pg "github.com/smartcontractkit/chainlink/v2/core/services/pg" +) + +// ORM is an autogenerated mock type for the ORM type +type ORM struct { + mock.Mock +} + +// CreateAllowedSenders provides a mock function with given fields: allowedSenders, qopts +func (_m *ORM) CreateAllowedSenders(allowedSenders []common.Address, qopts ...pg.QOpt) error { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, allowedSenders) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateAllowedSenders") + } + + var r0 error + if rf, ok := ret.Get(0).(func([]common.Address, ...pg.QOpt) error); ok { + r0 = rf(allowedSenders, qopts...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteAllowedSenders provides a mock function with given fields: blockedSenders, qopts +func (_m *ORM) DeleteAllowedSenders(blockedSenders []common.Address, qopts ...pg.QOpt) error { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, blockedSenders) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for DeleteAllowedSenders") + } + + var r0 error + if rf, ok := ret.Get(0).(func([]common.Address, ...pg.QOpt) error); ok { + r0 = rf(blockedSenders, qopts...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetAllowedSenders provides a mock function with given fields: offset, limit, qopts +func (_m *ORM) GetAllowedSenders(offset uint, limit uint, qopts ...pg.QOpt) ([]common.Address, error) { + _va := make([]interface{}, len(qopts)) + for _i := range qopts { + _va[_i] = qopts[_i] + } + var _ca []interface{} + _ca = append(_ca, offset, limit) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for GetAllowedSenders") + } + + var r0 []common.Address + var r1 error + if rf, ok := ret.Get(0).(func(uint, uint, ...pg.QOpt) ([]common.Address, error)); ok { + return rf(offset, limit, qopts...) + } + if rf, ok := ret.Get(0).(func(uint, uint, ...pg.QOpt) []common.Address); ok { + r0 = rf(offset, limit, qopts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]common.Address) + } + } + + if rf, ok := ret.Get(1).(func(uint, uint, ...pg.QOpt) error); ok { + r1 = rf(offset, limit, qopts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewORM creates a new instance of ORM. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewORM(t interface { + mock.TestingT + Cleanup(func()) +}) *ORM { + mock := &ORM{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/gateway/handlers/functions/allowlist/orm.go b/core/services/gateway/handlers/functions/allowlist/orm.go new file mode 100644 index 00000000000..07ee1ea3b3b --- /dev/null +++ b/core/services/gateway/handlers/functions/allowlist/orm.go @@ -0,0 +1,123 @@ +package allowlist + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/jmoiron/sqlx" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" +) + +//go:generate mockery --quiet --name ORM --output ./mocks/ --case=underscore +type ORM interface { + GetAllowedSenders(offset, limit uint, qopts ...pg.QOpt) ([]common.Address, error) + CreateAllowedSenders(allowedSenders []common.Address, qopts ...pg.QOpt) error + DeleteAllowedSenders(blockedSenders []common.Address, qopts ...pg.QOpt) error +} + +type orm struct { + q pg.Q + lggr logger.Logger + routerContractAddress common.Address +} + +var _ ORM = (*orm)(nil) +var ( + ErrInvalidParameters = errors.New("invalid parameters provided to create a functions contract cache ORM") +) + +const ( + tableName = "functions_allowlist" +) + +func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig, routerContractAddress common.Address) (ORM, error) { + if db == nil || cfg == nil || lggr == nil || routerContractAddress == (common.Address{}) { + return nil, ErrInvalidParameters + } + + return &orm{ + q: pg.NewQ(db, lggr, cfg), + lggr: lggr, + routerContractAddress: routerContractAddress, + }, nil +} + +func (o *orm) GetAllowedSenders(offset, limit uint, qopts ...pg.QOpt) ([]common.Address, error) { + var addresses []common.Address + stmt := fmt.Sprintf(` + SELECT allowed_address + FROM %s + WHERE router_contract_address = $1 + ORDER BY id ASC + OFFSET $2 + LIMIT $3; + `, tableName) + err := o.q.WithOpts(qopts...).Select(&addresses, stmt, o.routerContractAddress, offset, limit) + if err != nil { + return addresses, err + } + o.lggr.Debugf("Successfully fetched allowed sender list from DB. offset: %d, limit: %d, length: %d", offset, limit, len(addresses)) + + return addresses, nil +} + +func (o *orm) CreateAllowedSenders(allowedSenders []common.Address, qopts ...pg.QOpt) error { + var valuesPlaceholder []string + for i := 1; i <= len(allowedSenders)*2; i += 2 { + valuesPlaceholder = append(valuesPlaceholder, fmt.Sprintf("($%d, $%d)", i, i+1)) + } + + stmt := fmt.Sprintf(` + INSERT INTO %s (allowed_address, router_contract_address) + VALUES %s ON CONFLICT (allowed_address, router_contract_address) DO NOTHING;`, tableName, strings.Join(valuesPlaceholder, ", ")) + + var args []interface{} + for _, as := range allowedSenders { + args = append(args, as, o.routerContractAddress) + } + + _, err := o.q.WithOpts(qopts...).Exec(stmt, args...) + if err != nil { + return err + } + + o.lggr.Debugf("Successfully stored allowed senders: %v for routerContractAddress: %s", allowedSenders, o.routerContractAddress) + + return nil +} + +func (o *orm) DeleteAllowedSenders(blockedSenders []common.Address, qopts ...pg.QOpt) error { + var valuesPlaceholder []string + for i := 1; i <= len(blockedSenders); i++ { + valuesPlaceholder = append(valuesPlaceholder, fmt.Sprintf("$%d", i+1)) + } + + stmt := fmt.Sprintf(` + DELETE FROM %s + WHERE router_contract_address = $1 + AND allowed_address IN (%s);`, tableName, strings.Join(valuesPlaceholder, ", ")) + + args := []interface{}{o.routerContractAddress} + for _, bs := range blockedSenders { + args = append(args, bs) + } + + res, err := o.q.WithOpts(qopts...).Exec(stmt, args...) + if err != nil { + return err + } + + rowsAffected, err := res.RowsAffected() + if err != nil { + return err + } + + o.lggr.Debugf("Successfully removed blocked senders from the allowed list: %v for routerContractAddress: %s. rowsAffected: %d", blockedSenders, o.routerContractAddress, rowsAffected) + + return nil +} diff --git a/core/services/gateway/handlers/functions/allowlist/orm_test.go b/core/services/gateway/handlers/functions/allowlist/orm_test.go new file mode 100644 index 00000000000..0f63e83cd5f --- /dev/null +++ b/core/services/gateway/handlers/functions/allowlist/orm_test.go @@ -0,0 +1,190 @@ +package allowlist_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" +) + +func setupORM(t *testing.T) (allowlist.ORM, error) { + t.Helper() + + var ( + db = pgtest.NewSqlxDB(t) + lggr = logger.TestLogger(t) + ) + + return allowlist.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) +} + +func seedAllowedSenders(t *testing.T, orm allowlist.ORM, amount int) []common.Address { + storedAllowedSenders := make([]common.Address, amount) + for i := 0; i < amount; i++ { + address := testutils.NewAddress() + storedAllowedSenders[i] = address + } + + err := orm.CreateAllowedSenders(storedAllowedSenders) + require.NoError(t, err) + + return storedAllowedSenders +} +func TestORM_GetAllowedSenders(t *testing.T) { + t.Parallel() + t.Run("fetch first page", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + storedAllowedSenders := seedAllowedSenders(t, orm, 2) + results, err := orm.GetAllowedSenders(0, 1) + require.NoError(t, err) + require.Equal(t, 1, len(results), "incorrect results length") + require.Equal(t, storedAllowedSenders[0], results[0]) + }) + + t.Run("fetch second page", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + storedAllowedSenders := seedAllowedSenders(t, orm, 2) + results, err := orm.GetAllowedSenders(1, 5) + require.NoError(t, err) + require.Equal(t, 1, len(results), "incorrect results length") + require.Equal(t, storedAllowedSenders[1], results[0]) + }) +} + +func TestORM_CreateAllowedSenders(t *testing.T) { + t.Parallel() + + t.Run("OK-create_an_allowed_sender", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + expected := testutils.NewAddress() + err = orm.CreateAllowedSenders([]common.Address{expected}) + require.NoError(t, err) + + results, err := orm.GetAllowedSenders(0, 1) + require.NoError(t, err) + require.Equal(t, 1, len(results), "incorrect results length") + require.Equal(t, expected, results[0]) + }) + + t.Run("OK-create_an_existing_allowed_sender", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + expected := testutils.NewAddress() + err = orm.CreateAllowedSenders([]common.Address{expected}) + require.NoError(t, err) + + err = orm.CreateAllowedSenders([]common.Address{expected}) + require.NoError(t, err) + + results, err := orm.GetAllowedSenders(0, 5) + require.NoError(t, err) + require.Equal(t, 1, len(results), "incorrect results length") + require.Equal(t, expected, results[0]) + }) + + t.Run("OK-create_multiple_allowed_senders_in_one_query", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + expected := []common.Address{testutils.NewAddress(), testutils.NewAddress()} + err = orm.CreateAllowedSenders(expected) + require.NoError(t, err) + + results, err := orm.GetAllowedSenders(0, 2) + require.NoError(t, err) + require.Equal(t, 2, len(results), "incorrect results length") + require.Equal(t, expected[0], results[0]) + require.Equal(t, expected[1], results[1]) + }) + + t.Run("OK-create_multiple_allowed_senders_with_duplicates", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + addr1 := testutils.NewAddress() + addr2 := testutils.NewAddress() + expected := []common.Address{addr1, addr2} + + duplicatedAddressInput := []common.Address{addr1, addr1, addr1, addr2} + err = orm.CreateAllowedSenders(duplicatedAddressInput) + require.NoError(t, err) + + results, err := orm.GetAllowedSenders(0, 10) + require.NoError(t, err) + require.Equal(t, 2, len(results), "incorrect results length") + require.Equal(t, expected[0], results[0]) + require.Equal(t, expected[1], results[1]) + }) +} + +func TestORM_DeleteAllowedSenders(t *testing.T) { + t.Parallel() + + t.Run("OK-delete_blocked_sender_from_allowed_list", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + add1 := testutils.NewAddress() + add2 := testutils.NewAddress() + add3 := testutils.NewAddress() + err = orm.CreateAllowedSenders([]common.Address{add1, add2, add3}) + require.NoError(t, err) + + results, err := orm.GetAllowedSenders(0, 10) + require.NoError(t, err) + require.Equal(t, 3, len(results), "incorrect results length") + require.Equal(t, add1, results[0]) + + err = orm.DeleteAllowedSenders([]common.Address{add1, add3}) + require.NoError(t, err) + + results, err = orm.GetAllowedSenders(0, 10) + require.NoError(t, err) + require.Equal(t, 1, len(results), "incorrect results length") + require.Equal(t, add2, results[0]) + }) + + t.Run("OK-delete_non_existing_blocked_sender_from_allowed_list", func(t *testing.T) { + orm, err := setupORM(t) + require.NoError(t, err) + add1 := testutils.NewAddress() + add2 := testutils.NewAddress() + err = orm.CreateAllowedSenders([]common.Address{add1, add2}) + require.NoError(t, err) + + results, err := orm.GetAllowedSenders(0, 10) + require.NoError(t, err) + require.Equal(t, 2, len(results), "incorrect results length") + require.Equal(t, add1, results[0]) + + add3 := testutils.NewAddress() + err = orm.DeleteAllowedSenders([]common.Address{add3}) + require.NoError(t, err) + + results, err = orm.GetAllowedSenders(0, 10) + require.NoError(t, err) + require.Equal(t, 2, len(results), "incorrect results length") + require.Equal(t, add1, results[0]) + require.Equal(t, add2, results[1]) + }) +} + +func Test_NewORM(t *testing.T) { + t.Run("OK-create_ORM", func(t *testing.T) { + _, err := allowlist.NewORM(pgtest.NewSqlxDB(t), logger.TestLogger(t), pgtest.NewQConfig(true), testutils.NewAddress()) + require.NoError(t, err) + }) + t.Run("NOK-create_ORM_with_nil_fields", func(t *testing.T) { + _, err := allowlist.NewORM(nil, nil, nil, common.Address{}) + require.Error(t, err) + }) + t.Run("NOK-create_ORM_with_empty_address", func(t *testing.T) { + _, err := allowlist.NewORM(pgtest.NewSqlxDB(t), logger.TestLogger(t), pgtest.NewQConfig(true), common.Address{}) + require.Error(t, err) + }) +} diff --git a/core/services/gateway/handlers/functions/allowlist_test.go b/core/services/gateway/handlers/functions/allowlist_test.go deleted file mode 100644 index fdaea81b2c0..00000000000 --- a/core/services/gateway/handlers/functions/allowlist_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package functions_test - -import ( - "context" - "encoding/hex" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/onsi/gomega" - "github.com/stretchr/testify/assert" - "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/internal/testutils" - "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) - config := functions.OnchainAllowlistConfig{ - ContractVersion: 1, - ContractAddress: common.Address{}, - BlockConfirmations: 1, - } - allowlist, err := functions.NewOnchainAllowlist(client, config, logger.TestLogger(t)) - require.NoError(t, err) - - err = allowlist.Start(testutils.Context(t)) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, allowlist.Close()) - }) - - require.NoError(t, allowlist.UpdateFromContract(testutils.Context(t))) - 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_UnsupportedVersion(t *testing.T) { - t.Parallel() - - client := mocks.NewClient(t) - config := functions.OnchainAllowlistConfig{ - ContractVersion: 0, - ContractAddress: common.Address{}, - BlockConfirmations: 1, - } - _, err := functions.NewOnchainAllowlist(client, config, logger.TestLogger(t)) - require.Error(t, err) -} - -func TestAllowlist_UpdatePeriodically(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithCancel(testutils.Context(t)) - 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) - config := functions.OnchainAllowlistConfig{ - ContractAddress: common.Address{}, - ContractVersion: 1, - BlockConfirmations: 1, - UpdateFrequencySec: 1, - UpdateTimeoutSec: 1, - } - allowlist, err := functions.NewOnchainAllowlist(client, config, logger.TestLogger(t)) - require.NoError(t, err) - - err = allowlist.Start(ctx) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, allowlist.Close()) - }) - - gomega.NewGomegaWithT(t).Eventually(func() bool { - return allowlist.Allow(common.HexToAddress(addr1)) && !allowlist.Allow(common.HexToAddress(addr3)) - }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) -} diff --git a/core/services/gateway/handlers/functions/handler.functions.go b/core/services/gateway/handlers/functions/handler.functions.go index 2872c72f761..ff272e4e577 100644 --- a/core/services/gateway/handlers/functions/handler.functions.go +++ b/core/services/gateway/handlers/functions/handler.functions.go @@ -23,6 +23,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" + fallow "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" + fsub "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" "github.com/smartcontractkit/chainlink/v2/core/services/pg" ) @@ -60,10 +62,10 @@ var ( type FunctionsHandlerConfig struct { ChainID string `json:"chainId"` // Not specifying OnchainAllowlist config disables allowlist checks - OnchainAllowlist *OnchainAllowlistConfig `json:"onchainAllowlist"` + OnchainAllowlist *fallow.OnchainAllowlistConfig `json:"onchainAllowlist"` // Not specifying OnchainSubscriptions config disables minimum balance checks - OnchainSubscriptions *OnchainSubscriptionsConfig `json:"onchainSubscriptions"` - MinimumSubscriptionBalance *assets.Link `json:"minimumSubscriptionBalance"` + OnchainSubscriptions *fsub.OnchainSubscriptionsConfig `json:"onchainSubscriptions"` + MinimumSubscriptionBalance *assets.Link `json:"minimumSubscriptionBalance"` // Not specifying RateLimiter config disables rate limiting UserRateLimiter *hc.RateLimiterConfig `json:"userRateLimiter"` NodeRateLimiter *hc.RateLimiterConfig `json:"nodeRateLimiter"` @@ -79,8 +81,8 @@ type functionsHandler struct { donConfig *config.DONConfig don handlers.DON pendingRequests hc.RequestCache[PendingRequest] - allowlist OnchainAllowlist - subscriptions OnchainSubscriptions + allowlist fallow.OnchainAllowlist + subscriptions fsub.OnchainSubscriptions minimumBalance *assets.Link userRateLimiter *hc.RateLimiter nodeRateLimiter *hc.RateLimiter @@ -105,13 +107,18 @@ func NewFunctionsHandlerFromConfig(handlerConfig json.RawMessage, donConfig *con return nil, err } lggr = lggr.Named("FunctionsHandler:" + donConfig.DonId) - var allowlist OnchainAllowlist + var allowlist fallow.OnchainAllowlist if cfg.OnchainAllowlist != nil { chain, err2 := legacyChains.Get(cfg.ChainID) if err2 != nil { return nil, err2 } - allowlist, err2 = NewOnchainAllowlist(chain.Client(), *cfg.OnchainAllowlist, lggr) + + orm, err2 := fallow.NewORM(db, lggr, qcfg, cfg.OnchainAllowlist.ContractAddress) + if err2 != nil { + return nil, err2 + } + allowlist, err2 = fallow.NewOnchainAllowlist(chain.Client(), *cfg.OnchainAllowlist, orm, lggr) if err2 != nil { return nil, err2 } @@ -129,19 +136,19 @@ func NewFunctionsHandlerFromConfig(handlerConfig json.RawMessage, donConfig *con return nil, err } } - var subscriptions OnchainSubscriptions + var subscriptions fsub.OnchainSubscriptions if cfg.OnchainSubscriptions != nil { chain, err2 := legacyChains.Get(cfg.ChainID) if err2 != nil { return nil, err2 } - orm, err2 := NewORM(db, lggr, qcfg, cfg.OnchainSubscriptions.ContractAddress) + orm, err2 := fsub.NewORM(db, lggr, qcfg, cfg.OnchainSubscriptions.ContractAddress) if err2 != nil { return nil, err2 } - subscriptions, err2 = NewOnchainSubscriptions(chain.Client(), *cfg.OnchainSubscriptions, orm, lggr) + subscriptions, err2 = fsub.NewOnchainSubscriptions(chain.Client(), *cfg.OnchainSubscriptions, orm, lggr) if err2 != nil { return nil, err2 } @@ -159,8 +166,8 @@ func NewFunctionsHandler( donConfig *config.DONConfig, don handlers.DON, pendingRequestsCache hc.RequestCache[PendingRequest], - allowlist OnchainAllowlist, - subscriptions OnchainSubscriptions, + allowlist fallow.OnchainAllowlist, + subscriptions fsub.OnchainSubscriptions, minimumBalance *assets.Link, userRateLimiter *hc.RateLimiter, nodeRateLimiter *hc.RateLimiter, diff --git a/core/services/gateway/handlers/functions/handler.functions_test.go b/core/services/gateway/handlers/functions/handler.functions_test.go index 7e055815c88..2e2c1c77caf 100644 --- a/core/services/gateway/handlers/functions/handler.functions_test.go +++ b/core/services/gateway/handlers/functions/handler.functions_test.go @@ -22,11 +22,12 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" - functions_mocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/mocks" + allowlist_mocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist/mocks" + subscriptions_mocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions/mocks" handlers_mocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/mocks" ) -func newFunctionsHandlerForATestDON(t *testing.T, nodes []gc.TestNode, requestTimeout time.Duration, heartbeatSender string) (handlers.Handler, *handlers_mocks.DON, *functions_mocks.OnchainAllowlist, *functions_mocks.OnchainSubscriptions) { +func newFunctionsHandlerForATestDON(t *testing.T, nodes []gc.TestNode, requestTimeout time.Duration, heartbeatSender string) (handlers.Handler, *handlers_mocks.DON, *allowlist_mocks.OnchainAllowlist, *subscriptions_mocks.OnchainSubscriptions) { cfg := functions.FunctionsHandlerConfig{} donConfig := &config.DONConfig{ Members: []config.NodeConfig{}, @@ -41,8 +42,8 @@ func newFunctionsHandlerForATestDON(t *testing.T, nodes []gc.TestNode, requestTi } don := handlers_mocks.NewDON(t) - allowlist := functions_mocks.NewOnchainAllowlist(t) - subscriptions := functions_mocks.NewOnchainSubscriptions(t) + allowlist := allowlist_mocks.NewOnchainAllowlist(t) + subscriptions := subscriptions_mocks.NewOnchainSubscriptions(t) minBalance := assets.NewLinkFromJuels(100) userRateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) require.NoError(t, err) diff --git a/core/services/gateway/handlers/functions/mocks/onchain_subscriptions.go b/core/services/gateway/handlers/functions/subscriptions/mocks/onchain_subscriptions.go similarity index 100% rename from core/services/gateway/handlers/functions/mocks/onchain_subscriptions.go rename to core/services/gateway/handlers/functions/subscriptions/mocks/onchain_subscriptions.go diff --git a/core/services/gateway/handlers/functions/mocks/orm.go b/core/services/gateway/handlers/functions/subscriptions/mocks/orm.go similarity index 73% rename from core/services/gateway/handlers/functions/mocks/orm.go rename to core/services/gateway/handlers/functions/subscriptions/mocks/orm.go index 110675c8b55..0f278aa49b0 100644 --- a/core/services/gateway/handlers/functions/mocks/orm.go +++ b/core/services/gateway/handlers/functions/subscriptions/mocks/orm.go @@ -3,10 +3,9 @@ package mocks import ( - functions "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" - mock "github.com/stretchr/testify/mock" - + subscriptions "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" pg "github.com/smartcontractkit/chainlink/v2/core/services/pg" + mock "github.com/stretchr/testify/mock" ) // ORM is an autogenerated mock type for the ORM type @@ -15,7 +14,7 @@ type ORM struct { } // GetSubscriptions provides a mock function with given fields: offset, limit, qopts -func (_m *ORM) GetSubscriptions(offset uint, limit uint, qopts ...pg.QOpt) ([]functions.CachedSubscription, error) { +func (_m *ORM) GetSubscriptions(offset uint, limit uint, qopts ...pg.QOpt) ([]subscriptions.StoredSubscription, error) { _va := make([]interface{}, len(qopts)) for _i := range qopts { _va[_i] = qopts[_i] @@ -29,16 +28,16 @@ func (_m *ORM) GetSubscriptions(offset uint, limit uint, qopts ...pg.QOpt) ([]fu panic("no return value specified for GetSubscriptions") } - var r0 []functions.CachedSubscription + var r0 []subscriptions.StoredSubscription var r1 error - if rf, ok := ret.Get(0).(func(uint, uint, ...pg.QOpt) ([]functions.CachedSubscription, error)); ok { + if rf, ok := ret.Get(0).(func(uint, uint, ...pg.QOpt) ([]subscriptions.StoredSubscription, error)); ok { return rf(offset, limit, qopts...) } - if rf, ok := ret.Get(0).(func(uint, uint, ...pg.QOpt) []functions.CachedSubscription); ok { + if rf, ok := ret.Get(0).(func(uint, uint, ...pg.QOpt) []subscriptions.StoredSubscription); ok { r0 = rf(offset, limit, qopts...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]functions.CachedSubscription) + r0 = ret.Get(0).([]subscriptions.StoredSubscription) } } @@ -52,7 +51,7 @@ func (_m *ORM) GetSubscriptions(offset uint, limit uint, qopts ...pg.QOpt) ([]fu } // UpsertSubscription provides a mock function with given fields: subscription, qopts -func (_m *ORM) UpsertSubscription(subscription functions.CachedSubscription, qopts ...pg.QOpt) error { +func (_m *ORM) UpsertSubscription(subscription subscriptions.StoredSubscription, qopts ...pg.QOpt) error { _va := make([]interface{}, len(qopts)) for _i := range qopts { _va[_i] = qopts[_i] @@ -67,7 +66,7 @@ func (_m *ORM) UpsertSubscription(subscription functions.CachedSubscription, qop } var r0 error - if rf, ok := ret.Get(0).(func(functions.CachedSubscription, ...pg.QOpt) error); ok { + if rf, ok := ret.Get(0).(func(subscriptions.StoredSubscription, ...pg.QOpt) error); ok { r0 = rf(subscription, qopts...) } else { r0 = ret.Error(0) diff --git a/core/services/gateway/handlers/functions/orm.go b/core/services/gateway/handlers/functions/subscriptions/orm.go similarity index 73% rename from core/services/gateway/handlers/functions/orm.go rename to core/services/gateway/handlers/functions/subscriptions/orm.go index b7ec8d865d1..369291ace54 100644 --- a/core/services/gateway/handlers/functions/orm.go +++ b/core/services/gateway/handlers/functions/subscriptions/orm.go @@ -1,4 +1,4 @@ -package functions +package subscriptions import ( "fmt" @@ -16,27 +16,27 @@ import ( ) //go:generate mockery --quiet --name ORM --output ./mocks/ --case=underscore - type ORM interface { - GetSubscriptions(offset, limit uint, qopts ...pg.QOpt) ([]CachedSubscription, error) - UpsertSubscription(subscription CachedSubscription, qopts ...pg.QOpt) error + GetSubscriptions(offset, limit uint, qopts ...pg.QOpt) ([]StoredSubscription, error) + UpsertSubscription(subscription StoredSubscription, qopts ...pg.QOpt) error } type orm struct { q pg.Q + lggr logger.Logger routerContractAddress common.Address } var _ ORM = (*orm)(nil) var ( - ErrInvalidParameters = errors.New("invalid parameters provided to create a subscription cache ORM") + ErrInvalidParameters = errors.New("invalid parameters provided to create a subscription contract ORM") ) const ( tableName = "functions_subscriptions" ) -type cachedSubscriptionRow struct { +type storedSubscriptionRow struct { SubscriptionID uint64 Owner common.Address Balance int64 @@ -54,13 +54,14 @@ func NewORM(db *sqlx.DB, lggr logger.Logger, cfg pg.QConfig, routerContractAddre return &orm{ q: pg.NewQ(db, lggr, cfg), + lggr: lggr, routerContractAddress: routerContractAddress, }, nil } -func (o *orm) GetSubscriptions(offset, limit uint, qopts ...pg.QOpt) ([]CachedSubscription, error) { - var cacheSubscriptions []CachedSubscription - var cacheSubscriptionRows []cachedSubscriptionRow +func (o *orm) GetSubscriptions(offset, limit uint, qopts ...pg.QOpt) ([]StoredSubscription, error) { + var storedSubscriptions []StoredSubscription + var storedSubscriptionRows []storedSubscriptionRow stmt := fmt.Sprintf(` SELECT subscription_id, owner, balance, blocked_balance, proposed_owner, consumers, flags, router_contract_address FROM %s @@ -69,21 +70,21 @@ func (o *orm) GetSubscriptions(offset, limit uint, qopts ...pg.QOpt) ([]CachedSu OFFSET $2 LIMIT $3; `, tableName) - err := o.q.WithOpts(qopts...).Select(&cacheSubscriptionRows, stmt, o.routerContractAddress, offset, limit) + err := o.q.WithOpts(qopts...).Select(&storedSubscriptionRows, stmt, o.routerContractAddress, offset, limit) if err != nil { - return cacheSubscriptions, err + return storedSubscriptions, err } - for _, cs := range cacheSubscriptionRows { - cacheSubscriptions = append(cacheSubscriptions, cs.encode()) + for _, cs := range storedSubscriptionRows { + storedSubscriptions = append(storedSubscriptions, cs.encode()) } - return cacheSubscriptions, nil + return storedSubscriptions, nil } // UpsertSubscription will update if a subscription exists or create if it does not. // In case a subscription gets deleted we will update it with an owner address equal to 0x0. -func (o *orm) UpsertSubscription(subscription CachedSubscription, qopts ...pg.QOpt) error { +func (o *orm) UpsertSubscription(subscription StoredSubscription, qopts ...pg.QOpt) error { stmt := fmt.Sprintf(` INSERT INTO %s (subscription_id, owner, balance, blocked_balance, proposed_owner, consumers, flags, router_contract_address) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT (subscription_id, router_contract_address) DO UPDATE @@ -97,6 +98,11 @@ func (o *orm) UpsertSubscription(subscription CachedSubscription, qopts ...pg.QO subscription.BlockedBalance = big.NewInt(0) } + var consumers [][]byte + for _, c := range subscription.Consumers { + consumers = append(consumers, c.Bytes()) + } + _, err := o.q.WithOpts(qopts...).Exec( stmt, subscription.SubscriptionID, @@ -104,21 +110,26 @@ func (o *orm) UpsertSubscription(subscription CachedSubscription, qopts ...pg.QO subscription.Balance.Int64(), subscription.BlockedBalance.Int64(), subscription.ProposedOwner, - subscription.Consumers, + consumers, subscription.Flags[:], o.routerContractAddress, ) + if err != nil { + return err + } + + o.lggr.Debugf("Successfully updated subscription: %d for routerContractAddress: %s", subscription.SubscriptionID, o.routerContractAddress) - return err + return nil } -func (cs *cachedSubscriptionRow) encode() CachedSubscription { +func (cs *storedSubscriptionRow) encode() StoredSubscription { consumers := make([]common.Address, 0) for _, csc := range cs.Consumers { consumers = append(consumers, common.BytesToAddress(csc)) } - return CachedSubscription{ + return StoredSubscription{ SubscriptionID: cs.SubscriptionID, IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: big.NewInt(cs.Balance), diff --git a/core/services/gateway/handlers/functions/orm_test.go b/core/services/gateway/handlers/functions/subscriptions/orm_test.go similarity index 81% rename from core/services/gateway/handlers/functions/orm_test.go rename to core/services/gateway/handlers/functions/subscriptions/orm_test.go index 37ec24ea0b1..6cb1146f03c 100644 --- a/core/services/gateway/handlers/functions/orm_test.go +++ b/core/services/gateway/handlers/functions/subscriptions/orm_test.go @@ -1,4 +1,4 @@ -package functions_test +package subscriptions_test import ( "math/big" @@ -12,14 +12,14 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" ) var ( defaultFlags = [32]byte{0x1, 0x2, 0x3} ) -func setupORM(t *testing.T) (functions.ORM, error) { +func setupORM(t *testing.T) (subscriptions.ORM, error) { t.Helper() var ( @@ -27,13 +27,13 @@ func setupORM(t *testing.T) (functions.ORM, error) { lggr = logger.TestLogger(t) ) - return functions.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) + return subscriptions.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) } -func createSubscriptions(t *testing.T, orm functions.ORM, amount int) []functions.CachedSubscription { - cachedSubscriptions := make([]functions.CachedSubscription, 0) +func seedSubscriptions(t *testing.T, orm subscriptions.ORM, amount int) []subscriptions.StoredSubscription { + storedSubscriptions := make([]subscriptions.StoredSubscription, 0) for i := amount; i > 0; i-- { - cs := functions.CachedSubscription{ + cs := subscriptions.StoredSubscription{ SubscriptionID: uint64(i), IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: big.NewInt(10), @@ -44,11 +44,11 @@ func createSubscriptions(t *testing.T, orm functions.ORM, amount int) []function Flags: defaultFlags, }, } - cachedSubscriptions = append(cachedSubscriptions, cs) + storedSubscriptions = append(storedSubscriptions, cs) err := orm.UpsertSubscription(cs) require.NoError(t, err) } - return cachedSubscriptions + return storedSubscriptions } func TestORM_GetSubscriptions(t *testing.T) { @@ -56,21 +56,21 @@ func TestORM_GetSubscriptions(t *testing.T) { t.Run("fetch first page", func(t *testing.T) { orm, err := setupORM(t) require.NoError(t, err) - cachedSubscriptions := createSubscriptions(t, orm, 2) + storedSubscriptions := seedSubscriptions(t, orm, 2) results, err := orm.GetSubscriptions(0, 1) require.NoError(t, err) require.Equal(t, 1, len(results), "incorrect results length") - require.Equal(t, cachedSubscriptions[1], results[0]) + require.Equal(t, storedSubscriptions[1], results[0]) }) t.Run("fetch second page", func(t *testing.T) { orm, err := setupORM(t) require.NoError(t, err) - cachedSubscriptions := createSubscriptions(t, orm, 2) + storedSubscriptions := seedSubscriptions(t, orm, 2) results, err := orm.GetSubscriptions(1, 5) require.NoError(t, err) require.Equal(t, 1, len(results), "incorrect results length") - require.Equal(t, cachedSubscriptions[0], results[0]) + require.Equal(t, storedSubscriptions[0], results[0]) }) } @@ -80,14 +80,14 @@ func TestORM_UpsertSubscription(t *testing.T) { t.Run("create a subscription", func(t *testing.T) { orm, err := setupORM(t) require.NoError(t, err) - expected := functions.CachedSubscription{ + expected := subscriptions.StoredSubscription{ SubscriptionID: uint64(1), IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: big.NewInt(10), Owner: testutils.NewAddress(), BlockedBalance: big.NewInt(20), ProposedOwner: common.Address{}, - Consumers: []common.Address{}, + Consumers: []common.Address{testutils.NewAddress()}, Flags: defaultFlags, }, } @@ -104,7 +104,7 @@ func TestORM_UpsertSubscription(t *testing.T) { orm, err := setupORM(t) require.NoError(t, err) - expectedUpdated := functions.CachedSubscription{ + expectedUpdated := subscriptions.StoredSubscription{ SubscriptionID: uint64(1), IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: big.NewInt(10), @@ -118,7 +118,7 @@ func TestORM_UpsertSubscription(t *testing.T) { err = orm.UpsertSubscription(expectedUpdated) require.NoError(t, err) - expectedNotUpdated := functions.CachedSubscription{ + expectedNotUpdated := subscriptions.StoredSubscription{ SubscriptionID: uint64(2), IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: big.NewInt(10), @@ -148,7 +148,7 @@ func TestORM_UpsertSubscription(t *testing.T) { orm, err := setupORM(t) require.NoError(t, err) - subscription := functions.CachedSubscription{ + subscription := subscriptions.StoredSubscription{ SubscriptionID: uint64(1), IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: big.NewInt(10), @@ -187,12 +187,12 @@ func TestORM_UpsertSubscription(t *testing.T) { lggr = logger.TestLogger(t) ) - orm1, err := functions.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) + orm1, err := subscriptions.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) require.NoError(t, err) - orm2, err := functions.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) + orm2, err := subscriptions.NewORM(db, lggr, pgtest.NewQConfig(true), testutils.NewAddress()) require.NoError(t, err) - subscription := functions.CachedSubscription{ + subscription := subscriptions.StoredSubscription{ SubscriptionID: uint64(1), IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: assets.Ether(10).ToInt(), @@ -229,18 +229,17 @@ func TestORM_UpsertSubscription(t *testing.T) { require.Equal(t, 1, len(results), "incorrect results length") }) } - func Test_NewORM(t *testing.T) { t.Run("OK-create_ORM", func(t *testing.T) { - _, err := functions.NewORM(pgtest.NewSqlxDB(t), logger.TestLogger(t), pgtest.NewQConfig(true), testutils.NewAddress()) + _, err := subscriptions.NewORM(pgtest.NewSqlxDB(t), logger.TestLogger(t), pgtest.NewQConfig(true), testutils.NewAddress()) require.NoError(t, err) }) t.Run("NOK-create_ORM_with_nil_fields", func(t *testing.T) { - _, err := functions.NewORM(nil, nil, nil, common.Address{}) + _, err := subscriptions.NewORM(nil, nil, nil, common.Address{}) require.Error(t, err) }) t.Run("NOK-create_ORM_with_empty_address", func(t *testing.T) { - _, err := functions.NewORM(pgtest.NewSqlxDB(t), logger.TestLogger(t), pgtest.NewQConfig(true), common.Address{}) + _, err := subscriptions.NewORM(pgtest.NewSqlxDB(t), logger.TestLogger(t), pgtest.NewQConfig(true), common.Address{}) require.Error(t, err) }) } diff --git a/core/services/gateway/handlers/functions/subscriptions.go b/core/services/gateway/handlers/functions/subscriptions/subscriptions.go similarity index 90% rename from core/services/gateway/handlers/functions/subscriptions.go rename to core/services/gateway/handlers/functions/subscriptions/subscriptions.go index 8bc9cf09e7b..610aaa1d7f1 100644 --- a/core/services/gateway/handlers/functions/subscriptions.go +++ b/core/services/gateway/handlers/functions/subscriptions/subscriptions.go @@ -1,4 +1,4 @@ -package functions +package subscriptions import ( "context" @@ -19,7 +19,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/utils" ) -const defaultCacheBatchSize = 100 +const defaultStoreBatchSize = 100 type OnchainSubscriptionsConfig struct { ContractAddress common.Address `json:"contractAddress"` @@ -27,7 +27,7 @@ type OnchainSubscriptionsConfig struct { UpdateFrequencySec uint `json:"updateFrequencySec"` UpdateTimeoutSec uint `json:"updateTimeoutSec"` UpdateRangeSize uint `json:"updateRangeSize"` - CacheBatchSize uint `json:"cacheBatchSize"` + StoreBatchSize uint `json:"storeBatchSize"` } // OnchainSubscriptions maintains a mirror of all subscriptions fetched from the blockchain (EVM-only). @@ -68,10 +68,10 @@ func NewOnchainSubscriptions(client evmclient.Client, config OnchainSubscription return nil, fmt.Errorf("unexpected error during functions_router.NewFunctionsRouter: %s", err) } - // if CacheBatchSize is not specified use the default value - if config.CacheBatchSize == 0 { - lggr.Info("CacheBatchSize not specified, using default size: ", defaultCacheBatchSize) - config.CacheBatchSize = defaultCacheBatchSize + // if StoreBatchSize is not specified use the default value + if config.StoreBatchSize == 0 { + lggr.Info("StoreBatchSize not specified, using default size: ", defaultStoreBatchSize) + config.StoreBatchSize = defaultStoreBatchSize } return &onchainSubscriptions{ @@ -99,7 +99,7 @@ func (s *onchainSubscriptions) Start(ctx context.Context) error { return errors.New("OnchainSubscriptionsConfig.UpdateRangeSize must be greater than 0") } - s.loadCachedSubscriptions() + s.loadStoredSubscriptions() s.closeWait.Add(1) go s.queryLoop() @@ -206,11 +206,11 @@ func (s *onchainSubscriptions) querySubscriptionsRange(ctx context.Context, bloc subscription := subscription updated := s.subscriptions.UpdateSubscription(subscriptionId, &subscription) if updated { - if err = s.orm.UpsertSubscription(CachedSubscription{ + if err = s.orm.UpsertSubscription(StoredSubscription{ SubscriptionID: subscriptionId, IFunctionsSubscriptionsSubscription: subscription, }); err != nil { - s.lggr.Errorf("unexpected error updating subscription in the cache: %w", err) + s.lggr.Errorf("unexpected error updating subscription in the db: %w", err) } } } @@ -226,10 +226,10 @@ func (s *onchainSubscriptions) getSubscriptionsCount(ctx context.Context, blockN }) } -func (s *onchainSubscriptions) loadCachedSubscriptions() { +func (s *onchainSubscriptions) loadStoredSubscriptions() { offset := uint(0) for { - csBatch, err := s.orm.GetSubscriptions(offset, s.config.CacheBatchSize) + csBatch, err := s.orm.GetSubscriptions(offset, s.config.StoreBatchSize) if err != nil { break } @@ -244,11 +244,11 @@ func (s *onchainSubscriptions) loadCachedSubscriptions() { Flags: cs.Flags, }) } - s.lggr.Debugw("Loading cached subscriptions", "offset", offset, "batch_length", len(csBatch)) + s.lggr.Debugw("Loading stored subscriptions", "offset", offset, "batch_length", len(csBatch)) - if len(csBatch) != int(s.config.CacheBatchSize) { + if len(csBatch) != int(s.config.StoreBatchSize) { break } - offset += s.config.CacheBatchSize + offset += s.config.StoreBatchSize } } diff --git a/core/services/gateway/handlers/functions/subscriptions_test.go b/core/services/gateway/handlers/functions/subscriptions/subscriptions_test.go similarity index 89% rename from core/services/gateway/handlers/functions/subscriptions_test.go rename to core/services/gateway/handlers/functions/subscriptions/subscriptions_test.go index 782dcc2332d..be1d2520434 100644 --- a/core/services/gateway/handlers/functions/subscriptions_test.go +++ b/core/services/gateway/handlers/functions/subscriptions/subscriptions_test.go @@ -1,4 +1,4 @@ -package functions_test +package subscriptions_test import ( "math/big" @@ -18,14 +18,14 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_router" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" - fmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" + smocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions/mocks" ) const ( validUser = "0x9ED925d8206a4f88a2f643b28B3035B315753Cd6" invalidUser = "0x6E2dc0F9DB014aE19888F539E59285D2Ea04244C" - cachedUser = "0x3E2dc0F9DB014aE19888F539E59285D2Ea04233G" + storedUser = "0x3E2dc0F9DB014aE19888F539E59285D2Ea04233G" ) func TestSubscriptions_OnePass(t *testing.T) { @@ -43,17 +43,17 @@ func TestSubscriptions_OnePass(t *testing.T) { To: &common.Address{}, Data: hexutil.MustDecode("0xec2454e500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003"), }, mock.Anything).Return(getSubscriptionsInRange, nil) - config := functions.OnchainSubscriptionsConfig{ + config := subscriptions.OnchainSubscriptionsConfig{ ContractAddress: common.Address{}, BlockConfirmations: 1, UpdateFrequencySec: 1, UpdateTimeoutSec: 1, UpdateRangeSize: 3, } - orm := fmocks.NewORM(t) - orm.On("GetSubscriptions", uint(0), uint(100)).Return([]functions.CachedSubscription{}, nil) + orm := smocks.NewORM(t) + orm.On("GetSubscriptions", uint(0), uint(100)).Return([]subscriptions.StoredSubscription{}, nil) orm.On("UpsertSubscription", mock.Anything).Return(nil) - subscriptions, err := functions.NewOnchainSubscriptions(client, config, orm, logger.TestLogger(t)) + subscriptions, err := subscriptions.NewOnchainSubscriptions(client, config, orm, logger.TestLogger(t)) require.NoError(t, err) err = subscriptions.Start(ctx) @@ -94,17 +94,17 @@ func TestSubscriptions_MultiPass(t *testing.T) { To: &common.Address{}, Data: hexutil.MustDecode("0xec2454e500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006"), }, mock.Anything).Return(getSubscriptionsInRange, nil) - config := functions.OnchainSubscriptionsConfig{ + config := subscriptions.OnchainSubscriptionsConfig{ ContractAddress: common.Address{}, BlockConfirmations: 1, UpdateFrequencySec: 1, UpdateTimeoutSec: 1, UpdateRangeSize: 3, } - orm := fmocks.NewORM(t) - orm.On("GetSubscriptions", uint(0), uint(100)).Return([]functions.CachedSubscription{}, nil) + orm := smocks.NewORM(t) + orm.On("GetSubscriptions", uint(0), uint(100)).Return([]subscriptions.StoredSubscription{}, nil) orm.On("UpsertSubscription", mock.Anything).Return(nil) - subscriptions, err := functions.NewOnchainSubscriptions(client, config, orm, logger.TestLogger(t)) + subscriptions, err := subscriptions.NewOnchainSubscriptions(client, config, orm, logger.TestLogger(t)) require.NoError(t, err) err = subscriptions.Start(ctx) @@ -118,7 +118,7 @@ func TestSubscriptions_MultiPass(t *testing.T) { }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) } -func TestSubscriptions_Cached(t *testing.T) { +func TestSubscriptions_Stored(t *testing.T) { getSubscriptionCount := hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000003") getSubscriptionsInRange := hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000109e6e1b12098cc8f3a1e9719a817ec53ab9b35c000000000000000000000000000000000000000000000000000034e23f515cb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f5340f0968ee8b7dfd97e3327a6139273cc2c4fa000000000000000000000000000000000000000000000001158e460913d000000000000000000000000000009ed925d8206a4f88a2f643b28b3035b315753cd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bc14b92364c75e20000000000000000000000009ed925d8206a4f88a2f643b28b3035b315753cd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005439e5881a529f3ccbffc0e82d49f9db3950aefe") @@ -133,31 +133,31 @@ func TestSubscriptions_Cached(t *testing.T) { To: &common.Address{}, Data: hexutil.MustDecode("0xec2454e500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003"), }, mock.Anything).Return(getSubscriptionsInRange, nil) - config := functions.OnchainSubscriptionsConfig{ + config := subscriptions.OnchainSubscriptionsConfig{ ContractAddress: common.Address{}, BlockConfirmations: 1, UpdateFrequencySec: 1, UpdateTimeoutSec: 1, UpdateRangeSize: 3, - CacheBatchSize: 1, + StoreBatchSize: 1, } expectedBalance := big.NewInt(5) - orm := fmocks.NewORM(t) - orm.On("GetSubscriptions", uint(0), uint(1)).Return([]functions.CachedSubscription{ + orm := smocks.NewORM(t) + orm.On("GetSubscriptions", uint(0), uint(1)).Return([]subscriptions.StoredSubscription{ { SubscriptionID: 1, IFunctionsSubscriptionsSubscription: functions_router.IFunctionsSubscriptionsSubscription{ Balance: expectedBalance, - Owner: common.HexToAddress(cachedUser), + Owner: common.HexToAddress(storedUser), BlockedBalance: big.NewInt(10), }, }, }, nil) - orm.On("GetSubscriptions", uint(1), uint(1)).Return([]functions.CachedSubscription{}, nil) + orm.On("GetSubscriptions", uint(1), uint(1)).Return([]subscriptions.StoredSubscription{}, nil) orm.On("UpsertSubscription", mock.Anything).Return(nil) - subscriptions, err := functions.NewOnchainSubscriptions(client, config, orm, logger.TestLogger(t)) + subscriptions, err := subscriptions.NewOnchainSubscriptions(client, config, orm, logger.TestLogger(t)) require.NoError(t, err) err = subscriptions.Start(ctx) @@ -167,7 +167,7 @@ func TestSubscriptions_Cached(t *testing.T) { }) gomega.NewGomegaWithT(t).Eventually(func() bool { - actualBalance, err := subscriptions.GetMaxUserBalance(common.HexToAddress(cachedUser)) + actualBalance, err := subscriptions.GetMaxUserBalance(common.HexToAddress(storedUser)) return err == nil && assert.Equal(t, expectedBalance, actualBalance) }, testutils.WaitTimeout(t), time.Second).Should(gomega.BeTrue()) } diff --git a/core/services/gateway/handlers/functions/user_subscriptions.go b/core/services/gateway/handlers/functions/subscriptions/user_subscriptions.go similarity index 95% rename from core/services/gateway/handlers/functions/user_subscriptions.go rename to core/services/gateway/handlers/functions/subscriptions/user_subscriptions.go index 7e63720da71..ec506b6a864 100644 --- a/core/services/gateway/handlers/functions/user_subscriptions.go +++ b/core/services/gateway/handlers/functions/subscriptions/user_subscriptions.go @@ -1,4 +1,4 @@ -package functions +package subscriptions import ( "math/big" @@ -31,8 +31,8 @@ func NewUserSubscriptions() UserSubscriptions { } } -// CachedSubscription is used to populate the user subscription maps from a persistent layer like postgres. -type CachedSubscription struct { +// StoredSubscription is used to populate the user subscription maps from a persistent layer like postgres. +type StoredSubscription struct { SubscriptionID uint64 functions_router.IFunctionsSubscriptionsSubscription } diff --git a/core/services/gateway/handlers/functions/user_subscriptions_test.go b/core/services/gateway/handlers/functions/subscriptions/user_subscriptions_test.go similarity index 92% rename from core/services/gateway/handlers/functions/user_subscriptions_test.go rename to core/services/gateway/handlers/functions/subscriptions/user_subscriptions_test.go index 53827e07e1b..4d58155735f 100644 --- a/core/services/gateway/handlers/functions/user_subscriptions_test.go +++ b/core/services/gateway/handlers/functions/subscriptions/user_subscriptions_test.go @@ -1,4 +1,4 @@ -package functions_test +package subscriptions_test import ( "math/big" @@ -6,7 +6,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_router" - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" "github.com/stretchr/testify/assert" ) @@ -14,7 +14,7 @@ import ( func TestUserSubscriptions(t *testing.T) { t.Parallel() - us := functions.NewUserSubscriptions() + us := subscriptions.NewUserSubscriptions() t.Run("GetMaxUserBalance for unknown user", func(t *testing.T) { _, err := us.GetMaxUserBalance(utils.RandomAddress()) @@ -60,7 +60,7 @@ func TestUserSubscriptions_UpdateSubscription(t *testing.T) { t.Parallel() t.Run("update balance", func(t *testing.T) { - us := functions.NewUserSubscriptions() + us := subscriptions.NewUserSubscriptions() owner := utils.RandomAddress() updated := us.UpdateSubscription(1, &functions_router.IFunctionsSubscriptionsSubscription{ @@ -77,7 +77,7 @@ func TestUserSubscriptions_UpdateSubscription(t *testing.T) { }) t.Run("updated proposed owner", func(t *testing.T) { - us := functions.NewUserSubscriptions() + us := subscriptions.NewUserSubscriptions() owner := utils.RandomAddress() updated := us.UpdateSubscription(1, &functions_router.IFunctionsSubscriptionsSubscription{ @@ -94,7 +94,7 @@ func TestUserSubscriptions_UpdateSubscription(t *testing.T) { assert.True(t, updated) }) t.Run("remove subscriptions", func(t *testing.T) { - us := functions.NewUserSubscriptions() + us := subscriptions.NewUserSubscriptions() user2 := utils.RandomAddress() user2Balance1 := big.NewInt(50) user2Balance2 := big.NewInt(70) @@ -126,7 +126,7 @@ func TestUserSubscriptions_UpdateSubscription(t *testing.T) { }) t.Run("remove a non existing subscription", func(t *testing.T) { - us := functions.NewUserSubscriptions() + us := subscriptions.NewUserSubscriptions() updated := us.UpdateSubscription(3, &functions_router.IFunctionsSubscriptionsSubscription{ Owner: utils.ZeroAddress, }) @@ -134,7 +134,7 @@ func TestUserSubscriptions_UpdateSubscription(t *testing.T) { }) t.Run("no actual changes", func(t *testing.T) { - us := functions.NewUserSubscriptions() + us := subscriptions.NewUserSubscriptions() subscription := &functions_router.IFunctionsSubscriptionsSubscription{ Owner: utils.RandomAddress(), Balance: big.NewInt(25), diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index 2ea99d65cfe..c838316b1cc 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -25,6 +25,7 @@ import ( ocr2keepers20runner "github.com/smartcontractkit/chainlink-automation/pkg/v2/runner" ocr2keepers21config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" ocr2keepers21 "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" + "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink-vrf/altbn_128" dkgpkg "github.com/smartcontractkit/chainlink-vrf/dkg" @@ -504,12 +505,6 @@ type connProvider interface { ClientConn() grpc.ClientConnInterface } -func defaultPathFromPluginName(pluginName string) string { - // By default we install the command on the system path, in the - // form: `chainlink-` - return fmt.Sprintf("chainlink-%s", pluginName) -} - func (d *Delegate) newServicesGenericPlugin( ctx context.Context, lggr logger.SugaredLogger, @@ -530,9 +525,11 @@ func (d *Delegate) newServicesGenericPlugin( return nil, err } + plugEnv := env.NewPlugin(p.PluginName) + command := p.Command if command == "" { - command = defaultPathFromPluginName(p.PluginName) + command = plugEnv.Cmd.Get() } // Add the default pipeline to the pluginConfig @@ -587,8 +584,22 @@ func (d *Delegate) newServicesGenericPlugin( OffchainConfigDigester: provider.OffchainConfigDigester(), } + envVars, err := plugins.ParseEnvFile(plugEnv.Env.Get()) + if err != nil { + return nil, fmt.Errorf("failed to parse median env file: %w", err) + } + if len(p.EnvVars) > 0 { + for k, v := range p.EnvVars { + envVars = append(envVars, k+"="+v) + } + } + pluginLggr := lggr.Named(p.PluginName).Named(spec.ContractID).Named(spec.GetID()) - cmdFn, grpcOpts, err := d.cfg.RegisterLOOP(fmt.Sprintf("%s-%s-%s", p.PluginName, spec.ContractID, spec.GetID()), command) + cmdFn, grpcOpts, err := d.cfg.RegisterLOOP(plugins.CmdConfig{ + ID: fmt.Sprintf("%s-%s-%s", p.PluginName, spec.ContractID, spec.GetID()), + Cmd: command, + Env: envVars, + }) if err != nil { return nil, fmt.Errorf("failed to register loop: %w", err) } diff --git a/core/services/ocr2/plugins/functions/config/config.go b/core/services/ocr2/plugins/functions/config/config.go index 38af7b8587f..e0e1ba3bfa0 100644 --- a/core/services/ocr2/plugins/functions/config/config.go +++ b/core/services/ocr2/plugins/functions/config/config.go @@ -13,41 +13,42 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" s4PluginConfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/s4" "github.com/smartcontractkit/chainlink/v2/core/services/s4" ) // This config is part of the job spec and is loaded only once on node boot/job creation. type PluginConfig struct { - EnableRequestSignatureCheck bool `json:"enableRequestSignatureCheck"` - DONID string `json:"donID"` - ContractVersion uint32 `json:"contractVersion"` - MinRequestConfirmations uint32 `json:"minRequestConfirmations"` - MinResponseConfirmations uint32 `json:"minResponseConfirmations"` - MinIncomingConfirmations uint32 `json:"minIncomingConfirmations"` - PastBlocksToPoll uint32 `json:"pastBlocksToPoll"` - LogPollerCacheDurationSec uint32 `json:"logPollerCacheDurationSec"` // Duration to cache previously detected request or response logs such that they can be filtered when calling logpoller_wrapper.LatestEvents() - RequestTimeoutSec uint32 `json:"requestTimeoutSec"` - RequestTimeoutCheckFrequencySec uint32 `json:"requestTimeoutCheckFrequencySec"` - RequestTimeoutBatchLookupSize uint32 `json:"requestTimeoutBatchLookupSize"` - PruneMaxStoredRequests uint32 `json:"pruneMaxStoredRequests"` - PruneCheckFrequencySec uint32 `json:"pruneCheckFrequencySec"` - PruneBatchSize uint32 `json:"pruneBatchSize"` - ListenerEventHandlerTimeoutSec uint32 `json:"listenerEventHandlerTimeoutSec"` - ListenerEventsCheckFrequencyMillis uint32 `json:"listenerEventsCheckFrequencyMillis"` - ContractUpdateCheckFrequencySec uint32 `json:"contractUpdateCheckFrequencySec"` - MaxRequestSizeBytes uint32 `json:"maxRequestSizeBytes"` - MaxRequestSizesList []uint32 `json:"maxRequestSizesList"` - MaxSecretsSizesList []uint32 `json:"maxSecretsSizesList"` - MinimumSubscriptionBalance assets.Link `json:"minimumSubscriptionBalance"` - AllowedHeartbeatInitiators []string `json:"allowedHeartbeatInitiators"` - GatewayConnectorConfig *connector.ConnectorConfig `json:"gatewayConnectorConfig"` - OnchainAllowlist *functions.OnchainAllowlistConfig `json:"onchainAllowlist"` - OnchainSubscriptions *functions.OnchainSubscriptionsConfig `json:"onchainSubscriptions"` - RateLimiter *common.RateLimiterConfig `json:"rateLimiter"` - S4Constraints *s4.Constraints `json:"s4Constraints"` - DecryptionQueueConfig *DecryptionQueueConfig `json:"decryptionQueueConfig"` + EnableRequestSignatureCheck bool `json:"enableRequestSignatureCheck"` + DONID string `json:"donID"` + ContractVersion uint32 `json:"contractVersion"` + MinRequestConfirmations uint32 `json:"minRequestConfirmations"` + MinResponseConfirmations uint32 `json:"minResponseConfirmations"` + MinIncomingConfirmations uint32 `json:"minIncomingConfirmations"` + PastBlocksToPoll uint32 `json:"pastBlocksToPoll"` + LogPollerCacheDurationSec uint32 `json:"logPollerCacheDurationSec"` // Duration to cache previously detected request or response logs such that they can be filtered when calling logpoller_wrapper.LatestEvents() + RequestTimeoutSec uint32 `json:"requestTimeoutSec"` + RequestTimeoutCheckFrequencySec uint32 `json:"requestTimeoutCheckFrequencySec"` + RequestTimeoutBatchLookupSize uint32 `json:"requestTimeoutBatchLookupSize"` + PruneMaxStoredRequests uint32 `json:"pruneMaxStoredRequests"` + PruneCheckFrequencySec uint32 `json:"pruneCheckFrequencySec"` + PruneBatchSize uint32 `json:"pruneBatchSize"` + ListenerEventHandlerTimeoutSec uint32 `json:"listenerEventHandlerTimeoutSec"` + ListenerEventsCheckFrequencyMillis uint32 `json:"listenerEventsCheckFrequencyMillis"` + ContractUpdateCheckFrequencySec uint32 `json:"contractUpdateCheckFrequencySec"` + MaxRequestSizeBytes uint32 `json:"maxRequestSizeBytes"` + MaxRequestSizesList []uint32 `json:"maxRequestSizesList"` + MaxSecretsSizesList []uint32 `json:"maxSecretsSizesList"` + MinimumSubscriptionBalance assets.Link `json:"minimumSubscriptionBalance"` + AllowedHeartbeatInitiators []string `json:"allowedHeartbeatInitiators"` + GatewayConnectorConfig *connector.ConnectorConfig `json:"gatewayConnectorConfig"` + OnchainAllowlist *allowlist.OnchainAllowlistConfig `json:"onchainAllowlist"` + OnchainSubscriptions *subscriptions.OnchainSubscriptionsConfig `json:"onchainSubscriptions"` + RateLimiter *common.RateLimiterConfig `json:"rateLimiter"` + S4Constraints *s4.Constraints `json:"s4Constraints"` + DecryptionQueueConfig *DecryptionQueueConfig `json:"decryptionQueueConfig"` } type DecryptionQueueConfig struct { diff --git a/core/services/ocr2/plugins/functions/plugin.go b/core/services/ocr2/plugins/functions/plugin.go index fd72b6fd38e..15e36f07b09 100644 --- a/core/services/ocr2/plugins/functions/plugin.go +++ b/core/services/ocr2/plugins/functions/plugin.go @@ -21,7 +21,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/functions" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" - gwFunctions "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + gwAllowlist "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" + gwSubscriptions "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" @@ -134,7 +135,11 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs allServices = append(allServices, job.NewServiceAdapter(functionsReportingPluginOracle)) if pluginConfig.GatewayConnectorConfig != nil && s4Storage != nil && pluginConfig.OnchainAllowlist != nil && pluginConfig.RateLimiter != nil && pluginConfig.OnchainSubscriptions != nil { - allowlist, err2 := gwFunctions.NewOnchainAllowlist(conf.Chain.Client(), *pluginConfig.OnchainAllowlist, conf.Logger) + allowlistORM, err := gwAllowlist.NewORM(conf.DB, conf.Logger, conf.QConfig, pluginConfig.OnchainAllowlist.ContractAddress) + if err != nil { + return nil, errors.Wrap(err, "failed to create allowlist ORM") + } + allowlist, err2 := gwAllowlist.NewOnchainAllowlist(conf.Chain.Client(), *pluginConfig.OnchainAllowlist, allowlistORM, conf.Logger) if err2 != nil { return nil, errors.Wrap(err, "failed to create OnchainAllowlist") } @@ -142,11 +147,11 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs if err2 != nil { return nil, errors.Wrap(err, "failed to create a RateLimiter") } - gwFunctionsORM, err := gwFunctions.NewORM(conf.DB, conf.Logger, conf.QConfig, pluginConfig.OnchainSubscriptions.ContractAddress) + subscriptionsORM, err := gwSubscriptions.NewORM(conf.DB, conf.Logger, conf.QConfig, pluginConfig.OnchainSubscriptions.ContractAddress) if err != nil { - return nil, errors.Wrap(err, "failed to create functions ORM") + return nil, errors.Wrap(err, "failed to create subscriptions ORM") } - subscriptions, err2 := gwFunctions.NewOnchainSubscriptions(conf.Chain.Client(), *pluginConfig.OnchainSubscriptions, gwFunctionsORM, conf.Logger) + subscriptions, err2 := gwSubscriptions.NewOnchainSubscriptions(conf.Chain.Client(), *pluginConfig.OnchainSubscriptions, subscriptionsORM, conf.Logger) if err2 != nil { return nil, errors.Wrap(err, "failed to create a OnchainSubscriptions") } @@ -178,7 +183,7 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs return allServices, nil } -func NewConnector(pluginConfig *config.PluginConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwFunctions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwFunctions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, lggr logger.Logger) (connector.GatewayConnector, error) { +func NewConnector(pluginConfig *config.PluginConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwAllowlist.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwSubscriptions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, lggr logger.Logger) (connector.GatewayConnector, error) { enabledKeys, err := ethKeystore.EnabledKeysForChain(chainID) if err != nil { return nil, err diff --git a/core/services/ocr2/plugins/functions/plugin_test.go b/core/services/ocr2/plugins/functions/plugin_test.go index 6d3f57b086c..cd7956240df 100644 --- a/core/services/ocr2/plugins/functions/plugin_test.go +++ b/core/services/ocr2/plugins/functions/plugin_test.go @@ -12,7 +12,8 @@ import ( sfmocks "github.com/smartcontractkit/chainlink/v2/core/services/functions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" - gfmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/mocks" + gfaMocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist/mocks" + gfsMocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions" @@ -32,8 +33,8 @@ func TestNewConnector_Success(t *testing.T) { chainID := big.NewInt(80001) ethKeystore := ksmocks.NewEth(t) s4Storage := s4mocks.NewStorage(t) - allowlist := gfmocks.NewOnchainAllowlist(t) - subscriptions := gfmocks.NewOnchainSubscriptions(t) + allowlist := gfaMocks.NewOnchainAllowlist(t) + subscriptions := gfsMocks.NewOnchainSubscriptions(t) rateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) require.NoError(t, err) listener := sfmocks.NewFunctionsListener(t) @@ -60,8 +61,8 @@ func TestNewConnector_NoKeyForConfiguredAddress(t *testing.T) { chainID := big.NewInt(80001) ethKeystore := ksmocks.NewEth(t) s4Storage := s4mocks.NewStorage(t) - allowlist := gfmocks.NewOnchainAllowlist(t) - subscriptions := gfmocks.NewOnchainSubscriptions(t) + allowlist := gfaMocks.NewOnchainAllowlist(t) + subscriptions := gfsMocks.NewOnchainSubscriptions(t) rateLimiter, err := hc.NewRateLimiter(hc.RateLimiterConfig{GlobalRPS: 100.0, GlobalBurst: 100, PerSenderRPS: 100.0, PerSenderBurst: 100}) require.NoError(t, err) listener := sfmocks.NewFunctionsListener(t) diff --git a/core/services/ocr2/plugins/median/services.go b/core/services/ocr2/plugins/median/services.go index bdf6feaed47..2a874ff1756 100644 --- a/core/services/ocr2/plugins/median/services.go +++ b/core/services/ocr2/plugins/median/services.go @@ -125,10 +125,20 @@ func NewMedianServices(ctx context.Context, CreatedAt: time.Now(), }, lggr) - if cmdName := env.MedianPluginCmd.Get(); cmdName != "" { + if cmdName := env.MedianPlugin.Cmd.Get(); cmdName != "" { // use unique logger names so we can use it to register a loop medianLggr := lggr.Named("Median").Named(spec.ContractID).Named(spec.GetID()) - cmdFn, telem, err2 := cfg.RegisterLOOP(medianLggr.Name(), cmdName) + envVars, err2 := plugins.ParseEnvFile(env.MedianPlugin.Env.Get()) + if err2 != nil { + err = fmt.Errorf("failed to parse median env file: %w", err2) + abort() + return + } + cmdFn, telem, err2 := cfg.RegisterLOOP(plugins.CmdConfig{ + ID: medianLggr.Name(), + Cmd: cmdName, + Env: envVars, + }) if err2 != nil { err = fmt.Errorf("failed to register loop: %w", err2) abort() diff --git a/core/services/ocr2/validate/config.go b/core/services/ocr2/validate/config.go index 549b929b23c..0084a18308b 100644 --- a/core/services/ocr2/validate/config.go +++ b/core/services/ocr2/validate/config.go @@ -49,7 +49,7 @@ func ToLocalConfig(ocr2Config OCR2Config, insConf InsecureConfig, spec job.OCR2O ContractTransmitterTransmitTimeout: ocr2Config.ContractTransmitterTransmitTimeout(), DatabaseTimeout: ocr2Config.DatabaseTimeout(), } - if spec.Relay == relay.Solana && env.MedianPluginCmd.Get() != "" { + if spec.Relay == relay.Solana && env.MedianPlugin.Cmd.Get() != "" { // Work around for Solana Feeds configured with zero values to support LOOP Plugins. minOCR2MaxDurationQuery, err := getMinOCR2MaxDurationQuery() if err != nil { diff --git a/core/services/ocr2/validate/validate.go b/core/services/ocr2/validate/validate.go index bb9bb03a8ac..ad54ba4fea2 100644 --- a/core/services/ocr2/validate/validate.go +++ b/core/services/ocr2/validate/validate.go @@ -136,10 +136,11 @@ type Config struct { } type innerConfig struct { - Command string `json:"command"` - ProviderType string `json:"providerType"` - PluginName string `json:"pluginName"` - TelemetryType string `json:"telemetryType"` + Command string `json:"command"` + EnvVars map[string]string `json:"envVars"` + ProviderType string `json:"providerType"` + PluginName string `json:"pluginName"` + TelemetryType string `json:"telemetryType"` Config } diff --git a/core/store/migrate/migrations/0221_functions_allowlist.sql b/core/store/migrate/migrations/0221_functions_allowlist.sql new file mode 100644 index 00000000000..e97b2fc4077 --- /dev/null +++ b/core/store/migrate/migrations/0221_functions_allowlist.sql @@ -0,0 +1,22 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE functions_allowlist( + id BIGSERIAL, + router_contract_address bytea CHECK (octet_length(router_contract_address) = 20) NOT NULL, + allowed_address bytea CHECK (octet_length(allowed_address) = 20) NOT NULL, + PRIMARY KEY(router_contract_address, allowed_address) +); + +ALTER TABLE functions_subscriptions +ADD CONSTRAINT router_contract_address_octet_length CHECK (octet_length(router_contract_address) = 20); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +ALTER TABLE functions_subscriptions +DROP CONSTRAINT router_contract_address_octet_length; + +DROP TABLE IF EXISTS functions_allowlist; +-- +goose StatementEnd diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0718d9f8286..113ac50e701 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `chainlink health` CLI command and HTML `/health` endpoint, to provide human-readable views of the underlying JSON health data. - New job type `stream` to represent streamspecs. This job type is not yet used anywhere but will be required for Data Streams V1. +- Environment variables `CL_MEDIAN_ENV`, `CL_SOLANA_ENV`, and `CL_STARKNET_ENV` for setting environment variables in LOOP Plugins with an `.env` file. + ``` + echo "Foo=Bar" >> median.env + echo "Baz=Val" >> median.env + CL_MEDIAN_ENV="median.env" + ``` ### Fixed @@ -102,6 +108,11 @@ Starting in `v2.9.0`: ... +## 2.7.2 - 2023-12-14 + +### Fixed + +- Fixed a bug that caused nodes without OCR or OCR2 enabled to fail config validation if `P2P.V2` was not explicitly disabled. With this fix, NOPs will not have to make changes to their config. ## 2.7.1 - 2023-11-21 diff --git a/go.mod b/go.mod index 0bc08adc3fc..0eed0de9a0f 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/graph-gophers/dataloader v5.0.0+incompatible github.com/graph-gophers/graphql-go v1.3.0 github.com/hashicorp/consul/sdk v0.14.1 + github.com/hashicorp/go-envparse v0.1.0 github.com/hashicorp/go-plugin v1.5.2 github.com/hdevalence/ed25519consensus v0.1.0 github.com/jackc/pgconn v1.14.1 diff --git a/go.sum b/go.sum index ba26cb877d8..46a9a5343e5 100644 --- a/go.sum +++ b/go.sum @@ -685,6 +685,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= +github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc= github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index cbf11734716..4fce766a825 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -248,8 +248,10 @@ require ( github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/consul/api v1.25.1 // indirect + github.com/hashicorp/consul/sdk v0.14.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index ec5ad55cd1b..42825bb25a5 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -906,6 +906,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= +github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc= github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index 9e06ee527cd..0898dd69985 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -24,8 +24,8 @@ import ( func TestOCRv2Basic(t *testing.T) { t.Parallel() - noMedianPlugin := map[string]string{string(env.MedianPluginCmd): ""} - medianPlugin := map[string]string{string(env.MedianPluginCmd): "chainlink-feeds"} + noMedianPlugin := map[string]string{string(env.MedianPlugin.Cmd): ""} + medianPlugin := map[string]string{string(env.MedianPlugin.Cmd): "chainlink-feeds"} for _, test := range []struct { name string env map[string]string diff --git a/plugins/utils.go b/plugins/cmd.go similarity index 73% rename from plugins/utils.go rename to plugins/cmd.go index 5e5e4142e86..9a312c53882 100644 --- a/plugins/utils.go +++ b/plugins/cmd.go @@ -7,8 +7,9 @@ import ( // CmdConfig is configuration used to register the LOOP and generate an exec type CmdConfig struct { - ID string // unique string used by the node to track the LOOP. typically supplied by the loop logger name - Cmd string // string value of executable to exec + ID string // unique string used by the node to track the LOOP. typically supplied by the loop logger name + Cmd string // string value of executable to exec + Env []string // environment variables as described in [exec.Cmd.Env] } // NewCmdFactory is helper to ensure synchronization between the loop registry and os cmd to exec the LOOP @@ -19,6 +20,7 @@ func NewCmdFactory(register func(id string) (*RegisteredLoop, error), lcfg CmdCo } return func() *exec.Cmd { cmd := exec.Command(lcfg.Cmd) //#nosec G204 -- we control the value of the cmd so the lint/sec error is a false positive + cmd.Env = append(cmd.Env, lcfg.Env...) cmd.Env = append(cmd.Env, registeredLoop.EnvCfg.AsCmdEnv()...) return cmd }, nil diff --git a/plugins/cmd_test.go b/plugins/cmd_test.go new file mode 100644 index 00000000000..8068977314a --- /dev/null +++ b/plugins/cmd_test.go @@ -0,0 +1,51 @@ +package plugins + +import ( + "fmt" + "strings" + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" +) + +func TestNewCmdFactory_RegisterSuccess(t *testing.T) { + mockRegister := func(id string) (*RegisteredLoop, error) { + return &RegisteredLoop{EnvCfg: loop.EnvConfig{}}, nil + } + + cmdConfig := CmdConfig{ + ID: "test-loop", + Cmd: "echo", + Env: []string{"TEST_ENV=1"}, + } + + cmdFactory, err := NewCmdFactory(mockRegister, cmdConfig) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + cmd := cmdFactory() + if cmd.Args[0] != "echo" { + t.Errorf("Expected command 'echo', got %s", cmd.Args[0]) + } +} + +func TestNewCmdFactory_RegisterFail(t *testing.T) { + mockRegister := func(id string) (*RegisteredLoop, error) { + return nil, fmt.Errorf("registration failed") + } + + cmdConfig := CmdConfig{ + ID: "test-loop", + Cmd: "echo", + Env: []string{"TEST_ENV=1"}, + } + + _, err := NewCmdFactory(mockRegister, cmdConfig) + if err == nil { + t.Fatal("Expected error, got nil") + } + if !strings.Contains(err.Error(), "failed to register") { + t.Errorf("Unexpected error message: %v", err) + } +} diff --git a/plugins/env.go b/plugins/env.go new file mode 100644 index 00000000000..016a4e862d8 --- /dev/null +++ b/plugins/env.go @@ -0,0 +1,31 @@ +package plugins + +import ( + "os" + + "github.com/hashicorp/go-envparse" +) + +// ParseEnvFile returns a slice of key/value pairs parsed from the file at filepath. +// As a special case, empty filepath returns nil without error. +func ParseEnvFile(filepath string) ([]string, error) { + if filepath == "" { + return nil, nil + } + f, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer func() { + _ = f.Close() + }() + m, err := envparse.Parse(f) + if err != nil { + return nil, err + } + r := make([]string, 0, len(m)) + for k, v := range m { + r = append(r, k+"="+v) + } + return r, nil +} diff --git a/plugins/env_test.go b/plugins/env_test.go new file mode 100644 index 00000000000..6dd171b6cc0 --- /dev/null +++ b/plugins/env_test.go @@ -0,0 +1,25 @@ +package plugins + +import ( + _ "embed" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseEnvFile(t *testing.T) { + t.Run("valid", func(t *testing.T) { + got, err := ParseEnvFile("testdata/valid.env") + require.NoError(t, err) + require.Equal(t, []string{"GOMEMLIMIT=1MiB"}, got) + }) + t.Run("invalid", func(t *testing.T) { + _, err := ParseEnvFile("testdata/invalid.env") + require.Error(t, err) + }) + t.Run("missing", func(t *testing.T) { + _, err := ParseEnvFile("testdata/missing.env") + require.ErrorIs(t, err, os.ErrNotExist) + }) +} diff --git a/plugins/loop_registry.go b/plugins/loop_registry.go index 7a5274803d6..a2fcd8ef379 100644 --- a/plugins/loop_registry.go +++ b/plugins/loop_registry.go @@ -2,19 +2,18 @@ package plugins import ( "errors" + "fmt" "sort" "sync" + "github.com/hashicorp/consul/sdk/freeport" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink/v2/core/config" ) -const ( - pluginDefaultPort = 2112 -) - var ErrExists = errors.New("plugin already registered") type RegisteredLoop struct { @@ -49,8 +48,14 @@ func (m *LoopRegistry) Register(id string) (*RegisteredLoop, error) { if _, exists := m.registry[id]; exists { return nil, ErrExists } - nextPort := pluginDefaultPort + len(m.registry) - envCfg := loop.EnvConfig{PrometheusPort: nextPort} + ports, err := freeport.Take(1) + if err != nil { + return nil, fmt.Errorf("failed to get free port: %v", err) + } + if len(ports) != 1 { + return nil, fmt.Errorf("failed to get free port: no ports returned") + } + envCfg := loop.EnvConfig{PrometheusPort: ports[0]} if m.cfgTracing != nil { envCfg.TracingEnabled = m.cfgTracing.Enabled() diff --git a/plugins/config.go b/plugins/registrar.go similarity index 77% rename from plugins/config.go rename to plugins/registrar.go index 01574d82099..90300b738b6 100644 --- a/plugins/config.go +++ b/plugins/registrar.go @@ -8,7 +8,7 @@ import ( // RegistrarConfig generates contains static configuration inher type RegistrarConfig interface { - RegisterLOOP(loopId string, cmdName string) (func() *exec.Cmd, loop.GRPCOpts, error) + RegisterLOOP(config CmdConfig) (func() *exec.Cmd, loop.GRPCOpts, error) } type registarConfig struct { @@ -27,11 +27,8 @@ func NewRegistrarConfig(grpcOpts loop.GRPCOpts, loopRegistrationFn func(loopId s } // RegisterLOOP calls the configured loopRegistrationFn. The loopRegistrationFn must act as a global registry for LOOPs and must be idempotent. -func (pc *registarConfig) RegisterLOOP(loopID string, cmdName string) (func() *exec.Cmd, loop.GRPCOpts, error) { - cmdFn, err := NewCmdFactory(pc.loopRegistrationFn, CmdConfig{ - ID: loopID, - Cmd: cmdName, - }) +func (pc *registarConfig) RegisterLOOP(cfg CmdConfig) (func() *exec.Cmd, loop.GRPCOpts, error) { + cmdFn, err := NewCmdFactory(pc.loopRegistrationFn, cfg) if err != nil { return nil, loop.GRPCOpts{}, err } diff --git a/plugins/testdata/invalid.env b/plugins/testdata/invalid.env new file mode 100644 index 00000000000..b2f2407bccf --- /dev/null +++ b/plugins/testdata/invalid.env @@ -0,0 +1,2 @@ +FOO BAR +Baz: "Value" diff --git a/plugins/testdata/valid.env b/plugins/testdata/valid.env new file mode 100644 index 00000000000..5a73d037c60 --- /dev/null +++ b/plugins/testdata/valid.env @@ -0,0 +1 @@ +GOMEMLIMIT=1MiB