├── .gitignore ├── .header.md ├── .pre-commit-config.yaml ├── .terraform-docs.yaml ├── .tflint.hcl ├── .tfsec ├── launch_configuration_imdsv2_tfchecks.json ├── launch_template_imdsv2_tfchecks.json ├── no_launch_config_tfchecks.json ├── sg_no_embedded_egress_rules_tfchecks.json └── sg_no_embedded_ingress_rules_tfchecks.json ├── CODEOWNERS ├── LICENSE ├── NOTICE.txt ├── README.md ├── data.tf ├── examples └── basic │ ├── .header.md │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf ├── images ├── polygon_diagram.png └── polygon_diagram.pptx ├── main.tf ├── modules ├── alb │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── instances │ ├── data-sources.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── security │ ├── data-sources.tf │ ├── iam_roles.tf │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf └── user-data │ ├── main.tf │ ├── outputs.tf │ ├── scripts │ ├── polygon_edge_node.tfpl │ └── polygon_edge_server.tfpl │ ├── variables.tf │ └── versions.tf ├── outputs.tf ├── providers.tf ├── test └── examples_basic_test.go └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | plan.out 3 | plan.out.json 4 | 5 | # Local .terraform directories 6 | .terraform/ 7 | 8 | # .tfstate files 9 | *.tfstate 10 | *.tfstate.* 11 | 12 | # Crash log files 13 | crash.log 14 | 15 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 16 | # password, private keys, and other secrets. These should not be part of version 17 | # control as they are data points which are potentially sensitive and subject 18 | # to change depending on the environment. 19 | # 20 | *.tfvars 21 | 22 | # Ignore override files as they are usually used to override resources locally and so 23 | # are not checked in 24 | override.tf 25 | override.tf.json 26 | *_override.tf 27 | *_override.tf.json 28 | 29 | # Include override files you do wish to add to version control using negated pattern 30 | # 31 | # !example_override.tf 32 | 33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 34 | # example: *tfplan* 35 | 36 | # Ignore CLI configuration files 37 | .terraformrc 38 | terraform.rc 39 | .terraform.lock.hcl 40 | 41 | # Ignore custom aws profile file 42 | aws.profile.tf 43 | 44 | .idea 45 | main.zip -------------------------------------------------------------------------------- /.header.md: -------------------------------------------------------------------------------- 1 |

2 | Polygon Edge 3 |

4 | 5 | # Polygon Edge AWS Terraform 6 | 7 | Polygon Edge is a modular and extensible framework for building Ethereum-compatible blockchain networks. 8 | 9 | To find out more about Polygon, visit the [official website](https://polygon.technology/). 10 | 11 | ### Documentation 📝 12 | 13 | If you'd like to learn more about the Polygon Edge, how it works and how you can use it for your project, 14 | please check out the **[Polygon Edge Documentation](https://docs.polygon.technology/docs/edge/overview/)**. 15 | 16 | ## Terraform deployment 17 | 18 | This is a fully automated Polygon Edge blockchain infrastructure deployment for AWS cloud provider. 19 | 20 | High level overview of the resources that will be deployed: 21 | * Dedicated VPC 22 | * 4 validator nodes (which are also boot nodes) 23 | * 4 NAT gateways to allow nodes outbound internet traffic 24 | * Lambda function used for generating the first (`genesis`) block and starting the chain 25 | * Dedicated security groups and IAM roles 26 | * S3 bucket used for storing `genesis.json` file 27 | * Application Load Balancer used for exposing the `JSON-RPC` endpoint 28 | 29 | ### Prerequisites 30 | 31 | Three variables that must be provided, before running the deployment: 32 | 33 | * `account_id` - the AWS account ID that the Polygon Edge blockchain cluster will be deployed on. 34 | * `alb_ssl_certificate` - the ARN of the certificate from AWS Certificate Manager to be used by ALB for https protocol. 35 | The certificate must be generated before starting the deployment, and it must have **Issued** status. 36 | * `premine` - the account/s that will receive pre mined native currency. 37 | Value must follow the official [CLI](https://docs.polygon.technology/docs/edge/get-started/cli-commands#genesis-flags) flag specification. 38 | 39 | ### Fault tolerance 40 | 41 | Only regions that have 4 availability zones are required for this deployment. Each node is deployed in a single AZ. 42 | 43 | By placing each node in a single AZ, the whole blockchain cluster is fault-tolerant to a single node (AZ) failure, as Polygon Edge implements IBFT 44 | consensus which allows a single node to fail in a 4 validator node cluster. 45 | 46 | ### Command line access 47 | 48 | Validator nodes are not exposed in any way to the public internet (JSON-PRC is accessed only via ALB) 49 | and they don't even have public IP addresses attached to them. 50 | Nodes command line access is possible only via ***AWS Systems Manager - Session Manager***. 51 | 52 | ### Base AMI upgrade 53 | 54 | This deployment uses `ubuntu-focal-20.04-amd64-server` AWS AMI. It will **not** trigger EC2 *redeployment* if the AWS AMI gets updated. 55 | 56 | If, for some reason, base AMI is required to get updated, 57 | it can be achieved by running `terraform taint` command for each instance, before `terraform apply`. 58 | Instances can be tainted by running the `terraform taint module.instances[].aws_instance.polygon_edge_instance` command. 59 | 60 | Example: 61 | ```shell 62 | terraform taint module.instances[0].aws_instance.polygon_edge_instance 63 | terraform taint module.instances[1].aws_instance.polygon_edge_instance 64 | terraform taint module.instances[2].aws_instance.polygon_edge_instance 65 | terraform taint module.instances[3].aws_instance.polygon_edge_instance 66 | terraform apply 67 | ``` 68 | 69 | ### Resources cleanup 70 | 71 | When cleaning up all resources by running `terraform destory`, the only thing that needs to be manually deleted 72 | are **validator keys** from **AWS SSM Parameter Store** as they are not stored via Terraform, but with `polygon-edge` 73 | process itself. -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fail_fast: false 3 | minimum_pre_commit_version: "2.6.0" 4 | repos: 5 | - 6 | repo: https://github.com/aws-ia/pre-commit-configs 7 | # To update run: 8 | # pre-commit autoupdate --freeze 9 | rev: e2be6eb118666030010fdeb0edaba9db669619d1 # frozen: v1.3.2 10 | hooks: 11 | - id: aws-ia-meta-hook 12 | -------------------------------------------------------------------------------- /.terraform-docs.yaml: -------------------------------------------------------------------------------- 1 | formatter: markdown 2 | header-from: .header.md 3 | settings: 4 | anchor: true 5 | color: true 6 | default: true 7 | escape: true 8 | html: true 9 | indent: 2 10 | required: true 11 | sensitive: true 12 | type: true 13 | lockfile: false 14 | 15 | sort: 16 | enabled: true 17 | by: required 18 | 19 | output: 20 | file: README.md 21 | mode: replace 22 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | # https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/module-inspection.md 2 | # borrowed & modified indefinitely from https://github.com/ksatirli/building-infrastructure-you-can-mostly-trust/blob/main/.tflint.hcl 3 | 4 | plugin "aws" { 5 | enabled = true 6 | version = "0.21.2" 7 | source = "github.com/terraform-linters/tflint-ruleset-aws" 8 | } 9 | 10 | config { 11 | module = true 12 | force = false 13 | } 14 | 15 | rule "terraform_required_providers" { 16 | enabled = true 17 | } 18 | 19 | rule "terraform_required_version" { 20 | enabled = true 21 | } 22 | 23 | rule "terraform_naming_convention" { 24 | enabled = true 25 | format = "snake_case" 26 | } 27 | 28 | rule "terraform_typed_variables" { 29 | enabled = true 30 | } 31 | 32 | rule "terraform_unused_declarations" { 33 | enabled = true 34 | } 35 | 36 | rule "terraform_comment_syntax" { 37 | enabled = true 38 | } 39 | 40 | rule "terraform_deprecated_index" { 41 | enabled = true 42 | } 43 | 44 | rule "terraform_deprecated_interpolation" { 45 | enabled = true 46 | } 47 | 48 | rule "terraform_documented_outputs" { 49 | enabled = true 50 | } 51 | 52 | rule "terraform_documented_variables" { 53 | enabled = true 54 | } 55 | 56 | rule "terraform_module_pinned_source" { 57 | enabled = true 58 | } 59 | 60 | rule "terraform_standard_module_structure" { 61 | enabled = true 62 | } 63 | 64 | rule "terraform_workspace_remote" { 65 | enabled = true 66 | } 67 | -------------------------------------------------------------------------------- /.tfsec/launch_configuration_imdsv2_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS002", 5 | "description": "Check to IMDSv2 is required on EC2 instances created by this Launch Template", 6 | "impact": "Instance metadata service can be interacted with freely", 7 | "resolution": "Enable HTTP token requirement for IMDS", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_launch_configuration" 13 | ], 14 | "severity": "CRITICAL", 15 | "matchSpec": { 16 | "action": "isPresent", 17 | "name": "metadata_options", 18 | "subMatch": { 19 | "action": "and", 20 | "predicateMatchSpec": [ 21 | { 22 | "action": "equals", 23 | "name": "http_tokens", 24 | "value": "required" 25 | 26 | } 27 | ] 28 | } 29 | }, 30 | 31 | "errorMessage": "is missing `metadata_options` block - it is required with `http_tokens` set to `required` to make Instance Metadata Service more secure.", 32 | "relatedLinks": [ 33 | "https://tfsec.dev/docs/aws/ec2/enforce-http-token-imds#aws/ec2", 34 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_configuration#metadata-options", 35 | "https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.tfsec/launch_template_imdsv2_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS001", 5 | "description": "Check to IMDSv2 is required on EC2 instances created by this Launch Template", 6 | "impact": "Instance metadata service can be interacted with freely", 7 | "resolution": "Enable HTTP token requirement for IMDS", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_launch_template" 13 | ], 14 | "severity": "CRITICAL", 15 | "matchSpec": { 16 | "action": "isPresent", 17 | "name": "metadata_options", 18 | "subMatch": { 19 | "action": "and", 20 | "predicateMatchSpec": [ 21 | { 22 | "action": "equals", 23 | "name": "http_tokens", 24 | "value": "required" 25 | 26 | } 27 | ] 28 | } 29 | }, 30 | 31 | "errorMessage": "is missing `metadata_options` block - it is required with `http_tokens` set to `required` to make Instance Metadata Service more secure.", 32 | "relatedLinks": [ 33 | "https://tfsec.dev/docs/aws/ec2/enforce-http-token-imds#aws/ec2", 34 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template#metadata-options", 35 | "https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.tfsec/no_launch_config_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS003", 5 | "description": "Use `aws_launch_template` over `aws_launch_configuration", 6 | "impact": "Launch configurations are not capable of versions", 7 | "resolution": "Convert resource type and attributes to `aws_launch_template`", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_launch_configuration" 13 | ], 14 | "severity": "MEDIUM", 15 | "matchSpec": { 16 | "action": "notPresent", 17 | "name": "image_id" 18 | }, 19 | 20 | "errorMessage": "should be changed to `aws_launch_template` since the functionality is the same but templates can be versioned.", 21 | "relatedLinks": [ 22 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template", 23 | "https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.tfsec/sg_no_embedded_egress_rules_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS005", 5 | "description": "Security group rules should be defined with `aws_security_group_rule` instead of embedded.", 6 | "impact": "Embedded security group rules can cause issues during configuration updates.", 7 | "resolution": "Move `egress` rules to `aws_security_group_rule` and attach to `aws_security_group`.", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_security_group" 13 | ], 14 | "severity": "MEDIUM", 15 | "matchSpec": { 16 | "action": "notPresent", 17 | "name": "egress" 18 | }, 19 | 20 | "errorMessage": "`egress` rules should be moved to `aws_security_group_rule` and attached to `aws_security_group` instead of embedded.", 21 | "relatedLinks": [ 22 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule", 23 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.tfsec/sg_no_embedded_ingress_rules_tfchecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "code": "CUS004", 5 | "description": "Security group rules should be defined with `aws_security_group_rule` instead of embedded.", 6 | "impact": "Embedded security group rules can cause issues during configuration updates.", 7 | "resolution": "Move `ingress` rules to `aws_security_group_rule` and attach to `aws_security_group`.", 8 | "requiredTypes": [ 9 | "resource" 10 | ], 11 | "requiredLabels": [ 12 | "aws_security_group" 13 | ], 14 | "severity": "MEDIUM", 15 | "matchSpec": { 16 | "action": "notPresent", 17 | "name": "ingress" 18 | }, 19 | 20 | "errorMessage": "`ingress` rules should be moved to `aws_security_group_rule` and attached to `aws_security_group` instead of embedded.", 21 | "relatedLinks": [ 22 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule", 23 | "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sshvans @aws-ia/aws-ia 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | 5 | http://aws.amazon.com/apache2.0/ 6 | 7 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Polygon Edge 4 |

5 | 6 | # Polygon Edge AWS Terraform 7 | 8 | Polygon Edge is a modular and extensible framework for building Ethereum-compatible blockchain networks. 9 | 10 | To find out more about Polygon, visit the [official website](https://polygon.technology/). 11 | 12 | ### Documentation 📝 13 | 14 | If you'd like to learn more about the Polygon Edge, how it works and how you can use it for your project, 15 | please check out the **[Polygon Edge Documentation](https://docs.polygon.technology/docs/edge/overview/)**. 16 | 17 | ## Terraform deployment 18 | 19 | This is a fully automated Polygon Edge blockchain infrastructure deployment for AWS cloud provider. 20 | 21 | High level overview of the resources that will be deployed: 22 | * Dedicated VPC 23 | * 4 validator nodes (which are also boot nodes) 24 | * 4 NAT gateways to allow nodes outbound internet traffic 25 | * Lambda function used for generating the first (`genesis`) block and starting the chain 26 | * Dedicated security groups and IAM roles 27 | * S3 bucket used for storing `genesis.json` file 28 | * Application Load Balancer used for exposing the `JSON-RPC` endpoint 29 | 30 | ### Prerequisites 31 | 32 | Two variables that must be provided, before running the deployment: 33 | 34 | * `alb_ssl_certificate` - the ARN of the certificate from AWS Certificate Manager to be used by ALB for https protocol. 35 | The certificate must be generated before starting the deployment, and it must have **Issued** status. 36 | * `premine` - the account/s that will receive pre mined native currency. 37 | Value must follow the official [CLI](https://docs.polygon.technology/docs/edge/get-started/cli-commands#genesis-flags) flag specification. 38 | 39 | ### Fault tolerance 40 | 41 | Only regions that have 4 availability zones are required for this deployment. Each node is deployed in a single AZ. 42 | 43 | By placing each node in a single AZ, the whole blockchain cluster is fault-tolerant to a single node (AZ) failure, as Polygon Edge implements IBFT 44 | consensus which allows a single node to fail in a 4 validator node cluster. 45 | 46 | ### Command line access 47 | 48 | Validator nodes are not exposed in any way to the public internet (JSON-PRC is accessed only via ALB) 49 | and they don't even have public IP addresses attached to them. 50 | Nodes command line access is possible only via ***AWS Systems Manager - Session Manager***. 51 | 52 | ### Base AMI upgrade 53 | 54 | This deployment uses `ubuntu-focal-20.04-amd64-server` AWS AMI. It will **not** trigger EC2 *redeployment* if the AWS AMI gets updated. 55 | 56 | If, for some reason, base AMI is required to get updated, 57 | it can be achieved by running `terraform taint` command for each instance, before `terraform apply`. 58 | Instances can be tainted by running the `terraform taint module.instances[].aws_instance.polygon_edge_instance` command. 59 | 60 | Example: 61 | ```shell 62 | terraform taint module.instances[0].aws_instance.polygon_edge_instance 63 | terraform taint module.instances[1].aws_instance.polygon_edge_instance 64 | terraform taint module.instances[2].aws_instance.polygon_edge_instance 65 | terraform taint module.instances[3].aws_instance.polygon_edge_instance 66 | terraform apply 67 | ``` 68 | 69 | ### Resources cleanup 70 | 71 | When cleaning up all resources by running `terraform destory`, the only thing that needs to be manually deleted 72 | are **validator keys** from **AWS SSM Parameter Store** as they are not stored via Terraform, but with `polygon-edge` 73 | process itself. 74 | 75 | ## Requirements 76 | 77 | | Name | Version | 78 | |------|---------| 79 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 80 | | [aws](#requirement\_aws) | >= 4.22.0 | 81 | | [awscc](#requirement\_awscc) | >= 0.27.0 | 82 | | [external](#requirement\_external) | >= 2.2.2 | 83 | | [local](#requirement\_local) | >= 2.2.3 | 84 | | [null](#requirement\_null) | >=3.1.1 | 85 | 86 | ## Providers 87 | 88 | | Name | Version | 89 | |------|---------| 90 | | [aws](#provider\_aws) | >= 4.22.0 | 91 | | [null](#provider\_null) | >=3.1.1 | 92 | 93 | ## Modules 94 | 95 | | Name | Source | Version | 96 | |------|--------|---------| 97 | | [alb](#module\_alb) | ./modules/alb | n/a | 98 | | [instances](#module\_instances) | ./modules/instances | n/a | 99 | | [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | >=3.3.1 | 100 | | [s3](#module\_s3) | terraform-aws-modules/s3-bucket/aws | >= 3.3.0 | 101 | | [security](#module\_security) | ./modules/security | n/a | 102 | | [user\_data](#module\_user\_data) | ./modules/user-data | n/a | 103 | | [vpc](#module\_vpc) | aws-ia/vpc/aws | >= 3.0.1 | 104 | 105 | ## Resources 106 | 107 | | Name | Type | 108 | |------|------| 109 | | [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | 110 | | [aws_availability_zones.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | 111 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 112 | | [aws_iam_policy_document.genesis_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 113 | | [aws_iam_policy_document.genesis_ssm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 114 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | 115 | | [null_data_source.downloaded_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source) | data source | 116 | 117 | ## Inputs 118 | 119 | | Name | Description | Type | Default | Required | 120 | |------|-------------|------|---------|:--------:| 121 | | [alb\_ssl\_certificate](#input\_alb\_ssl\_certificate) | SSL certificate ARN for JSON-RPC loadblancer | `string` | n/a | yes | 122 | | [premine](#input\_premine) | Premine the accounts with the specified ammount. Format: account:ammount,account:ammount | `string` | n/a | yes | 123 | | [alb\_sec\_gr\_name\_tag](#input\_alb\_sec\_gr\_name\_tag) | External security group name tag | `string` | `"Polygon Edge External"` | no | 124 | | [block\_gas\_limit](#input\_block\_gas\_limit) | Set the block gas limit | `string` | `""` | no | 125 | | [block\_gas\_target](#input\_block\_gas\_target) | Sets the target block gas limit for the chain | `string` | `""` | no | 126 | | [block\_time](#input\_block\_time) | Set block production time in seconds | `string` | `""` | no | 127 | | [chain\_data\_ebs\_name\_tag](#input\_chain\_data\_ebs\_name\_tag) | The name of the chain data EBS volume. | `string` | `"Polygon_Edge_chain_data_volume"` | no | 128 | | [chain\_data\_ebs\_volume\_size](#input\_chain\_data\_ebs\_volume\_size) | The size of the chain data EBS volume. | `number` | `30` | no | 129 | | [chain\_id](#input\_chain\_id) | Set the Chain ID | `string` | `""` | no | 130 | | [chain\_name](#input\_chain\_name) | Set the name of chain | `string` | `""` | no | 131 | | [consensus](#input\_consensus) | Sets consensus protocol. | `string` | `""` | no | 132 | | [dns\_name](#input\_dns\_name) | Sets the DNS name for the network package | `string` | `""` | no | 133 | | [ebs\_device](#input\_ebs\_device) | The ebs device path. Defined when creating EBS volume. | `string` | `"/dev/nvme1n1"` | no | 134 | | [ebs\_root\_name\_tag](#input\_ebs\_root\_name\_tag) | The name tag for the Polygon Edge instance root volume. | `string` | `"Polygon_Edge_Root_Volume"` | no | 135 | | [epoch\_size](#input\_epoch\_size) | Set the epoch size | `string` | `""` | no | 136 | | [instance\_interface\_name\_tag](#input\_instance\_interface\_name\_tag) | The name of the instance interface. | `string` | `"Polygon_Edge_Instance_Interface"` | no | 137 | | [instance\_name](#input\_instance\_name) | The name of Polygon Edge instance | `string` | `"Polygon_Edge_Node"` | no | 138 | | [instance\_type](#input\_instance\_type) | Polygon Edge nodes instance type. | `string` | `"t3.medium"` | no | 139 | | [internal\_sec\_gr\_name\_tag](#input\_internal\_sec\_gr\_name\_tag) | Internal security group name tag | `string` | `"Polygon Edge Internal"` | no | 140 | | [lambda\_function\_name](#input\_lambda\_function\_name) | The name of the Lambda function used for chain init | `string` | `"polygon-edge-init"` | no | 141 | | [lambda\_function\_zip](#input\_lambda\_function\_zip) | The lambda function code in zip archive | `string` | `"https://raw.githubusercontent.com/Trapesys/polygon-edge-assm/aws-lambda/artifacts/main.zip"` | no | 142 | | [max\_slots](#input\_max\_slots) | Sets maximum slots in the pool | `string` | `""` | no | 143 | | [max\_validator\_count](#input\_max\_validator\_count) | The maximum number of stakers able to join the validator set in a PoS consensus. | `string` | `""` | no | 144 | | [min\_validator\_count](#input\_min\_validator\_count) | The minimum number of stakers needed to join the validator set in a PoS consensus. | `string` | `""` | no | 145 | | [nat\_address](#input\_nat\_address) | Sets the NAT address for the networking package | `string` | `""` | no | 146 | | [node\_name\_prefix](#input\_node\_name\_prefix) | The name prefix that will be used to store secrets | `string` | `"node"` | no | 147 | | [nodes\_alb\_name\_prefix](#input\_nodes\_alb\_name\_prefix) | ALB name | `string` | `"jrpc-"` | no | 148 | | [nodes\_alb\_name\_tag](#input\_nodes\_alb\_name\_tag) | ALB name tag | `string` | `"Polygon Edge JSON-RPC ALB"` | no | 149 | | [nodes\_alb\_targetgroup\_name\_prefix](#input\_nodes\_alb\_targetgroup\_name\_prefix) | ALB target group name | `string` | `"jrpc-"` | no | 150 | | [polygon\_edge\_dir](#input\_polygon\_edge\_dir) | The directory to place all polygon-edge data and logs | `string` | `"/home/ubuntu/polygon"` | no | 151 | | [pos](#input\_pos) | Use PoS IBFT consensus | `bool` | `false` | no | 152 | | [price\_limit](#input\_price\_limit) | Sets minimum gas price limit to enforce for acceptance into the pool | `string` | `""` | no | 153 | | [prometheus\_address](#input\_prometheus\_address) | Enable Prometheus API | `string` | `""` | no | 154 | | [s3\_bucket\_prefix](#input\_s3\_bucket\_prefix) | Name prefix for new S3 bucket | `string` | `"polygon-edge-shared-"` | no | 155 | | [s3\_force\_destroy](#input\_s3\_force\_destroy) | Delete S3 bucket on destroy, even if the bucket is not empty | `bool` | `true` | no | 156 | | [s3\_key\_name](#input\_s3\_key\_name) | Name of the file in S3 that will hold configuration | `string` | `"chain-config"` | no | 157 | | [ssm\_parameter\_id](#input\_ssm\_parameter\_id) | The id that will be used for storing and fetching from SSM Parameter Store | `string` | `"polygon-edge-validators"` | no | 158 | | [vpc\_cidr\_block](#input\_vpc\_cidr\_block) | CIDR block for VPC | `string` | `"10.250.0.0/16"` | no | 159 | | [vpc\_name](#input\_vpc\_name) | Name of the VPC | `string` | `"polygon-edge-vpc"` | no | 160 | 161 | ## Outputs 162 | 163 | | Name | Description | 164 | |------|-------------| 165 | | [jsonrpc\_dns\_name](#output\_jsonrpc\_dns\_name) | The dns name for the JSON-RPC API | 166 | 167 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | data "null_data_source" "downloaded_package" { 2 | inputs = { 3 | id = null_resource.download_package.id 4 | filename = local.downloaded 5 | } 6 | } 7 | 8 | data "aws_availability_zones" "current" {} 9 | data "aws_region" "current" {} 10 | data "aws_caller_identity" "current" {} 11 | 12 | 13 | data "aws_iam_policy_document" "genesis_s3" { 14 | version = "2012-10-17" 15 | statement { 16 | actions = [ 17 | "s3:PutObject", 18 | "s3:GetObject", 19 | "s3:ListBucket" 20 | ] 21 | resources = [ 22 | module.s3.s3_bucket_arn, 23 | "${module.s3.s3_bucket_arn}/*" 24 | ] 25 | } 26 | } 27 | 28 | data "aws_iam_policy_document" "genesis_ssm" { 29 | version = "2012-10-17" 30 | statement { 31 | actions = [ 32 | "ssm:GetParameter", 33 | "ssm:GetParameters", 34 | "ssm:GetParametersByPath" 35 | ] 36 | resources = [ 37 | "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/${var.ssm_parameter_id}/*" 38 | ] 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/basic/.header.md: -------------------------------------------------------------------------------- 1 | # Polygon Edge simple deployment on AWS 2 | 3 | ## Prerequisites 4 | 5 | Three variables that must be provided, before running the deployment: 6 | 7 | * `account_id` - the AWS account ID that the Polygon Edge blockchain cluster will be deployed on. 8 | * `alb_ssl_certificate` - the ARN of the certificate from AWS Certificate Manager to be used by ALB for https protocol. 9 | The certificate must be generated before starting the deployment, and it must have **Issued** status. 10 | * `premine` - the account/s that will receive pre mined native currency. 11 | Value must follow the official [CLI](https://docs.polygon.technology/docs/edge/get-started/cli-commands#genesis-flags) flag specification. 12 | 13 | ## Deployment 14 | To get Polygon Edge cluster quickly up and running default values: 15 | * include this module 16 | * define mandatory variables or provide them at cli prompt 17 | * `terraform init` - to initialize modules 18 | * `terraform apply` - to deploy the infrastructure 19 | 20 | After everything is deployed the JSON-RPC URL should be outputted in the CLI, which needs to be set as a CNAME target 21 | for a domain that you've created the certificate for. -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Polygon Edge simple deployment on AWS 3 | 4 | ## Prerequisites 5 | 6 | Three variables that must be provided, before running the deployment: 7 | 8 | * `account_id` - the AWS account ID that the Polygon Edge blockchain cluster will be deployed on. 9 | * `alb_ssl_certificate` - the ARN of the certificate from AWS Certificate Manager to be used by ALB for https protocol. 10 | The certificate must be generated before starting the deployment, and it must have **Issued** status. 11 | * `premine` - the account/s that will receive pre mined native currency. 12 | Value must follow the official [CLI](https://docs.polygon.technology/docs/edge/get-started/cli-commands#genesis-flags) flag specification. 13 | 14 | ## Deployment 15 | To get Polygon Edge cluster quickly up and running default values: 16 | * include this module 17 | * define mandatory variables or provide them at cli prompt 18 | * `terraform init` - to initialize modules 19 | * `terraform apply` - to deploy the infrastructure 20 | 21 | After everything is deployed the JSON-RPC URL should be outputted in the CLI, which needs to be set as a CNAME target 22 | for a domain that you've created the certificate for. 23 | 24 | ## Requirements 25 | 26 | No requirements. 27 | 28 | ## Providers 29 | 30 | No providers. 31 | 32 | ## Modules 33 | 34 | | Name | Source | Version | 35 | |------|--------|---------| 36 | | [polygon-edge](#module\_polygon-edge) | aws-ia/polygon-technology-edge/aws | >=0.0.1 | 37 | 38 | ## Resources 39 | 40 | No resources. 41 | 42 | ## Inputs 43 | 44 | | Name | Description | Type | Default | Required | 45 | |------|-------------|------|---------|:--------:| 46 | | [alb\_ssl\_certificate](#input\_alb\_ssl\_certificate) | The ARN of SSL certificate that will be placed on JSON-RPC ALB | `string` | n/a | yes | 47 | | [premine](#input\_premine) | Public account that will receive premined native currency | `string` | n/a | yes | 48 | 49 | ## Outputs 50 | 51 | | Name | Description | 52 | |------|-------------| 53 | | [json\_rpc\_dns\_name](#output\_json\_rpc\_dns\_name) | The dns name for the JSON-RPC API | 54 | -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | module "polygon-edge" { 2 | source = "aws-ia/polygon-technology-edge/aws" 3 | version = ">=0.0.1" 4 | 5 | premine = var.premine 6 | alb_ssl_certificate = var.alb_ssl_certificate 7 | } 8 | -------------------------------------------------------------------------------- /examples/basic/outputs.tf: -------------------------------------------------------------------------------- 1 | output "json_rpc_dns_name" { 2 | value = module.polygon-edge.jsonrpc_dns_name 3 | description = "The dns name for the JSON-RPC API" 4 | } -------------------------------------------------------------------------------- /examples/basic/providers.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-ia/terraform-aws-polygon-technology-edge/ac610d5b2fbde2406b0827a69e10b6795ff7f6c2/examples/basic/providers.tf -------------------------------------------------------------------------------- /examples/basic/variables.tf: -------------------------------------------------------------------------------- 1 | variable "premine" { 2 | type = string 3 | description = "Public account that will receive premined native currency" 4 | } 5 | 6 | variable "alb_ssl_certificate" { 7 | type = string 8 | description = "The ARN of SSL certificate that will be placed on JSON-RPC ALB" 9 | } 10 | -------------------------------------------------------------------------------- /images/polygon_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-ia/terraform-aws-polygon-technology-edge/ac610d5b2fbde2406b0827a69e10b6795ff7f6c2/images/polygon_diagram.png -------------------------------------------------------------------------------- /images/polygon_diagram.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-ia/terraform-aws-polygon-technology-edge/ac610d5b2fbde2406b0827a69e10b6795ff7f6c2/images/polygon_diagram.pptx -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "aws-ia/vpc/aws" 3 | version = ">= 3.0.1" 4 | 5 | name = var.vpc_name 6 | cidr_block = var.vpc_cidr_block 7 | az_count = 4 8 | 9 | subnets = { 10 | public = { 11 | netmask = 24 12 | nat_gateway_configuration = "all_azs" 13 | } 14 | 15 | private = { 16 | netmask = 24 17 | connect_to_public_natgw = true 18 | } 19 | } 20 | 21 | vpc_flow_logs = { 22 | log_destination_type = "cloud-watch-logs" 23 | retention_in_days = 180 24 | } 25 | } 26 | 27 | locals { 28 | package_url = var.lambda_function_zip 29 | downloaded = basename(var.lambda_function_zip) 30 | azs = slice(data.aws_availability_zones.current.names, 0, 4) 31 | private_subnets = [for _, value in module.vpc.private_subnet_attributes_by_az : value.id] 32 | private_azs = { 33 | for idx, az_name in local.azs : idx => az_name 34 | } 35 | 36 | } 37 | 38 | module "s3" { 39 | source = "terraform-aws-modules/s3-bucket/aws" 40 | version = ">= 3.3.0" 41 | 42 | bucket_prefix = var.s3_bucket_prefix 43 | acl = "private" 44 | force_destroy = var.s3_force_destroy 45 | } 46 | 47 | module "security" { 48 | source = "./modules/security" 49 | 50 | vpc_id = module.vpc.vpc_attributes.id 51 | account_id = data.aws_caller_identity.current.account_id 52 | s3_shared_bucket_name = module.s3.s3_bucket_id 53 | ssm_parameter_id = var.ssm_parameter_id 54 | region = data.aws_region.current.name 55 | internal_sec_gr_name_tag = var.internal_sec_gr_name_tag 56 | alb_sec_gr_name_tag = var.alb_sec_gr_name_tag 57 | lambda_function_name = var.lambda_function_name 58 | } 59 | 60 | module "instances" { 61 | source = "./modules/instances" 62 | 63 | for_each = local.private_azs 64 | 65 | internal_subnet = local.private_subnets[each.key] 66 | internal_sec_groups = [module.security.internal_sec_group_id] 67 | user_data_base64 = module.user_data[each.key].polygon_edge_node 68 | instance_iam_role = module.security.ec2_to_assm_iam_policy_id 69 | az = each.value 70 | instance_type = var.instance_type 71 | instance_name = var.instance_name 72 | ebs_root_name_tag = var.ebs_root_name_tag 73 | instance_interface_name_tag = var.instance_interface_name_tag 74 | chain_data_ebs_volume_size = var.chain_data_ebs_volume_size 75 | chain_data_ebs_name_tag = var.chain_data_ebs_name_tag 76 | 77 | depends_on = [module.lambda] 78 | } 79 | 80 | module "user_data" { 81 | source = "./modules/user-data" 82 | 83 | for_each = local.private_azs 84 | node_name = "${var.node_name_prefix}-${each.value}" 85 | 86 | assm_path = var.ssm_parameter_id 87 | assm_region = data.aws_region.current.name 88 | s3_bucket_name = module.s3.s3_bucket_id 89 | s3_key_name = var.s3_key_name 90 | total_nodes = length(module.vpc.private_subnet_attributes_by_az) 91 | 92 | polygon_edge_dir = var.polygon_edge_dir 93 | ebs_device = var.ebs_device 94 | 95 | # Server configuration 96 | 97 | max_slots = var.max_slots 98 | block_time = var.block_time 99 | prometheus_address = var.prometheus_address 100 | block_gas_target = var.block_gas_target 101 | nat_address = var.nat_address 102 | dns_name = var.dns_name 103 | price_limit = var.price_limit 104 | 105 | # # Chain configuration 106 | premine = var.premine 107 | chain_name = var.chain_name 108 | chain_id = var.chain_id 109 | block_gas_limit = var.block_gas_limit 110 | epoch_size = var.epoch_size 111 | consensus = var.consensus 112 | lambda_function_name = var.lambda_function_name 113 | max_validator_count = var.max_validator_count 114 | min_validator_count = var.min_validator_count 115 | pos = var.pos 116 | 117 | } 118 | 119 | module "alb" { 120 | source = "./modules/alb" 121 | 122 | public_subnets = [for _, value in module.vpc.public_subnet_attributes_by_az : value.id] 123 | alb_sec_group = module.security.jsonrpc_sec_group_id 124 | vpc_id = module.vpc.vpc_attributes.id 125 | node_ids = [for _, instance in module.instances : instance.instance_id] 126 | alb_ssl_certificate = var.alb_ssl_certificate 127 | 128 | nodes_alb_name_prefix = var.nodes_alb_name_prefix 129 | nodes_alb_name_tag = var.nodes_alb_name_tag 130 | nodes_alb_targetgroup_name_prefix = var.nodes_alb_targetgroup_name_prefix 131 | } 132 | 133 | 134 | 135 | resource "null_resource" "download_package" { 136 | triggers = { 137 | downloaded = local.downloaded 138 | } 139 | 140 | provisioner "local-exec" { 141 | command = "curl -L -o ${local.downloaded} ${local.package_url}" 142 | } 143 | } 144 | 145 | module "lambda" { 146 | source = "terraform-aws-modules/lambda/aws" 147 | version = ">=3.3.1" 148 | 149 | function_name = var.lambda_function_name 150 | description = "Lambda function used to initialize chain and generate genesis.json" 151 | handler = "main" 152 | runtime = "go1.x" 153 | timeout = 20 154 | memory_size = 256 155 | 156 | create_package = false 157 | local_existing_package = data.null_data_source.downloaded_package.outputs["filename"] 158 | 159 | attach_policy_jsons = true 160 | number_of_policy_jsons = 2 161 | policy_jsons = [data.aws_iam_policy_document.genesis_s3.json, data.aws_iam_policy_document.genesis_ssm.json] 162 | } 163 | -------------------------------------------------------------------------------- /modules/alb/main.tf: -------------------------------------------------------------------------------- 1 | # Create new ALB 2 | resource "aws_lb" "polygon_nodes" { 3 | name_prefix = var.nodes_alb_name_prefix 4 | #tfsec:ignore:aws-elb-alb-not-public 5 | internal = false 6 | load_balancer_type = "application" 7 | subnets = var.public_subnets 8 | security_groups = [var.alb_sec_group] 9 | drop_invalid_header_fields = true 10 | 11 | 12 | tags = { 13 | Name = var.nodes_alb_name_tag 14 | } 15 | } 16 | # Create new ALB Target Group 17 | resource "aws_lb_target_group" "polygon_nodes" { 18 | name_prefix = var.nodes_alb_targetgroup_name_prefix 19 | port = 8545 20 | protocol = "HTTP" 21 | vpc_id = var.vpc_id 22 | } 23 | 24 | # Set http listener on ALB 25 | resource "aws_lb_listener" "polygon_nodes_http" { 26 | load_balancer_arn = aws_lb.polygon_nodes.arn 27 | port = 80 28 | protocol = "HTTP" 29 | 30 | default_action { 31 | type = "redirect" 32 | 33 | redirect { 34 | status_code = "HTTP_301" 35 | port = 443 36 | protocol = "HTTPS" 37 | } 38 | } 39 | } 40 | 41 | # Set https listener on ALB 42 | resource "aws_lb_listener" "polygon_nodes_https" { 43 | load_balancer_arn = aws_lb.polygon_nodes.arn 44 | port = 443 45 | protocol = "HTTPS" 46 | ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01" 47 | certificate_arn = var.alb_ssl_certificate 48 | 49 | default_action { 50 | type = "forward" 51 | target_group_arn = aws_lb_target_group.polygon_nodes.arn 52 | } 53 | } 54 | 55 | # Attach instances to ALB 56 | resource "aws_lb_target_group_attachment" "polygon_nodes" { 57 | count = length(var.node_ids) 58 | 59 | target_group_arn = aws_lb_target_group.polygon_nodes.arn 60 | target_id = var.node_ids[count.index] 61 | port = 8545 62 | } 63 | -------------------------------------------------------------------------------- /modules/alb/outputs.tf: -------------------------------------------------------------------------------- 1 | output "dns_name" { 2 | value = aws_lb.polygon_nodes.dns_name 3 | description = "The dns name for the JSON-RPC" 4 | } -------------------------------------------------------------------------------- /modules/alb/variables.tf: -------------------------------------------------------------------------------- 1 | variable "nodes_alb_name_prefix" { 2 | type = string 3 | description = "ALB name" 4 | } 5 | variable "nodes_alb_name_tag" { 6 | type = string 7 | description = "ALB name tag" 8 | } 9 | variable "public_subnets" { 10 | type = list(string) 11 | description = "The list of public subnet IDs" 12 | } 13 | variable "alb_sec_group" { 14 | type = string 15 | description = "The security group to place the ALB in" 16 | } 17 | variable "nodes_alb_targetgroup_name_prefix" { 18 | type = string 19 | description = "ALB target group name" 20 | } 21 | variable "vpc_id" { 22 | type = string 23 | description = "VPC id" 24 | } 25 | variable "node_ids" { 26 | type = list(string) 27 | description = "The ids of the nodes to place in targetgroup" 28 | } 29 | variable "alb_ssl_certificate" { 30 | type = string 31 | description = "The SSL certificate ARN for JSON-RPC load balancer" 32 | } -------------------------------------------------------------------------------- /modules/alb/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.22.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /modules/instances/data-sources.tf: -------------------------------------------------------------------------------- 1 | data "aws_ami" "ubuntu_20_04" { 2 | most_recent = true 3 | 4 | filter { 5 | name = "name" 6 | values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] 7 | } 8 | 9 | filter { 10 | name = "virtualization-type" 11 | values = ["hvm"] 12 | } 13 | 14 | owners = ["099720109477"] 15 | } -------------------------------------------------------------------------------- /modules/instances/main.tf: -------------------------------------------------------------------------------- 1 | # create the instance 2 | resource "aws_instance" "polygon_edge_instance" { 3 | ami = data.aws_ami.ubuntu_20_04.id 4 | instance_type = var.instance_type 5 | user_data_base64 = var.user_data_base64 6 | availability_zone = var.az 7 | iam_instance_profile = var.instance_iam_role 8 | 9 | metadata_options { 10 | http_tokens = "required" 11 | http_endpoint = "enabled" 12 | } 13 | 14 | root_block_device { 15 | encrypted = true 16 | tags = { 17 | Name = var.ebs_root_name_tag 18 | } 19 | } 20 | 21 | lifecycle { 22 | ignore_changes = [ami] 23 | } 24 | 25 | tags = { 26 | Name = var.instance_name 27 | } 28 | 29 | # attach the network interface 30 | network_interface { 31 | network_interface_id = aws_network_interface.instance_interface.id 32 | device_index = 0 33 | } 34 | } 35 | 36 | # create the instance network interface 37 | resource "aws_network_interface" "instance_interface" { 38 | subnet_id = var.internal_subnet 39 | security_groups = var.internal_sec_groups 40 | 41 | tags = { 42 | Name = var.instance_interface_name_tag 43 | } 44 | } 45 | 46 | # create the EBS volume to hold the chain data 47 | #tfsec:ignore:aws-ebs-encryption-customer-key 48 | resource "aws_ebs_volume" "chain_data" { 49 | 50 | availability_zone = var.az 51 | size = var.chain_data_ebs_volume_size 52 | encrypted = true 53 | 54 | tags = { 55 | Name = var.chain_data_ebs_name_tag 56 | } 57 | } 58 | 59 | resource "aws_volume_attachment" "attach_chain_data" { 60 | 61 | device_name = "/dev/sdf" 62 | volume_id = aws_ebs_volume.chain_data.id 63 | instance_id = aws_instance.polygon_edge_instance.id 64 | force_detach = true 65 | } 66 | -------------------------------------------------------------------------------- /modules/instances/outputs.tf: -------------------------------------------------------------------------------- 1 | output "instance_dns_name" { 2 | value = aws_instance.polygon_edge_instance.private_dns 3 | description = "Private dns name of the instance" 4 | } 5 | 6 | output "instance_id" { 7 | value = aws_instance.polygon_edge_instance.id 8 | description = "Instance id" 9 | } -------------------------------------------------------------------------------- /modules/instances/variables.tf: -------------------------------------------------------------------------------- 1 | variable "instance_type" { 2 | type = string 3 | description = "Polygon Edge nodes instance type. Default: t3.medium" 4 | } 5 | variable "user_data_base64" { 6 | type = string 7 | description = "The base64 encoded data of user data ( cloud-init script )" 8 | } 9 | variable "az" { 10 | type = string 11 | description = "The availability zone of the instance." 12 | } 13 | variable "ebs_root_name_tag" { 14 | type = string 15 | description = "The name tag for the Polygon Edge instance root volume." 16 | } 17 | 18 | variable "instance_name" { 19 | type = string 20 | description = "The name of Polygon Edge instance" 21 | } 22 | 23 | variable "internal_subnet" { 24 | description = "The subnet id in which to place the instance." 25 | type = string 26 | } 27 | 28 | variable "internal_sec_groups" { 29 | type = list(string) 30 | description = "The list of security groups to attach to the instance." 31 | } 32 | 33 | variable "instance_interface_name_tag" { 34 | type = string 35 | description = "The name of the instance interface. Default: Polygon_Edge_Instance_Interface" 36 | } 37 | 38 | variable "chain_data_ebs_volume_size" { 39 | type = number 40 | description = "The size of the chain data EBS volume. Default: 30" 41 | } 42 | 43 | variable "chain_data_ebs_name_tag" { 44 | type = string 45 | description = "The name of the chain data EBS volume. Default: Polygon_Edge_chain_data_ebs_volume" 46 | } 47 | 48 | 49 | variable "instance_iam_role" { 50 | type = string 51 | description = "The IAM role to attach to the instance" 52 | } -------------------------------------------------------------------------------- /modules/instances/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.22.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /modules/security/data-sources.tf: -------------------------------------------------------------------------------- 1 | data "aws_partition" "current" {} 2 | 3 | data "aws_iam_policy" "amazon_ssm_managed_instance_core" { 4 | arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonSSMManagedInstanceCore" 5 | } 6 | 7 | data "aws_iam_policy_document" "polygon_edge_node" { 8 | version = "2012-10-17" 9 | statement { 10 | actions = [ 11 | "ssm:PutParameter", 12 | "ssm:DeleteParameter", 13 | "ssm:GetParameter" 14 | ] 15 | resources = [ 16 | "arn:aws:ssm:${var.region}:${var.account_id}:parameter/${var.ssm_parameter_id}/*" 17 | ] 18 | } 19 | statement { 20 | actions = [ 21 | "s3:PutObject", 22 | "s3:GetObject", 23 | "s3:DeleteObject" 24 | ] 25 | resources = [ 26 | "arn:aws:s3:::${var.s3_shared_bucket_name}/*" 27 | ] 28 | } 29 | statement { 30 | actions = [ 31 | "s3:ListBucket" 32 | ] 33 | resources = [ 34 | "arn:aws:s3:::${var.s3_shared_bucket_name}" 35 | ] 36 | } 37 | statement { 38 | actions = [ 39 | "lambda:InvokeFunction" 40 | ] 41 | resources = [ 42 | "arn:aws:lambda:${var.region}:${var.account_id}:function:${var.lambda_function_name}" 43 | ] 44 | } 45 | } 46 | 47 | data "aws_iam_policy_document" "ec2_trust" { 48 | version = "2012-10-17" 49 | statement { 50 | actions = [ 51 | "sts:AssumeRole" 52 | ] 53 | principals { 54 | type = "Service" 55 | identifiers = [ 56 | "ec2.amazonaws.com" 57 | ] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /modules/security/iam_roles.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role_policy" "polygon_edge_node" { 2 | name_prefix = "polygon-edge-node-" 3 | role = aws_iam_role.polygon_edge_node.id 4 | 5 | policy = data.aws_iam_policy_document.polygon_edge_node.json 6 | } 7 | resource "aws_iam_role" "polygon_edge_node" { 8 | name_prefix = "polygon-edge-node-" 9 | 10 | assume_role_policy = data.aws_iam_policy_document.ec2_trust.json 11 | 12 | } 13 | 14 | resource "aws_iam_role_policy_attachment" "ssm_role_policy_attach" { 15 | role = aws_iam_role.polygon_edge_node.name 16 | policy_arn = data.aws_iam_policy.amazon_ssm_managed_instance_core.arn 17 | } 18 | 19 | resource "aws_iam_instance_profile" "polygon_edge_node" { 20 | name_prefix = "polygon-edge-node-" 21 | role = aws_iam_role.polygon_edge_node.name 22 | } 23 | -------------------------------------------------------------------------------- /modules/security/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "polygon_internal" { 2 | name_prefix = "Polygon_Edge_Node_Internal_" 3 | description = "Allow Internal Traffic on Polygon Edge Nodes" 4 | vpc_id = var.vpc_id 5 | 6 | tags = { 7 | Name = var.internal_sec_gr_name_tag 8 | } 9 | } 10 | 11 | resource "aws_security_group" "json_rpc_alb" { 12 | name_prefix = "Poligon_Edge_JSONRPC_ALB_" 13 | description = "Allow External Traffic to ALB" 14 | vpc_id = var.vpc_id 15 | 16 | tags = { 17 | Name = var.alb_sec_gr_name_tag 18 | } 19 | } 20 | 21 | #tfsec:ignore:aws-vpc-no-public-egress-sgr 22 | resource "aws_security_group_rule" "allow_nodes_egress" { 23 | description = "Allow all outbound" 24 | from_port = 0 25 | to_port = 0 26 | protocol = "-1" 27 | cidr_blocks = ["0.0.0.0/0"] 28 | ipv6_cidr_blocks = ["::/0"] 29 | security_group_id = aws_security_group.polygon_internal.id 30 | type = "egress" 31 | } 32 | 33 | resource "aws_security_group_rule" "allow_jsonrpc" { 34 | description = "Allow Public Access to nodes JSON-RPC" 35 | from_port = 8545 36 | to_port = 8545 37 | protocol = "tcp" 38 | source_security_group_id = aws_security_group.json_rpc_alb.id 39 | security_group_id = aws_security_group.polygon_internal.id 40 | type = "ingress" 41 | } 42 | 43 | resource "aws_security_group_rule" "allow_libp2p" { 44 | description = "LibP2P Allow" 45 | from_port = 1478 46 | to_port = 1478 47 | protocol = "tcp" 48 | self = true 49 | security_group_id = aws_security_group.polygon_internal.id 50 | type = "ingress" 51 | } 52 | #tfsec:ignore:aws-vpc-no-public-egress-sgr 53 | resource "aws_security_group_rule" "allow_alb_egress" { 54 | description = "Allow all outbound" 55 | from_port = 0 56 | to_port = 0 57 | protocol = "-1" 58 | cidr_blocks = ["0.0.0.0/0"] 59 | ipv6_cidr_blocks = ["::/0"] 60 | security_group_id = aws_security_group.json_rpc_alb.id 61 | type = "egress" 62 | } 63 | #tfsec:ignore:aws-vpc-no-public-ingress-sgr 64 | resource "aws_security_group_rule" "allow_alb_http" { 65 | description = "Allow HTTP traffic for ALB" 66 | from_port = 80 67 | protocol = "tcp" 68 | security_group_id = aws_security_group.json_rpc_alb.id 69 | to_port = 80 70 | type = "ingress" 71 | cidr_blocks = ["0.0.0.0/0"] 72 | } 73 | #tfsec:ignore:aws-vpc-no-public-ingress-sgr 74 | resource "aws_security_group_rule" "allow_alb_https" { 75 | description = "Allow HTTPS traffic for ALB" 76 | from_port = 443 77 | protocol = "tcp" 78 | security_group_id = aws_security_group.json_rpc_alb.id 79 | to_port = 443 80 | type = "ingress" 81 | cidr_blocks = ["0.0.0.0/0"] 82 | } 83 | -------------------------------------------------------------------------------- /modules/security/outputs.tf: -------------------------------------------------------------------------------- 1 | output "internal_sec_group_id" { 2 | value = aws_security_group.polygon_internal.id 3 | description = "Internal security group. Should be attached to Polygon Edge nodes only." 4 | } 5 | 6 | output "jsonrpc_sec_group_id" { 7 | value = aws_security_group.json_rpc_alb.id 8 | description = "Jsonrpc instance security group. Should be attached to ALB" 9 | } 10 | 11 | output "ec2_to_assm_iam_policy_id" { 12 | value = aws_iam_instance_profile.polygon_edge_node.id 13 | description = "IAM policy that allows communication between EC2 and ASSM" 14 | } -------------------------------------------------------------------------------- /modules/security/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.1.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 4.22.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /modules/security/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" { 2 | type = string 3 | description = "VPC ID" 4 | } 5 | 6 | variable "internal_sec_gr_name_tag" { 7 | type = string 8 | description = "Internal security group name tag" 9 | } 10 | 11 | variable "s3_shared_bucket_name" { 12 | type = string 13 | description = "The name of the S3 bucket that holds shared polygon-edge nodes data" 14 | } 15 | 16 | variable "ssm_parameter_id" { 17 | description = "The id that will be used for storing and fetching from SSM Parameter Store" 18 | type = string 19 | } 20 | 21 | variable "region" { 22 | type = string 23 | description = "The region in which instances reside. Default: us-west-2" 24 | } 25 | 26 | variable "account_id" { 27 | type = string 28 | description = "The AWS account ID" 29 | } 30 | 31 | variable "alb_sec_gr_name_tag" { 32 | type = string 33 | description = "The name tag for ALB security group" 34 | } 35 | 36 | variable "lambda_function_name" { 37 | type = string 38 | description = "The name of the chain initialization Lambda function" 39 | } -------------------------------------------------------------------------------- /modules/user-data/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | polygon_edge_node = templatefile("${path.module}/scripts/polygon_edge_node.tfpl", { 3 | "polygon_edge_dir" = var.polygon_edge_dir 4 | "ebs_device" = var.ebs_device 5 | "node_name" = var.node_name 6 | "assm_path" = var.assm_path 7 | "assm_region" = var.assm_region 8 | "total_nodes" = var.total_nodes 9 | "s3_bucket_name" = var.s3_bucket_name 10 | "s3_key_name" = var.s3_key_name 11 | "lambda_function_name" = var.lambda_function_name 12 | 13 | "premine" = var.premine 14 | "chain_name" = var.chain_name 15 | "chain_id" = var.chain_id 16 | "pos" = var.pos 17 | "epoch_size" = var.epoch_size 18 | "block_gas_limit" = var.block_gas_limit 19 | "max_validator_count" = var.max_validator_count 20 | "min_validator_count" = var.min_validator_count 21 | "consensus" = var.consensus 22 | }) 23 | polygon_edge_server = templatefile("${path.module}/scripts/polygon_edge_server.tfpl", { 24 | "polygon_edge_dir" = var.polygon_edge_dir 25 | "s3_bucket_name" = var.s3_bucket_name 26 | "prometheus_address" = var.prometheus_address 27 | "block_gas_target" = var.block_gas_target 28 | "nat_address" = var.nat_address 29 | "dns_name" = var.dns_name 30 | "price_limit" = var.price_limit 31 | "max_slots" = var.max_slots 32 | "block_time" = var.block_time 33 | }) 34 | } 35 | 36 | data "cloudinit_config" "polygon_edge" { 37 | gzip = true 38 | base64_encode = true 39 | 40 | part { 41 | content_type = "text/x-shellscript" 42 | content = local.polygon_edge_node 43 | filename = "01-polygon-edge-node.sh" 44 | } 45 | 46 | part { 47 | content_type = "text/x-shellscript" 48 | content = local.polygon_edge_server 49 | filename = "02-polygon-edge-server.sh" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/user-data/outputs.tf: -------------------------------------------------------------------------------- 1 | output "polygon_edge_node" { 2 | value = data.cloudinit_config.polygon_edge.rendered 3 | description = "Base64 rendered polygon edge node user-init data" 4 | } 5 | -------------------------------------------------------------------------------- /modules/user-data/scripts/polygon_edge_node.tfpl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | POLYGON_FOLDER="${polygon_edge_dir}" 3 | LOGS_FOLDER="${polygon_edge_dir}/logs" 4 | DATA_FOLDER="${polygon_edge_dir}/data" 5 | EBS_DEVICE="${ebs_device}" 6 | LOG_FILE="$LOGS_FOLDER/init.log" 7 | NODE_INIT_FILE="$LOGS_FOLDER/node_init.json" 8 | LINUX_USER="ubuntu" 9 | NODE_NAME="${node_name}" 10 | ASSM_PARAM_PATH="/${assm_path}" 11 | ASSM_REGION="${assm_region}" 12 | IP="$(hostname -I)" 13 | 14 | TOTAL_NODES="${total_nodes}" 15 | 16 | # create central dir that will hold chain data and logs 17 | if [ ! -d "$POLYGON_FOLDER" ]; 18 | then 19 | mkdir "$POLYGON_FOLDER" 20 | fi 21 | # crate logs dir 22 | if [ ! -d "$LOGS_FOLDER" ]; 23 | then 24 | mkdir "$LOGS_FOLDER" 25 | fi 26 | 27 | echo "----------- Script starting on :$(date) ----------------" >> "$LOG_FILE" 28 | ## Check if data directory already exists 29 | if [ -d "$DATA_FOLDER" ]; 30 | then 31 | echo "$DATA_FOLDER direcotory is already created. Skipping directory creation..." >> "$LOG_FILE" 32 | else 33 | ERR=$(mkdir "$DATA_FOLDER" 2>&1 > /dev/null) 34 | [ $? -eq 0 ] && echo "Data directory created!" >> "$LOG_FILE" || echo "Directory creation failed! Error: $ERR" >> "$LOG_FILE" 35 | fi 36 | 37 | # Wait for the system to fully boot before attemting to mount the EBS 38 | counter=0 39 | while [ ! -b "$EBS_DEVICE" ]; 40 | do 41 | sleep 10 42 | counter=$((counter + 1)) 43 | if [ $counter -ge 30 ]; 44 | then 45 | echo "Could not initialize $EBS_DEVICE for more than 5 minutes. Exiting ...">> "$LOG_FILE" 46 | exit 1 47 | fi 48 | done 49 | 50 | # Check if EBS storage exists 51 | if [ -b "$EBS_DEVICE" ]; 52 | then 53 | # Check if we already have a filesystem on $EBS_DEVICE. If not create one. 54 | if [[ $(file -s "$EBS_DEVICE") == "$EBS_DEVICE: data" ]]; 55 | then 56 | ERR=$(mkfs -t xfs "$EBS_DEVICE" 2>&1 > /dev/null) 57 | [ $? -eq 0 ] && echo "EBS volume successfuly formated" >> "$LOG_FILE" || echo "Error formating EBS volume. ERR: $ERR" >> "$LOG_FILE" 58 | else 59 | echo "$EBS_DEVICE already has a file system! Skipping... ">> "$LOG_FILE" 60 | fi 61 | # If block ebs device exists and it has a filesystem, check if it is mounted and mount if it is not already mounted 62 | if grep -qs "$EBS_DEVICE" /proc/mounts; 63 | then 64 | echo "EBS Volume already mounted. Skipping..." >> "$LOG_FILE" 65 | else 66 | # Mount EBS volume 67 | mount "$EBS_DEVICE" "$DATA_FOLDER" 68 | [ $? -eq 0 ] && echo "EBS Volume successfuly mounted!" >> "$LOG_FILE" 69 | ## Edit fstab 70 | echo "$EBS_DEVICE $DATA_FOLDER xfs defaults,nofail 0 2" >> /etc/fstab 71 | [ $? -eq 0 ] && echo "Fstab successfuly edited. EBS Volume added." >> "$LOG_FILE" || echo "Fstab EBS volume edit failed!" >> "$LOG_FILE" 72 | ## Set right permissions for ubuntu user 73 | chown -R "$LINUX_USER". "$DATA_FOLDER" 74 | [ $? -eq 0 ] && echo "User permissions on $DATA_FOLDER set..." >> $LOG_FILE || echo "Could not set $DATA_FOLDER permissions!" 75 | fi 76 | else 77 | echo "No EBS Volume detected! Check if it is attached and/or if it is mounted on $EBS_DEVICE" >> "$LOG_FILE" 78 | fi 79 | 80 | echo "--------------------------------------------------------------" >> "$LOG_FILE" 81 | echo "Getting polygon-edge binary ..." >> "$LOG_FILE" 82 | 83 | # wait for system to fully boot 84 | sleep 60 85 | 86 | # install packages 87 | sudo apt update && sudo apt install -y jq awscli 88 | 89 | # get polygon-edge binary from github releases #77894422 - v0.5.1 90 | mkdir /tmp/polygon-edge 91 | wget -q -O /tmp/polygon-edge/polygon-edge.tar.gz "$(curl -s https://api.github.com/repos/0xPolygon/polygon-edge/releases/77894422 | jq .assets[3].browser_download_url | tr -d '"')" 92 | tar -xvf /tmp/polygon-edge/polygon-edge.tar.gz -C /tmp/polygon-edge 93 | sudo mv /tmp/polygon-edge/polygon-edge /usr/local/bin/ 94 | rm -R /tmp/polygon-edge 95 | 96 | 97 | # create json lambda query 98 | jsonLambda=$(cat <> "$LOG_FILE" 128 | echo "System init complete. Starting node initialization ..." >> "$LOG_FILE" 129 | 130 | # Check if data folder is empty 131 | if [ "$(ls -A "$DATA_FOLDER")" ]; then 132 | echo "Files in data dir found. Skipping Polygon edge initialization..." >> "$LOG_FILE" 133 | else 134 | echo "Starting node initialization" >> "$LOG_FILE" 135 | /usr/local/bin/polygon-edge secrets generate --type aws-ssm --name "$NODE_NAME" --extra region="$ASSM_REGION",ssm-parameter-path="$ASSM_PARAM_PATH" --dir "$DATA_FOLDER/secretsConfig.json" 136 | POLYGON=$(/usr/local/bin/polygon-edge secrets init --config "$DATA_FOLDER"/secretsConfig.json --json 2>&1) 137 | # Check if everything went ok 138 | if [ $? -eq 0 ]; then 139 | aws lambda invoke --function-name "${lambda_function_name}" --region "$ASSM_REGION" --payload "$jsonLambda" "$LOGS_FOLDER/lambda.json" 140 | else 141 | echo "The initialization of polygon edge failed. Check log @ $LOG_FILE" >> "$LOG_FILE" 142 | echo "$POLYGON" >> "$LOG_FILE" 143 | fi 144 | fi 145 | 146 | chown -R "$LINUX_USER". "$POLYGON_FOLDER" 147 | 148 | echo "ALL DONE!" >> "$LOG_FILE" 149 | echo "-------- Finished on: $(date) ----------" >> "$LOG_FILE" 150 | exit 0 151 | -------------------------------------------------------------------------------- /modules/user-data/scripts/polygon_edge_server.tfpl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DATA_DIR="${polygon_edge_dir}/data" 3 | SECRETS_FILE="$DATA_DIR/secretsConfig.json" 4 | GENESIS_FILE="$DATA_DIR/genesis.json" 5 | S3_GENESIS="${s3_bucket_name}/genesis.json" 6 | LOG_FILE="${polygon_edge_dir}/logs/edge-server.log" 7 | 8 | # install awscli so that we can fetch genesis.json 9 | sudo apt update && sudo apt install -y awscli 10 | 11 | # wait untill genesis.json is found in the S3 bucket 12 | while ! aws s3 ls ${s3_bucket_name} | grep genesis.json > /dev/null; 13 | do 14 | echo "Waiting for genesis.json to appear in S3 bucket..." 15 | sleep 10 16 | done 17 | 18 | # we found genesis.json, now we download it to data folder 19 | aws s3 cp s3://$S3_GENESIS $DATA_DIR 20 | 21 | 22 | # Create polygon-edge service and start it after genesis.json file is detected 23 | cat > /etc/systemd/system/polygon_genesis.target << EOF 24 | # check if genesys.json exists 25 | [Unit] 26 | TimeoutStartSec=infinity 27 | ConditionPathExists=$GENESIS_FILE 28 | ExecStart=/usr/bin/sleep 5 29 | RemainAfterExit=yes 30 | EOF 31 | 32 | ## TODO make server options customisable 33 | cat > /etc/systemd/system/polygon-edge.service << EOF 34 | [Unit] 35 | Description=Polygon Edge Server 36 | After=network.target polygon_genesis.target 37 | Wants=polygon_genesis.target 38 | StartLimitIntervalSec=0 39 | 40 | [Service] 41 | Type=simple 42 | Restart=always 43 | RestartSec=10 44 | User=ubuntu 45 | StandardOutput=syslog 46 | StandardError=file:$LOG_FILE 47 | ExecStartPre=/bin/bash -c "sudo rm $LOG_FILE" 48 | ExecStart=/usr/local/bin/polygon-edge server --data-dir $DATA_DIR --chain $GENESIS_FILE --libp2p 0.0.0.0:1478 --grpc-address 0.0.0.0:9632 --secrets-config $SECRETS_FILE --seal SERVER_OPTIONS 49 | 50 | [Install] 51 | WantedBy=multi-user.target 52 | EOF 53 | 54 | #### customise server options 55 | 56 | # enable Prometheus API 57 | if [ "${prometheus_address}" != "" ]; then 58 | prometheus="--prometheus ${prometheus_address}" 59 | fi 60 | 61 | # set block gas limit 62 | if [ "${block_gas_target}" != "" ]; then 63 | block_gas_target="--block-gas-target ${block_gas_target}" 64 | fi 65 | 66 | # set nated address 67 | if [ "${nat_address}" != "" ]; then 68 | nat="--nat ${nat_address}" 69 | fi 70 | 71 | # set dns name 72 | if [ "${dns_name}" != "" ]; then 73 | dns_name="--dns ${dns_name}" 74 | fi 75 | 76 | # set price limit 77 | if [ "${price_limit}" != "" ]; then 78 | price_limit="--price-limit ${price_limit}" 79 | fi 80 | 81 | # set max slots 82 | if [ "${max_slots}" != "" ]; then 83 | max_slots="--max-slots ${max_slots}" 84 | fi 85 | 86 | # set block time in seconds 87 | if [ "${block_time}" != "" ]; then 88 | block_time="--block-time ${block_time}" 89 | fi 90 | 91 | # set these parameters in service file 92 | sed -i "s/SERVER_OPTIONS/$prometheus $block_gas_target $nat $dns $price_limit $max_slots $block_time/g" /etc/systemd/system/polygon-edge.service 93 | 94 | # change ownership of the polygon folder to ubuntu user 95 | sudo chown -R ubuntu. ${polygon_edge_dir} 96 | 97 | # Enable polygon-edge on startup 98 | sudo /usr/bin/systemctl enable polygon-edge.service 99 | 100 | # Start polygon-edge service 101 | sudo /usr/bin/systemctl start polygon-edge.service 102 | -------------------------------------------------------------------------------- /modules/user-data/variables.tf: -------------------------------------------------------------------------------- 1 | variable "polygon_edge_dir" { 2 | type = string 3 | description = "The directory to place all polygon-edge data and logs" 4 | } 5 | variable "ebs_device" { 6 | type = string 7 | description = "The ebs device path. Defined when creating EBS volume." 8 | } 9 | variable "assm_region" { 10 | type = string 11 | description = "The region for AWS SSM service." 12 | } 13 | variable "assm_path" { 14 | type = string 15 | description = "The SSM paramter path." 16 | } 17 | variable "node_name" { 18 | type = string 19 | description = "The name of the node that will be different for every node and stored in AWS SSM" 20 | } 21 | variable "total_nodes" { 22 | type = string 23 | description = "The total number of validator nodes." 24 | } 25 | variable "s3_bucket_name" { 26 | type = string 27 | description = "The name of the S3 bucket that holds genesis.json." 28 | } 29 | 30 | ## genesis options 31 | variable "chain_name" { 32 | type = string 33 | description = "Set the name of chain" 34 | } 35 | variable "chain_id" { 36 | type = string 37 | description = "Set the Chain ID" 38 | } 39 | variable "block_gas_limit" { 40 | type = string 41 | description = "Set the block gas limit" 42 | } 43 | variable "premine" { 44 | type = string 45 | description = "Premine the accounts with the specified ammount." 46 | } 47 | variable "epoch_size" { 48 | type = string 49 | description = "Set the epoch size" 50 | } 51 | variable "pos" { 52 | type = bool 53 | description = "Deploy with PoS consensus" 54 | } 55 | variable "max_validator_count" { 56 | type = string 57 | description = "Maximum number of stakers able to join the validator set in a PoS consensus." 58 | } 59 | variable "min_validator_count" { 60 | type = string 61 | description = "Minimum number of stakers needed to join the validator set in a PoS consensus." 62 | } 63 | variable "consensus" { 64 | type = string 65 | description = "Sets the consensus protocol" 66 | } 67 | 68 | # server options 69 | variable "prometheus_address" { 70 | type = string 71 | description = "Enable Prometheus API" 72 | } 73 | variable "block_gas_target" { 74 | type = string 75 | description = "Sets the target block gas limit for the chain" 76 | } 77 | variable "nat_address" { 78 | type = string 79 | description = "Sets the NAT address for the networking package" 80 | } 81 | variable "dns_name" { 82 | type = string 83 | description = "Sets the DNS name for the network package" 84 | } 85 | variable "price_limit" { 86 | type = string 87 | description = "Sets minimum gas price limit to enforce for acceptance into the pool" 88 | } 89 | variable "max_slots" { 90 | type = string 91 | description = "Sets maximum slots in the pool" 92 | } 93 | variable "block_time" { 94 | type = string 95 | description = "Set block production time in seconds" 96 | } 97 | 98 | variable "lambda_function_name" { 99 | type = string 100 | description = "The name of the chain init Lambda function" 101 | } 102 | 103 | variable "s3_key_name" { 104 | type = string 105 | description = "Name for the config file stored in S3" 106 | } -------------------------------------------------------------------------------- /modules/user-data/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.22.0" 8 | } 9 | cloudinit = { 10 | source = "hashicorp/cloudinit" 11 | version = "2.2.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "jsonrpc_dns_name" { 2 | value = module.alb.dns_name 3 | description = "The dns name for the JSON-RPC API" 4 | } -------------------------------------------------------------------------------- /providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 4.22.0" 7 | } 8 | awscc = { 9 | source = "hashicorp/awscc" 10 | version = ">= 0.27.0" 11 | } 12 | local = { 13 | source = "hashicorp/local" 14 | version = ">= 2.2.3" 15 | } 16 | external = { 17 | source = "hashicorp/external" 18 | version = ">= 2.2.2" 19 | } 20 | null = { 21 | source = "hashicorp/null" 22 | version = ">=3.1.1" 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /test/examples_basic_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gruntwork-io/terratest/modules/terraform" 7 | ) 8 | 9 | func TestExamplesBasic(t *testing.T) { 10 | 11 | terraformOptions := &terraform.Options{ 12 | TerraformDir: "../examples/basic", 13 | // Vars: map[string]interface{}{ 14 | // "myvar": "test", 15 | // "mylistvar": []string{"list_item_1"}, 16 | // }, 17 | } 18 | 19 | defer terraform.Destroy(t, terraformOptions) 20 | terraform.InitAndApply(t, terraformOptions) 21 | } 22 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # VPC 2 | variable "vpc_name" { 3 | description = "Name of the VPC" 4 | type = string 5 | default = "polygon-edge-vpc" 6 | } 7 | variable "vpc_cidr_block" { 8 | description = "CIDR block for VPC" 9 | type = string 10 | default = "10.250.0.0/16" 11 | } 12 | 13 | # S3 14 | variable "s3_bucket_prefix" { 15 | description = "Name prefix for new S3 bucket" 16 | type = string 17 | default = "polygon-edge-shared-" 18 | } 19 | variable "s3_force_destroy" { 20 | type = bool 21 | default = true 22 | description = "Delete S3 bucket on destroy, even if the bucket is not empty" 23 | } 24 | variable "s3_key_name" { 25 | type = string 26 | description = "Name of the file in S3 that will hold configuration" 27 | default = "chain-config" 28 | } 29 | 30 | # SECURITY 31 | variable "ssm_parameter_id" { 32 | description = "The id that will be used for storing and fetching from SSM Parameter Store" 33 | type = string 34 | default = "polygon-edge-validators" 35 | } 36 | variable "internal_sec_gr_name_tag" { 37 | type = string 38 | description = "Internal security group name tag" 39 | default = "Polygon Edge Internal" 40 | } 41 | variable "alb_sec_gr_name_tag" { 42 | type = string 43 | description = "External security group name tag" 44 | default = "Polygon Edge External" 45 | } 46 | 47 | # EC2 48 | variable "instance_type" { 49 | default = "t3.medium" 50 | type = string 51 | description = "Polygon Edge nodes instance type." 52 | } 53 | variable "ebs_root_name_tag" { 54 | default = "Polygon_Edge_Root_Volume" 55 | type = string 56 | description = "The name tag for the Polygon Edge instance root volume." 57 | } 58 | variable "instance_name" { 59 | default = "Polygon_Edge_Node" 60 | type = string 61 | description = "The name of Polygon Edge instance" 62 | } 63 | variable "instance_interface_name_tag" { 64 | default = "Polygon_Edge_Instance_Interface" 65 | type = string 66 | description = "The name of the instance interface." 67 | } 68 | variable "chain_data_ebs_volume_size" { 69 | default = 30 70 | type = number 71 | description = "The size of the chain data EBS volume." 72 | } 73 | variable "chain_data_ebs_name_tag" { 74 | default = "Polygon_Edge_chain_data_volume" 75 | type = string 76 | description = "The name of the chain data EBS volume." 77 | } 78 | 79 | #CHAIN 80 | variable "polygon_edge_dir" { 81 | default = "/home/ubuntu/polygon" 82 | type = string 83 | description = "The directory to place all polygon-edge data and logs" 84 | } 85 | 86 | variable "ebs_device" { 87 | default = "/dev/nvme1n1" 88 | type = string 89 | description = "The ebs device path. Defined when creating EBS volume." 90 | } 91 | 92 | variable "node_name_prefix" { 93 | type = string 94 | description = "The name prefix that will be used to store secrets" 95 | default = "node" 96 | } 97 | 98 | ## GENESIS 99 | variable "chain_name" { 100 | type = string 101 | description = "Set the name of chain" 102 | default = "" 103 | } 104 | 105 | variable "chain_id" { 106 | type = string 107 | description = "Set the Chain ID" 108 | default = "" 109 | } 110 | 111 | variable "block_gas_limit" { 112 | type = string 113 | description = "Set the block gas limit" 114 | default = "" 115 | } 116 | 117 | variable "premine" { 118 | type = string 119 | description = "Premine the accounts with the specified ammount. Format: account:ammount,account:ammount" 120 | } 121 | 122 | variable "epoch_size" { 123 | type = string 124 | description = "Set the epoch size" 125 | default = "" 126 | } 127 | variable "consensus" { 128 | type = string 129 | description = "Sets consensus protocol." 130 | default = "" 131 | } 132 | variable "max_validator_count" { 133 | type = string 134 | description = "The maximum number of stakers able to join the validator set in a PoS consensus." 135 | default = "" 136 | } 137 | variable "min_validator_count" { 138 | type = string 139 | description = "The minimum number of stakers needed to join the validator set in a PoS consensus." 140 | default = "" 141 | } 142 | variable "pos" { 143 | type = bool 144 | description = "Use PoS IBFT consensus" 145 | default = false 146 | } 147 | # server options 148 | variable "prometheus_address" { 149 | type = string 150 | description = "Enable Prometheus API" 151 | default = "" 152 | } 153 | variable "block_gas_target" { 154 | type = string 155 | description = "Sets the target block gas limit for the chain" 156 | default = "" 157 | } 158 | variable "nat_address" { 159 | type = string 160 | description = "Sets the NAT address for the networking package" 161 | default = "" 162 | } 163 | variable "dns_name" { 164 | type = string 165 | description = "Sets the DNS name for the network package" 166 | default = "" 167 | } 168 | variable "price_limit" { 169 | type = string 170 | description = "Sets minimum gas price limit to enforce for acceptance into the pool" 171 | default = "" 172 | } 173 | variable "max_slots" { 174 | type = string 175 | description = "Sets maximum slots in the pool" 176 | default = "" 177 | } 178 | variable "block_time" { 179 | type = string 180 | description = "Set block production time in seconds" 181 | default = "" 182 | } 183 | 184 | #ALB 185 | variable "alb_ssl_certificate" { 186 | type = string 187 | description = "SSL certificate ARN for JSON-RPC loadblancer" 188 | } 189 | variable "nodes_alb_name_prefix" { 190 | type = string 191 | description = "ALB name" 192 | default = "jrpc-" 193 | } 194 | variable "nodes_alb_name_tag" { 195 | type = string 196 | description = "ALB name tag" 197 | default = "Polygon Edge JSON-RPC ALB" 198 | } 199 | variable "nodes_alb_targetgroup_name_prefix" { 200 | type = string 201 | description = "ALB target group name" 202 | default = "jrpc-" 203 | } 204 | # LAMBDA 205 | variable "lambda_function_name" { 206 | type = string 207 | description = "The name of the Lambda function used for chain init" 208 | default = "polygon-edge-init" 209 | } 210 | variable "lambda_function_zip" { 211 | type = string 212 | description = "The lambda function code in zip archive" 213 | default = "https://raw.githubusercontent.com/Trapesys/polygon-edge-assm/aws-lambda/artifacts/main.zip" 214 | } 215 | --------------------------------------------------------------------------------