Skip to content

Commit aa7299b

Browse files
authored
Merge pull request #4 from smartcontractkit/feat/local-testnet-simulator
2 parents 133fa09 + df76e4d commit aa7299b

20 files changed

+571
-166
lines changed

.changeset/honest-rockets-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/functions-toolkit': minor
3+
---
4+
5+
Added localFunctionsTestnet

.github/workflows/cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
registry-url: 'https://registry.npmjs.org'
2828

2929
- name: Run npm ci
30-
run: npm ci
30+
run: npm install # npm install instead of npm ci is used to prevent unsupported platform errors due to the fsevents sub-dependency --no-optional
3131

3232
- name: Setup project
3333
run: npm run build

.github/workflows/prettier.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
node-version: 18
1818

1919
- name: Install dependencies
20-
run: npm ci
20+
run: npm install # npm install instead of npm ci is used to prevent unsupported platform errors due to the fsevents sub-dependency --no-optional
2121

2222
- name: Run Prettier check
2323
run: npx prettier --check .

.github/workflows/test-converage.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
node-version: 18
2020

2121
- name: Install dependencies
22-
run: npm ci
22+
run: npm install # npm install instead of npm ci is used to prevent unsupported platform errors due to the fsevents sub-dependency --no-optional
2323

2424
- name: Setup Deno
2525
uses: denolib/setup-deno@v2

.github/workflows/test-package.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
node-version: 18
2020

2121
- name: Install dependencies
22-
run: npm ci
22+
run: npm install # npm install instead of npm ci is used to prevent unsupported platform errors due to the fsevents sub-dependency --no-optional
2323

2424
- name: Setup Deno
2525
uses: denolib/setup-deno@v2

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
node-version: 18
2020

2121
- name: Install dependencies
22-
run: npm ci
22+
run: npm install # npm install instead of npm ci is used to prevent unsupported platform errors due to the fsevents sub-dependency
2323

2424
- name: Setup Deno
2525
uses: denolib/setup-deno@v2

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The typical subscriptions-related operations are
4444
- [Functions Response Listener](#functions-response-listener)
4545
- [Functions Utilities](#functions-utilities)
4646
- [Local Functions Simulator](#local-functions-simulator)
47+
- [Local Functions Testnet](#local-functions-testnet)
4748
- [Decoding Response Bytes](#decoding-response-bytes)
4849
- [Storing Encrypted Secrets in Gists](#storing-encrypted-secrets-in-gists)
4950
- [Building Functions Request CBOR Bytes](#building-functions-request-cbor-bytes)
@@ -535,8 +536,61 @@ const result = await simulateScript({
535536
}
536537
```
537538

539+
**_NOTE:_** When running `simulateScript`, depending on your security settings, you may get a popup asking if you would like to accept incoming network connections. You can safely ignore this popup and it should disappear when the simulation is complete.
540+
538541
**_NOTE:_** The `simulateScript` function is a debugging tool and hence is not a perfect representation of the actual Chainlink oracle execution environment. Therefore, it is important to make a Functions request on a supported testnet blockchain before mainnet usage.
539542

543+
### Local Functions Testnet
544+
545+
For debugging smart contracts and the end-to-end request flow on your local machine, you can use the `localFunctionsTestnet` function. This creates a local testnet RPC node with a mock Chainlink Functions contracts. You can then deploy your own Functions consumer contract to this local network, create and manage subscriptions, and send requests. Request processing will simulate the behavior of an actual DON where the request is executed 4 times and the discrete median response is transmitted back to the consumer contract. (Note that Functions uses the following calculation to select the discrete median response: `const medianResponse = responses[responses.length - 1) / 2]`).
546+
547+
The `localFunctionsTestnet` function takes the following values as arguments.
548+
549+
```
550+
const localFunctionsTestnet = await startLocalFunctionsTestnet(
551+
simulationConfigPath?: string // Absolute path to config file which exports simulation config parameters
552+
options?: ServerOptions, // Ganache server options
553+
port?: number, // Defaults to 8545
554+
)
555+
```
556+
557+
Observe that `localFunctionsTestnet` takes in a `simulationConfigPath` string as an optional argument. The primary reason for this is because the local testnet does not have the ability to access or decrypt encrypted secrets provided within request transactions. Instead, you can export an object named `secrets` from a TypeScript or JavaScript file and provide the absolute path to that file as the `simulationConfigPath` argument. When the JavaScript code is executed during the request, secrets specified in that file will be made accessible within the JavaScript code regardless of the `secretsLocation` or `encryptedSecretsReference` values sent in the request transaction. This config file can also contain other simulation config parameters. An example of this config file is shown below.
558+
559+
```
560+
export const secrets: { test: 'hello world' } // `secrets` object which can be accessed by the JavaScript code during request execution (can only contain string values)
561+
export const maxOnChainResponseBytes = 256 // Maximum size of the returned value in bytes (defaults to 256)
562+
export const maxExecutionTimeMs = 10000 // Maximum execution duration (defaults to 10_000ms)
563+
export const maxMemoryUsageMb = 128 // Maximum RAM usage (defaults to 128mb)
564+
export const numAllowedQueries = 5 // Maximum number of HTTP requests (defaults to 5)
565+
export const maxQueryDurationMs = 9000// Maximum duration of each HTTP request (defaults to 9_000ms)
566+
export const maxQueryUrlLength = 2048 // Maximum HTTP request URL length (defaults to 2048)
567+
export const maxQueryRequestBytes = 2048 // Maximum size of outgoing HTTP request payload (defaults to 2048 == 2 KB)
568+
export const maxQueryResponseBytes = 2097152 // Maximum size of incoming HTTP response payload (defaults to 2_097_152 == 2 MB)
569+
```
570+
571+
`localFunctionsTestnet` returns a promise which resolves to the following type.
572+
573+
```
574+
{
575+
server: Server // Ganache server
576+
adminWallet: { address: string, privateKey: string } // Funded admin wallet
577+
getFunds: (address: string, { weiAmount, juelsAmount }: { weiAmount?: BigInt | string; juelsAmount?: BigInt | string }) => Promise<void> // Method which can be called to send funds to any address
578+
close: () => Promise<void> // Method to close the server
579+
donId: string // DON ID for simulated DON
580+
// The following values are all Ethers.js contract types: https://docs.ethers.org/v5/api/contract/contract/
581+
linkTokenContract: Contract // Mock LINK token contract
582+
functionsRouterContract: Contract // Mock FunctionsRouter contract
583+
}
584+
```
585+
586+
Now you can connect to the local Functions testnet RPC node with your preferred blockchain tooling, deploy a FunctionsConsumer contract, instantiate and initialize the`SubscriptionManager`, create, add the consumer contract and fund the subscription, send requests, and use the `ResponseListener` to listen for responses all on your machine.
587+
588+
**_NOTE:_** When simulating request executions, depending on your security settings, you may get multiple popups asking if you would like to accept incoming network connections. You can safely ignore these popups and they should disappear when the executions are complete.
589+
590+
**_NOTE:_** Cost estimates and other configuration values may differ significantly from actual values on live testnet or mainnet chains.
591+
592+
**_NOTE:_** The `localFunctionsTestnet` function is a debugging tool and hence is not a perfect representation of the actual Chainlink oracle execution environment. Therefore, it is important to make a Functions request on a supported testnet blockchain before mainnet usage.
593+
540594
### Decoding Response Bytes
541595

542596
On-chain responses are encoded as Solidity `bytes` which are most frequently displayed as hex strings. However, these hex strings often need to be decoded into a useable type. In order to decode hex strings into human-readable values, this package provides the `decodeResult` function. Currently, the `decodeResult` function supports decoding hex strings into `uint256`, `int256` or `string` values.

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module.exports = {
33
preset: 'ts-jest',
44
testEnvironment: 'node',
55
testMatch: ['**/test/**/*.test.ts'],
6-
testTimeout: 240 * 1000,
6+
testTimeout: 2 * 60 * 1000,
77

88
coverageReporters: ['html'],
99
collectCoverageFrom: ['src/**/*.ts', '!src/test/*.ts', '!src/simulateScript/deno-sandbox/*.ts'],

package-lock.json

Lines changed: 3 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)