├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── dependabot.yml ├── labels.yml ├── pull_request_template.md ├── release.yml ├── security_scann.yaml └── workflows │ ├── add-issues-to-devx-project.yml │ ├── ci.yml │ └── sync-labels.yml ├── .gitignore ├── .golangci.yml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── SECURITY.md ├── adapters ├── access.go ├── access_test.go ├── sdk.go └── sdk_test.go ├── check-headers.sh ├── cmd └── emulator │ ├── Dockerfile │ ├── Makefile │ ├── main.go │ └── start │ └── start.go ├── convert ├── emu.go ├── flow.go ├── flow_test.go ├── vm.go └── vm_test.go ├── docs ├── emulator-banner.svg └── overview.md ├── emulator.go ├── emulator ├── accounts_test.go ├── attachments_test.go ├── blockTicker.go ├── block_info_test.go ├── block_test.go ├── blockchain.go ├── blockchain_test.go ├── blocks.go ├── capcons_test.go ├── clock.go ├── collection_test.go ├── computation_report.go ├── computation_report_test.go ├── contracts.go ├── contracts_test.go ├── coverage_report_test.go ├── coverage_reported_runtime.go ├── emulator.go ├── events_test.go ├── init_test.go ├── logs_test.go ├── mocks │ └── emulator.go ├── pendingBlock.go ├── pendingBlock_test.go ├── pragma.go ├── pragma_test.go ├── random_source_history_test.go ├── script_test.go ├── snapshots_test.go ├── templates │ └── systemChunkTransactionTemplate.cdc └── transaction_test.go ├── go.mod ├── go.sum ├── internal ├── access.go ├── executiondata.go └── mocks │ ├── access.go │ └── executiondata.go ├── server ├── access │ ├── grpc.go │ ├── rest.go │ ├── rest_test.go │ └── streamBackend.go ├── debugger │ ├── debugger.go │ └── debugsession.go ├── server.go ├── server_test.go └── utils │ ├── admin.go │ ├── emulator.go │ ├── liveness.go │ ├── liveness │ ├── README.md │ ├── check.go │ ├── check_test.go │ └── collector.go │ └── utils_test.go ├── storage ├── checkpoint │ ├── checkpoint.go │ └── checkpoint_test.go ├── encoding.go ├── encoding_test.go ├── errors.go ├── memstore │ ├── memstore.go │ └── memstore_test.go ├── mocks │ └── store.go ├── redis │ └── store.go ├── remote │ └── store.go ├── sqlite │ ├── createTables.sql │ ├── store.go │ └── store_test.go ├── store.go ├── store_test.go └── util │ ├── defaultStore.go │ └── defaultStore_wasm.go ├── types ├── errors.go ├── result.go └── result_test.go └── utils ├── logging.go ├── temp_dep_test.go └── unittest └── fixtures.go /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Reporting a Problem/Bug 3 | about: Reporting a Problem/Bug 4 | title: '' 5 | labels: bug, Feedback 6 | assignees: bluesign 7 | 8 | --- 9 | ### Instructions 10 | 11 | Please fill out the template below to the best of your ability and include a label indicating which tool/service you were working with when you encountered the problem. 12 | 13 | ### Problem 14 | 15 | 16 | 17 | ### Steps to Reproduce 18 | 19 | 20 | 21 | ### Acceptance Criteria 22 | 23 | 24 | 25 | ### Context 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Requesting a Feature or Improvement 3 | about: "For feature requests. Please search for existing issues first. Also see CONTRIBUTING.md" 4 | title: '' 5 | labels: Feedback, Feature 6 | assignees: bluesign 7 | 8 | --- 9 | 10 | ## Instructions 11 | 12 | Please fill out the template below to the best of your ability. 13 | 14 | ### Issue To Be Solved 15 | 16 | (Replace this text: 17 | Please present a concise description of the problem to be addressed by this feature request. 18 | Please be clear what parts of the problem are considered to be in-scope and out-of-scope.) 19 | 20 | ### (Optional): Suggest A Solution 21 | 22 | (Replace this text: A concise description of your preferred solution. Things to address include: 23 | 24 | * Details of the technical implementation 25 | * Tradeoffs made in design decisions 26 | * Caveats and considerations for the future 27 | 28 | If there are multiple solutions, please present each one separately. Save comparisons for the very end.) 29 | 30 | ### (Optional): Context 31 | 32 | (Replace this text: 33 | What are you currently working on that this is blocking?) 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | ignore: 13 | - dependency-name: "github.com/onflow/flow-go" 14 | - dependency-name: "github.com/onflow/cadence" 15 | 16 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - color: 3E4B9E 2 | description: "" 3 | name: Epic 4 | - color: 0e8a16 5 | description: A new user feature or a new package API 6 | name: Feature 7 | - color: d4c5f9 8 | description: "" 9 | name: Feedback 10 | - color: 1d76db 11 | description: Technical work without new features, refactoring, improving tests 12 | name: Improvement 13 | - color: d73a4a 14 | description: The issue represents a bug, malfunction, regression 15 | name: Bug 16 | - color: 0075ca 17 | description: The issue in documentation, missing docs, invalid docs, outdated docs 18 | name: Documentation 19 | - color: c2e0c6 20 | description: A feature we want the help from community 21 | name: Help wanted 22 | - color: cccccc 23 | description: The issue was not well defined, waiting for more information from the submittor 24 | name: Needs information 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Closes #??? 2 | 3 | ## Description 4 | 5 | 9 | 10 | ______ 11 | 12 | For contributor use: 13 | 14 | - [ ] Targeted PR against `master` branch 15 | - [ ] Linked to GitHub issue with discussion and accepted design OR link to spec that describes this work 16 | - [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-emulator/blob/master/CONTRIBUTING.md#styleguides) 17 | - [ ] Updated relevant documentation 18 | - [ ] Re-reviewed `Files changed` in the GitHub PR explorer 19 | - [ ] Added appropriate labels 20 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: 💥 Breaking Changes 4 | labels: 5 | - Breaking Change 6 | - title: ⭐ Features 7 | labels: 8 | - Feature 9 | - title: 🛠 Improvements 10 | labels: 11 | - Improvement 12 | - title: 🐞 Bug Fixes 13 | labels: 14 | - Bugfix 15 | - title: 📖 Documentation 16 | labels: 17 | - Documentation 18 | - title: Other Changes 19 | labels: 20 | - "*" 21 | -------------------------------------------------------------------------------- /.github/security_scann.yaml: -------------------------------------------------------------------------------- 1 | name: Security Scanner 2 | on: 3 | pull_request: 4 | push: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: "0 4 * * *" # run once a day at 4 AM 8 | jobs: 9 | scan: 10 | name: gitleaks 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - uses: gitleaks/gitleaks-action@v2 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}} 20 | build: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Running govulncheck 25 | uses: Templum/govulncheck-action@latest 26 | with: 27 | go-version: ${{ env.GO_VERSION }} 28 | vulncheck-version: latest 29 | package: ./... 30 | github-token: ${{ secrets.GITHUB_TOKEN }} 31 | fail-on-vuln: true 32 | -------------------------------------------------------------------------------- /.github/workflows/add-issues-to-devx-project.yml: -------------------------------------------------------------------------------- 1 | name: Adds all issues to the DevEx project board. 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add issue to project 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/add-to-project@v0.5.0 14 | with: 15 | project-url: https://github.com/orgs/onflow/projects/13 16 | github-token: ${{ secrets.GH_ACTION_FOR_PROJECTS }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | push: 7 | branches: 8 | - master 9 | - "feature/**" 10 | - "release/**" 11 | tags: 12 | - "v*" 13 | pull_request: 14 | branches: 15 | - master 16 | - "feature/**" 17 | - "release/**" 18 | 19 | env: 20 | GO_VERSION: 1.23 21 | 22 | jobs: 23 | test: 24 | name: "Test" 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | - uses: actions/setup-go@v4 31 | with: 32 | go-version: ${{ env.GO_VERSION }} 33 | - uses: actions/cache@v3 34 | with: 35 | path: ~/go/pkg/mod 36 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 37 | restore-keys: | 38 | ${{ runner.os }}-go- 39 | - run: make ci 40 | - name: "Coverage Report" 41 | uses: codecov/codecov-action@v5 42 | with: 43 | file: ./cover.out 44 | flags: unittests 45 | 46 | lint: 47 | name: "Lint" 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: actions/setup-go@v4 52 | with: 53 | go-version: ${{ env.GO_VERSION }} 54 | - name: Run golangci-lint 55 | uses: golangci/golangci-lint-action@v3 56 | with: 57 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 58 | version: v1.63 59 | args: --timeout=10m 60 | 61 | docker: 62 | if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/') 63 | needs: test 64 | name: "Docker image" 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Prepare 68 | id: prep 69 | run: | 70 | DOCKER_IMAGE=gcr.io/flow-container-registry/emulator 71 | VERSION=edge 72 | 73 | if [[ $GITHUB_REF == refs/tags/* ]]; then 74 | VERSION=${GITHUB_REF#refs/tags/v} 75 | fi 76 | 77 | TAGS="${DOCKER_IMAGE}:${VERSION}" 78 | if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 79 | TAGS="$TAGS,${DOCKER_IMAGE}:latest" 80 | fi 81 | 82 | echo ::set-output name=tags::${TAGS} 83 | 84 | - name: Set up Docker Buildx 85 | id: buildx 86 | uses: docker/setup-buildx-action@v3 87 | 88 | - name: Cache Docker layers 89 | uses: actions/cache@v3 90 | with: 91 | path: /tmp/.buildx-cache 92 | key: ${{ runner.os }}-buildx-${{ github.sha }} 93 | restore-keys: | 94 | ${{ runner.os }}-buildx- 95 | 96 | - name: Checkout 97 | uses: actions/checkout@v4 98 | 99 | - name: Login to GCR 100 | uses: docker/login-action@v2 101 | with: 102 | registry: gcr.io 103 | username: _json_key 104 | password: ${{ secrets.GCR_JSON_KEY }} 105 | 106 | - name: Build and push 107 | id: docker_build 108 | uses: docker/build-push-action@v5 109 | with: 110 | builder: ${{ steps.buildx.outputs.name }} 111 | push: true 112 | tags: ${{ steps.prep.outputs.tags }} 113 | context: . 114 | platforms: linux/amd64,linux/arm64 115 | file: ./cmd/emulator/Dockerfile 116 | cache-from: type=local,src=/tmp/.buildx-cache 117 | cache-to: type=local,dest=/tmp/.buildx-cache 118 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync Labels 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - .github/labels.yml 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: micnncim/action-label-syncer@v1 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | with: 19 | manifest: .github/labels.yml 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test binary, build with `go test -c` 2 | *.test 3 | 4 | # Output of the go coverage tool, specifically when used with LiteIDE 5 | *.out 6 | 7 | # Coverage artifacts 8 | coverage.zip 9 | cover.json 10 | cover-summary 11 | index.html 12 | 13 | # Since we have tooling that uses node 14 | node_modules 15 | 16 | .DS_Store 17 | 18 | flow.json 19 | flowdb 20 | 21 | # IDE related files 22 | .idea 23 | .vscode 24 | git 25 | 26 | vendor 27 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - govet 5 | - gosimple 6 | - errcheck 7 | - staticcheck 8 | - ineffassign 9 | - typecheck 10 | - misspell 11 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @janezpodhostnik @bluesign @chasefleming @jribbink @peterargue 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at . 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 119 | 120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 121 | enforcement ladder](https://github.com/mozilla/diversity). 122 | 123 | [homepage]: https://www.contributor-covenant.org 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | https://www.contributor-covenant.org/faq. Translations are available at 127 | https://www.contributor-covenant.org/translations. 128 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Flow Emulator 2 | 3 | The following is a set of guidelines for contributing to the Flow Emulator. 4 | These are mostly guidelines, not rules. 5 | Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ## Table Of Contents 8 | 9 | [Getting Started](#project-overview) 10 | 11 | [How Can I Contribute?](#how-can-i-contribute) 12 | 13 | - [Reporting Bugs](#reporting-bugs) 14 | - [Suggesting Enhancements](#suggesting-enhancements) 15 | - [Your First Code Contribution](#your-first-code-contribution) 16 | - [Pull Requests](#pull-requests) 17 | 18 | [Styleguides](#styleguides) 19 | 20 | - [Git Commit Messages](#git-commit-messages) 21 | - [Go Styleguide](#go-styleguide) 22 | 23 | [Additional Notes](#additional-notes) 24 | 25 | ## Development 26 | 27 | Run a development version of emulator server: 28 | 29 | ```shell script 30 | make run 31 | ``` 32 | 33 | Run all unit tests in this repository: 34 | 35 | ```shell script 36 | make test 37 | ``` 38 | 39 | ## Building 40 | 41 | To build the container locally, use `make docker-build` from the root of this repository. 42 | 43 | Images are automatically built and pushed to `gcr.io/flow-container-registry/emulator` on any push to master, i.e. Pull Request merge 44 | 45 | ### Creating your own deployment 46 | 47 | If not using Kubernetes, you can run the Docker container independently. Make sure to run the Docker container with the gRPC port exposed (default is `3569`). Metrics are also available on port `8080` on the `/metrics` endpoint. 48 | 49 | To gain persistence for data on the emulator, you will have to provision a volume for the docker container. We've done this through `Persistent Volumes` on Kubernetes, but a mounted volume would suffice. The mount point can be set with the `FLOW_DBPATH` environment variable. We suggest a volume of at least 10GB (100GB for a long-term deployment). 50 | 51 | Make sure the emulator also has access to the same `flow.json` file, or always launch it with the same service key, as mentioned above. 52 | 53 | ```bash 54 | docker run -e FLOW_SERVICEPUBLICKEY= -e FLOW_DBPATH="/flowdb" -v "$(pwd)/flowdb":"/flowdb" -p 3569:3569 gcr.io/flow-container-registry/emulator 55 | ``` 56 | 57 | 58 | ## How Can I Contribute? 59 | 60 | ### Reporting Bugs 61 | 62 | #### Before Submitting A Bug Report 63 | 64 | - **Search existing issues** to see if the problem has already been reported. 65 | If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. 66 | 67 | #### How Do I Submit A (Good) Bug Report? 68 | 69 | Explain the problem and include additional details to help maintainers reproduce the problem: 70 | 71 | - **Use a clear and descriptive title** for the issue to identify the problem. 72 | - **Describe the exact steps which reproduce the problem** in as many details as possible. 73 | When listing steps, **don't just say what you did, but explain how you did it**. 74 | - **Provide specific examples to demonstrate the steps**. 75 | Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. 76 | If you're providing snippets in the issue, 77 | use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 78 | - **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 79 | - **Explain which behavior you expected to see instead and why.** 80 | - **Include error messages and stack traces** which show the output / crash and clearly demonstrate the problem. 81 | 82 | Provide more context by answering these questions: 83 | 84 | - **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens 85 | and under which conditions it normally happens. 86 | 87 | Include details about your configuration and environment: 88 | 89 | - **What is the version of the emulator you're using**? 90 | - **What's the name and version of the Operating System you're using**? 91 | 92 | ### Suggesting Enhancements 93 | 94 | #### Before Submitting An Enhancement Suggestion 95 | 96 | - **Perform a cursory search** to see if the enhancement has already been suggested. 97 | If it has, add a comment to the existing issue instead of opening a new one. 98 | 99 | #### How Do I Submit A (Good) Enhancement Suggestion? 100 | 101 | Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). 102 | Create an issue and provide the following information: 103 | 104 | - **Use a clear and descriptive title** for the issue to identify the suggestion. 105 | - **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 106 | - **Provide specific examples to demonstrate the steps**. 107 | Include copy/pasteable snippets which you use in those examples, 108 | as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 109 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 110 | - **Explain why this enhancement would be useful** to emulator users. 111 | 112 | ### Your First Code Contribution 113 | 114 | Unsure where to begin contributing to the Flow Emulator? 115 | You can start by looking through these `good-first-issue` and `help-wanted` issues: 116 | 117 | - [Good first issues](https://github.com/onflow/flow-emulator/labels/good%20first%20issue): 118 | issues which should only require a few lines of code, and a test or two. 119 | - [Help wanted issues](https://github.com/onflow/flow-emulator/labels/help%20wanted): 120 | issues which should be a bit more involved than `good-first-issue` issues. 121 | 122 | Both issue lists are sorted by total number of comments. 123 | While not perfect, number of comments is a reasonable proxy for impact a given change will have. 124 | 125 | ### Pull Requests 126 | 127 | The process described here has several goals: 128 | 129 | - Maintain code quality 130 | - Fix problems that are important to users 131 | - Engage the community in working toward the best possible UX 132 | - Enable a sustainable system for the emulator's maintainers to review contributions 133 | 134 | Please follow the [styleguides](#styleguides) to have your contribution considered by the maintainers. 135 | Reviewer(s) may ask you to complete additional design work, tests, 136 | or other changes before your pull request can be ultimately accepted. 137 | 138 | ## Styleguides 139 | 140 | Before contributing, make sure to examine the project to get familiar with the patterns and style already being used. 141 | 142 | ### Git Commit Messages 143 | 144 | - Use the present tense ("Add feature" not "Added feature") 145 | - Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 146 | - Limit the first line to 72 characters or less 147 | - Reference issues and pull requests liberally after the first line 148 | 149 | ### Go Styleguide 150 | 151 | The majority of this project is written Go. 152 | 153 | We try to follow the coding guidelines from the Go community. 154 | 155 | - Code should be formatted using `gofmt` 156 | - Code should pass the linter: `make lint` 157 | - Code should follow the guidelines covered in 158 | [Effective Go](http://golang.org/doc/effective_go.html) 159 | and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 160 | - Code should be commented 161 | - Code should pass all tests: `make test` 162 | 163 | ## Additional Notes 164 | 165 | Thank you for your interest in contributing to the Flow Emulator! 166 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # The short Git commit hash 2 | SHORT_COMMIT := $(shell git rev-parse --short HEAD) 3 | # Name of the cover profile 4 | COVER_PROFILE := cover.out 5 | # Disable go sum database lookup for private repos 6 | GOPRIVATE := github.com/dapperlabs/* 7 | # Ensure go bin path is in path (Especially for CI) 8 | PATH := $(PATH):$(GOPATH)/bin 9 | # OS 10 | UNAME := $(shell uname) 11 | 12 | GOPATH ?= $(HOME)/go 13 | 14 | .PHONY: install-tools 15 | install-tools: 16 | mkdir -p ${GOPATH}; \ 17 | cd ${GOPATH}; \ 18 | GO111MODULE=on go install go.uber.org/mock/mockgen@latest; \ 19 | GO111MODULE=on go install github.com/axw/gocov/gocov@latest; \ 20 | GO111MODULE=on go install github.com/matm/gocov-html@latest; \ 21 | GO111MODULE=on go install github.com/sanderhahn/gozip/cmd/gozip@latest; 22 | 23 | .PHONY: test 24 | test: 25 | GO111MODULE=on go test -coverprofile=$(COVER_PROFILE) $(if $(JSON_OUTPUT),-json,) ./... 26 | 27 | .PHONY: run 28 | run: 29 | GO111MODULE=on go run ./cmd/emulator 30 | 31 | .PHONY: coverage 32 | coverage: 33 | ifeq ($(COVER), true) 34 | # file has to be called index.html 35 | gocov convert $(COVER_PROFILE) > cover.json 36 | ./cover-summary.sh 37 | gocov-html cover.json > index.html 38 | # coverage.zip will automatically be picked up by teamcity 39 | gozip -c coverage.zip index.html 40 | endif 41 | 42 | .PHONY: generate 43 | generate: generate-mocks 44 | 45 | .PHONY: generate-mocks 46 | generate-mocks: 47 | GO111MODULE=on ${GOPATH}/bin/mockgen -destination=emulator/mocks/emulator.go -package=mocks github.com/onflow/flow-emulator/emulator Emulator 48 | GO111MODULE=on ${GOPATH}/bin/mockgen -destination=storage/mocks/store.go -package=mocks github.com/onflow/flow-emulator/storage Store 49 | GO111MODULE=on ${GOPATH}/bin/mockgen -destination=internal/mocks/access.go -package=mocks github.com/onflow/flow-emulator/internal AccessAPIClient 50 | GO111MODULE=on ${GOPATH}/bin/mockgen -destination=internal/mocks/executiondata.go -package=mocks github.com/onflow/flow-emulator/internal ExecutionDataAPIClient 51 | 52 | .PHONY: ci 53 | ci: install-tools test check-tidy test coverage check-headers 54 | 55 | .PHONY: install-linter 56 | install-linter: 57 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ${GOPATH}/bin v1.54.2 58 | 59 | .PHONY: lint 60 | lint: 61 | golangci-lint run -v ./... 62 | 63 | .PHONY: fix-lint 64 | fix-lint: 65 | golangci-lint run -v --fix ./... 66 | 67 | 68 | .PHONY: check-headers 69 | check-headers: 70 | @./check-headers.sh 71 | 72 | .PHONY: check-tidy 73 | check-tidy: generate 74 | go mod tidy 75 | git diff --exit-code 76 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Flow Emulator 2 | Copyright 2019-2024 Flow Foundation 3 | 4 | This product includes software developed at the Flow Foundation (https://flow.com/flow-foundation). -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | # Responsible Disclosure Policy 3 | 4 | Flow was built from the ground up with security in mind. Our code, infrastructure, and development methodology helps us keep our users safe. 5 | 6 | We really appreciate the community's help. Responsible disclosure of vulnerabilities helps to maintain the security and privacy of everyone. 7 | 8 | If you care about making a difference, please follow the guidelines below. 9 | 10 | # **Guidelines For Responsible Disclosure** 11 | 12 | We ask that all researchers adhere to these guidelines. 13 | 14 | ## **Rules of Engagement** 15 | 16 | - Make every effort to avoid unauthorized access, use, and disclosure of personal information. 17 | - Avoid actions which could impact user experience, disrupt production systems, change, or destroy data during security testing. 18 | - Don’t perform any attack that is intended to cause Denial of Service to the network, hosts, or services on any port or using any protocol. 19 | - Use our provided communication channels to securely report vulnerability information to us. 20 | - Keep information about any bug or vulnerability you discover confidential between us until we publicly disclose it. 21 | - Please don’t use scanners to crawl us and hammer endpoints. They’re noisy and we already do this. If you find anything this way, we have likely already identified it. 22 | - Never attempt non-technical attacks such as social engineering, phishing, or physical attacks against our employees, users, or infrastructure. 23 | 24 | ## **In Scope URIs** 25 | 26 | Be careful that you're looking at domains and systems that belong to us and not someone else. When in doubt, please ask us. Maybe ask us anyway. 27 | 28 | Bottom line, we suggest that you limit your testing to infrastructure that is clearly ours. 29 | 30 | ## **Out of Scope URIs** 31 | 32 | The following base URIs are explicitly out of scope: 33 | 34 | - None 35 | 36 | ## **Things Not To Do** 37 | 38 | In the interests of your safety, our safety, and for our customers, the following test types are prohibited: 39 | 40 | - Physical testing such as office and data-centre access (e.g. open doors, tailgating, card reader attacks, physically destructive testing) 41 | - Social engineering (e.g. phishing, vishing) 42 | - Testing of applications or systems NOT covered by the ‘In Scope’ section, or that are explicitly out of scope. 43 | - Network level Denial of Service (DoS/DDoS) attacks 44 | 45 | ## **Sensitive Data** 46 | 47 | In the interests of protecting privacy, we never want to receive: 48 | 49 | - Personally identifiable information (PII) 50 | - Payment card (e.g. credit card) data 51 | - Financial information (e.g. bank records) 52 | - Health or medical information 53 | - Accessed or cracked credentials in cleartext 54 | 55 | ## **Our Commitment To You** 56 | 57 | If you follow these guidelines when researching and reporting an issue to us, we commit to: 58 | 59 | - Not send lawyers after you related to your research under this policy; 60 | - Work with you to understand and resolve any issues within a reasonable timeframe, including an initial confirmation of your report within 72 hours of submission; and 61 | - At a minimum, we will recognize your contribution in our Disclosure Acknowledgements if you are the first to report the issue and we make a code or configuration change based on the issue. 62 | 63 | ## **Disclosure Acknowledgements** 64 | 65 | We're happy to acknowledge contributors. Security acknowledgements can be found here. 66 | 67 | ## Rewards 68 | 69 | We run closed bug bounty programs, but beyond that we also pay out rewards, once per eligible bug, to the first responsibly disclosing third party. Rewards are based on the seriousness of the bug, but the minimum is $100 and we have and are willing to pay $5,000 or more at our sole discretion. 70 | 71 | ### **Eligibility** 72 | 73 | To qualify, the bug must fall within our scope and rules and meet the following criteria: 74 | 75 | 1. **Previously unknown** - When reported, we must not have already known of the issue, either by internal discovery or separate disclosure. 76 | 2. **Material impact** - Demonstrable exploitability where, if exploited, the bug would materially affect the confidentiality, integrity, or availability of our services. 77 | 3. **Requires action** - The bug requires some mitigation. It is both valid and actionable. 78 | 79 | ## **Reporting Security Findings To Us** 80 | 81 | Reports are welcome! Please definitely reach out to us if you have a security concern. 82 | 83 | We prefer you to please send us an email: security@onflow.org 84 | 85 | Note: If you believe you may have found a security vulnerability in our open source repos, to be on the safe side, do NOT open a public issue. 86 | 87 | We encourage you to encrypt the information you send us using our PGP key at [keys.openpgp.org/security@onflow.org](https://keys.openpgp.org/vks/v1/by-fingerprint/AE3264F330AB51F7DBC52C400BB5D3D7516D168C) 88 | 89 | Please include the following details with your report: 90 | 91 | - A description of the location and potential impact of the finding(s); 92 | - A detailed description of the steps required to reproduce the issue; and 93 | - Any POC scripts, screenshots, and compressed screen captures, where feasible. 94 | -------------------------------------------------------------------------------- /check-headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | files=$(find . -name \*.go -type f -print0 | xargs -0 egrep -L '(Licensed under the Apache License)|(Code generated (from|by))') 4 | if [ -n "$files" ]; then 5 | echo "Missing license header in:" 6 | echo "$files" 7 | exit 1 8 | fi 9 | -------------------------------------------------------------------------------- /cmd/emulator/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.0.0-experimental 2 | 3 | # NOTE: Must be run in the context of the repo's root directory 4 | 5 | ## Build the app binary 6 | FROM --platform=$BUILDPLATFORM golang:1.23 AS build-app 7 | 8 | # Build the app binary in /app 9 | RUN mkdir /app 10 | WORKDIR /app 11 | 12 | # add the pubkey of github.com to knownhosts, so ssh-agent doesn't bark 13 | RUN mkdir -p /root/.ssh && ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts 14 | RUN git config --global 'url.ssh://git@github.com/.insteadOf' https://github.com/ 15 | RUN apt-get update && apt-get -y install apt-utils gcc-aarch64-linux-gnu 16 | 17 | COPY . . 18 | 19 | ARG TARGETOS 20 | ARG TARGETARCH 21 | ENV GO111MODULE=on \ 22 | CGO_ENABLED=1 \ 23 | CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" \ 24 | GOOS=$TARGETOS \ 25 | GOARCH=$TARGETARCH 26 | RUN --mount=type=ssh \ 27 | --mount=type=cache,target=/go/pkg/mod \ 28 | --mount=type=cache,target=/root/.cache/go-build \ 29 | if [ "$TARGETARCH" = "arm64" ] ; then \ 30 | export CC=aarch64-linux-gnu-gcc; \ 31 | elif [ "$TARGETARCH" = "amd64" ] ; then \ 32 | export CC=x86_64-linux-gnu-gcc; \ 33 | fi; \ 34 | go build -ldflags "-extldflags -static" -o ./app ./cmd/emulator 35 | 36 | ## Add the binary to a fresh distroless image 37 | FROM gcr.io/distroless/static 38 | 39 | COPY --from=build-app /app/app /bin/app 40 | 41 | # Expose GRPC and HTTP ports 42 | EXPOSE 8080 43 | EXPOSE 3569 44 | 45 | # Run the CLI binary as the entrypoint 46 | ENTRYPOINT ["/bin/app"] 47 | # These arguments are separated from the entrypoint to simplify running other 48 | # commands with this image. 49 | CMD ["emulator", "start"] 50 | -------------------------------------------------------------------------------- /cmd/emulator/Makefile: -------------------------------------------------------------------------------- 1 | # The short Git commit hash 2 | SHORT_COMMIT := $(shell git rev-parse --short HEAD) 3 | # The Git commit hash 4 | COMMIT := $(shell git rev-parse HEAD) 5 | # The tag of the current commit, otherwise empty 6 | VERSION := $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null) 7 | IMAGE_TAG := ${VERSION} 8 | ifeq (${IMAGE_TAG},) 9 | IMAGE_TAG := ${SHORT_COMMIT} 10 | endif 11 | # Service information 12 | #---------------------------------------------------------------------- 13 | SERVICE_SHORT_NAME=emulator 14 | SERVICE_LONG_NAME=flow-container-registry/${SERVICE_SHORT_NAME} 15 | DEPLOYMENT_NAME=flow-emulator-v2 16 | 17 | # Build information 18 | #---------------------------------------------------------------------- 19 | REPO:=gcr.io 20 | IMAGE_URL:=${REPO}/${SERVICE_LONG_NAME} 21 | 22 | MONOREPO_ROOT=`pwd`/../../../ 23 | # The location of the k8s YAML files 24 | K8S_YAMLS_LOCATION=./k8s 25 | 26 | KUBECONFIG := $(shell uuidgen) 27 | 28 | #---------------------------------------------------------------------- 29 | # CD COMMANDS 30 | #---------------------------------------------------------------------- 31 | 32 | .PHONY: deploy-staging 33 | deploy-staging: apply-staging-files monitor-rollout 34 | 35 | .PHONY: deploy-production 36 | deploy-production: apply-prod-files monitor-rollout 37 | 38 | # Staging YAMLs must have 'staging' in their name. 39 | .PHONY: apply-staging-files 40 | apply-staging-files: 41 | echo "$$KUBECONFIG_STAGING_2" > ${KUBECONFIG}; \ 42 | files=$$(find ${K8S_YAMLS_LOCATION} -type f \( -name "*.yml" -or -name "*.yaml" \) | grep "staging-v[0-9]"); \ 43 | echo "$$files" | xargs -I {} kubectl --kubeconfig=${KUBECONFIG} apply -f {} 44 | 45 | # Production YAMLs must have 'production' in their name. 46 | .PHONY: apply-prod-files 47 | apply-prod-files: 48 | echo "$$KUBECONFIG_PRODUCTION_2" > ${KUBECONFIG}; \ 49 | files=$$(find ${K8S_YAMLS_LOCATION} -type f \( -name "*.yml" -or -name "*.yaml" \) | grep production); \ 50 | echo "$$files" | xargs -I {} kubectl --kubeconfig=${KUBECONFIG} apply -f {} 51 | 52 | # Deployment YAMLs must have 'deployment' in their name. 53 | .PHONY: update-deployment-image 54 | update-deployment-image: CONTAINER=flow-emulator 55 | update-deployment-image: 56 | @files=$$(find ${K8S_YAMLS_LOCATION} -type f \( -name "*.yml" -or -name "*.yaml" \) | grep deployment); \ 57 | for i in $$files; do \ 58 | patched=`openssl rand -hex 8`; \ 59 | kubectl patch -f $$i -p '{"spec":{"template":{"spec":{"containers":[{"name":"${CONTAINER}","image":"${IMAGE_URL}:${IMAGE_TAG}"}]}}}}' --local -o yaml > $$patched; \ 60 | mv -f $$patched $$i; \ 61 | done 62 | 63 | # Has to come after one of the `apply` targets 64 | .PHONY: monitor-rollout 65 | monitor-rollout: 66 | kubectl --kubeconfig=${KUBECONFIG} rollout status statefulsets.apps ${DEPLOYMENT_NAME} 67 | -------------------------------------------------------------------------------- /cmd/emulator/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package main 20 | 21 | import ( 22 | emulator "github.com/onflow/flow-emulator/emulator" 23 | "github.com/onflow/flow-go-sdk/crypto" 24 | 25 | "github.com/onflow/flow-emulator/cmd/emulator/start" 26 | ) 27 | 28 | func defaultServiceKey( 29 | init bool, 30 | sigAlgo crypto.SignatureAlgorithm, 31 | hashAlgo crypto.HashAlgorithm, 32 | ) (crypto.PrivateKey, crypto.SignatureAlgorithm, crypto.HashAlgorithm) { 33 | if sigAlgo == crypto.UnknownSignatureAlgorithm { 34 | sigAlgo = emulator.DefaultServiceKeySigAlgo 35 | } 36 | 37 | if hashAlgo == crypto.UnknownHashAlgorithm { 38 | hashAlgo = emulator.DefaultServiceKeyHashAlgo 39 | } 40 | 41 | serviceKey := emulator.GenerateDefaultServiceKey(sigAlgo, hashAlgo) 42 | return serviceKey.PrivateKey, serviceKey.SigAlgo, serviceKey.HashAlgo 43 | } 44 | 45 | func main() { 46 | config := start.StartConfig{ 47 | GetServiceKey: defaultServiceKey, 48 | RestMiddlewares: []start.HttpMiddleware{}, 49 | } 50 | 51 | if err := start.Cmd(config).Execute(); err != nil { 52 | start.Exit(1, err.Error()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /convert/emu.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package convert 20 | 21 | import ( 22 | "github.com/onflow/flow-go/fvm" 23 | flowgo "github.com/onflow/flow-go/model/flow" 24 | 25 | "github.com/onflow/flow-emulator/types" 26 | ) 27 | 28 | func ToStorableResult( 29 | output fvm.ProcedureOutput, 30 | blockID flowgo.Identifier, 31 | blockHeight uint64, 32 | ) ( 33 | types.StorableTransactionResult, 34 | error, 35 | ) { 36 | var errorCode int 37 | var errorMessage string 38 | 39 | if output.Err != nil { 40 | errorCode = int(output.Err.Code()) 41 | errorMessage = output.Err.Error() 42 | } 43 | 44 | return types.StorableTransactionResult{ 45 | BlockID: blockID, 46 | BlockHeight: blockHeight, 47 | ErrorCode: errorCode, 48 | ErrorMessage: errorMessage, 49 | Logs: output.Logs, 50 | Events: output.Events, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /convert/flow_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package convert 20 | 21 | import ( 22 | "testing" 23 | 24 | sdk "github.com/onflow/flow-go-sdk" 25 | "github.com/onflow/flow-go-sdk/crypto" 26 | "github.com/onflow/flow-go-sdk/test" 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestSDKAccountToFlowAndBack(t *testing.T) { 31 | 32 | t.Parallel() 33 | 34 | contract := []byte("access(all) contract Test {}") 35 | var keys []*sdk.AccountKey 36 | 37 | keys = append(keys, &sdk.AccountKey{ 38 | Index: 0, 39 | PublicKey: test.AccountKeyGenerator().New().PublicKey, 40 | SigAlgo: crypto.ECDSA_P256, 41 | HashAlgo: crypto.SHA3_256, 42 | Weight: 1000, 43 | SequenceNumber: 2, 44 | Revoked: true, 45 | }, &sdk.AccountKey{ 46 | Index: 1, 47 | PublicKey: test.AccountKeyGenerator().New().PublicKey, 48 | SigAlgo: crypto.ECDSA_P256, 49 | HashAlgo: crypto.SHA3_256, 50 | Weight: 500, 51 | SequenceNumber: 0, 52 | Revoked: false, 53 | }) 54 | 55 | acc := &sdk.Account{ 56 | Address: sdk.HexToAddress("0x1"), 57 | Balance: 10, 58 | Code: contract, 59 | Keys: keys, 60 | Contracts: map[string][]byte{ 61 | "Test": contract, 62 | }, 63 | } 64 | 65 | flowAcc, err := SDKAccountToFlow(acc) 66 | 67 | assert.NoError(t, err) 68 | assert.Equal(t, len(flowAcc.Keys), len(acc.Keys)) 69 | assert.Equal(t, flowAcc.Address.Hex(), acc.Address.Hex()) 70 | assert.Equal(t, flowAcc.Contracts, acc.Contracts) 71 | assert.Equal(t, flowAcc.Balance, acc.Balance) 72 | 73 | for i, k := range acc.Keys { 74 | assert.Equal(t, k.Revoked, flowAcc.Keys[i].Revoked) 75 | assert.Equal(t, k.Weight, flowAcc.Keys[i].Weight) 76 | assert.Equal(t, k.SequenceNumber, flowAcc.Keys[i].SeqNumber) 77 | assert.Equal(t, k.HashAlgo, flowAcc.Keys[i].HashAlgo) 78 | assert.Equal(t, k.SigAlgo, flowAcc.Keys[i].SignAlgo) 79 | } 80 | 81 | sdkAccount, err := FlowAccountToSDK(*flowAcc) 82 | assert.NoError(t, err) 83 | assert.Equal(t, len(flowAcc.Keys), len(sdkAccount.Keys)) 84 | assert.Equal(t, flowAcc.Address.Hex(), sdkAccount.Address.Hex()) 85 | assert.Equal(t, flowAcc.Contracts, sdkAccount.Contracts) 86 | assert.Equal(t, flowAcc.Balance, sdkAccount.Balance) 87 | 88 | for i, k := range sdkAccount.Keys { 89 | assert.Equal(t, k.Revoked, flowAcc.Keys[i].Revoked) 90 | assert.Equal(t, k.Weight, flowAcc.Keys[i].Weight) 91 | assert.Equal(t, k.SequenceNumber, flowAcc.Keys[i].SeqNumber) 92 | assert.Equal(t, k.HashAlgo, flowAcc.Keys[i].HashAlgo) 93 | assert.Equal(t, k.SigAlgo, flowAcc.Keys[i].SignAlgo) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /convert/vm.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package convert 20 | 21 | import ( 22 | "github.com/onflow/flow-go/fvm" 23 | fvmerrors "github.com/onflow/flow-go/fvm/errors" 24 | "github.com/onflow/flow-go/model/flow" 25 | 26 | "github.com/onflow/flow-emulator/types" 27 | ) 28 | 29 | func VMTransactionResultToEmulator( 30 | txnId flow.Identifier, 31 | output fvm.ProcedureOutput, 32 | ) ( 33 | *types.TransactionResult, 34 | error, 35 | ) { 36 | txID := FlowIdentifierToSDK(txnId) 37 | 38 | sdkEvents, err := FlowEventsToSDK(output.Events) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &types.TransactionResult{ 44 | TransactionID: txID, 45 | ComputationUsed: output.ComputationUsed, 46 | MemoryEstimate: output.MemoryEstimate, 47 | Error: VMErrorToEmulator(output.Err), 48 | Logs: output.Logs, 49 | Events: sdkEvents, 50 | }, nil 51 | } 52 | 53 | func VMErrorToEmulator(vmError fvmerrors.CodedError) error { 54 | if vmError == nil { 55 | return nil 56 | } 57 | 58 | return &types.FVMError{FlowError: vmError} 59 | } 60 | -------------------------------------------------------------------------------- /convert/vm_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package convert_test 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/onflow/flow-go-sdk/test" 25 | "github.com/onflow/flow-go/fvm" 26 | "github.com/onflow/flow/protobuf/go/flow/entities" 27 | "github.com/stretchr/testify/assert" 28 | 29 | flowgo "github.com/onflow/flow-go/model/flow" 30 | 31 | "github.com/onflow/flow-emulator/convert" 32 | ) 33 | 34 | func TestVm(t *testing.T) { 35 | 36 | t.Parallel() 37 | 38 | test := func(eventEncodingVersion entities.EventEncodingVersion) { 39 | t.Run(eventEncodingVersion.String(), func(t *testing.T) { 40 | t.Parallel() 41 | t.Run("should be able to convert", func(t *testing.T) { 42 | 43 | t.Parallel() 44 | 45 | idGenerator := test.IdentifierGenerator() 46 | 47 | eventGenerator := test.EventGenerator(eventEncodingVersion) 48 | event1, err := convert.SDKEventToFlow(eventGenerator.New()) 49 | assert.NoError(t, err) 50 | 51 | event2, err := convert.SDKEventToFlow(eventGenerator.New()) 52 | assert.NoError(t, err) 53 | 54 | txnId := flowgo.Identifier(idGenerator.New()) 55 | output := fvm.ProcedureOutput{ 56 | Logs: []string{"TestLog1", "TestLog2"}, 57 | Events: []flowgo.Event{event1, event2}, 58 | ComputationUsed: 5, 59 | MemoryEstimate: 1211, 60 | Err: nil, 61 | } 62 | 63 | tr, err := convert.VMTransactionResultToEmulator(txnId, output) 64 | assert.NoError(t, err) 65 | 66 | assert.Equal(t, txnId, flowgo.Identifier(tr.TransactionID)) 67 | assert.Equal(t, output.Logs, tr.Logs) 68 | 69 | flowEvents, err := convert.FlowEventsToSDK(output.Events) 70 | assert.NoError(t, err) 71 | assert.Equal(t, flowEvents, tr.Events) 72 | 73 | assert.Equal(t, output.ComputationUsed, tr.ComputationUsed) 74 | assert.Equal(t, output.MemoryEstimate, tr.MemoryEstimate) 75 | assert.Equal(t, output.Err, tr.Error) 76 | }) 77 | }) 78 | } 79 | 80 | test(entities.EventEncodingVersion_JSON_CDC_V0) 81 | test(entities.EventEncodingVersion_CCF_V0) 82 | } 83 | -------------------------------------------------------------------------------- /docs/emulator-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /emulator.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package emulator 19 | 20 | import ( 21 | "github.com/onflow/flow-emulator/emulator" 22 | ) 23 | 24 | func New(opts ...emulator.Option) (emulator.Emulator, error) { 25 | return emulator.New(opts...) 26 | } 27 | -------------------------------------------------------------------------------- /emulator/attachments_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/onflow/flow-emulator/emulator" 25 | 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestAttachments(t *testing.T) { 30 | 31 | t.Parallel() 32 | 33 | b, err := emulator.New() 34 | require.NoError(t, err) 35 | 36 | script := ` 37 | access(all) resource R {} 38 | 39 | access(all) attachment A for R {} 40 | 41 | access(all) fun main() { 42 | let r <- create R() 43 | r[A] 44 | destroy r 45 | } 46 | ` 47 | 48 | _, err = b.ExecuteScript([]byte(script), nil) 49 | require.NoError(t, err) 50 | } 51 | -------------------------------------------------------------------------------- /emulator/blockTicker.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import ( 22 | "time" 23 | ) 24 | 25 | type BlocksTicker struct { 26 | emulator Emulator 27 | ticker *time.Ticker 28 | done chan bool 29 | } 30 | 31 | func NewBlocksTicker( 32 | emulator Emulator, 33 | blockTime time.Duration, 34 | ) *BlocksTicker { 35 | return &BlocksTicker{ 36 | emulator: emulator, 37 | ticker: time.NewTicker(blockTime), 38 | done: make(chan bool, 1), 39 | } 40 | } 41 | 42 | func (t *BlocksTicker) Start() error { 43 | for { 44 | select { 45 | case <-t.ticker.C: 46 | _, _ = t.emulator.ExecuteBlock() 47 | _, _ = t.emulator.CommitBlock() 48 | case <-t.done: 49 | return nil 50 | } 51 | } 52 | } 53 | 54 | func (t *BlocksTicker) Stop() { 55 | t.done <- true 56 | } 57 | -------------------------------------------------------------------------------- /emulator/block_info_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "testing" 25 | 26 | "github.com/rs/zerolog" 27 | 28 | "github.com/onflow/flow-emulator/adapters" 29 | "github.com/onflow/flow-emulator/emulator" 30 | 31 | flowsdk "github.com/onflow/flow-go-sdk" 32 | flowgo "github.com/onflow/flow-go/model/flow" 33 | "github.com/stretchr/testify/assert" 34 | "github.com/stretchr/testify/require" 35 | ) 36 | 37 | func TestBlockInfo(t *testing.T) { 38 | 39 | t.Parallel() 40 | 41 | b, err := emulator.New() 42 | require.NoError(t, err) 43 | 44 | logger := zerolog.Nop() 45 | adapter := adapters.NewSDKAdapter(&logger, b) 46 | 47 | block1, err := b.CommitBlock() 48 | require.NoError(t, err) 49 | 50 | block2, err := b.CommitBlock() 51 | require.NoError(t, err) 52 | 53 | t.Run("works as transaction", func(t *testing.T) { 54 | tx := flowsdk.NewTransaction(). 55 | SetScript([]byte(` 56 | transaction { 57 | execute { 58 | let block = getCurrentBlock() 59 | log(block) 60 | 61 | let lastBlock = getBlock(at: block.height - 1) 62 | log(lastBlock) 63 | } 64 | } 65 | `)). 66 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 67 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 68 | SetPayer(b.ServiceKey().Address) 69 | 70 | signer, err := b.ServiceKey().Signer() 71 | require.NoError(t, err) 72 | 73 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 74 | require.NoError(t, err) 75 | 76 | err = adapter.SendTransaction(context.Background(), *tx) 77 | require.NoError(t, err) 78 | 79 | result, err := b.ExecuteNextTransaction() 80 | assert.NoError(t, err) 81 | AssertTransactionSucceeded(t, result) 82 | 83 | require.Len(t, result.Logs, 2) 84 | assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block2.Header.Height+1, 85 | b.PendingBlockView(), b.PendingBlockID(), float64(b.PendingBlockTimestamp().Unix())), result.Logs[0]) 86 | assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block2.Header.Height, 87 | block2.Header.View, block2.ID(), float64(block2.Header.Timestamp.Unix())), result.Logs[1]) 88 | }) 89 | 90 | t.Run("works as script", func(t *testing.T) { 91 | script := []byte(` 92 | access(all) fun main() { 93 | let block = getCurrentBlock() 94 | log(block) 95 | 96 | let lastBlock = getBlock(at: block.height - 1) 97 | log(lastBlock) 98 | } 99 | `) 100 | 101 | result, err := b.ExecuteScript(script, nil) 102 | assert.NoError(t, err) 103 | 104 | assert.True(t, result.Succeeded()) 105 | 106 | require.Len(t, result.Logs, 2) 107 | assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block2.Header.Height, 108 | block2.Header.View, block2.ID(), float64(block2.Header.Timestamp.Unix())), result.Logs[0]) 109 | assert.Equal(t, fmt.Sprintf("Block(height: %v, view: %v, id: 0x%x, timestamp: %.8f)", block1.Header.Height, 110 | block1.Header.View, block1.ID(), float64(block1.Header.Timestamp.Unix())), result.Logs[1]) 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /emulator/block_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "testing" 25 | 26 | "github.com/onflow/flow-emulator/adapters" 27 | "github.com/onflow/flow-emulator/emulator" 28 | "github.com/rs/zerolog" 29 | 30 | flowsdk "github.com/onflow/flow-go-sdk" 31 | flowgo "github.com/onflow/flow-go/model/flow" 32 | "github.com/stretchr/testify/assert" 33 | "github.com/stretchr/testify/require" 34 | ) 35 | 36 | func TestCommitBlock(t *testing.T) { 37 | 38 | t.Parallel() 39 | 40 | b, err := emulator.New( 41 | emulator.WithStorageLimitEnabled(false), 42 | ) 43 | 44 | require.NoError(t, err) 45 | 46 | logger := zerolog.Nop() 47 | adapter := adapters.NewSDKAdapter(&logger, b) 48 | 49 | addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) 50 | 51 | tx1 := flowsdk.NewTransaction(). 52 | SetScript([]byte(addTwoScript)). 53 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 54 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 55 | SetPayer(b.ServiceKey().Address). 56 | AddAuthorizer(b.ServiceKey().Address) 57 | 58 | signer, err := b.ServiceKey().Signer() 59 | require.NoError(t, err) 60 | 61 | err = tx1.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 62 | require.NoError(t, err) 63 | 64 | // Add tx1 to pending block 65 | err = adapter.SendTransaction(context.Background(), *tx1) 66 | assert.NoError(t, err) 67 | 68 | tx1Result, err := adapter.GetTransactionResult(context.Background(), tx1.ID()) 69 | assert.NoError(t, err) 70 | assert.Equal(t, flowsdk.TransactionStatusPending, tx1Result.Status) 71 | 72 | tx2 := flowsdk.NewTransaction(). 73 | SetScript([]byte(`transaction { execute { panic("revert!") } }`)). 74 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 75 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 76 | SetPayer(b.ServiceKey().Address). 77 | AddAuthorizer(b.ServiceKey().Address) 78 | 79 | signer, err = b.ServiceKey().Signer() 80 | require.NoError(t, err) 81 | 82 | err = tx2.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 83 | require.NoError(t, err) 84 | 85 | // Add tx2 to pending block 86 | err = adapter.SendTransaction(context.Background(), *tx2) 87 | require.NoError(t, err) 88 | 89 | tx2Result, err := adapter.GetTransactionResult(context.Background(), tx2.ID()) 90 | assert.NoError(t, err) 91 | assert.Equal(t, flowsdk.TransactionStatusPending, tx2Result.Status) 92 | 93 | // Execute tx1 94 | result, err := b.ExecuteNextTransaction() 95 | assert.NoError(t, err) 96 | assert.True(t, result.Succeeded()) 97 | 98 | // Execute tx2 99 | result, err = b.ExecuteNextTransaction() 100 | assert.NoError(t, err) 101 | assert.True(t, result.Reverted()) 102 | 103 | // Commit tx1 and tx2 into new block 104 | _, err = b.CommitBlock() 105 | assert.NoError(t, err) 106 | 107 | // tx1 status becomes TransactionStatusSealed 108 | tx1Result, err = adapter.GetTransactionResult(context.Background(), tx1.ID()) 109 | require.NoError(t, err) 110 | assert.Equal(t, flowsdk.TransactionStatusSealed, tx1Result.Status) 111 | 112 | // tx2 status also becomes TransactionStatusSealed, even though it is reverted 113 | tx2Result, err = adapter.GetTransactionResult(context.Background(), tx2.ID()) 114 | require.NoError(t, err) 115 | assert.Equal(t, flowsdk.TransactionStatusSealed, tx2Result.Status) 116 | assert.Error(t, tx2Result.Error) 117 | } 118 | 119 | func TestBlockView(t *testing.T) { 120 | 121 | t.Parallel() 122 | 123 | const nBlocks = 3 124 | 125 | b, err := emulator.New() 126 | require.NoError(t, err) 127 | 128 | logger := zerolog.Nop() 129 | adapter := adapters.NewSDKAdapter(&logger, b) 130 | 131 | t.Run("genesis should have 0 view", func(t *testing.T) { 132 | block, err := b.GetBlockByHeight(0) 133 | require.NoError(t, err) 134 | assert.Equal(t, uint64(0), block.Header.Height) 135 | assert.Equal(t, uint64(0), block.Header.View) 136 | }) 137 | 138 | addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) 139 | 140 | // create a few blocks, each with one transaction 141 | for i := 0; i < nBlocks; i++ { 142 | 143 | tx := flowsdk.NewTransaction(). 144 | SetScript([]byte(addTwoScript)). 145 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 146 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 147 | SetPayer(b.ServiceKey().Address). 148 | AddAuthorizer(b.ServiceKey().Address) 149 | 150 | signer, err := b.ServiceKey().Signer() 151 | require.NoError(t, err) 152 | 153 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 154 | require.NoError(t, err) 155 | 156 | // Add tx to pending block 157 | err = adapter.SendTransaction(context.Background(), *tx) 158 | assert.NoError(t, err) 159 | 160 | // execute and commit the block 161 | _, _, err = b.ExecuteAndCommitBlock() 162 | require.NoError(t, err) 163 | } 164 | const MaxViewIncrease = 3 165 | 166 | for height := uint64(1); height <= nBlocks+1; height++ { 167 | block, err := b.GetBlockByHeight(height) 168 | require.NoError(t, err) 169 | 170 | maxView := height * MaxViewIncrease 171 | t.Run(fmt.Sprintf("block %d should have view <%d", height, maxView), func(t *testing.T) { 172 | assert.Equal(t, height, block.Header.Height) 173 | assert.LessOrEqual(t, block.Header.View, maxView) 174 | }) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /emulator/blockchain_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "testing" 25 | 26 | "github.com/onflow/cadence/stdlib" 27 | "github.com/rs/zerolog" 28 | 29 | "github.com/onflow/flow-emulator/adapters" 30 | "github.com/onflow/flow-emulator/emulator" 31 | 32 | "github.com/onflow/cadence" 33 | flowsdk "github.com/onflow/flow-go-sdk" 34 | "github.com/onflow/flow-go-sdk/templates" 35 | "github.com/stretchr/testify/assert" 36 | "github.com/stretchr/testify/require" 37 | 38 | "github.com/onflow/flow-emulator/types" 39 | ) 40 | 41 | const counterScript = ` 42 | 43 | access(all) contract Counting { 44 | 45 | access(all) event CountIncremented(count: Int) 46 | 47 | access(all) resource Counter { 48 | access(all) var count: Int 49 | 50 | init() { 51 | self.count = 0 52 | } 53 | 54 | access(all) fun add(_ count: Int) { 55 | self.count = self.count + count 56 | emit CountIncremented(count: self.count) 57 | } 58 | } 59 | 60 | access(all) fun createCounter(): @Counter { 61 | return <-create Counter() 62 | } 63 | } 64 | ` 65 | 66 | // GenerateAddTwoToCounterScript generates a script that increments a counter. 67 | // If no counter exists, it is created. 68 | func GenerateAddTwoToCounterScript(counterAddress flowsdk.Address) string { 69 | return fmt.Sprintf( 70 | ` 71 | import 0x%s 72 | 73 | transaction { 74 | prepare(signer: auth(Storage, Capabilities) &Account) { 75 | var counter = signer.storage.borrow<&Counting.Counter>(from: /storage/counter) 76 | if counter == nil { 77 | signer.storage.save(<-Counting.createCounter(), to: /storage/counter) 78 | counter = signer.storage.borrow<&Counting.Counter>(from: /storage/counter) 79 | 80 | // Also publish this for others to borrow. 81 | let cap = signer.capabilities.storage.issue<&Counting.Counter>(/storage/counter) 82 | signer.capabilities.publish(cap, at: /public/counter) 83 | } 84 | counter?.add(2) 85 | } 86 | } 87 | `, 88 | counterAddress, 89 | ) 90 | } 91 | 92 | func DeployAndGenerateAddTwoScript(t *testing.T, adapter *adapters.SDKAdapter) (string, flowsdk.Address) { 93 | 94 | contracts := []templates.Contract{ 95 | { 96 | Name: "Counting", 97 | Source: counterScript, 98 | }, 99 | } 100 | 101 | counterAddress, err := adapter.CreateAccount( 102 | context.Background(), 103 | nil, 104 | contracts, 105 | ) 106 | require.NoError(t, err) 107 | 108 | return GenerateAddTwoToCounterScript(counterAddress), counterAddress 109 | } 110 | 111 | func GenerateGetCounterCountScript(counterAddress flowsdk.Address, accountAddress flowsdk.Address) string { 112 | return fmt.Sprintf( 113 | ` 114 | import 0x%s 115 | 116 | access(all) fun main(): Int { 117 | return getAccount(0x%s).capabilities.borrow<&Counting.Counter>(/public/counter)?.count ?? 0 118 | } 119 | `, 120 | counterAddress, 121 | accountAddress, 122 | ) 123 | } 124 | 125 | func AssertTransactionSucceeded(t *testing.T, result *types.TransactionResult) { 126 | if !assert.True(t, result.Succeeded()) { 127 | t.Error(result.Error) 128 | } 129 | } 130 | 131 | func LastCreatedAccount(b *emulator.Blockchain, result *types.TransactionResult) (*flowsdk.Account, error) { 132 | logger := zerolog.Nop() 133 | adapter := adapters.NewSDKAdapter(&logger, b) 134 | 135 | address, err := LastCreatedAccountAddress(result) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | return adapter.GetAccount(context.Background(), address) 141 | } 142 | 143 | func LastCreatedAccountAddress(result *types.TransactionResult) (flowsdk.Address, error) { 144 | for _, event := range result.Events { 145 | if event.Type == flowsdk.EventAccountCreated { 146 | addressFieldValue := cadence.SearchFieldByName( 147 | event.Value, 148 | stdlib.AccountEventAddressParameter.Identifier, 149 | ) 150 | return flowsdk.Address(addressFieldValue.(cadence.Address)), nil 151 | } 152 | } 153 | 154 | return flowsdk.Address{}, fmt.Errorf("no account created in this result") 155 | } 156 | -------------------------------------------------------------------------------- /emulator/blocks.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import ( 22 | "context" 23 | "errors" 24 | 25 | "github.com/onflow/flow-go/access/validator" 26 | "github.com/onflow/flow-go/fvm/environment" 27 | flowgo "github.com/onflow/flow-go/model/flow" 28 | 29 | "github.com/onflow/flow-emulator/storage" 30 | ) 31 | 32 | var _ environment.Blocks = &blocks{} 33 | var _ validator.Blocks = &blocks{} 34 | 35 | type blocks struct { 36 | blockchain *Blockchain 37 | } 38 | 39 | func newBlocks(b *Blockchain) *blocks { 40 | return &blocks{ 41 | blockchain: b, 42 | } 43 | } 44 | 45 | func (b *blocks) HeaderByID(id flowgo.Identifier) (*flowgo.Header, error) { 46 | block, err := b.blockchain.storage.BlockByID(context.Background(), id) 47 | if err != nil { 48 | if errors.Is(err, storage.ErrNotFound) { 49 | return nil, nil 50 | } 51 | 52 | return nil, err 53 | } 54 | 55 | return block.Header, nil 56 | } 57 | 58 | func (b *blocks) FinalizedHeader() (*flowgo.Header, error) { 59 | block, err := b.blockchain.storage.LatestBlock(context.Background()) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | return block.Header, nil 65 | } 66 | 67 | func (b *blocks) SealedHeader() (*flowgo.Header, error) { 68 | block, err := b.blockchain.storage.LatestBlock(context.Background()) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return block.Header, nil 74 | } 75 | 76 | func (b *blocks) IndexedHeight() (uint64, error) { 77 | block, err := b.blockchain.storage.LatestBlock(context.Background()) 78 | if err != nil { 79 | return 0, err 80 | } 81 | 82 | return block.Header.Height, nil 83 | } 84 | 85 | // We don't have to do anything complex here, as emulator does not fork the chain 86 | func (b *blocks) ByHeightFrom(height uint64, header *flowgo.Header) (*flowgo.Header, error) { 87 | if height > header.Height { 88 | return nil, storage.ErrNotFound 89 | } 90 | block, err := b.blockchain.storage.BlockByHeight(context.Background(), height) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return block.Header, nil 96 | } 97 | -------------------------------------------------------------------------------- /emulator/capcons_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/onflow/flow-emulator/emulator" 25 | 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestCapabilityControllers(t *testing.T) { 30 | 31 | t.Parallel() 32 | 33 | b, err := emulator.New() 34 | require.NoError(t, err) 35 | 36 | script := ` 37 | access(all) fun main() { 38 | getAccount(0x1).capabilities.get 39 | } 40 | ` 41 | 42 | _, err = b.ExecuteScript([]byte(script), nil) 43 | require.NoError(t, err) 44 | } 45 | -------------------------------------------------------------------------------- /emulator/clock.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import "time" 22 | 23 | type Clock interface { 24 | Now() time.Time 25 | } 26 | 27 | type SystemClock struct{} 28 | 29 | func (sc SystemClock) Now() time.Time { 30 | return time.Now().UTC() 31 | } 32 | 33 | func NewSystemClock() SystemClock { 34 | return SystemClock{} 35 | } 36 | -------------------------------------------------------------------------------- /emulator/collection_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package emulator_test 19 | 20 | import ( 21 | "context" 22 | "testing" 23 | 24 | "github.com/onflow/flow-emulator/adapters" 25 | "github.com/onflow/flow-emulator/convert" 26 | "github.com/onflow/flow-emulator/emulator" 27 | "github.com/rs/zerolog" 28 | 29 | flowsdk "github.com/onflow/flow-go-sdk" 30 | flowgo "github.com/onflow/flow-go/model/flow" 31 | "github.com/stretchr/testify/assert" 32 | "github.com/stretchr/testify/require" 33 | ) 34 | 35 | func TestCollections(t *testing.T) { 36 | 37 | t.Parallel() 38 | 39 | t.Run("Empty block", func(t *testing.T) { 40 | 41 | t.Parallel() 42 | 43 | b, err := emulator.New() 44 | require.NoError(t, err) 45 | 46 | block, err := b.CommitBlock() 47 | require.NoError(t, err) 48 | 49 | // block should not contain any collections 50 | assert.Empty(t, block.Payload.Guarantees) 51 | }) 52 | 53 | t.Run("Non-empty block", func(t *testing.T) { 54 | 55 | t.Parallel() 56 | 57 | b, err := emulator.New( 58 | emulator.WithStorageLimitEnabled(false), 59 | ) 60 | require.NoError(t, err) 61 | 62 | logger := zerolog.Nop() 63 | adapter := adapters.NewSDKAdapter(&logger, b) 64 | 65 | addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) 66 | 67 | tx1 := flowsdk.NewTransaction(). 68 | SetScript([]byte(addTwoScript)). 69 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 70 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 71 | SetPayer(b.ServiceKey().Address). 72 | AddAuthorizer(b.ServiceKey().Address) 73 | 74 | signer, err := b.ServiceKey().Signer() 75 | require.NoError(t, err) 76 | 77 | err = tx1.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 78 | require.NoError(t, err) 79 | 80 | tx2 := flowsdk.NewTransaction(). 81 | SetScript([]byte(addTwoScript)). 82 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 83 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 84 | SetPayer(b.ServiceKey().Address). 85 | AddAuthorizer(b.ServiceKey().Address) 86 | 87 | err = tx2.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 88 | require.NoError(t, err) 89 | 90 | // generate a list of transactions 91 | transactions := []*flowsdk.Transaction{tx1, tx2} 92 | 93 | // add all transactions to block 94 | for _, tx := range transactions { 95 | err = adapter.SendTransaction(context.Background(), *tx) 96 | require.NoError(t, err) 97 | } 98 | 99 | block, _, err := b.ExecuteAndCommitBlock() 100 | require.NoError(t, err) 101 | 102 | // block should contain at least one collection 103 | assert.NotEmpty(t, block.Payload.Guarantees) 104 | 105 | i := 0 106 | for _, guarantee := range block.Payload.Guarantees { 107 | collection, err := adapter.GetCollectionByID(context.Background(), convert.FlowIdentifierToSDK(guarantee.ID())) 108 | require.NoError(t, err) 109 | 110 | for _, txID := range collection.TransactionIDs { 111 | assert.Equal(t, transactions[i].ID(), txID) 112 | i++ 113 | } 114 | } 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /emulator/computation_report_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "testing" 24 | 25 | "github.com/onflow/cadence/common" 26 | "github.com/onflow/flow-go/fvm/environment" 27 | "github.com/onflow/flow-go/fvm/meter" 28 | 29 | "github.com/onflow/cadence" 30 | flowsdk "github.com/onflow/flow-go-sdk" 31 | flowgo "github.com/onflow/flow-go/model/flow" 32 | "github.com/rs/zerolog" 33 | "github.com/stretchr/testify/assert" 34 | "github.com/stretchr/testify/require" 35 | 36 | "github.com/onflow/flow-emulator/adapters" 37 | "github.com/onflow/flow-emulator/emulator" 38 | ) 39 | 40 | func TestComputationReportingForScript(t *testing.T) { 41 | 42 | t.Parallel() 43 | 44 | b, err := emulator.New( 45 | emulator.WithComputationReporting(true), 46 | // computation weights shifted by meter.MeterExecutionInternalPrecisionBytes 47 | // so that they correspond directly to computation used. 48 | emulator.WithExecutionEffortWeights(meter.ExecutionEffortWeights{ 49 | common.ComputationKindFunctionInvocation: 2 << meter.MeterExecutionInternalPrecisionBytes, 50 | environment.ComputationKindGetCode: 3 << meter.MeterExecutionInternalPrecisionBytes, 51 | environment.ComputationKindGetAccountContractCode: 5 << meter.MeterExecutionInternalPrecisionBytes, 52 | environment.ComputationKindResolveLocation: 7 << meter.MeterExecutionInternalPrecisionBytes, 53 | common.ComputationKindStatement: 11 << meter.MeterExecutionInternalPrecisionBytes, 54 | }), 55 | ) 56 | require.NoError(t, err) 57 | 58 | logger := zerolog.Nop() 59 | adapter := adapters.NewSDKAdapter(&logger, b) 60 | 61 | addTwoScript, counterAddress := DeployAndGenerateAddTwoScript(t, adapter) 62 | 63 | tx := flowsdk.NewTransaction(). 64 | SetScript([]byte(addTwoScript)). 65 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 66 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 67 | SetPayer(b.ServiceKey().Address). 68 | AddAuthorizer(b.ServiceKey().Address) 69 | 70 | signer, err := b.ServiceKey().Signer() 71 | require.NoError(t, err) 72 | 73 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 74 | require.NoError(t, err) 75 | 76 | // Submit tx (script adds 2) 77 | err = adapter.SendTransaction(context.Background(), *tx) 78 | require.NoError(t, err) 79 | 80 | txResult, err := b.ExecuteNextTransaction() 81 | require.NoError(t, err) 82 | AssertTransactionSucceeded(t, txResult) 83 | 84 | callScript := GenerateGetCounterCountScript(counterAddress, b.ServiceKey().Address) 85 | 86 | // Sample call (value is 0) 87 | scriptResult, err := b.ExecuteScript([]byte(callScript), nil) 88 | require.NoError(t, err) 89 | assert.Equal(t, cadence.NewInt(0), scriptResult.Value) 90 | 91 | computationReport := b.ComputationReport() 92 | require.NotNil(t, computationReport) 93 | require.Len(t, computationReport.Scripts, 1) 94 | 95 | scriptProfile := computationReport.Scripts[scriptResult.ScriptID.String()] 96 | assert.GreaterOrEqual(t, scriptProfile.ComputationUsed, uint64(2*2+3+5+7+11)) 97 | 98 | expectedIntensities := map[string]uint{ 99 | "FunctionInvocation": 2, 100 | "GetAccountContractCode": 1, 101 | "GetCode": 1, 102 | "ResolveLocation": 1, 103 | "Statement": 1, 104 | } 105 | for kind, intensity := range expectedIntensities { 106 | assert.GreaterOrEqual(t, scriptProfile.Intensities[kind], intensity) 107 | } 108 | } 109 | 110 | func TestComputationReportingForTransaction(t *testing.T) { 111 | 112 | t.Parallel() 113 | 114 | b, err := emulator.New( 115 | emulator.WithComputationReporting(true), 116 | ) 117 | require.NoError(t, err) 118 | 119 | logger := zerolog.Nop() 120 | adapter := adapters.NewSDKAdapter(&logger, b) 121 | 122 | addTwoScript, _ := DeployAndGenerateAddTwoScript(t, adapter) 123 | 124 | tx := flowsdk.NewTransaction(). 125 | SetScript([]byte(addTwoScript)). 126 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 127 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 128 | SetPayer(b.ServiceKey().Address). 129 | AddAuthorizer(b.ServiceKey().Address) 130 | 131 | signer, err := b.ServiceKey().Signer() 132 | require.NoError(t, err) 133 | 134 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 135 | require.NoError(t, err) 136 | 137 | // Submit tx (script adds 2) 138 | err = adapter.SendTransaction(context.Background(), *tx) 139 | require.NoError(t, err) 140 | 141 | txResult, err := b.ExecuteNextTransaction() 142 | require.NoError(t, err) 143 | AssertTransactionSucceeded(t, txResult) 144 | 145 | computationReport := b.ComputationReport() 146 | require.NotNil(t, computationReport) 147 | // The 1st transaction creates a new account and deploys the Counting contract. 148 | // The 2nd transaction interacts with the Counting contract. 149 | require.Len(t, computationReport.Transactions, 2) 150 | 151 | txProfile := computationReport.Transactions[txResult.TransactionID.String()] 152 | assert.GreaterOrEqual(t, txProfile.ComputationUsed, uint64(1)) 153 | 154 | expectedIntensities := map[string]uint{ 155 | "CreateCompositeValue": 2, 156 | "CreateDictionaryValue": 1, 157 | "EmitEvent": 73, 158 | "EncodeEvent": 1, 159 | "FunctionInvocation": 9, 160 | "GenerateAccountLocalID": 1, 161 | "GenerateUUID": 1, 162 | "GetAccountContractCode": 1, 163 | "GetCode": 1, 164 | "ResolveLocation": 1, 165 | "Statement": 11, 166 | "TransferCompositeValue": 3, 167 | } 168 | for kind, intensity := range expectedIntensities { 169 | assert.GreaterOrEqual(t, txProfile.Intensities[kind], intensity) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /emulator/contracts.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/onflow/flow-emulator/convert" 25 | 26 | flowsdk "github.com/onflow/flow-go-sdk" 27 | "github.com/onflow/flow-go-sdk/templates" 28 | flowgo "github.com/onflow/flow-go/model/flow" 29 | "github.com/onflow/flow-nft/lib/go/contracts" 30 | ) 31 | 32 | func NewCommonContracts(chain flowgo.Chain) []ContractDescription { 33 | serviceAddress := flowsdk.HexToAddress(chain.ServiceAddress().HexWithPrefix()) 34 | return []ContractDescription{ 35 | { 36 | Name: "ExampleNFT", 37 | Address: serviceAddress, 38 | Description: "✨ Example NFT contract", 39 | Source: contracts.ExampleNFT(serviceAddress, serviceAddress, serviceAddress), 40 | }, 41 | } 42 | } 43 | 44 | var CommonContracts = NewCommonContracts(flowgo.Emulator.Chain()) 45 | 46 | type ContractDescription struct { 47 | Name string 48 | Address flowsdk.Address 49 | Description string 50 | Source []byte 51 | } 52 | 53 | func DeployContracts(b *Blockchain, deployments []ContractDescription) error { 54 | for _, c := range deployments { 55 | err := deployContract(b, c.Name, c.Source) 56 | if err != nil { 57 | return err 58 | } 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func deployContract(b *Blockchain, name string, contract []byte) error { 65 | serviceKey := b.ServiceKey() 66 | serviceAddress := serviceKey.Address 67 | 68 | if serviceKey.PrivateKey == nil { 69 | return fmt.Errorf("not able to deploy contracts without set private key") 70 | } 71 | 72 | latestBlock, err := b.GetLatestBlock() 73 | if err != nil { 74 | return err 75 | } 76 | 77 | tx := templates.AddAccountContract(serviceAddress, templates.Contract{ 78 | Name: name, 79 | Source: string(contract), 80 | }) 81 | 82 | tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 83 | SetReferenceBlockID(flowsdk.Identifier(latestBlock.ID())). 84 | SetProposalKey(serviceAddress, serviceKey.Index, serviceKey.SequenceNumber). 85 | SetPayer(serviceAddress) 86 | 87 | signer, err := serviceKey.Signer() 88 | if err != nil { 89 | return err 90 | } 91 | 92 | err = tx.SignEnvelope(serviceAddress, serviceKey.Index, signer) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | err = b.AddTransaction(*convert.SDKTransactionToFlow(*tx)) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | _, results, err := b.ExecuteAndCommitBlock() 103 | if err != nil { 104 | return err 105 | } 106 | 107 | lastResult := results[len(results)-1] 108 | if !lastResult.Succeeded() { 109 | return lastResult.Error 110 | } 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /emulator/contracts_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package emulator_test 19 | 20 | import ( 21 | "fmt" 22 | flowgo "github.com/onflow/flow-go/model/flow" 23 | "testing" 24 | 25 | "github.com/onflow/flow-emulator/emulator" 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestCommonContractsDeployment(t *testing.T) { 30 | 31 | t.Parallel() 32 | 33 | //only test monotonic and emulator ( mainnet / testnet is used for remote debugging ) 34 | chains := []flowgo.Chain{ 35 | flowgo.Emulator.Chain(), 36 | flowgo.MonotonicEmulator.Chain(), 37 | } 38 | 39 | for _, chain := range chains { 40 | contracts := emulator.NewCommonContracts(chain) 41 | 42 | b, err := emulator.New( 43 | emulator.Contracts(contracts), 44 | emulator.WithChainID(chain.ChainID()), 45 | ) 46 | require.NoError(t, err) 47 | 48 | for _, contract := range contracts { 49 | 50 | require.Equal(t, contract.Address.Hex(), chain.ServiceAddress().Hex()) 51 | 52 | scriptCode := fmt.Sprintf(` 53 | access(all) fun main() { 54 | getAccount(0x%s).contracts.get(name: "%s") ?? panic("contract is not deployed") 55 | }`, contract.Address, contract.Name) 56 | 57 | scriptResult, err := b.ExecuteScript([]byte(scriptCode), [][]byte{}) 58 | require.NoError(t, err) 59 | require.NoError(t, scriptResult.Error) 60 | 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /emulator/coverage_report_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "testing" 24 | 25 | "github.com/onflow/cadence" 26 | "github.com/onflow/cadence/common" 27 | "github.com/onflow/cadence/runtime" 28 | flowsdk "github.com/onflow/flow-go-sdk" 29 | flowgo "github.com/onflow/flow-go/model/flow" 30 | "github.com/rs/zerolog" 31 | "github.com/stretchr/testify/assert" 32 | "github.com/stretchr/testify/require" 33 | 34 | "github.com/onflow/flow-emulator/adapters" 35 | "github.com/onflow/flow-emulator/emulator" 36 | ) 37 | 38 | func TestCoverageReport(t *testing.T) { 39 | 40 | t.Parallel() 41 | 42 | coverageReport := runtime.NewCoverageReport() 43 | b, err := emulator.New( 44 | emulator.WithCoverageReport(coverageReport), 45 | ) 46 | require.NoError(t, err) 47 | 48 | coverageReport.Reset() 49 | logger := zerolog.Nop() 50 | adapter := adapters.NewSDKAdapter(&logger, b) 51 | 52 | addTwoScript, counterAddress := DeployAndGenerateAddTwoScript(t, adapter) 53 | 54 | tx := flowsdk.NewTransaction(). 55 | SetScript([]byte(addTwoScript)). 56 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 57 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 58 | SetPayer(b.ServiceKey().Address). 59 | AddAuthorizer(b.ServiceKey().Address) 60 | 61 | signer, err := b.ServiceKey().Signer() 62 | require.NoError(t, err) 63 | 64 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 65 | require.NoError(t, err) 66 | 67 | callScript := GenerateGetCounterCountScript(counterAddress, b.ServiceKey().Address) 68 | 69 | // Sample call (value is 0) 70 | scriptResult, err := b.ExecuteScript([]byte(callScript), nil) 71 | require.NoError(t, err) 72 | assert.Equal(t, cadence.NewInt(0), scriptResult.Value) 73 | 74 | // Submit tx (script adds 2) 75 | err = adapter.SendTransaction(context.Background(), *tx) 76 | require.NoError(t, err) 77 | 78 | txResult, err := b.ExecuteNextTransaction() 79 | require.NoError(t, err) 80 | AssertTransactionSucceeded(t, txResult) 81 | 82 | address, err := common.HexToAddress(counterAddress.Hex()) 83 | require.NoError(t, err) 84 | location := common.AddressLocation{ 85 | Address: address, 86 | Name: "Counting", 87 | } 88 | coverage := coverageReport.Coverage[location] 89 | 90 | assert.Equal(t, []int{}, coverage.MissedLines()) 91 | assert.Equal(t, 4, coverage.Statements) 92 | assert.Equal(t, "100.0%", coverage.Percentage()) 93 | assert.EqualValues( 94 | t, 95 | map[int]int{ 96 | 11: 1, 15: 1, 16: 1, 21: 1, 97 | }, 98 | coverage.LineHits, 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /emulator/coverage_reported_runtime.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import ( 22 | "github.com/onflow/cadence" 23 | "github.com/onflow/cadence/common" 24 | "github.com/onflow/cadence/interpreter" 25 | "github.com/onflow/cadence/runtime" 26 | "github.com/onflow/cadence/sema" 27 | ) 28 | 29 | type CoverageReportedRuntime struct { 30 | runtime.Runtime 31 | runtime.Environment 32 | *runtime.CoverageReport 33 | } 34 | 35 | func (crr CoverageReportedRuntime) NewScriptExecutor( 36 | script runtime.Script, 37 | context runtime.Context, 38 | ) runtime.Executor { 39 | context.CoverageReport = crr.CoverageReport 40 | return crr.Runtime.NewScriptExecutor(script, context) 41 | } 42 | 43 | func (crr CoverageReportedRuntime) ExecuteScript( 44 | script runtime.Script, 45 | context runtime.Context, 46 | ) ( 47 | cadence.Value, 48 | error, 49 | ) { 50 | context.CoverageReport = crr.CoverageReport 51 | return crr.Runtime.ExecuteScript(script, context) 52 | } 53 | 54 | func (crr CoverageReportedRuntime) NewTransactionExecutor( 55 | script runtime.Script, 56 | context runtime.Context, 57 | ) runtime.Executor { 58 | context.CoverageReport = crr.CoverageReport 59 | return crr.Runtime.NewTransactionExecutor(script, context) 60 | } 61 | 62 | func (crr CoverageReportedRuntime) ExecuteTransaction( 63 | script runtime.Script, 64 | context runtime.Context, 65 | ) error { 66 | context.CoverageReport = crr.CoverageReport 67 | return crr.Runtime.ExecuteTransaction(script, context) 68 | } 69 | 70 | func (crr CoverageReportedRuntime) NewContractFunctionExecutor( 71 | contractLocation common.AddressLocation, 72 | functionName string, 73 | arguments []cadence.Value, 74 | argumentTypes []sema.Type, 75 | context runtime.Context, 76 | ) runtime.Executor { 77 | context.CoverageReport = crr.CoverageReport 78 | return crr.Runtime.NewContractFunctionExecutor( 79 | contractLocation, 80 | functionName, 81 | arguments, 82 | argumentTypes, 83 | context, 84 | ) 85 | } 86 | 87 | func (crr CoverageReportedRuntime) InvokeContractFunction( 88 | contractLocation common.AddressLocation, 89 | functionName string, 90 | arguments []cadence.Value, 91 | argumentTypes []sema.Type, 92 | context runtime.Context, 93 | ) ( 94 | cadence.Value, 95 | error, 96 | ) { 97 | context.CoverageReport = crr.CoverageReport 98 | return crr.Runtime.InvokeContractFunction( 99 | contractLocation, 100 | functionName, 101 | arguments, 102 | argumentTypes, 103 | context, 104 | ) 105 | } 106 | 107 | func (crr CoverageReportedRuntime) ParseAndCheckProgram( 108 | source []byte, 109 | context runtime.Context, 110 | ) ( 111 | *interpreter.Program, 112 | error, 113 | ) { 114 | context.CoverageReport = crr.CoverageReport 115 | return crr.Runtime.ParseAndCheckProgram(source, context) 116 | } 117 | 118 | func (crr CoverageReportedRuntime) ReadStored( 119 | address common.Address, 120 | path cadence.Path, 121 | context runtime.Context, 122 | ) ( 123 | cadence.Value, 124 | error, 125 | ) { 126 | context.CoverageReport = crr.CoverageReport 127 | return crr.Runtime.ReadStored(address, path, context) 128 | } 129 | 130 | func (crr CoverageReportedRuntime) Storage( 131 | context runtime.Context, 132 | ) ( 133 | *runtime.Storage, 134 | *interpreter.Interpreter, 135 | error, 136 | ) { 137 | context.CoverageReport = crr.CoverageReport 138 | return crr.Runtime.Storage(context) 139 | } 140 | -------------------------------------------------------------------------------- /emulator/emulator.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/onflow/cadence/common" 25 | "github.com/onflow/cadence/interpreter" 26 | "github.com/onflow/cadence/runtime" 27 | flowgosdk "github.com/onflow/flow-go-sdk" 28 | sdkcrypto "github.com/onflow/flow-go-sdk/crypto" 29 | accessmodel "github.com/onflow/flow-go/model/access" 30 | flowgo "github.com/onflow/flow-go/model/flow" 31 | 32 | "github.com/onflow/flow-emulator/types" 33 | ) 34 | 35 | type ServiceKey struct { 36 | Index uint32 37 | Address flowgosdk.Address 38 | SequenceNumber uint64 39 | PrivateKey sdkcrypto.PrivateKey 40 | PublicKey sdkcrypto.PublicKey 41 | HashAlgo sdkcrypto.HashAlgorithm 42 | SigAlgo sdkcrypto.SignatureAlgorithm 43 | Weight int 44 | } 45 | 46 | const defaultServiceKeyPrivateKeySeed = "elephant ears space cowboy octopus rodeo potato cannon pineapple" 47 | const DefaultServiceKeySigAlgo = sdkcrypto.ECDSA_P256 48 | const DefaultServiceKeyHashAlgo = sdkcrypto.SHA3_256 49 | 50 | func DefaultServiceKey() ServiceKey { 51 | return GenerateDefaultServiceKey(DefaultServiceKeySigAlgo, DefaultServiceKeyHashAlgo) 52 | } 53 | 54 | func GenerateDefaultServiceKey( 55 | sigAlgo sdkcrypto.SignatureAlgorithm, 56 | hashAlgo sdkcrypto.HashAlgorithm, 57 | ) ServiceKey { 58 | privateKey, err := sdkcrypto.GeneratePrivateKey( 59 | sigAlgo, 60 | []byte(defaultServiceKeyPrivateKeySeed), 61 | ) 62 | if err != nil { 63 | panic(fmt.Sprintf("Failed to generate default service key: %s", err.Error())) 64 | } 65 | 66 | return ServiceKey{ 67 | PrivateKey: privateKey, 68 | SigAlgo: sigAlgo, 69 | HashAlgo: hashAlgo, 70 | } 71 | } 72 | 73 | func (s ServiceKey) Signer() (sdkcrypto.Signer, error) { 74 | return sdkcrypto.NewInMemorySigner(s.PrivateKey, s.HashAlgo) 75 | } 76 | 77 | func (s ServiceKey) AccountKey() *flowgosdk.AccountKey { 78 | 79 | var publicKey sdkcrypto.PublicKey 80 | if s.PublicKey != nil { 81 | publicKey = s.PublicKey 82 | } 83 | 84 | if s.PrivateKey != nil { 85 | publicKey = s.PrivateKey.PublicKey() 86 | } 87 | 88 | return &flowgosdk.AccountKey{ 89 | Index: s.Index, 90 | PublicKey: publicKey, 91 | SigAlgo: s.SigAlgo, 92 | HashAlgo: s.HashAlgo, 93 | Weight: s.Weight, 94 | SequenceNumber: s.SequenceNumber, 95 | } 96 | } 97 | 98 | type CoverageReportCapable interface { 99 | CoverageReport() *runtime.CoverageReport 100 | ResetCoverageReport() 101 | } 102 | 103 | type ComputationReportCapable interface { 104 | ComputationReport() *ComputationReport 105 | } 106 | 107 | type DebuggingCapable interface { 108 | StartDebugger() *interpreter.Debugger 109 | EndDebugging() 110 | // Deprecated: Needed for the debugger right now, do NOT use for other purposes. 111 | // TODO: refactor 112 | GetAccountUnsafe(address flowgo.Address) (*flowgo.Account, error) 113 | } 114 | 115 | type SnapshotCapable interface { 116 | Snapshots() ([]string, error) 117 | CreateSnapshot(name string) error 118 | LoadSnapshot(name string) error 119 | } 120 | 121 | type RollbackCapable interface { 122 | RollbackToBlockHeight(height uint64) error 123 | } 124 | 125 | type AccessProvider interface { 126 | Ping() error 127 | GetNetworkParameters() accessmodel.NetworkParameters 128 | 129 | GetLatestBlock() (*flowgo.Block, error) 130 | GetBlockByID(id flowgo.Identifier) (*flowgo.Block, error) 131 | GetBlockByHeight(height uint64) (*flowgo.Block, error) 132 | 133 | GetCollectionByID(colID flowgo.Identifier) (*flowgo.LightCollection, error) 134 | GetFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) 135 | 136 | GetTransaction(txID flowgo.Identifier) (*flowgo.TransactionBody, error) 137 | GetTransactionResult(txID flowgo.Identifier) (*accessmodel.TransactionResult, error) 138 | GetTransactionsByBlockID(blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) 139 | GetTransactionResultsByBlockID(blockID flowgo.Identifier) ([]*accessmodel.TransactionResult, error) 140 | 141 | GetAccount(address flowgo.Address) (*flowgo.Account, error) 142 | GetAccountAtBlockHeight(address flowgo.Address, blockHeight uint64) (*flowgo.Account, error) 143 | GetAccountByIndex(uint) (*flowgo.Account, error) 144 | 145 | GetEventsByHeight(blockHeight uint64, eventType string) ([]flowgo.Event, error) 146 | GetEventsForBlockIDs(eventType string, blockIDs []flowgo.Identifier) ([]flowgo.BlockEvents, error) 147 | GetEventsForHeightRange(eventType string, startHeight, endHeight uint64) ([]flowgo.BlockEvents, error) 148 | 149 | ExecuteScript(script []byte, arguments [][]byte) (*types.ScriptResult, error) 150 | ExecuteScriptAtBlockHeight(script []byte, arguments [][]byte, blockHeight uint64) (*types.ScriptResult, error) 151 | ExecuteScriptAtBlockID(script []byte, arguments [][]byte, id flowgo.Identifier) (*types.ScriptResult, error) 152 | 153 | SendTransaction(tx *flowgo.TransactionBody) error 154 | AddTransaction(tx flowgo.TransactionBody) error 155 | } 156 | 157 | type AutoMineCapable interface { 158 | EnableAutoMine() 159 | DisableAutoMine() 160 | } 161 | 162 | type ExecutionCapable interface { 163 | ExecuteAndCommitBlock() (*flowgo.Block, []*types.TransactionResult, error) 164 | ExecuteNextTransaction() (*types.TransactionResult, error) 165 | ExecuteBlock() ([]*types.TransactionResult, error) 166 | CommitBlock() (*flowgo.Block, error) 167 | } 168 | 169 | type LogProvider interface { 170 | GetLogs(flowgo.Identifier) ([]string, error) 171 | } 172 | 173 | type SourceMapCapable interface { 174 | GetSourceFile(location common.Location) string 175 | } 176 | 177 | // Emulator defines the method set of an emulated emulator. 178 | type Emulator interface { 179 | ServiceKey() ServiceKey 180 | 181 | AccessProvider 182 | 183 | CoverageReportCapable 184 | ComputationReportCapable 185 | DebuggingCapable 186 | SnapshotCapable 187 | RollbackCapable 188 | AutoMineCapable 189 | ExecutionCapable 190 | LogProvider 191 | SourceMapCapable 192 | } 193 | -------------------------------------------------------------------------------- /emulator/events_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "testing" 25 | 26 | "github.com/rs/zerolog" 27 | 28 | "github.com/onflow/flow-emulator/adapters" 29 | "github.com/onflow/flow-emulator/emulator" 30 | 31 | "github.com/onflow/cadence/common" 32 | "github.com/onflow/flow-go-sdk/templates" 33 | flowgo "github.com/onflow/flow-go/model/flow" 34 | "github.com/stretchr/testify/assert" 35 | "github.com/stretchr/testify/require" 36 | 37 | "github.com/onflow/cadence" 38 | flowsdk "github.com/onflow/flow-go-sdk" 39 | ) 40 | 41 | func TestEventEmitted(t *testing.T) { 42 | 43 | t.Parallel() 44 | 45 | t.Run("EmittedFromScript", func(t *testing.T) { 46 | 47 | t.Parallel() 48 | 49 | // Emitting events in scripts is not supported 50 | 51 | b, err := emulator.New() 52 | require.NoError(t, err) 53 | 54 | script := []byte(` 55 | access(all) event MyEvent(x: Int, y: Int) 56 | 57 | access(all) fun main() { 58 | emit MyEvent(x: 1, y: 2) 59 | } 60 | `) 61 | 62 | result, err := b.ExecuteScript(script, nil) 63 | assert.NoError(t, err) 64 | require.NoError(t, result.Error) 65 | require.Empty(t, result.Events) 66 | }) 67 | 68 | t.Run("EmittedFromAccount", func(t *testing.T) { 69 | 70 | t.Parallel() 71 | 72 | b, err := emulator.New( 73 | emulator.WithStorageLimitEnabled(false), 74 | ) 75 | require.NoError(t, err) 76 | 77 | logger := zerolog.Nop() 78 | adapter := adapters.NewSDKAdapter(&logger, b) 79 | 80 | accountContracts := []templates.Contract{ 81 | { 82 | Name: "Test", 83 | Source: ` 84 | access(all) contract Test { 85 | access(all) event MyEvent(x: Int, y: Int) 86 | 87 | access(all) fun emitMyEvent(x: Int, y: Int) { 88 | emit MyEvent(x: x, y: y) 89 | } 90 | } 91 | `, 92 | }, 93 | } 94 | 95 | publicKey := b.ServiceKey().AccountKey() 96 | 97 | address, err := adapter.CreateAccount( 98 | context.Background(), 99 | []*flowsdk.AccountKey{publicKey}, 100 | accountContracts, 101 | ) 102 | require.NoError(t, err) 103 | 104 | script := []byte(fmt.Sprintf(` 105 | import 0x%s 106 | 107 | transaction { 108 | execute { 109 | Test.emitMyEvent(x: 1, y: 2) 110 | } 111 | } 112 | `, address.Hex())) 113 | 114 | tx := flowsdk.NewTransaction(). 115 | SetScript(script). 116 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 117 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 118 | SetPayer(b.ServiceKey().Address) 119 | 120 | signer, err := b.ServiceKey().Signer() 121 | require.NoError(t, err) 122 | 123 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 124 | require.NoError(t, err) 125 | 126 | err = adapter.SendTransaction(context.Background(), *tx) 127 | assert.NoError(t, err) 128 | 129 | result, err := b.ExecuteNextTransaction() 130 | assert.NoError(t, err) 131 | assert.True(t, result.Succeeded()) 132 | 133 | block, err := b.CommitBlock() 134 | require.NoError(t, err) 135 | 136 | addr, _ := common.BytesToAddress(address.Bytes()) 137 | location := common.AddressLocation{ 138 | Address: addr, 139 | Name: "Test", 140 | } 141 | expectedType := location.TypeID(nil, "Test.MyEvent") 142 | 143 | events, err := adapter.GetEventsForHeightRange(context.Background(), string(expectedType), block.Header.Height, block.Header.Height) 144 | require.NoError(t, err) 145 | require.Len(t, events, 1) 146 | 147 | actualEvent := events[0].Events[0] 148 | decodedEvent := actualEvent.Value 149 | decodedEventType := decodedEvent.Type().(*cadence.EventType) 150 | expectedID := flowsdk.Event{TransactionID: tx.ID(), EventIndex: 0}.ID() 151 | 152 | assert.Equal(t, string(expectedType), actualEvent.Type) 153 | assert.Equal(t, expectedID, actualEvent.ID()) 154 | 155 | fields := decodedEventType.FieldsMappedByName() 156 | 157 | assert.Contains(t, fields, "x") 158 | assert.Contains(t, fields, "y") 159 | 160 | fieldValues := decodedEvent.FieldsMappedByName() 161 | 162 | assert.Equal(t, cadence.NewInt(1), fieldValues["x"]) 163 | assert.Equal(t, cadence.NewInt(2), fieldValues["y"]) 164 | 165 | events, err = adapter.GetEventsForBlockIDs( 166 | context.Background(), 167 | string(expectedType), 168 | []flowsdk.Identifier{ 169 | flowsdk.Identifier(block.Header.ID()), 170 | }, 171 | ) 172 | require.NoError(t, err) 173 | require.Len(t, events, 1) 174 | 175 | actualEvent = events[0].Events[0] 176 | decodedEvent = actualEvent.Value 177 | decodedEventType = decodedEvent.Type().(*cadence.EventType) 178 | expectedID = flowsdk.Event{TransactionID: tx.ID(), EventIndex: 0}.ID() 179 | 180 | assert.Equal(t, string(expectedType), actualEvent.Type) 181 | assert.Equal(t, expectedID, actualEvent.ID()) 182 | 183 | fields = decodedEventType.FieldsMappedByName() 184 | 185 | assert.Contains(t, fields, "x") 186 | assert.Contains(t, fields, "y") 187 | 188 | fieldValues = decodedEvent.FieldsMappedByName() 189 | 190 | assert.Equal(t, cadence.NewInt(1), fieldValues["x"]) 191 | assert.Equal(t, cadence.NewInt(2), fieldValues["y"]) 192 | 193 | }) 194 | } 195 | -------------------------------------------------------------------------------- /emulator/init_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "os" 25 | "testing" 26 | 27 | "github.com/rs/zerolog" 28 | 29 | "github.com/onflow/flow-emulator/adapters" 30 | "github.com/onflow/flow-emulator/convert" 31 | "github.com/onflow/flow-emulator/emulator" 32 | 33 | "github.com/onflow/cadence" 34 | flowsdk "github.com/onflow/flow-go-sdk" 35 | "github.com/onflow/flow-go-sdk/templates" 36 | flowgo "github.com/onflow/flow-go/model/flow" 37 | "github.com/stretchr/testify/assert" 38 | "github.com/stretchr/testify/require" 39 | 40 | "github.com/onflow/flow-emulator/storage/sqlite" 41 | ) 42 | 43 | func TestInitialization(t *testing.T) { 44 | 45 | t.Parallel() 46 | 47 | t.Run("should inject initial state when initialized with empty store", func(t *testing.T) { 48 | 49 | t.Parallel() 50 | 51 | file, err := os.CreateTemp("", "sqlite-test") 52 | require.Nil(t, err) 53 | store, err := sqlite.New(file.Name()) 54 | require.Nil(t, err) 55 | defer store.Close() 56 | 57 | b, _ := emulator.New(emulator.WithStore(store)) 58 | logger := zerolog.Nop() 59 | adapter := adapters.NewSDKAdapter(&logger, b) 60 | 61 | serviceAcct, err := adapter.GetAccount(context.Background(), flowsdk.ServiceAddress(flowsdk.Emulator)) 62 | require.NoError(t, err) 63 | 64 | assert.NotNil(t, serviceAcct) 65 | 66 | latestBlock, err := b.GetLatestBlock() 67 | require.NoError(t, err) 68 | 69 | assert.EqualValues(t, 0, latestBlock.Header.Height) 70 | assert.Equal(t, 71 | flowgo.Genesis(flowgo.Emulator).ID(), 72 | latestBlock.ID(), 73 | ) 74 | }) 75 | 76 | t.Run("should restore state when initialized with non-empty store", func(t *testing.T) { 77 | 78 | t.Parallel() 79 | 80 | file, err := os.CreateTemp("", "sqlite-test") 81 | require.Nil(t, err) 82 | store, err := sqlite.New(file.Name()) 83 | require.Nil(t, err) 84 | defer store.Close() 85 | 86 | b, _ := emulator.New(emulator.WithStore(store), emulator.WithStorageLimitEnabled(false)) 87 | logger := zerolog.Nop() 88 | adapter := adapters.NewSDKAdapter(&logger, b) 89 | 90 | contracts := []templates.Contract{ 91 | { 92 | Name: "Counting", 93 | Source: counterScript, 94 | }, 95 | } 96 | 97 | counterAddress, err := adapter.CreateAccount(context.Background(), nil, contracts) 98 | require.NoError(t, err) 99 | 100 | // Submit a transaction adds some ledger state and event state 101 | script := fmt.Sprintf( 102 | ` 103 | import 0x%s 104 | 105 | transaction { 106 | 107 | prepare(acct: auth(Storage, Capabilities) &Account) { 108 | 109 | let counter <- Counting.createCounter() 110 | counter.add(1) 111 | 112 | acct.storage.save(<-counter, to: /storage/counter) 113 | 114 | let counterCap = acct.capabilities.storage.issue<&Counting.Counter>(/storage/counter) 115 | acct.capabilities.publish(counterCap, at: /public/counter) 116 | } 117 | } 118 | `, 119 | counterAddress, 120 | ) 121 | 122 | tx := flowsdk.NewTransaction(). 123 | SetScript([]byte(script)). 124 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 125 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). 126 | SetPayer(b.ServiceKey().Address). 127 | AddAuthorizer(b.ServiceKey().Address) 128 | 129 | signer, err := b.ServiceKey().Signer() 130 | require.NoError(t, err) 131 | 132 | err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer) 133 | require.NoError(t, err) 134 | 135 | err = adapter.SendTransaction(context.Background(), *tx) 136 | require.NoError(t, err) 137 | 138 | result, err := b.ExecuteNextTransaction() 139 | assert.NoError(t, err) 140 | require.True(t, result.Succeeded()) 141 | 142 | block, err := b.CommitBlock() 143 | assert.NoError(t, err) 144 | require.NotNil(t, block) 145 | 146 | minedTx, err := adapter.GetTransaction(context.Background(), tx.ID()) 147 | require.NoError(t, err) 148 | 149 | minedEvents, err := adapter.GetEventsForHeightRange(context.Background(), "", block.Header.Height, block.Header.Height) 150 | require.NoError(t, err) 151 | 152 | // Create a new emulator with the same store 153 | b, _ = emulator.New(emulator.WithStore(store)) 154 | adapter = adapters.NewSDKAdapter(&logger, b) 155 | 156 | t.Run("should be able to read blocks", func(t *testing.T) { 157 | latestBlock, _, err := adapter.GetLatestBlock(context.Background(), true) 158 | require.NoError(t, err) 159 | 160 | assert.Equal(t, flowsdk.Identifier(block.ID()), latestBlock.ID) 161 | 162 | blockByHeight, _, err := adapter.GetBlockByHeight(context.Background(), block.Header.Height) 163 | require.NoError(t, err) 164 | 165 | assert.Equal(t, flowsdk.Identifier(block.ID()), blockByHeight.ID) 166 | 167 | blockByHash, _, err := adapter.GetBlockByID(context.Background(), convert.FlowIdentifierToSDK(block.ID())) 168 | require.NoError(t, err) 169 | 170 | assert.Equal(t, flowsdk.Identifier(block.ID()), blockByHash.ID) 171 | }) 172 | 173 | t.Run("should be able to read transactions", func(t *testing.T) { 174 | txByHash, err := adapter.GetTransaction(context.Background(), tx.ID()) 175 | require.NoError(t, err) 176 | 177 | assert.Equal(t, minedTx, txByHash) 178 | }) 179 | 180 | t.Run("should be able to read events", func(t *testing.T) { 181 | gotEvents, err := adapter.GetEventsForHeightRange(context.Background(), "", block.Header.Height, block.Header.Height) 182 | require.NoError(t, err) 183 | 184 | assert.Equal(t, minedEvents[0].Events, gotEvents[0].Events) 185 | }) 186 | 187 | t.Run("should be able to read ledger state", func(t *testing.T) { 188 | readScript := fmt.Sprintf( 189 | ` 190 | import 0x%s 191 | 192 | access(all) fun main(): Int { 193 | return getAccount(0x%s).capabilities.borrow<&Counting.Counter>(/public/counter)?.count ?? 0 194 | } 195 | `, 196 | counterAddress, 197 | b.ServiceKey().Address, 198 | ) 199 | 200 | result, err := b.ExecuteScript([]byte(readScript), nil) 201 | require.NoError(t, err) 202 | 203 | assert.Equal(t, cadence.NewInt(1), result.Value) 204 | }) 205 | }) 206 | } 207 | -------------------------------------------------------------------------------- /emulator/logs_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/onflow/flow-emulator/emulator" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestRuntimeLogs(t *testing.T) { 31 | 32 | t.Parallel() 33 | 34 | b, err := emulator.New() 35 | require.NoError(t, err) 36 | 37 | script := []byte(` 38 | access(all) fun main() { 39 | log("elephant ears") 40 | } 41 | `) 42 | 43 | result, err := b.ExecuteScript(script, nil) 44 | assert.NoError(t, err) 45 | assert.Equal(t, []string{`"elephant ears"`}, result.Logs) 46 | } 47 | -------------------------------------------------------------------------------- /emulator/pendingBlock.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package emulator 19 | 20 | import ( 21 | "math/rand" 22 | "time" 23 | 24 | "github.com/onflow/flow-go/fvm" 25 | "github.com/onflow/flow-go/fvm/storage/snapshot" 26 | "github.com/onflow/flow-go/fvm/storage/state" 27 | flowgo "github.com/onflow/flow-go/model/flow" 28 | ) 29 | 30 | type IndexedTransactionResult struct { 31 | fvm.ProcedureOutput 32 | Index uint32 33 | } 34 | 35 | // MaxViewIncrease represents the largest difference in view number between 36 | // two consecutive blocks. The minimum view increment is 1. 37 | const MaxViewIncrease = 3 38 | 39 | // A pendingBlock contains the pending state required to form a new block. 40 | type pendingBlock struct { 41 | height uint64 42 | view uint64 43 | parentID flowgo.Identifier 44 | clock Clock 45 | timestamp time.Time 46 | // mapping from transaction ID to transaction 47 | transactions map[flowgo.Identifier]*flowgo.TransactionBody 48 | // list of transaction IDs in the block 49 | transactionIDs []flowgo.Identifier 50 | // mapping from transaction ID to transaction result 51 | transactionResults map[flowgo.Identifier]IndexedTransactionResult 52 | // current working ledger, updated after each transaction execution 53 | ledgerState *state.ExecutionState 54 | // events emitted during execution 55 | events []flowgo.Event 56 | // index of transaction execution 57 | index uint32 58 | } 59 | 60 | // newPendingBlock creates a new pending block sequentially after a specified block. 61 | func newPendingBlock( 62 | prevBlock *flowgo.Block, 63 | ledgerSnapshot snapshot.StorageSnapshot, 64 | clock Clock, 65 | ) *pendingBlock { 66 | return &pendingBlock{ 67 | height: prevBlock.Header.Height + 1, 68 | // the view increments by between 1 and MaxViewIncrease to match 69 | // behaviour on a real network, where views are not consecutive 70 | view: prevBlock.Header.View + uint64(rand.Intn(MaxViewIncrease)+1), 71 | parentID: prevBlock.ID(), 72 | clock: clock, 73 | timestamp: clock.Now(), 74 | transactions: make(map[flowgo.Identifier]*flowgo.TransactionBody), 75 | transactionIDs: make([]flowgo.Identifier, 0), 76 | transactionResults: make(map[flowgo.Identifier]IndexedTransactionResult), 77 | ledgerState: state.NewExecutionState( 78 | ledgerSnapshot, 79 | state.DefaultParameters()), 80 | events: make([]flowgo.Event, 0), 81 | index: 0, 82 | } 83 | } 84 | 85 | // ID returns the ID of the pending block. 86 | func (b *pendingBlock) ID() flowgo.Identifier { 87 | return b.Block().ID() 88 | } 89 | 90 | // Block returns the block information for the pending block. 91 | func (b *pendingBlock) Block() *flowgo.Block { 92 | collections := b.Collections() 93 | 94 | guarantees := make([]*flowgo.CollectionGuarantee, len(collections)) 95 | for i, collection := range collections { 96 | guarantees[i] = &flowgo.CollectionGuarantee{ 97 | CollectionID: collection.ID(), 98 | } 99 | } 100 | 101 | return &flowgo.Block{ 102 | Header: &flowgo.Header{ 103 | Height: b.height, 104 | View: b.view, 105 | ParentID: b.parentID, 106 | Timestamp: b.timestamp, 107 | }, 108 | Payload: &flowgo.Payload{ 109 | Guarantees: guarantees, 110 | }, 111 | } 112 | } 113 | 114 | func (b *pendingBlock) Collections() []*flowgo.LightCollection { 115 | if len(b.transactionIDs) == 0 { 116 | return []*flowgo.LightCollection{} 117 | } 118 | 119 | transactionIDs := make([]flowgo.Identifier, len(b.transactionIDs)) 120 | 121 | // TODO: remove once SDK models are removed 122 | copy(transactionIDs, b.transactionIDs) 123 | 124 | collection := flowgo.LightCollection{Transactions: transactionIDs} 125 | 126 | return []*flowgo.LightCollection{&collection} 127 | } 128 | 129 | func (b *pendingBlock) Transactions() map[flowgo.Identifier]*flowgo.TransactionBody { 130 | return b.transactions 131 | } 132 | 133 | func (b *pendingBlock) TransactionResults() map[flowgo.Identifier]IndexedTransactionResult { 134 | return b.transactionResults 135 | } 136 | 137 | // Finalize returns the execution snapshot for the pending block. 138 | func (b *pendingBlock) Finalize() *snapshot.ExecutionSnapshot { 139 | return b.ledgerState.Finalize() 140 | } 141 | 142 | // AddTransaction adds a transaction to the pending block. 143 | func (b *pendingBlock) AddTransaction(tx flowgo.TransactionBody) { 144 | b.transactionIDs = append(b.transactionIDs, tx.ID()) 145 | b.transactions[tx.ID()] = &tx 146 | } 147 | 148 | // ContainsTransaction checks if a transaction is included in the pending block. 149 | func (b *pendingBlock) ContainsTransaction(txID flowgo.Identifier) bool { 150 | _, exists := b.transactions[txID] 151 | return exists 152 | } 153 | 154 | // GetTransaction retrieves a transaction in the pending block by ID. 155 | func (b *pendingBlock) GetTransaction(txID flowgo.Identifier) *flowgo.TransactionBody { 156 | return b.transactions[txID] 157 | } 158 | 159 | // NextTransaction returns the next indexed transaction. 160 | func (b *pendingBlock) NextTransaction() *flowgo.TransactionBody { 161 | if int(b.index) > len(b.transactionIDs) { 162 | return nil 163 | } 164 | 165 | txID := b.transactionIDs[b.index] 166 | return b.GetTransaction(txID) 167 | } 168 | 169 | // ExecuteNextTransaction executes the next transaction in the pending block. 170 | // 171 | // This function uses the provided execute function to perform the actual 172 | // execution, then updates the pending block with the output. 173 | func (b *pendingBlock) ExecuteNextTransaction( 174 | vm *fvm.VirtualMachine, 175 | ctx fvm.Context, 176 | ) ( 177 | fvm.ProcedureOutput, 178 | error, 179 | ) { 180 | txnBody := b.NextTransaction() 181 | txnIndex := b.index 182 | 183 | // increment transaction index even if transaction reverts 184 | b.index++ 185 | 186 | executionSnapshot, output, err := vm.Run( 187 | ctx, 188 | fvm.Transaction(txnBody, txnIndex), 189 | b.ledgerState) 190 | if err != nil { 191 | // fail fast if fatal error occurs 192 | return fvm.ProcedureOutput{}, err 193 | } 194 | 195 | b.events = append(b.events, output.Events...) 196 | 197 | err = b.ledgerState.Merge(executionSnapshot) 198 | if err != nil { 199 | // fail fast if fatal error occurs 200 | return fvm.ProcedureOutput{}, err 201 | } 202 | 203 | b.transactionResults[txnBody.ID()] = IndexedTransactionResult{ 204 | ProcedureOutput: output, 205 | Index: txnIndex, 206 | } 207 | 208 | return output, nil 209 | } 210 | 211 | // Events returns all events captured during the execution of the pending block. 212 | func (b *pendingBlock) Events() []flowgo.Event { 213 | return b.events 214 | } 215 | 216 | // ExecutionStarted returns true if the pending block has started executing. 217 | func (b *pendingBlock) ExecutionStarted() bool { 218 | return b.index > 0 219 | } 220 | 221 | // ExecutionComplete returns true if the pending block is fully executed. 222 | func (b *pendingBlock) ExecutionComplete() bool { 223 | return b.index >= uint32(b.Size()) 224 | } 225 | 226 | // Size returns the number of transactions in the pending block. 227 | func (b *pendingBlock) Size() int { 228 | return len(b.transactionIDs) 229 | } 230 | 231 | // Empty returns true if the pending block is empty. 232 | func (b *pendingBlock) Empty() bool { 233 | return b.Size() == 0 234 | } 235 | 236 | // SetClock sets the given clock on the pending block. 237 | // After this block is committed, the block timestamp will 238 | // contain the value of clock.Now(). 239 | func (b *pendingBlock) SetClock(clock Clock) { 240 | b.clock = clock 241 | b.timestamp = clock.Now() 242 | } 243 | -------------------------------------------------------------------------------- /emulator/pragma.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator 20 | 21 | import ( 22 | "github.com/onflow/cadence/ast" 23 | "github.com/onflow/cadence/parser" 24 | ) 25 | 26 | var ( 27 | PragmaDebug = "debug" 28 | PragmaSourceFile = "sourceFile" 29 | ) 30 | 31 | type PragmaList []Pragma 32 | 33 | type Pragma interface { 34 | Name() string 35 | Argument() string 36 | } 37 | 38 | var _ Pragma = &BasicPragma{} 39 | 40 | type BasicPragma struct { 41 | name string 42 | argument string 43 | } 44 | 45 | func (p *BasicPragma) Name() string { 46 | return p.name 47 | } 48 | 49 | func (p *BasicPragma) Argument() string { 50 | return p.argument 51 | } 52 | 53 | func (l PragmaList) FilterByName(name string) (result PragmaList) { 54 | result = PragmaList{} 55 | for _, p := range l { 56 | if p.Name() == name { 57 | result = append(result, p) 58 | } 59 | } 60 | return result 61 | } 62 | 63 | func (l PragmaList) First() Pragma { 64 | if len(l) == 0 { 65 | return nil 66 | } 67 | return l[0] 68 | } 69 | 70 | func (l PragmaList) Contains(name string) bool { 71 | for _, p := range l { 72 | if p.Name() == name { 73 | return true 74 | } 75 | } 76 | return false 77 | } 78 | 79 | func (l PragmaList) Count(name string) int { 80 | c := 0 81 | for _, p := range l { 82 | if p.Name() == name { 83 | c = c + 1 84 | } 85 | } 86 | return c 87 | } 88 | 89 | func ExtractPragmas(code string) (result PragmaList) { 90 | 91 | program, err := parser.ParseProgram(nil, []byte(code), parser.Config{}) 92 | if err != nil { 93 | return PragmaList{} 94 | } 95 | 96 | program.Walk(func(el ast.Element) { 97 | pragmaDeclaration, ok := el.(*ast.PragmaDeclaration) 98 | if !ok { 99 | return 100 | } 101 | expression, ok := pragmaDeclaration.Expression.(*ast.InvocationExpression) 102 | if !ok { 103 | return 104 | } 105 | 106 | if len(expression.Arguments) > 1 { 107 | return 108 | } 109 | 110 | var argument string 111 | if len(expression.Arguments) == 1 { 112 | stringParameter, ok := expression.Arguments[0].Expression.(*ast.StringExpression) 113 | if !ok { 114 | return 115 | } 116 | argument = stringParameter.Value 117 | } 118 | 119 | result = append(result, &BasicPragma{ 120 | name: expression.InvokedExpression.String(), 121 | argument: argument, 122 | }) 123 | }) 124 | 125 | return result 126 | } 127 | -------------------------------------------------------------------------------- /emulator/pragma_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "context" 23 | "encoding/hex" 24 | "testing" 25 | 26 | "github.com/onflow/cadence/common" 27 | flowsdk "github.com/onflow/flow-go-sdk" 28 | "github.com/onflow/flow-go-sdk/templates" 29 | flowgo "github.com/onflow/flow-go/model/flow" 30 | "github.com/rs/zerolog" 31 | "github.com/stretchr/testify/assert" 32 | "github.com/stretchr/testify/require" 33 | 34 | "github.com/onflow/flow-emulator/adapters" 35 | "github.com/onflow/flow-emulator/emulator" 36 | ) 37 | 38 | func TestSourceFilePragmaForScript(t *testing.T) { 39 | 40 | t.Parallel() 41 | 42 | b, err := emulator.New() 43 | require.NoError(t, err) 44 | 45 | scriptCode := `#sourceFile("script.cdc") 46 | access(all) fun main() { 47 | log("42") 48 | }` 49 | 50 | scriptResult, err := b.ExecuteScript([]byte(scriptCode), [][]byte{}) 51 | require.NoError(t, err) 52 | require.NoError(t, scriptResult.Error) 53 | 54 | assert.Equal(t, "script.cdc", b.GetSourceFile(common.NewScriptLocation(nil, scriptResult.ScriptID.Bytes()))) 55 | } 56 | 57 | func TestSourceFilePragmaForTransaction(t *testing.T) { 58 | 59 | t.Parallel() 60 | 61 | b, err := emulator.New( 62 | emulator.WithTransactionValidationEnabled(false), 63 | ) 64 | b.EnableAutoMine() 65 | 66 | require.NoError(t, err) 67 | logger := zerolog.Nop() 68 | adapter := adapters.NewSDKAdapter(&logger, b) 69 | 70 | txCode := ` 71 | //some comment 72 | #sourceFile("transaction.cdc") 73 | transaction{ 74 | } 75 | ` 76 | tx := flowsdk.NewTransaction(). 77 | SetScript([]byte(txCode)). 78 | SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 79 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber+1). 80 | SetPayer(b.ServiceKey().Address). 81 | AddAuthorizer(b.ServiceKey().Address) 82 | 83 | txID, _ := hex.DecodeString(tx.ID().String()) 84 | 85 | err = adapter.SendTransaction(context.Background(), *tx) 86 | require.NoError(t, err) 87 | 88 | assert.Equal(t, "transaction.cdc", b.GetSourceFile(common.NewTransactionLocation(nil, txID))) 89 | } 90 | 91 | func TestSourceFilePragmaForContract(t *testing.T) { 92 | 93 | t.Parallel() 94 | 95 | b, err := emulator.New( 96 | emulator.WithTransactionValidationEnabled(false), 97 | ) 98 | b.EnableAutoMine() 99 | 100 | require.NoError(t, err) 101 | logger := zerolog.Nop() 102 | adapter := adapters.NewSDKAdapter(&logger, b) 103 | 104 | contract := `#sourceFile("contracts/C.cdc") 105 | access(all) contract C { 106 | }` 107 | 108 | tx := templates.AddAccountContract( 109 | b.ServiceKey().Address, 110 | templates.Contract{ 111 | Name: "C", 112 | Source: contract, 113 | }) 114 | 115 | tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 116 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber+1). 117 | SetPayer(b.ServiceKey().Address) 118 | 119 | err = adapter.SendTransaction(context.Background(), *tx) 120 | require.NoError(t, err) 121 | 122 | assert.Equal(t, "contracts/C.cdc", b.GetSourceFile(common.NewAddressLocation( 123 | nil, 124 | common.Address(b.ServiceKey().Address), 125 | "C", 126 | ))) 127 | } 128 | 129 | func TestSourceFileCommentedOutPragmaForContract(t *testing.T) { 130 | 131 | t.Parallel() 132 | 133 | b, err := emulator.New( 134 | emulator.WithTransactionValidationEnabled(false), 135 | ) 136 | b.EnableAutoMine() 137 | 138 | require.NoError(t, err) 139 | logger := zerolog.Nop() 140 | adapter := adapters.NewSDKAdapter(&logger, b) 141 | 142 | contract := `//#sourceFile("contracts/C.cdc") 143 | access(all) contract C { 144 | }` 145 | 146 | tx := templates.AddAccountContract( 147 | b.ServiceKey().Address, 148 | templates.Contract{ 149 | Name: "C", 150 | Source: contract, 151 | }) 152 | 153 | tx.SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit). 154 | SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber+1). 155 | SetPayer(b.ServiceKey().Address) 156 | 157 | err = adapter.SendTransaction(context.Background(), *tx) 158 | require.NoError(t, err) 159 | 160 | assert.Equal(t, "A.f8d6e0586b0a20c7.C", b.GetSourceFile(common.NewAddressLocation( 161 | nil, 162 | common.Address(b.ServiceKey().Address), 163 | "C", 164 | ))) 165 | } 166 | -------------------------------------------------------------------------------- /emulator/random_source_history_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package emulator_test 19 | 20 | import ( 21 | "fmt" 22 | "testing" 23 | 24 | "github.com/onflow/cadence" 25 | flowgo "github.com/onflow/flow-go/model/flow" 26 | 27 | "github.com/stretchr/testify/assert" 28 | "github.com/stretchr/testify/require" 29 | 30 | "github.com/onflow/flow-emulator/emulator" 31 | ) 32 | 33 | func TestRandomSourceHistoryLowestHeight(t *testing.T) { 34 | 35 | t.Parallel() 36 | 37 | chain := flowgo.Emulator.Chain() 38 | contracts := emulator.NewCommonContracts(flowgo.Emulator.Chain()) 39 | 40 | b, err := emulator.New( 41 | emulator.Contracts(contracts), 42 | emulator.WithChainID(chain.ChainID()), 43 | ) 44 | require.NoError(t, err) 45 | 46 | serviceAddress := chain.ServiceAddress().Hex() 47 | 48 | scriptCode := fmt.Sprintf(` 49 | import RandomBeaconHistory from 0x%s 50 | 51 | access(all) 52 | fun main(): UInt64 { 53 | return RandomBeaconHistory.getLowestHeight() 54 | } 55 | `, serviceAddress) 56 | 57 | scriptResult, err := b.ExecuteScript([]byte(scriptCode), [][]byte{}) 58 | require.NoError(t, err) 59 | require.NoError(t, scriptResult.Error) 60 | 61 | assert.Equal(t, cadence.UInt64(1), scriptResult.Value) 62 | } 63 | 64 | func TestRandomSourceHistoryAtBlockHeight(t *testing.T) { 65 | 66 | t.Parallel() 67 | 68 | chain := flowgo.Emulator.Chain() 69 | contracts := emulator.NewCommonContracts(flowgo.Emulator.Chain()) 70 | 71 | b, err := emulator.New( 72 | emulator.Contracts(contracts), 73 | emulator.WithChainID(chain.ChainID()), 74 | ) 75 | require.NoError(t, err) 76 | 77 | // We need to ensure that at least two blocks exist 78 | for { 79 | block, err := b.GetLatestBlock() 80 | require.NoError(t, err) 81 | if block.Header.Height > 2 { 82 | break 83 | } 84 | _, err = b.CommitBlock() 85 | require.NoError(t, err) 86 | } 87 | 88 | serviceAddress := chain.ServiceAddress().Hex() 89 | 90 | scriptCode := fmt.Sprintf(` 91 | import RandomBeaconHistory from 0x%s 92 | 93 | access(all) 94 | fun main(): Bool { 95 | let sorAt1 = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: 1) 96 | let sorAt2 = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: 2) 97 | 98 | assert(sorAt1.blockHeight == 1) 99 | assert(sorAt2.blockHeight == 2) 100 | assert(sorAt1.value != sorAt2.value) 101 | 102 | return true 103 | } 104 | `, serviceAddress) 105 | 106 | scriptResult, err := b.ExecuteScript([]byte(scriptCode), [][]byte{}) 107 | require.NoError(t, err) 108 | require.NoError(t, scriptResult.Error) 109 | 110 | assert.Equal(t, cadence.Bool(true), scriptResult.Value) 111 | } 112 | -------------------------------------------------------------------------------- /emulator/snapshots_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package emulator_test 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "github.com/stretchr/testify/require" 26 | ) 27 | 28 | func TestCreateAndLoadSnapshots(t *testing.T) { 29 | 30 | t.Parallel() 31 | 32 | blockchain1, adapter1 := setupTransactionTests(t) 33 | blockchain2, adapter2 := setupTransactionTests(t) 34 | 35 | err := blockchain1.CreateSnapshot("BlockchainCreated") 36 | require.NoError(t, err) 37 | 38 | err = blockchain2.CreateSnapshot("BlockchainCreated") 39 | require.NoError(t, err) 40 | 41 | _, _ = DeployAndGenerateAddTwoScript(t, adapter1) 42 | _, _ = DeployAndGenerateAddTwoScript(t, adapter2) 43 | 44 | err = blockchain1.CreateSnapshot("ContractDeployed") 45 | require.NoError(t, err) 46 | 47 | err = blockchain2.CreateSnapshot("ContractDeployed") 48 | require.NoError(t, err) 49 | 50 | block1, err := blockchain1.GetLatestBlock() 51 | require.NoError(t, err) 52 | assert.Equal(t, uint64(2), block1.Header.Height) 53 | 54 | block2, err := blockchain2.GetLatestBlock() 55 | require.NoError(t, err) 56 | assert.Equal(t, uint64(2), block2.Header.Height) 57 | 58 | err = blockchain1.LoadSnapshot("BlockchainCreated") 59 | require.NoError(t, err) 60 | 61 | err = blockchain2.LoadSnapshot("BlockchainCreated") 62 | require.NoError(t, err) 63 | 64 | block1, err = blockchain1.GetLatestBlock() 65 | require.NoError(t, err) 66 | assert.Equal(t, uint64(0), block1.Header.Height) 67 | 68 | block2, err = blockchain2.GetLatestBlock() 69 | require.NoError(t, err) 70 | assert.Equal(t, uint64(0), block2.Header.Height) 71 | } 72 | -------------------------------------------------------------------------------- /emulator/templates/systemChunkTransactionTemplate.cdc: -------------------------------------------------------------------------------- 1 | import "RandomBeaconHistory" 2 | import EVM from "EVM" 3 | 4 | transaction { 5 | prepare(serviceAccount: auth(BorrowValue) &Account) { 6 | let randomBeaconHistoryHeartbeat = serviceAccount.storage 7 | .borrow<&RandomBeaconHistory.Heartbeat>(from: RandomBeaconHistory.HeartbeatStoragePath) 8 | ?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource") 9 | randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: randomSourceHistory()) 10 | 11 | let evmHeartbeat = serviceAccount.storage 12 | .borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat) 13 | ?? panic("Couldn't borrow EVM.Heartbeat Resource") 14 | evmHeartbeat.heartbeat() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/access.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package internal 20 | 21 | import ( 22 | "github.com/onflow/flow/protobuf/go/flow/access" 23 | ) 24 | 25 | type AccessAPIClient access.AccessAPIClient 26 | -------------------------------------------------------------------------------- /internal/executiondata.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package internal 20 | 21 | import ( 22 | "github.com/onflow/flow/protobuf/go/flow/executiondata" 23 | ) 24 | 25 | type ExecutionDataAPIClient executiondata.ExecutionDataAPIClient 26 | -------------------------------------------------------------------------------- /server/access/grpc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package access 20 | 21 | import ( 22 | "fmt" 23 | "net" 24 | 25 | grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 26 | legacyaccess "github.com/onflow/flow-go/access/legacy" 27 | "github.com/onflow/flow-go/engine/access/rpc" 28 | "github.com/onflow/flow-go/engine/access/state_stream" 29 | "github.com/onflow/flow-go/engine/access/state_stream/backend" 30 | "github.com/onflow/flow-go/engine/access/subscription" 31 | "github.com/onflow/flow-go/model/flow" 32 | flowgo "github.com/onflow/flow-go/model/flow" 33 | mockModule "github.com/onflow/flow-go/module/mock" 34 | accessproto "github.com/onflow/flow/protobuf/go/flow/access" 35 | "github.com/onflow/flow/protobuf/go/flow/executiondata" 36 | legacyaccessproto "github.com/onflow/flow/protobuf/go/flow/legacy/access" 37 | "github.com/rs/zerolog" 38 | "google.golang.org/grpc" 39 | "google.golang.org/grpc/reflection" 40 | 41 | "github.com/onflow/flow-emulator/adapters" 42 | "github.com/onflow/flow-emulator/emulator" 43 | ) 44 | 45 | type mockHeaderCache struct { 46 | } 47 | 48 | func (mockHeaderCache) Get() *flowgo.Header { 49 | return &flowgo.Header{} 50 | } 51 | 52 | type GRPCServer struct { 53 | logger *zerolog.Logger 54 | host string 55 | port int 56 | grpcServer *grpc.Server 57 | listener net.Listener 58 | } 59 | 60 | func NewGRPCServer(logger *zerolog.Logger, blockchain *emulator.Blockchain, adapter *adapters.AccessAdapter, chain flow.Chain, host string, port int, debug bool) *GRPCServer { 61 | grpcServer := grpc.NewServer( 62 | grpc.StreamInterceptor(grpcprometheus.StreamServerInterceptor), 63 | grpc.UnaryInterceptor(grpcprometheus.UnaryServerInterceptor), 64 | ) 65 | 66 | //TODO: bluesign: clean this up 67 | me := new(mockModule.Local) 68 | me.On("NodeID").Return(flowgo.ZeroID) 69 | 70 | legacyaccessproto.RegisterAccessAPIServer(grpcServer, legacyaccess.NewHandler(adapter, chain)) 71 | accessproto.RegisterAccessAPIServer(grpcServer, rpc.NewHandler(adapter, chain, mockHeaderCache{}, me, subscription.DefaultMaxGlobalStreams)) 72 | 73 | grpcprometheus.Register(grpcServer) 74 | 75 | if debug { 76 | reflection.Register(grpcServer) 77 | } 78 | 79 | streamConfig := backend.Config{ 80 | EventFilterConfig: state_stream.DefaultEventFilterConfig, 81 | RpcMetricsEnabled: false, 82 | MaxGlobalStreams: subscription.DefaultMaxGlobalStreams, 83 | ClientSendTimeout: subscription.DefaultSendTimeout, 84 | ClientSendBufferSize: subscription.DefaultSendBufferSize, 85 | ResponseLimit: subscription.DefaultResponseLimit, 86 | HeartbeatInterval: subscription.DefaultHeartbeatInterval, 87 | } 88 | streamBackend := NewStateStreamBackend(blockchain, *logger) 89 | handler := backend.NewHandler(streamBackend, chain, streamConfig) 90 | executiondata.RegisterExecutionDataAPIServer(grpcServer, handler) 91 | 92 | return &GRPCServer{ 93 | logger: logger, 94 | host: host, 95 | port: port, 96 | grpcServer: grpcServer, 97 | } 98 | } 99 | 100 | func (g *GRPCServer) Server() *grpc.Server { 101 | return g.grpcServer 102 | } 103 | 104 | func (g *GRPCServer) Listen() error { 105 | lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", g.host, g.port)) 106 | if err != nil { 107 | return err 108 | } 109 | g.listener = lis 110 | return nil 111 | } 112 | 113 | func (g *GRPCServer) Start() error { 114 | if g.listener == nil { 115 | if err := g.Listen(); err != nil { 116 | return err 117 | } 118 | } 119 | 120 | g.logger.Info().Int("port", g.port).Msgf("✅ Started gRPC server on port %d", g.port) 121 | 122 | err := g.grpcServer.Serve(g.listener) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | return nil 128 | } 129 | 130 | func (g *GRPCServer) Stop() { 131 | g.grpcServer.GracefulStop() 132 | } 133 | -------------------------------------------------------------------------------- /server/access/rest.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package access 20 | 21 | import ( 22 | "context" 23 | "flag" 24 | "fmt" 25 | "net" 26 | "net/http" 27 | "os" 28 | 29 | "github.com/onflow/flow-go/engine/access/rest" 30 | "github.com/onflow/flow-go/engine/access/rest/common" 31 | "github.com/onflow/flow-go/engine/access/rest/router" 32 | "github.com/onflow/flow-go/engine/access/rest/websockets" 33 | "github.com/onflow/flow-go/engine/access/state_stream" 34 | "github.com/onflow/flow-go/engine/access/state_stream/backend" 35 | "github.com/onflow/flow-go/engine/access/subscription" 36 | "github.com/onflow/flow-go/model/flow" 37 | "github.com/onflow/flow-go/module" 38 | "github.com/onflow/flow-go/module/irrecoverable" 39 | "github.com/onflow/flow-go/module/metrics" 40 | modutil "github.com/onflow/flow-go/module/util" 41 | "github.com/prometheus/client_golang/prometheus" 42 | "github.com/rs/zerolog" 43 | 44 | "github.com/onflow/flow-emulator/adapters" 45 | "github.com/onflow/flow-emulator/emulator" 46 | ) 47 | 48 | type RestServer struct { 49 | logger *zerolog.Logger 50 | host string 51 | port int 52 | server *http.Server 53 | listener net.Listener 54 | } 55 | 56 | func (r *RestServer) Listen() error { 57 | l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", r.host, r.port)) 58 | if err != nil { 59 | return err 60 | } 61 | r.listener = l 62 | return nil 63 | } 64 | 65 | func (r *RestServer) Start() error { 66 | if r.listener == nil { 67 | if err := r.Listen(); err != nil { 68 | return err 69 | } 70 | } 71 | 72 | r.logger.Info(). 73 | Int("port", r.port). 74 | Msgf("✅ Started REST API server on port %d", r.port) 75 | 76 | err := r.server.Serve(r.listener) 77 | if err != nil { 78 | return err 79 | } 80 | return nil 81 | } 82 | 83 | func (r *RestServer) Stop() { 84 | _ = r.server.Shutdown(context.Background()) 85 | } 86 | 87 | func (r *RestServer) UseMiddleware(middleware func(http.Handler) http.Handler) { 88 | if r.server != nil { 89 | r.server.Handler = middleware(r.server.Handler) 90 | } 91 | } 92 | 93 | func NewRestServer(logger *zerolog.Logger, blockchain *emulator.Blockchain, adapter *adapters.AccessAdapter, chain flow.Chain, host string, port int, debug bool) (*RestServer, error) { 94 | 95 | debugLogger := zerolog.Logger{} 96 | if debug { 97 | debugLogger = zerolog.New(os.Stdout) 98 | } 99 | var restCollector module.RestMetrics = metrics.NewNoopCollector() 100 | 101 | // only collect metrics if not test 102 | if flag.Lookup("test.v") == nil { 103 | var err error 104 | restCollector, err = metrics.NewRestCollector(router.URLToRoute, prometheus.DefaultRegisterer) 105 | if err != nil { 106 | return nil, err 107 | } 108 | } 109 | 110 | streamConfig := backend.Config{ 111 | EventFilterConfig: state_stream.DefaultEventFilterConfig, 112 | RpcMetricsEnabled: false, 113 | MaxGlobalStreams: subscription.DefaultMaxGlobalStreams, 114 | ClientSendTimeout: subscription.DefaultSendTimeout, 115 | ClientSendBufferSize: subscription.DefaultSendBufferSize, 116 | ResponseLimit: subscription.DefaultResponseLimit, 117 | HeartbeatInterval: subscription.DefaultHeartbeatInterval, 118 | } 119 | 120 | irrCtx, errCh := irrecoverable.WithSignaler(context.Background()) 121 | go func() { 122 | err := modutil.WaitError(errCh, irrCtx.Done()) 123 | if err != nil { 124 | logger.Fatal().Err(err).Msg("Rest server error") 125 | } 126 | }() 127 | 128 | srv, err := rest.NewServer( 129 | irrCtx, 130 | adapter, 131 | rest.Config{ 132 | ListenAddress: fmt.Sprintf("%s:3333", host), 133 | WriteTimeout: rest.DefaultWriteTimeout, 134 | ReadTimeout: rest.DefaultReadTimeout, 135 | IdleTimeout: rest.DefaultIdleTimeout, 136 | MaxRequestSize: common.DefaultMaxRequestSize, 137 | }, 138 | debugLogger, 139 | chain, 140 | restCollector, 141 | NewStateStreamBackend(blockchain, debugLogger), 142 | streamConfig, 143 | true, 144 | websockets.NewDefaultWebsocketConfig(), 145 | ) 146 | 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | return &RestServer{ 152 | logger: logger, 153 | host: host, 154 | port: port, 155 | server: srv, 156 | }, nil 157 | } 158 | -------------------------------------------------------------------------------- /server/access/rest_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package access_test 19 | -------------------------------------------------------------------------------- /server/debugger/debugger.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package debugger 20 | 21 | import ( 22 | "bufio" 23 | "fmt" 24 | "io" 25 | "net" 26 | "sync" 27 | 28 | "github.com/google/go-dap" 29 | "github.com/onflow/flow-emulator/emulator" 30 | "github.com/rs/zerolog" 31 | ) 32 | 33 | type Debugger struct { 34 | logger *zerolog.Logger 35 | emulator emulator.Emulator 36 | port int 37 | listener net.Listener 38 | quit chan interface{} 39 | wg sync.WaitGroup 40 | stopOnce sync.Once 41 | connections []net.Conn 42 | } 43 | 44 | func New(logger *zerolog.Logger, emulator emulator.Emulator, port int) *Debugger { 45 | return &Debugger{ 46 | logger: logger, 47 | emulator: emulator, 48 | port: port, 49 | quit: make(chan interface{}), 50 | } 51 | } 52 | 53 | func (d *Debugger) Start() error { 54 | 55 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", d.port)) 56 | if err != nil { 57 | return err 58 | } 59 | d.listener = listener 60 | defer listener.Close() 61 | 62 | d.wg.Add(1) 63 | go d.serve() 64 | d.wg.Wait() 65 | return nil 66 | } 67 | 68 | func (d *Debugger) serve() { 69 | defer d.wg.Done() 70 | 71 | for { 72 | conn, err := d.listener.Accept() 73 | if err != nil { 74 | select { 75 | case <-d.quit: 76 | return 77 | default: 78 | d.logger.Fatal().Err(err).Msg("failed to accept") 79 | } 80 | } else { 81 | d.wg.Add(1) 82 | go func() { 83 | d.handleConnection(conn) 84 | d.wg.Done() 85 | }() 86 | } 87 | } 88 | } 89 | 90 | func (d *Debugger) handleConnection(conn net.Conn) { 91 | d.connections = append(d.connections, conn) 92 | session := session{ 93 | emulator: d.emulator, 94 | logger: d.logger, 95 | readWriter: bufio.NewReadWriter( 96 | bufio.NewReader(conn), 97 | bufio.NewWriter(conn), 98 | ), 99 | sendQueue: make(chan dap.Message), 100 | } 101 | go session.sendFromQueue() 102 | 103 | for { 104 | err := session.handleRequest() 105 | if err != nil { 106 | _, opError := err.(*net.OpError) 107 | if err == io.EOF { 108 | break 109 | } 110 | if opError { 111 | close(session.sendQueue) 112 | conn.Close() 113 | return 114 | } 115 | d.logger.Fatal().Err(err).Msg("Debug Server error") 116 | } 117 | } 118 | session.sendWg.Wait() 119 | close(session.sendQueue) 120 | conn.Close() 121 | } 122 | 123 | func (d *Debugger) Stop() { 124 | d.stopOnce.Do(func() { 125 | close(d.quit) 126 | if d.listener != nil { 127 | d.listener.Close() 128 | } 129 | for _, conn := range d.connections { 130 | conn.Close() 131 | } 132 | }) 133 | } 134 | -------------------------------------------------------------------------------- /server/server_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package server 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "os" 25 | "testing" 26 | 27 | "github.com/onflow/flow-emulator/emulator" 28 | "github.com/rs/zerolog" 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | func TestNoPersistence(t *testing.T) { 33 | logger := zerolog.Nop() 34 | 35 | dbPath := "test_no_persistence" 36 | 37 | os.RemoveAll(dbPath) 38 | defer os.RemoveAll(dbPath) 39 | 40 | conf := &Config{DBPath: dbPath} 41 | server := NewEmulatorServer(&logger, conf) 42 | defer server.Stop() 43 | 44 | require.NotNil(t, server) 45 | _, err := os.Stat(conf.DBPath) 46 | require.True(t, os.IsNotExist(err), "DB should not exist") 47 | } 48 | 49 | func TestPersistenceWithPersistFlag(t *testing.T) { 50 | logger := zerolog.Nop() 51 | 52 | dbPath := "test_persistence" 53 | 54 | os.RemoveAll(dbPath) 55 | defer os.RemoveAll(dbPath) 56 | 57 | conf := &Config{Persist: true, DBPath: dbPath} 58 | server := NewEmulatorServer(&logger, conf) 59 | defer server.Stop() 60 | 61 | require.NotNil(t, server) 62 | _, err := os.Stat(conf.DBPath) 63 | require.NoError(t, err, "DB should exist") 64 | } 65 | 66 | func TestPersistenceWithSnapshotFlag(t *testing.T) { 67 | logger := zerolog.Nop() 68 | 69 | dbPath := "test_persistence_with_snapshot" 70 | 71 | os.RemoveAll(dbPath) 72 | defer os.RemoveAll(dbPath) 73 | 74 | conf := &Config{Snapshot: true, DBPath: dbPath} 75 | server := NewEmulatorServer(&logger, conf) 76 | defer server.Stop() 77 | 78 | require.NotNil(t, server) 79 | _, err := os.Stat(conf.DBPath) 80 | require.True(t, os.IsNotExist(err), "DB should not exist") 81 | } 82 | 83 | func TestLegacyUpgradeFlag(t *testing.T) { 84 | logger := zerolog.Nop() 85 | 86 | conf := &Config{LegacyContractUpgradeEnabled: true} 87 | server := NewEmulatorServer(&logger, conf) 88 | defer server.Stop() 89 | 90 | require.NotNil(t, server) 91 | require.True(t, server.config.LegacyContractUpgradeEnabled) 92 | 93 | e := server.Emulator() 94 | 95 | require.IsType(t, &emulator.Blockchain{}, e) 96 | require.True(t, e.(*emulator.Blockchain).Runtime().Config().LegacyContractUpgradeEnabled) 97 | } 98 | 99 | func TestExecuteScript(t *testing.T) { 100 | 101 | logger := zerolog.Nop() 102 | server := NewEmulatorServer(&logger, &Config{}) 103 | go server.Start() 104 | defer server.Stop() 105 | 106 | require.NotNil(t, server) 107 | 108 | const code = ` 109 | access(all) fun main(): String { 110 | return "Hello" 111 | } 112 | ` 113 | adapter := server.AccessAdapter() 114 | result, err := adapter.ExecuteScriptAtLatestBlock(context.Background(), []byte(code), nil) 115 | require.NoError(t, err) 116 | 117 | require.JSONEq(t, `{"type":"String","value":"Hello"}`, string(result)) 118 | 119 | } 120 | 121 | func TestExecuteScriptImportingContracts(t *testing.T) { 122 | conf := &Config{ 123 | WithContracts: true, 124 | } 125 | 126 | logger := zerolog.Nop() 127 | server := NewEmulatorServer(&logger, conf) 128 | require.NotNil(t, server) 129 | serviceAccount := server.Emulator().ServiceKey().Address.Hex() 130 | 131 | code := fmt.Sprintf( 132 | ` 133 | import ExampleNFT, NFTStorefront from 0x%s 134 | 135 | access(all) fun main() { 136 | let collection <- ExampleNFT.createEmptyCollection() 137 | destroy collection 138 | 139 | NFTStorefront 140 | } 141 | `, 142 | serviceAccount, 143 | ) 144 | 145 | _, err := server.Emulator().ExecuteScript([]byte(code), nil) 146 | require.NoError(t, err) 147 | 148 | } 149 | 150 | func TestCustomChainID(t *testing.T) { 151 | 152 | conf := &Config{ 153 | WithContracts: true, 154 | ChainID: "flow-sandboxnet", 155 | } 156 | logger := zerolog.Nop() 157 | server := NewEmulatorServer(&logger, conf) 158 | 159 | serviceAccount := server.Emulator().ServiceKey().Address.Hex() 160 | 161 | require.Equal(t, "f4527793ee68aede", serviceAccount) 162 | } 163 | -------------------------------------------------------------------------------- /server/utils/admin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package utils 20 | 21 | import ( 22 | "context" 23 | "errors" 24 | "fmt" 25 | "net" 26 | "net/http" 27 | 28 | "github.com/onflow/flow-emulator/adapters" 29 | "github.com/onflow/flow-emulator/emulator" 30 | "github.com/onflow/flow-emulator/server/access" 31 | 32 | "github.com/improbable-eng/grpc-web/go/grpcweb" 33 | "github.com/prometheus/client_golang/prometheus/promhttp" 34 | "github.com/rs/zerolog" 35 | ) 36 | 37 | const ( 38 | LivenessPath = "/live" 39 | MetricsPath = "/metrics" 40 | EmulatorApiPath = "/emulator/" 41 | ) 42 | 43 | type HTTPHeader struct { 44 | Key string 45 | Value string 46 | } 47 | 48 | type HTTPServer struct { 49 | logger *zerolog.Logger 50 | host string 51 | port int 52 | httpServer *http.Server 53 | listener net.Listener 54 | } 55 | 56 | func NewAdminServer( 57 | logger *zerolog.Logger, 58 | emulator emulator.Emulator, 59 | adapter *adapters.AccessAdapter, 60 | grpcServer *access.GRPCServer, 61 | liveness *LivenessTicker, 62 | host string, 63 | port int, 64 | headers []HTTPHeader, 65 | ) *HTTPServer { 66 | wrappedServer := grpcweb.WrapServer( 67 | grpcServer.Server(), 68 | // TODO: is this needed? 69 | grpcweb.WithOriginFunc(func(origin string) bool { return true }), 70 | ) 71 | 72 | mux := http.NewServeMux() 73 | 74 | // register metrics handler 75 | mux.Handle(MetricsPath, promhttp.Handler()) 76 | 77 | // register liveness handler 78 | mux.Handle(LivenessPath, liveness.Handler()) 79 | 80 | // register gRPC HTTP proxy 81 | mux.Handle("/", wrappedHandler(wrappedServer, headers)) 82 | 83 | // register API handler 84 | mux.Handle(EmulatorApiPath, NewEmulatorAPIServer(emulator, adapter)) 85 | 86 | httpServer := &http.Server{ 87 | Addr: fmt.Sprintf("%s:%d", host, port), 88 | Handler: mux, 89 | } 90 | 91 | return &HTTPServer{ 92 | logger: logger, 93 | host: host, 94 | port: port, 95 | httpServer: httpServer, 96 | } 97 | } 98 | 99 | func (h *HTTPServer) Listen() error { 100 | lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", h.host, h.port)) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | h.listener = lis 106 | return nil 107 | } 108 | 109 | func (h *HTTPServer) Start() error { 110 | if h.listener == nil { 111 | if err := h.Listen(); err != nil { 112 | return err 113 | } 114 | } 115 | 116 | h.logger.Info(). 117 | Int("port", h.port). 118 | Msgf("✅ Started admin server on port %d", h.port) 119 | 120 | err := h.httpServer.Serve(h.listener) 121 | if errors.Is(err, http.ErrServerClosed) { 122 | return nil 123 | } 124 | 125 | return err 126 | } 127 | 128 | func (h *HTTPServer) Stop() { 129 | _ = h.httpServer.Shutdown(context.Background()) 130 | } 131 | 132 | func wrappedHandler(wrappedServer *grpcweb.WrappedGrpcServer, headers []HTTPHeader) http.HandlerFunc { 133 | return func(res http.ResponseWriter, req *http.Request) { 134 | setResponseHeaders(&res, headers) 135 | 136 | if (*req).Method == "OPTIONS" { 137 | return 138 | } 139 | 140 | wrappedServer.ServeHTTP(res, req) 141 | } 142 | } 143 | 144 | func setResponseHeaders(w *http.ResponseWriter, headers []HTTPHeader) { 145 | for _, header := range headers { 146 | (*w).Header().Set(header.Key, header.Value) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /server/utils/liveness.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package utils 20 | 21 | import ( 22 | "net/http" 23 | "time" 24 | 25 | "github.com/onflow/flow-emulator/server/utils/liveness" 26 | ) 27 | 28 | type LivenessTicker struct { 29 | collector *liveness.CheckCollector 30 | ticker *time.Ticker 31 | done chan bool 32 | } 33 | 34 | func NewLivenessTicker(tolerance time.Duration) *LivenessTicker { 35 | return &LivenessTicker{ 36 | collector: liveness.NewCheckCollector(tolerance), 37 | ticker: time.NewTicker(tolerance / 2), 38 | done: make(chan bool, 1), 39 | } 40 | } 41 | 42 | func (l *LivenessTicker) Start() error { 43 | check := l.collector.NewCheck() 44 | 45 | for { 46 | select { 47 | case <-l.ticker.C: 48 | check.CheckIn() 49 | case <-l.done: 50 | return nil 51 | } 52 | } 53 | } 54 | 55 | func (l *LivenessTicker) Stop() { 56 | l.done <- true 57 | } 58 | 59 | func (l *LivenessTicker) Handler() http.Handler { 60 | return l.collector 61 | } 62 | -------------------------------------------------------------------------------- /server/utils/liveness/README.md: -------------------------------------------------------------------------------- 1 | # Liveness Checks Helper 2 | For applications that need to perform liveness checks, this utility creates a simple object capable of checking in with multiple routines. 3 | 4 | ## Usage 5 | The suggested usage is to create a collector and then spawn checks from it to use in your application. 6 | 7 | Each worker gets a single Check to work from, and the collector itself is registered as an HTTP handler to respond to liveness probes. 8 | 9 | ```go 10 | multiCheck := liveness.NewCheckCollector(time.Second) 11 | 12 | http.Handle("/live", multiCheck) 13 | 14 | go worker1(multiCheck.NewCheck()) 15 | go worker2(multiCheck.NewCheck()) 16 | 17 | check3 = multiCheck.NewCheck() 18 | 19 | for { 20 | check3.CheckIn() 21 | // do work 22 | // ... 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /server/utils/liveness/check.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package liveness 20 | 21 | import ( 22 | "sync" 23 | "time" 24 | ) 25 | 26 | // Check is a heartbeat style liveness reporter. 27 | // 28 | // It is not guaranteed to be safe to CheckIn across multiple goroutines. 29 | // IsLive must be safe to be called concurrently with CheckIn. 30 | type Check interface { 31 | CheckIn() 32 | IsLive(time.Duration) bool 33 | } 34 | 35 | // internalCheck implements a Check. 36 | type internalCheck struct { 37 | lock sync.RWMutex 38 | lastCheckIn time.Time 39 | defaultTolerance time.Duration 40 | } 41 | 42 | // CheckIn adds a heartbeat at the current time. 43 | func (c *internalCheck) CheckIn() { 44 | c.lock.Lock() 45 | c.lastCheckIn = time.Now() 46 | c.lock.Unlock() 47 | } 48 | 49 | // IsLive checks if we are still live against the given the tolerance between heartbeats. 50 | // 51 | // If tolerance is 0, the default tolerance is used. 52 | func (c *internalCheck) IsLive(tolerance time.Duration) bool { 53 | c.lock.RLock() 54 | defer c.lock.RUnlock() 55 | if tolerance == 0 { 56 | tolerance = c.defaultTolerance 57 | } 58 | 59 | return c.lastCheckIn.Add(tolerance).After(time.Now()) 60 | } 61 | -------------------------------------------------------------------------------- /server/utils/liveness/check_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package liveness 20 | 21 | import ( 22 | "net/http" 23 | "net/http/httptest" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | func Test_BasicCheck(t *testing.T) { 29 | 30 | t.Parallel() 31 | 32 | mc := NewCheckCollector(time.Millisecond * 20) 33 | if !mc.IsLive(0) { 34 | t.Errorf("Multicheck with no checks should always pass") 35 | } 36 | 37 | c1 := mc.NewCheck() 38 | if !mc.IsLive(0) { 39 | t.Errorf("Just made check should pass") 40 | } 41 | 42 | time.Sleep(time.Millisecond * 30) 43 | if mc.IsLive(0) { 44 | t.Errorf("Multi check should have failed") 45 | } 46 | 47 | c1.CheckIn() 48 | if !mc.IsLive(0) { 49 | t.Errorf("Checker should passed after checkin") 50 | } 51 | 52 | c2 := mc.NewCheck() 53 | if !mc.IsLive(0) { 54 | t.Errorf("Just made check 2 should pass") 55 | } 56 | 57 | time.Sleep(time.Millisecond * 30) 58 | c1.CheckIn() 59 | // don't checkIn c2 60 | 61 | if mc.IsLive(0) { 62 | t.Errorf("Multi check should have failed by c2") 63 | } 64 | 65 | c2.CheckIn() 66 | if !mc.IsLive(0) { 67 | t.Errorf("Check 2 should pass after checkin") 68 | } 69 | } 70 | 71 | func Test_CheckHTTP(t *testing.T) { 72 | 73 | t.Parallel() 74 | 75 | c := NewCheckCollector(time.Millisecond * 20) 76 | r := httptest.NewRequest(http.MethodGet, "/live", nil) 77 | wr := httptest.NewRecorder() 78 | 79 | c1 := c.NewCheck() 80 | _ = c.NewCheck() // never check-in c2 81 | 82 | c.ServeHTTP(wr, r) 83 | if wr.Code != http.StatusOK { 84 | t.Errorf("Check should have passed") 85 | } 86 | 87 | time.Sleep(time.Millisecond * 30) 88 | c1.CheckIn() 89 | 90 | wr = httptest.NewRecorder() 91 | c.ServeHTTP(wr, r) 92 | if wr.Code != http.StatusServiceUnavailable { 93 | t.Errorf("Check should not have passed") 94 | } 95 | } 96 | 97 | func Test_CheckHTTPOverride(t *testing.T) { 98 | 99 | t.Parallel() 100 | 101 | c := NewCheckCollector(time.Millisecond * 20) 102 | r := httptest.NewRequest(http.MethodGet, "/live", nil) 103 | r.Header.Add(ToleranceHeader, "30s") 104 | wr := httptest.NewRecorder() 105 | 106 | c1 := c.NewCheck() 107 | 108 | c1.CheckIn() 109 | 110 | c.ServeHTTP(wr, r) 111 | if wr.Code != http.StatusOK { 112 | t.Errorf("Check should have passed") 113 | } 114 | 115 | time.Sleep(time.Millisecond * 60) 116 | 117 | wr = httptest.NewRecorder() 118 | c.ServeHTTP(wr, r) 119 | if wr.Code != http.StatusOK { 120 | t.Errorf("Check should still have passed") 121 | } 122 | 123 | r.Header.Del(ToleranceHeader) 124 | wr = httptest.NewRecorder() 125 | c.ServeHTTP(wr, r) 126 | if wr.Code != http.StatusServiceUnavailable { 127 | t.Errorf("Check should not have passed") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /server/utils/liveness/collector.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package liveness 20 | 21 | import ( 22 | "net/http" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | const ( 28 | // DefaultTolerance is the default time (in seconds) allowed between heartbeats. 29 | DefaultTolerance = time.Second * 30 30 | 31 | // ToleranceHeader is the HTTP header name used to override a collector's configured tolerance. 32 | ToleranceHeader = "X-Liveness-Tolerance" 33 | ) 34 | 35 | // CheckCollector produces multiple checks and returns 36 | // live only if all descendant checks are live. 37 | // 38 | // Each child check may only be used by one goroutine, 39 | // the CheckCollector may be used by multiple routines at once to 40 | // produce checks. 41 | type CheckCollector struct { 42 | lock sync.RWMutex 43 | defaultTolerance time.Duration 44 | checks []Check 45 | } 46 | 47 | // NewCheckCollector creates a threadsafe Collector. 48 | func NewCheckCollector(tolerance time.Duration) *CheckCollector { 49 | if tolerance == 0 { 50 | tolerance = DefaultTolerance 51 | } 52 | 53 | return &CheckCollector{ 54 | defaultTolerance: tolerance, 55 | checks: make([]Check, 0, 1), 56 | } 57 | } 58 | 59 | // NewCheck returns a Check which is safe for use on a single routine. 60 | func (c *CheckCollector) NewCheck() Check { 61 | c.lock.Lock() 62 | check := &internalCheck{ 63 | defaultTolerance: c.defaultTolerance, 64 | lastCheckIn: time.Now(), 65 | } 66 | c.checks = append(c.checks, check) 67 | c.lock.Unlock() 68 | return check 69 | } 70 | 71 | // Register adds a check to a collector 72 | func (c *CheckCollector) Register(ck Check) { 73 | c.lock.Lock() 74 | c.checks = append(c.checks, ck) 75 | c.lock.Unlock() 76 | } 77 | 78 | func (c *CheckCollector) ServeHTTP(w http.ResponseWriter, r *http.Request) { 79 | tolerance := c.defaultTolerance 80 | var err error 81 | 82 | if toleranceStr := r.Header.Get(ToleranceHeader); toleranceStr != "" { 83 | tolerance, err = time.ParseDuration(toleranceStr) 84 | if err != nil { 85 | http.Error(w, "Invalid tolerance: "+toleranceStr, http.StatusBadRequest) 86 | return 87 | } 88 | } 89 | 90 | if !c.IsLive(tolerance) { 91 | w.WriteHeader(http.StatusServiceUnavailable) 92 | return 93 | } 94 | 95 | w.WriteHeader(http.StatusOK) 96 | } 97 | 98 | // IsLive checks if we are still live against the given the tolerance between heartbeats. 99 | // 100 | // If tolerance is 0, the default tolerance is used. 101 | func (c *CheckCollector) IsLive(tolerance time.Duration) bool { 102 | if tolerance == 0 { 103 | tolerance = c.defaultTolerance 104 | } 105 | 106 | c.lock.RLock() 107 | defer c.lock.RUnlock() 108 | 109 | for i := range c.checks { 110 | if !c.checks[i].IsLive(tolerance) { 111 | return false 112 | } 113 | } 114 | 115 | return true 116 | } 117 | -------------------------------------------------------------------------------- /server/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package utils_test 19 | -------------------------------------------------------------------------------- /storage/checkpoint/checkpoint.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package checkpoint 20 | 21 | import ( 22 | "context" 23 | "encoding/hex" 24 | "fmt" 25 | "math" 26 | 27 | "github.com/rs/zerolog" 28 | "go.uber.org/atomic" 29 | 30 | "github.com/onflow/flow-go/fvm/storage/snapshot" 31 | "github.com/onflow/flow-go/ledger/common/convert" 32 | "github.com/onflow/flow-go/ledger/common/pathfinder" 33 | "github.com/onflow/flow-go/ledger/complete" 34 | "github.com/onflow/flow-go/ledger/complete/wal" 35 | "github.com/onflow/flow-go/model/flow" 36 | "github.com/onflow/flow-go/module/metrics" 37 | 38 | "github.com/onflow/flow-emulator/storage" 39 | "github.com/onflow/flow-emulator/storage/memstore" 40 | ) 41 | 42 | // Store is just a memstore, but the starting state is loaded from a checkpoint folder 43 | // any new blocks exist in memory only and are not persisted to disk. 44 | type Store struct { 45 | // Store is a memstore 46 | // Theoretically this could also be a persistent store 47 | *memstore.Store 48 | } 49 | 50 | // New returns a new Store implementation. 51 | func New( 52 | log zerolog.Logger, 53 | path string, 54 | stateCommitment string, 55 | chainID flow.ChainID, 56 | ) (*Store, error) { 57 | var err error 58 | stateCommitmentBytes, err := hex.DecodeString(stateCommitment) 59 | if err != nil { 60 | return nil, fmt.Errorf("invalid state commitment hex: %w", err) 61 | } 62 | state, err := flow.ToStateCommitment(stateCommitmentBytes) 63 | if err != nil { 64 | return nil, fmt.Errorf("invalid state commitment: %w", err) 65 | } 66 | 67 | store := memstore.New() 68 | snap, err := loadSnapshotFromCheckpoint(log, path, state) 69 | if err != nil { 70 | return nil, fmt.Errorf("failed to load snapshot from checkpoint: %w", err) 71 | } 72 | 73 | // pretend this state was the genesis state 74 | genesis := flow.Genesis(chainID) 75 | err = store.CommitBlock( 76 | context.Background(), 77 | *genesis, 78 | nil, 79 | nil, 80 | nil, 81 | snap, 82 | nil, 83 | ) 84 | if err != nil { 85 | return nil, fmt.Errorf("failed to commit genesis block: %s", err) 86 | } 87 | 88 | return &Store{ 89 | Store: store, 90 | }, nil 91 | } 92 | 93 | func loadSnapshotFromCheckpoint( 94 | log zerolog.Logger, 95 | dir string, 96 | targetHash flow.StateCommitment, 97 | ) (*snapshot.ExecutionSnapshot, error) { 98 | log.Info().Msg("init WAL") 99 | 100 | diskWal, err := wal.NewDiskWAL( 101 | log, 102 | nil, 103 | metrics.NewNoopCollector(), 104 | dir, 105 | complete.DefaultCacheSize, 106 | pathfinder.PathByteSize, 107 | wal.SegmentSize, 108 | ) 109 | if err != nil { 110 | return nil, fmt.Errorf("cannot create disk WAL: %w", err) 111 | } 112 | 113 | log.Info().Msg("init ledger") 114 | 115 | led, err := complete.NewLedger( 116 | diskWal, 117 | complete.DefaultCacheSize, 118 | &metrics.NoopCollector{}, 119 | log, 120 | complete.DefaultPathFinderVersion) 121 | if err != nil { 122 | return nil, fmt.Errorf("cannot create ledger from write-a-head logs and checkpoints: %w", err) 123 | } 124 | 125 | const ( 126 | checkpointDistance = math.MaxInt // A large number to prevent checkpoint creation. 127 | checkpointsToKeep = 1 128 | ) 129 | 130 | log.Info().Msg("init compactor") 131 | 132 | compactor, err := complete.NewCompactor(led, diskWal, log, complete.DefaultCacheSize, checkpointDistance, checkpointsToKeep, atomic.NewBool(false), &metrics.NoopCollector{}) 133 | if err != nil { 134 | return nil, fmt.Errorf("cannot create compactor: %w", err) 135 | } 136 | 137 | log.Info().Msgf("waiting for compactor to load checkpoint and WAL") 138 | 139 | <-compactor.Ready() 140 | 141 | defer func() { 142 | <-led.Done() 143 | <-compactor.Done() 144 | }() 145 | 146 | trie, err := led.FindTrieByStateCommit(targetHash) 147 | if err != nil { 148 | return nil, fmt.Errorf("cannot find trie by state commitment: %w", err) 149 | } 150 | payloads := trie.AllPayloads() 151 | 152 | writeSet := make(map[flow.RegisterID]flow.RegisterValue, len(payloads)) 153 | for _, p := range payloads { 154 | id, value, err := convert.PayloadToRegister(p) 155 | if err != nil { 156 | return nil, fmt.Errorf("cannot convert payload to register: %w", err) 157 | } 158 | 159 | writeSet[id] = value 160 | } 161 | 162 | log.Info().Msg("snapshot loaded") 163 | 164 | // garbage collector should clean up the WAL and the checkpoint 165 | 166 | // only the write set is needed for the emulator 167 | return &snapshot.ExecutionSnapshot{ 168 | WriteSet: writeSet, 169 | }, err 170 | } 171 | 172 | var _ storage.Store = &Store{} 173 | -------------------------------------------------------------------------------- /storage/encoding.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package storage 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/fxamacker/cbor/v2" 25 | flowgo "github.com/onflow/flow-go/model/flow" 26 | 27 | "github.com/onflow/flow-emulator/types" 28 | ) 29 | 30 | var em cbor.EncMode 31 | 32 | func init() { 33 | opts := cbor.CanonicalEncOptions() 34 | opts.Time = cbor.TimeRFC3339Nano 35 | var err error 36 | em, err = opts.EncMode() 37 | if err != nil { 38 | panic(fmt.Sprintf("could not initialize cbor encoding mode: %s", err.Error())) 39 | } 40 | } 41 | 42 | func encodeBlock(block flowgo.Block) ([]byte, error) { 43 | return em.Marshal(block) 44 | } 45 | 46 | func decodeBlock(block *flowgo.Block, from []byte) error { 47 | return cbor.Unmarshal(from, block) 48 | } 49 | 50 | func encodeCollection(col flowgo.LightCollection) ([]byte, error) { 51 | return em.Marshal(col) 52 | } 53 | 54 | func decodeCollection(col *flowgo.LightCollection, from []byte) error { 55 | return cbor.Unmarshal(from, col) 56 | } 57 | 58 | func encodeTransaction(tx flowgo.TransactionBody) ([]byte, error) { 59 | return em.Marshal(tx) 60 | } 61 | 62 | func decodeTransaction(tx *flowgo.TransactionBody, from []byte) error { 63 | return cbor.Unmarshal(from, tx) 64 | } 65 | 66 | func encodeTransactionResult(result types.StorableTransactionResult) ([]byte, error) { 67 | return em.Marshal(result) 68 | } 69 | 70 | func decodeTransactionResult(result *types.StorableTransactionResult, from []byte) error { 71 | return cbor.Unmarshal(from, result) 72 | } 73 | 74 | func mustEncodeUint64(v uint64) []byte { 75 | bytes, err := em.Marshal(v) 76 | if err != nil { // bluesign: it should be able to encode all uint64 77 | panic(err) 78 | } 79 | return bytes 80 | } 81 | 82 | func decodeUint64(v *uint64, from []byte) error { 83 | return cbor.Unmarshal(from, v) 84 | } 85 | 86 | func encodeEvents(events []flowgo.Event) ([]byte, error) { 87 | return em.Marshal(events) 88 | } 89 | 90 | func decodeEvents(events *[]flowgo.Event, from []byte) error { 91 | return cbor.Unmarshal(from, events) 92 | } 93 | -------------------------------------------------------------------------------- /storage/encoding_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package storage 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/onflow/flow-go-sdk/test" 25 | flowgo "github.com/onflow/flow-go/model/flow" 26 | "github.com/onflow/flow/protobuf/go/flow/entities" 27 | "github.com/stretchr/testify/assert" 28 | "github.com/stretchr/testify/require" 29 | 30 | "github.com/onflow/flow-emulator/convert" 31 | "github.com/onflow/flow-emulator/types" 32 | "github.com/onflow/flow-emulator/utils/unittest" 33 | ) 34 | 35 | func TestEncodeTransaction(t *testing.T) { 36 | 37 | t.Parallel() 38 | 39 | tx := unittest.TransactionFixture() 40 | data, err := encodeTransaction(tx) 41 | require.Nil(t, err) 42 | 43 | var decodedTx flowgo.TransactionBody 44 | err = decodeTransaction(&decodedTx, data) 45 | require.Nil(t, err) 46 | 47 | assert.Equal(t, tx.ID(), decodedTx.ID()) 48 | } 49 | 50 | func TestEncodeTransactionResult(t *testing.T) { 51 | 52 | t.Parallel() 53 | 54 | test := func(eventEncodingVersion entities.EventEncodingVersion) { 55 | 56 | t.Run(eventEncodingVersion.String(), func(t *testing.T) { 57 | t.Parallel() 58 | 59 | result := unittest.StorableTransactionResultFixture(eventEncodingVersion) 60 | 61 | data, err := encodeTransactionResult(result) 62 | require.Nil(t, err) 63 | 64 | var decodedResult types.StorableTransactionResult 65 | err = decodeTransactionResult(&decodedResult, data) 66 | require.Nil(t, err) 67 | 68 | assert.Equal(t, result, decodedResult) 69 | }) 70 | } 71 | 72 | test(entities.EventEncodingVersion_CCF_V0) 73 | test(entities.EventEncodingVersion_JSON_CDC_V0) 74 | } 75 | 76 | func TestEncodeBlock(t *testing.T) { 77 | 78 | t.Parallel() 79 | 80 | ids := test.IdentifierGenerator() 81 | 82 | block := flowgo.Block{ 83 | Header: &flowgo.Header{ 84 | Height: 1234, 85 | ParentID: flowgo.Identifier(ids.New()), 86 | }, 87 | Payload: &flowgo.Payload{ 88 | Guarantees: []*flowgo.CollectionGuarantee{ 89 | { 90 | CollectionID: flowgo.Identifier(ids.New()), 91 | }, 92 | }, 93 | }, 94 | } 95 | 96 | data, err := encodeBlock(block) 97 | require.Nil(t, err) 98 | 99 | var decodedBlock flowgo.Block 100 | err = decodeBlock(&decodedBlock, data) 101 | require.Nil(t, err) 102 | 103 | assert.Equal(t, block.ID(), decodedBlock.ID()) 104 | assert.Equal(t, *block.Header, *decodedBlock.Header) 105 | assert.Equal(t, *block.Payload, *decodedBlock.Payload) 106 | } 107 | func TestEncodeGenesisBlock(t *testing.T) { 108 | 109 | t.Parallel() 110 | 111 | block := flowgo.Genesis(flowgo.Emulator) 112 | 113 | data, err := encodeBlock(*block) 114 | require.Nil(t, err) 115 | 116 | var decodedBlock flowgo.Block 117 | err = decodeBlock(&decodedBlock, data) 118 | require.Nil(t, err) 119 | 120 | assert.Equal(t, block.ID(), decodedBlock.ID()) 121 | assert.Equal(t, *block.Header, *decodedBlock.Header) 122 | assert.Equal(t, *block.Payload, *decodedBlock.Payload) 123 | } 124 | 125 | func TestEncodeEvents(t *testing.T) { 126 | 127 | t.Parallel() 128 | 129 | test := func(eventEncodingVersion entities.EventEncodingVersion) { 130 | 131 | t.Run(eventEncodingVersion.String(), func(t *testing.T) { 132 | t.Parallel() 133 | 134 | event1, _ := convert.SDKEventToFlow(test.EventGenerator(eventEncodingVersion).New()) 135 | event2, _ := convert.SDKEventToFlow(test.EventGenerator(eventEncodingVersion).New()) 136 | 137 | events := []flowgo.Event{ 138 | event1, 139 | event2, 140 | } 141 | 142 | data, err := encodeEvents(events) 143 | require.Nil(t, err) 144 | 145 | var decodedEvents []flowgo.Event 146 | err = decodeEvents(&decodedEvents, data) 147 | require.Nil(t, err) 148 | assert.Equal(t, events, decodedEvents) 149 | }) 150 | } 151 | 152 | test(entities.EventEncodingVersion_CCF_V0) 153 | test(entities.EventEncodingVersion_JSON_CDC_V0) 154 | } 155 | -------------------------------------------------------------------------------- /storage/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package storage 20 | 21 | import "errors" 22 | 23 | // ErrNotFound is an error returned when an entity cannot be found. 24 | var ErrNotFound = errors.New("could not find entity") 25 | -------------------------------------------------------------------------------- /storage/memstore/memstore_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package memstore 20 | 21 | import ( 22 | "context" 23 | "sync" 24 | "testing" 25 | 26 | "github.com/onflow/flow-go/fvm/storage/snapshot" 27 | "github.com/onflow/flow-go/model/flow" 28 | flowgo "github.com/onflow/flow-go/model/flow" 29 | "github.com/stretchr/testify/assert" 30 | "github.com/stretchr/testify/require" 31 | ) 32 | 33 | func TestMemstore(t *testing.T) { 34 | 35 | t.Parallel() 36 | 37 | const blockHeight = 0 38 | key := flow.NewRegisterID(flowgo.EmptyAddress, "foo") 39 | value := []byte("bar") 40 | store := New() 41 | 42 | err := store.insertExecutionSnapshot( 43 | blockHeight, 44 | &snapshot.ExecutionSnapshot{ 45 | WriteSet: map[flowgo.RegisterID]flowgo.RegisterValue{ 46 | key: value, 47 | }, 48 | }, 49 | ) 50 | require.NoError(t, err) 51 | 52 | var wg sync.WaitGroup 53 | 54 | for i := 0; i < 100; i++ { 55 | wg.Add(1) 56 | go func() { 57 | defer wg.Done() 58 | 59 | snapshot, err := store.LedgerByHeight( 60 | context.Background(), 61 | blockHeight) 62 | require.NoError(t, err) 63 | actualValue, err := snapshot.Get(key) 64 | 65 | require.NoError(t, err) 66 | assert.Equal(t, value, actualValue) 67 | }() 68 | } 69 | 70 | wg.Wait() 71 | } 72 | 73 | func TestMemstoreSetValueToNil(t *testing.T) { 74 | 75 | t.Parallel() 76 | 77 | store := New() 78 | key := flow.NewRegisterID(flowgo.EmptyAddress, "foo") 79 | value := []byte("bar") 80 | var nilByte []byte 81 | nilValue := nilByte 82 | 83 | // set initial value 84 | err := store.insertExecutionSnapshot( 85 | 0, 86 | &snapshot.ExecutionSnapshot{ 87 | WriteSet: map[flowgo.RegisterID]flowgo.RegisterValue{ 88 | key: value, 89 | }, 90 | }) 91 | require.NoError(t, err) 92 | 93 | // check initial value 94 | ledger, err := store.LedgerByHeight(context.Background(), 0) 95 | require.NoError(t, err) 96 | register, err := ledger.Get(key) 97 | require.NoError(t, err) 98 | require.Equal(t, string(value), string(register)) 99 | 100 | // set value to nil 101 | err = store.insertExecutionSnapshot( 102 | 1, 103 | &snapshot.ExecutionSnapshot{ 104 | WriteSet: map[flowgo.RegisterID]flowgo.RegisterValue{ 105 | key: nilValue, 106 | }, 107 | }) 108 | require.NoError(t, err) 109 | 110 | // check value is nil 111 | ledger, err = store.LedgerByHeight(context.Background(), 1) 112 | require.NoError(t, err) 113 | register, err = ledger.Get(key) 114 | require.NoError(t, err) 115 | require.Equal(t, string(nilValue), string(register)) 116 | } 117 | -------------------------------------------------------------------------------- /storage/redis/store.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package redis 20 | 21 | import ( 22 | "context" 23 | "encoding/hex" 24 | "fmt" 25 | 26 | "github.com/go-redis/redis/v8" 27 | 28 | "github.com/onflow/flow-emulator/storage" 29 | ) 30 | 31 | // Store implements the Store interface 32 | type Store struct { 33 | storage.DefaultStore 34 | options *redis.Options 35 | rdb *redis.Client 36 | } 37 | 38 | // New returns a new in-memory Store implementation. 39 | func New(url string) (*Store, error) { 40 | options, err := redis.ParseURL(url) 41 | if err != nil { 42 | return nil, err 43 | } 44 | store := &Store{ 45 | options: options, 46 | rdb: redis.NewClient(options), 47 | } 48 | store.DataSetter = store 49 | store.DataGetter = store 50 | store.KeyGenerator = &storage.DefaultKeyGenerator{} 51 | 52 | return store, nil 53 | } 54 | 55 | func storeKey(store string, key []byte) string { 56 | return fmt.Sprintf("%s_%s", store, hex.EncodeToString(key)) 57 | } 58 | 59 | func (s *Store) GetBytes(ctx context.Context, store string, key []byte) ([]byte, error) { 60 | val, err := s.rdb.Get(ctx, storeKey(store, key)).Result() 61 | if err != nil { 62 | if err == redis.Nil { 63 | return nil, storage.ErrNotFound 64 | } 65 | return nil, err 66 | } 67 | rawBytes, err := hex.DecodeString(val) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return rawBytes, nil 72 | } 73 | 74 | func (s *Store) SetBytes(ctx context.Context, store string, key []byte, value []byte) error { 75 | err := s.rdb.Set(ctx, storeKey(store, key), hex.EncodeToString(value), 0).Err() 76 | if err != nil { 77 | return err 78 | } 79 | return nil 80 | } 81 | 82 | func (s *Store) SetBytesWithVersion(ctx context.Context, store string, key []byte, value []byte, version uint64) error { 83 | err := s.rdb.ZAdd(ctx, 84 | storeKey(store, key), 85 | &redis.Z{ 86 | Score: float64(version), 87 | Member: hex.EncodeToString(value), 88 | }, 89 | ).Err() 90 | if err != nil { 91 | return err 92 | } 93 | return nil 94 | } 95 | 96 | func (s *Store) GetBytesAtVersion(ctx context.Context, store string, key []byte, version uint64) ([]byte, error) { 97 | val, err := s.rdb.ZRevRangeByScore(ctx, 98 | storeKey(store, key), 99 | &redis.ZRangeBy{ 100 | Max: fmt.Sprintf("%d", version), 101 | Offset: 0, 102 | Count: 1, 103 | }, 104 | ).Result() 105 | 106 | if err != nil { 107 | if err == redis.Nil { 108 | return nil, storage.ErrNotFound 109 | } 110 | return nil, err 111 | } 112 | 113 | if len(val) == 0 { 114 | return nil, storage.ErrNotFound 115 | } 116 | rawBytes, err := hex.DecodeString(val[0]) 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | return rawBytes, nil 122 | } 123 | 124 | var _ storage.Store = &Store{} 125 | -------------------------------------------------------------------------------- /storage/sqlite/createTables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS global(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 2 | CREATE TABLE IF NOT EXISTS ledger(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 3 | CREATE TABLE IF NOT EXISTS blocks(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 4 | CREATE TABLE IF NOT EXISTS blockIndex(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 5 | CREATE TABLE IF NOT EXISTS events(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 6 | CREATE TABLE IF NOT EXISTS transactions(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 7 | CREATE TABLE IF NOT EXISTS collections(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 8 | CREATE TABLE IF NOT EXISTS transactionResults(key TEXT, value TEXT, version INTEGER, height INTEGER, UNIQUE(key,version,height)); 9 | 10 | -------------------------------------------------------------------------------- /storage/sqlite/store_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package sqlite 20 | 21 | import ( 22 | "os" 23 | "testing" 24 | 25 | "github.com/stretchr/testify/assert" 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestNew(t *testing.T) { 30 | file, err := os.CreateTemp("", "test.sqlite") 31 | require.NoError(t, err) 32 | 33 | store, err := New(file.Name()) 34 | require.NoError(t, err) 35 | 36 | err = store.Close() 37 | assert.NoError(t, err) 38 | 39 | _, err = New("/invalidLocation") 40 | assert.NotContains( 41 | t, 42 | err.Error(), 43 | "unable to open database file: out of memory", 44 | "should not attempt to open the database file if the location is invalid", 45 | ) 46 | assert.ErrorContains( 47 | t, 48 | err, 49 | "no such file or directory", 50 | "should return an error indicating the location is invalid", 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /storage/util/defaultStore.go: -------------------------------------------------------------------------------- 1 | //go:build !wasm 2 | // +build !wasm 3 | 4 | /* 5 | * Flow Emulator 6 | * 7 | * Copyright Flow Foundation 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | package util 23 | 24 | import ( 25 | "github.com/onflow/flow-emulator/storage" 26 | "github.com/onflow/flow-emulator/storage/redis" 27 | "github.com/onflow/flow-emulator/storage/sqlite" 28 | ) 29 | 30 | func CreateDefaultStorage() (storage.Store, error) { 31 | return sqlite.New(sqlite.InMemory) 32 | } 33 | 34 | func NewSqliteStorage(url string) (storage.Store, error) { 35 | return sqlite.New(url) 36 | } 37 | 38 | func NewRedisStorage(url string) (storage.Store, error) { 39 | return redis.New(url) 40 | } 41 | -------------------------------------------------------------------------------- /storage/util/defaultStore_wasm.go: -------------------------------------------------------------------------------- 1 | //go:build wasm 2 | // +build wasm 3 | 4 | /* 5 | * Flow Emulator 6 | * 7 | * Copyright Flow Foundation 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | package util 23 | 24 | import ( 25 | "github.com/onflow/flow-emulator/storage" 26 | "github.com/onflow/flow-emulator/storage/memstore" 27 | "github.com/onflow/flow-emulator/storage/redis" 28 | ) 29 | 30 | func CreateDefaultStorage() (storage.Store, error) { 31 | return memstore.New(), nil 32 | } 33 | 34 | func NewSqliteStorage(url string) (storage.Store, error) { 35 | return memstore.New(), nil 36 | } 37 | 38 | func NewRedisStorage(url string) (storage.Store, error) { 39 | return redis.New(url) 40 | } 41 | -------------------------------------------------------------------------------- /types/result.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package types 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/onflow/cadence" 25 | "github.com/onflow/crypto/hash" 26 | flowsdk "github.com/onflow/flow-go-sdk" 27 | flowgo "github.com/onflow/flow-go/model/flow" 28 | ) 29 | 30 | type StorableTransactionResult struct { 31 | ErrorCode int 32 | ErrorMessage string 33 | Logs []string 34 | Events []flowgo.Event 35 | BlockID flowgo.Identifier 36 | BlockHeight uint64 37 | } 38 | 39 | // A TransactionResult is the result of executing a transaction. 40 | type TransactionResult struct { 41 | TransactionID flowsdk.Identifier 42 | ComputationUsed uint64 43 | MemoryEstimate uint64 44 | Error error 45 | Logs []string 46 | Events []flowsdk.Event 47 | Debug *TransactionResultDebug 48 | } 49 | 50 | // Succeeded returns true if the transaction executed without errors. 51 | func (r TransactionResult) Succeeded() bool { 52 | return r.Error == nil 53 | } 54 | 55 | // Reverted returns true if the transaction executed with errors. 56 | func (r TransactionResult) Reverted() bool { 57 | return !r.Succeeded() 58 | } 59 | 60 | // TransactionResultDebug provides details about unsuccessful transaction execution 61 | type TransactionResultDebug struct { 62 | Message string 63 | Meta map[string]any 64 | } 65 | 66 | // NewTransactionInvalidHashAlgo creates debug details for transactions with invalid hashing algorithm 67 | func NewTransactionInvalidHashAlgo( 68 | key flowgo.AccountPublicKey, 69 | address flowgo.Address, 70 | invalidAlgo hash.HashingAlgorithm, 71 | ) *TransactionResultDebug { 72 | return &TransactionResultDebug{ 73 | Message: fmt.Sprintf( 74 | "invalid hashing algorithm signature: public key %d on account %s does not have a valid signature: key requires %s hashing algorithm, but %s was used", 75 | key.Index, address, key.HashAlgo, invalidAlgo, 76 | ), 77 | Meta: nil, 78 | } 79 | } 80 | 81 | // NewTransactionInvalidSignature creates more debug details for transactions with invalid signature 82 | func NewTransactionInvalidSignature( 83 | tx *flowgo.TransactionBody, 84 | ) *TransactionResultDebug { 85 | return &TransactionResultDebug{ 86 | Message: "", 87 | Meta: map[string]any{ 88 | "payer": tx.Payer.String(), 89 | "proposer": tx.ProposalKey.Address.String(), 90 | "proposerKeyIndex": fmt.Sprintf("%d", tx.ProposalKey.KeyIndex), 91 | "authorizers": fmt.Sprintf("%v", tx.Authorizers), 92 | "gasLimit": fmt.Sprintf("%d", tx.GasLimit), 93 | }, 94 | } 95 | } 96 | 97 | // TODO - this class should be part of SDK for consistency 98 | 99 | // A ScriptResult is the result of executing a script. 100 | type ScriptResult struct { 101 | ScriptID flowsdk.Identifier 102 | Value cadence.Value 103 | Error error 104 | Logs []string 105 | Events []flowsdk.Event 106 | ComputationUsed uint64 107 | MemoryEstimate uint64 108 | } 109 | 110 | // Succeeded returns true if the script executed without errors. 111 | func (r ScriptResult) Succeeded() bool { 112 | return r.Error == nil 113 | } 114 | 115 | // Reverted returns true if the script executed with errors. 116 | func (r ScriptResult) Reverted() bool { 117 | return !r.Succeeded() 118 | } 119 | -------------------------------------------------------------------------------- /types/result_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package types_test 20 | 21 | import ( 22 | "errors" 23 | "testing" 24 | 25 | "github.com/onflow/cadence" 26 | flowsdk "github.com/onflow/flow-go-sdk" 27 | "github.com/onflow/flow-go-sdk/test" 28 | "github.com/stretchr/testify/assert" 29 | 30 | "github.com/onflow/flow-emulator/types" 31 | ) 32 | 33 | func TestResult(t *testing.T) { 34 | 35 | t.Parallel() 36 | 37 | t.Run("should return correct boolean", func(t *testing.T) { 38 | 39 | t.Parallel() 40 | 41 | idGenerator := test.IdentifierGenerator() 42 | 43 | trSucceed := &types.TransactionResult{ 44 | TransactionID: idGenerator.New(), 45 | ComputationUsed: 20, 46 | MemoryEstimate: 2048, 47 | Error: nil, 48 | Logs: []string{}, 49 | Events: []flowsdk.Event{}, 50 | } 51 | assert.True(t, trSucceed.Succeeded()) 52 | assert.False(t, trSucceed.Reverted()) 53 | 54 | trReverted := &types.TransactionResult{ 55 | TransactionID: idGenerator.New(), 56 | ComputationUsed: 20, 57 | MemoryEstimate: 2048, 58 | Error: errors.New("transaction execution error"), 59 | Logs: []string{}, 60 | Events: []flowsdk.Event{}, 61 | } 62 | assert.True(t, trReverted.Reverted()) 63 | assert.False(t, trReverted.Succeeded()) 64 | 65 | srSucceed := &types.ScriptResult{ 66 | ScriptID: idGenerator.New(), 67 | Value: cadence.Value(cadence.NewInt(1)), 68 | Error: nil, 69 | Logs: []string{}, 70 | Events: []flowsdk.Event{}, 71 | } 72 | assert.True(t, srSucceed.Succeeded()) 73 | assert.False(t, srSucceed.Reverted()) 74 | 75 | srReverted := &types.ScriptResult{ 76 | ScriptID: idGenerator.New(), 77 | Value: cadence.Value(cadence.NewInt(1)), 78 | Error: errors.New("transaction execution error"), 79 | Logs: []string{}, 80 | Events: []flowsdk.Event{}, 81 | } 82 | assert.True(t, srReverted.Reverted()) 83 | assert.False(t, srReverted.Succeeded()) 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /utils/logging.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package utils 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/logrusorgru/aurora" 25 | "github.com/onflow/flow-emulator/types" 26 | sdk "github.com/onflow/flow-go-sdk" 27 | "github.com/rs/zerolog" 28 | ) 29 | 30 | func PrintScriptResult(logger *zerolog.Logger, result *types.ScriptResult) { 31 | if result.Succeeded() { 32 | logger.Debug(). 33 | Str("scriptID", result.ScriptID.String()). 34 | Uint64("computationUsed", result.ComputationUsed). 35 | Uint64("memoryEstimate", result.MemoryEstimate). 36 | Msg("⭐ Script executed") 37 | } else { 38 | logger.Warn(). 39 | Str("scriptID", result.ScriptID.String()). 40 | Uint64("computationUsed", result.ComputationUsed). 41 | Uint64("memoryEstimate", result.MemoryEstimate). 42 | Msg("❗ Script reverted") 43 | } 44 | 45 | if !result.Succeeded() { 46 | logger.Warn().Msgf( 47 | "%s %s", 48 | logPrefix("ERR", result.ScriptID, aurora.RedFg), 49 | result.Error.Error(), 50 | ) 51 | } 52 | } 53 | 54 | func PrintTransactionResult(logger *zerolog.Logger, result *types.TransactionResult) { 55 | if result.Succeeded() { 56 | logger.Debug(). 57 | Str("txID", result.TransactionID.String()). 58 | Uint64("computationUsed", result.ComputationUsed). 59 | Uint64("memoryEstimate", result.MemoryEstimate). 60 | Msg("⭐ Transaction executed") 61 | } else { 62 | logger.Warn(). 63 | Str("txID", result.TransactionID.String()). 64 | Uint64("computationUsed", result.ComputationUsed). 65 | Uint64("memoryEstimate", result.MemoryEstimate). 66 | Msg("❗ Transaction reverted") 67 | } 68 | 69 | for _, event := range result.Events { 70 | logger.Debug().Msgf( 71 | "%s %s", 72 | logPrefix("EVT", result.TransactionID, aurora.GreenFg), 73 | event, 74 | ) 75 | } 76 | 77 | if !result.Succeeded() { 78 | logger.Warn().Msgf( 79 | "%s %s", 80 | logPrefix("ERR", result.TransactionID, aurora.RedFg), 81 | result.Error.Error(), 82 | ) 83 | 84 | if result.Debug != nil { 85 | logger.Debug().Fields(result.Debug.Meta).Msgf("%s %s", "❗ Transaction Signature Error", result.Debug.Message) 86 | } 87 | } 88 | } 89 | 90 | func logPrefix(prefix string, id sdk.Identifier, color aurora.Color) string { 91 | prefix = aurora.Colorize(prefix, color|aurora.BoldFm).String() 92 | shortID := fmt.Sprintf("[%s]", id.String()[:6]) 93 | shortID = aurora.Colorize(shortID, aurora.FaintFm).String() 94 | return fmt.Sprintf("%s %s", prefix, shortID) 95 | } 96 | -------------------------------------------------------------------------------- /utils/temp_dep_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package utils 19 | 20 | import "github.com/btcsuite/btcd/chaincfg/chainhash" 21 | 22 | // this is added to resolve the issue with chainhash ambiguous import, 23 | // the code is not used, but it's needed to force go.mod specify and retain chainhash version 24 | // workaround for issue: https://github.com/golang/go/issues/27899 25 | var _ = chainhash.Hash{} 26 | -------------------------------------------------------------------------------- /utils/unittest/fixtures.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow Emulator 3 | * 4 | * Copyright Flow Foundation 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package unittest 20 | 21 | import ( 22 | "github.com/onflow/flow-go-sdk/test" 23 | flowgo "github.com/onflow/flow-go/model/flow" 24 | "github.com/onflow/flow/protobuf/go/flow/entities" 25 | 26 | "github.com/onflow/flow-emulator/convert" 27 | 28 | "github.com/onflow/flow-emulator/types" 29 | ) 30 | 31 | func TransactionFixture() flowgo.TransactionBody { 32 | return *convert.SDKTransactionToFlow(*test.TransactionGenerator().New()) 33 | } 34 | 35 | func StorableTransactionResultFixture(eventEncodingVersion entities.EventEncodingVersion) types.StorableTransactionResult { 36 | events := test.EventGenerator(eventEncodingVersion) 37 | 38 | eventA, _ := convert.SDKEventToFlow(events.New()) 39 | eventB, _ := convert.SDKEventToFlow(events.New()) 40 | 41 | return types.StorableTransactionResult{ 42 | ErrorCode: 42, 43 | ErrorMessage: "foo", 44 | Logs: []string{"a", "b", "c"}, 45 | Events: []flowgo.Event{ 46 | eventA, 47 | eventB, 48 | }, 49 | } 50 | } 51 | 52 | func FullCollectionFixture(n int) flowgo.Collection { 53 | transactions := make([]*flowgo.TransactionBody, n) 54 | for i := 0; i < n; i++ { 55 | tx := TransactionFixture() 56 | transactions[i] = &tx 57 | } 58 | 59 | return flowgo.Collection{ 60 | Transactions: transactions, 61 | } 62 | } 63 | --------------------------------------------------------------------------------