diff --git a/.env.example b/.env.example index 6ab7800..56cce06 100644 --- a/.env.example +++ b/.env.example @@ -4,9 +4,10 @@ BESU_NETWORK=mainnet ERIGON_NETWORK=mainnet LIGHTHOUSE_NETWORK=mainnet OPERATOR_NETWORK=mainnet -IMAGE_TAG=v3.4.0-mainnet +IMAGE_TAG=v3.5.0-mainnet REGISTRY_CONTRACT_ADDRESS=1a1f82f0365571A0b06df0992FC4D1BCc5Fdc6aD NETWORK_CONTRACT_ADDRESS=829f3c089fE315FCB2BC9506B237BB56b7c3335B +CONFIG_CONTRACT_ADDRESS=07FA0F7f3C67e4cdE0FC23A072dcD712CF9a06C1 API_SERVER=https://api-node.safestake.xyz/api/op/ # different chain has different ttd TTD=10790000 diff --git a/.github/workflows/ci_dev.yml b/.github/workflows/ci_dev.yml index bf6998f..f025474 100644 --- a/.github/workflows/ci_dev.yml +++ b/.github/workflows/ci_dev.yml @@ -1,33 +1,56 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# GitHub recommends pinning actions to a commit SHA. -# To get a newer version, you will need to update the SHA. -# You can also reference a tag or branch, but the action may change without warning. - -name: UnitTest & Publish Image - +name: do-the-job on: push: branches: - dev - jobs: - push_to_registry: - name: Push Docker image to Docker Hub - runs-on: self-hosted + start-runner: + name: Start self-hosted EC2 runner + runs-on: ubuntu-latest + outputs: + label: ${{ steps.start-ec2-runner.outputs.label }} + ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }} steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Start EC2 runner + id: start-ec2-runner + uses: machulav/ec2-github-runner@v2.3.7 + with: + mode: start + github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + ec2-image-id: ami-0a2d071e715c3a808 + ec2-instance-type: c6a.4xlarge + subnet-id: subnet-9aa9c8e1 + security-group-id: sg-0c35d2e12fe165fbb + aws-resource-tags: > # optional, requires additional permissions + [ + {"Key": "Name", "Value": "ec2-github-runner"}, + {"Key": "GitHubRepository", "Value": "${{ github.repository }}"} + ] + do-the-job: + name: UnitTest & Build & Publish Image + needs: start-runner # required to start the main job when the runner is ready + runs-on: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner + steps: + - name: Hello World1 + run: | + echo 'Hello World!' + /root/.cargo/bin/cargo version + - name: Check out the repo uses: actions/checkout@v3 - - - name: Log in to Docker Hub + + - name: Login to Docker Hub uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 @@ -37,9 +60,10 @@ jobs: - name: Checkout submodules run: git submodule update --init --recursive - - name: Unit Test - run: sudo runuser -f ubuntu -c '/home/ubuntu/.cargo/bin/cargo test test_dkg_secure_net -- --show-output' - + - name: Unit Testing + run: | + /root/.cargo/bin/cargo test test_dkg_secure_net -- --show-output + - name: Build and push Docker image uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: @@ -49,3 +73,29 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | CPU_NUM=16 + + - name: Bye + run: echo 'Congratulations!' + + stop-runner: + name: Stop self-hosted EC2 runner + needs: + - start-runner # required to get output from the start-runner job + - do-the-job # required to wait when the main job is done + runs-on: ubuntu-latest + if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Stop EC2 runner + uses: machulav/ec2-github-runner@v2 + with: + mode: stop + github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + label: ${{ needs.start-runner.outputs.label }} + ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }} + diff --git a/.github/workflows/ci_main.yml b/.github/workflows/ci_main.yml index badbbf8..d4a6fd7 100644 --- a/.github/workflows/ci_main.yml +++ b/.github/workflows/ci_main.yml @@ -1,33 +1,56 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# GitHub recommends pinning actions to a commit SHA. -# To get a newer version, you will need to update the SHA. -# You can also reference a tag or branch, but the action may change without warning. - -name: Publish Docker image - +name: do-the-job on: push: tags: - '**-mainnet' - jobs: - push_to_registry: - name: Push Docker image to Docker Hub - runs-on: self-hosted + start-runner: + name: Start self-hosted EC2 runner + runs-on: ubuntu-latest + outputs: + label: ${{ steps.start-ec2-runner.outputs.label }} + ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }} + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Start EC2 runner + id: start-ec2-runner + uses: machulav/ec2-github-runner@v2.3.7 + with: + mode: start + github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + ec2-image-id: ami-0a2d071e715c3a808 + ec2-instance-type: c6a.4xlarge + subnet-id: subnet-9aa9c8e1 + security-group-id: sg-0c35d2e12fe165fbb + aws-resource-tags: > # optional, requires additional permissions + [ + {"Key": "Name", "Value": "ec2-github-runner"}, + {"Key": "GitHubRepository", "Value": "${{ github.repository }}"} + ] + do-the-job: + name: UnitTest & Build & Publish Image + needs: start-runner # required to start the main job when the runner is ready + runs-on: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner steps: + - name: Hello World1 + run: | + echo 'Hello World!' + /root/.cargo/bin/cargo version + - name: Check out the repo uses: actions/checkout@v3 - - - name: Log in to Docker Hub + + - name: Login to Docker Hub uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 @@ -36,10 +59,9 @@ jobs: flavor: | latest=false - - name: Checkout submodules run: git submodule update --init --recursive - + - name: Build and push Docker image uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: @@ -48,4 +70,30 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | - CPU_NUM=16 \ No newline at end of file + CPU_NUM=16 + + - name: Bye + run: echo 'Congratulations!' + + stop-runner: + name: Stop self-hosted EC2 runner + needs: + - start-runner # required to get output from the start-runner job + - do-the-job # required to wait when the main job is done + runs-on: ubuntu-latest + if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Stop EC2 runner + uses: machulav/ec2-github-runner@v2 + with: + mode: stop + github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + label: ${{ needs.start-runner.outputs.label }} + ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }} + diff --git a/Cargo.toml b/Cargo.toml index 6233594..f9153ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,7 +139,12 @@ sysinfo = "0.26" default = ["hotstuff_committee"] fake_committee = [] hotstuff_committee = [] - +spec-minimal = [] +gnosis = [] +jemalloc = [] +# Compiles the BLS crypto code so that the binary is portable across machines. +portable = ["bls/supranational-portable"] +modern = ["bls/supranational-force-adx"] [dev-dependencies] tokio-test = "*" diff --git a/common/dvf_version/src/lib.rs b/common/dvf_version/src/lib.rs index b03dab1..7b9df99 100644 --- a/common/dvf_version/src/lib.rs +++ b/common/dvf_version/src/lib.rs @@ -8,4 +8,8 @@ pub const MAJOR_VERSION: u64 = 3; /// Up to 1 million pub const MINOR_VERSION: u64 = 4; -pub static VERSION: u64 = ROOT_VERSION * 1000_000_000_000 + MAJOR_VERSION * 1000_000 + MINOR_VERSION; \ No newline at end of file +pub static VERSION: u64 = ROOT_VERSION * 1_000_000_000_000 + MAJOR_VERSION * 1_000_000 + MINOR_VERSION; + + +pub const SOFTWARE_MINOR_VERSION: u64 = 5; +pub static SOFTWARE_VERSION: u64 = ROOT_VERSION * 1_000_000_000_000 + MAJOR_VERSION * 1_000_000 + SOFTWARE_MINOR_VERSION; \ No newline at end of file diff --git a/contract_config/SafeStakeOperatorConfig.json b/contract_config/SafeStakeOperatorConfig.json new file mode 100644 index 0000000..217e02c --- /dev/null +++ b/contract_config/SafeStakeOperatorConfig.json @@ -0,0 +1,263 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "SafeStakeOperatorConfig", + "sourceName": "contracts/SafeStakeOperatorConfig.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "name": "FeeRecipientAddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "inputs": [], + "name": "ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NODE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SIGNER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_storage", + "outputs": [ + { + "internalType": "contract ISafeStakeStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "checkRole", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "generate_storage_key", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "getFeeRecipientAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "storageAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "setFeeRecipientAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_storageAddress", + "type": "address" + } + ], + "name": "setStorageAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610a72806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063c3fb90d61161008c578063d547741f11610066578063d547741f1461028e578063dbcdc2cc146102a1578063ef5d6bbb146102b4578063fb895733146102c757600080fd5b8063c3fb90d614610255578063c4d66de814610268578063c5b951901461027b57600080fd5b80636088089e116100c85780636088089e1461014757806375b238fc146101be57806391d14854146101e5578063a1ebf35d1461022e57600080fd5b80632f2ff15d146100ef57806354892f021461010457806359b910d614610134575b600080fd5b6101026100fd3660046109b9565b6102ee565b005b6101176101123660046109e9565b61037e565b6040516001600160a01b0390911681526020015b60405180910390f35b6101026101423660046109e9565b610455565b6101b06101553660046109e9565b6040516bffffffffffffffffffffffff19606083901b16602082015272466565526563697069656e744164647265737360681b6034820152600090604701604051602081830303815290604052805190602001209050919050565b60405190815260200161012b565b6101b07f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b76081565b61021e6101f33660046109b9565b60009182526001602090815260408084206001600160a01b0393909316845291905290205460ff1690565b604051901515815260200161012b565b6101b07f017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f7281565b600254610117906001600160a01b031681565b6101026102763660046109e9565b6104af565b610102610289366004610a06565b61054e565b61010261029c3660046109b9565b61058c565b6101026102af3660046109e9565b610614565b6101026102c23660046109b9565b610711565b6101b07f1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a6911181565b6103187f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760336101f3565b61034e5760405162461bcd60e51b8152602060048201526002602482015261583160f01b60448201526064015b60405180910390fd5b60009182526001602081815260408085206001600160a01b0390941685529290529120805460ff19169091179055565b6000806103e0836040516bffffffffffffffffffffffff19606083901b16602082015272466565526563697069656e744164647265737360681b6034820152600090604701604051602081830303815290604052805190602001209050919050565b6002546040516321f8a72160e01b8152600481018390529192506001600160a01b0316906321f8a72190602401602060405180830381865afa15801561042a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044e9190610a1f565b9392505050565b7f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b76061047f8161054e565b506002805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b60006104bb6001610771565b905080156104d3576000805461ff0019166101001790555b6002805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038416179055610503610887565b801561054a576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15b5050565b61055881336101f3565b6105895760405162461bcd60e51b8152602060048201526002602482015261058360f41b6044820152606401610345565b50565b6105b67f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760336101f3565b6105e75760405162461bcd60e51b81526020600482015260026024820152612c1960f11b6044820152606401610345565b60009182526001602090815260408084206001600160a01b0390931684529190529020805460ff19169055565b604080513360601b6bffffffffffffffffffffffff191660208083019190915272466565526563697069656e744164647265737360681b6034830152825160278184030181526047909201909252805191012060009060025460405163ca446dd960e01b8152600481018390526001600160a01b03858116602483015292935091169063ca446dd990604401600060405180830381600087803b1580156106ba57600080fd5b505af11580156106ce573d6000803e3d6000fd5b5050604080513381526001600160a01b03861660208201527f05d899f2c039ff7edfb413f1f4fe82889c2efaace7f08d7ac5aebad905b27ced9350019050610541565b8161071b8161054e565b6001600160a01b03821661072e57600080fd5b506000918252600160208181526040808520338652909152808420805460ff199081169091556001600160a01b0393909316845290922080549091169091179055565b60008054610100900460ff16156107ff578160ff1660011480156107945750303b155b6107f75760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610345565b506000919050565b60005460ff80841691161061086d5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610345565b506000805460ff191660ff92909216919091179055600190565b600054610100900460ff166109045760405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610345565b3360009081527f331bba8513ee4dd5811afc38fa48458378fe9b930e1c5bd9a2e37d456ee8c897602090815260408083208054600160ff1991821681179092557f911c709b9be4f1ae2b9f62542e6578f826afdf6fa62cd1aac81cae683df0add2845282852080548216831790557f427c45ac74610ad1057fc3524fc3fecaa3416dd8aa9baf907541659f889ee436909352922080549091169091179055565b6001600160a01b038116811461058957600080fd5b600080604083850312156109cc57600080fd5b8235915060208301356109de816109a4565b809150509250929050565b6000602082840312156109fb57600080fd5b813561044e816109a4565b600060208284031215610a1857600080fd5b5035919050565b600060208284031215610a3157600080fd5b815161044e816109a456fea2646970667358221220df8a267b97a47fb672406d4352b538f48dbdc28002ce5d838ddfbd494e7469c464736f6c63430008130033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063c3fb90d61161008c578063d547741f11610066578063d547741f1461028e578063dbcdc2cc146102a1578063ef5d6bbb146102b4578063fb895733146102c757600080fd5b8063c3fb90d614610255578063c4d66de814610268578063c5b951901461027b57600080fd5b80636088089e116100c85780636088089e1461014757806375b238fc146101be57806391d14854146101e5578063a1ebf35d1461022e57600080fd5b80632f2ff15d146100ef57806354892f021461010457806359b910d614610134575b600080fd5b6101026100fd3660046109b9565b6102ee565b005b6101176101123660046109e9565b61037e565b6040516001600160a01b0390911681526020015b60405180910390f35b6101026101423660046109e9565b610455565b6101b06101553660046109e9565b6040516bffffffffffffffffffffffff19606083901b16602082015272466565526563697069656e744164647265737360681b6034820152600090604701604051602081830303815290604052805190602001209050919050565b60405190815260200161012b565b6101b07f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b76081565b61021e6101f33660046109b9565b60009182526001602090815260408084206001600160a01b0393909316845291905290205460ff1690565b604051901515815260200161012b565b6101b07f017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f7281565b600254610117906001600160a01b031681565b6101026102763660046109e9565b6104af565b610102610289366004610a06565b61054e565b61010261029c3660046109b9565b61058c565b6101026102af3660046109e9565b610614565b6101026102c23660046109b9565b610711565b6101b07f1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a6911181565b6103187f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760336101f3565b61034e5760405162461bcd60e51b8152602060048201526002602482015261583160f01b60448201526064015b60405180910390fd5b60009182526001602081815260408085206001600160a01b0390941685529290529120805460ff19169091179055565b6000806103e0836040516bffffffffffffffffffffffff19606083901b16602082015272466565526563697069656e744164647265737360681b6034820152600090604701604051602081830303815290604052805190602001209050919050565b6002546040516321f8a72160e01b8152600481018390529192506001600160a01b0316906321f8a72190602401602060405180830381865afa15801561042a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044e9190610a1f565b9392505050565b7f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b76061047f8161054e565b506002805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b60006104bb6001610771565b905080156104d3576000805461ff0019166101001790555b6002805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038416179055610503610887565b801561054a576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15b5050565b61055881336101f3565b6105895760405162461bcd60e51b8152602060048201526002602482015261058360f41b6044820152606401610345565b50565b6105b67f03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760336101f3565b6105e75760405162461bcd60e51b81526020600482015260026024820152612c1960f11b6044820152606401610345565b60009182526001602090815260408084206001600160a01b0390931684529190529020805460ff19169055565b604080513360601b6bffffffffffffffffffffffff191660208083019190915272466565526563697069656e744164647265737360681b6034830152825160278184030181526047909201909252805191012060009060025460405163ca446dd960e01b8152600481018390526001600160a01b03858116602483015292935091169063ca446dd990604401600060405180830381600087803b1580156106ba57600080fd5b505af11580156106ce573d6000803e3d6000fd5b5050604080513381526001600160a01b03861660208201527f05d899f2c039ff7edfb413f1f4fe82889c2efaace7f08d7ac5aebad905b27ced9350019050610541565b8161071b8161054e565b6001600160a01b03821661072e57600080fd5b506000918252600160208181526040808520338652909152808420805460ff199081169091556001600160a01b0393909316845290922080549091169091179055565b60008054610100900460ff16156107ff578160ff1660011480156107945750303b155b6107f75760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610345565b506000919050565b60005460ff80841691161061086d5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610345565b506000805460ff191660ff92909216919091179055600190565b600054610100900460ff166109045760405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610345565b3360009081527f331bba8513ee4dd5811afc38fa48458378fe9b930e1c5bd9a2e37d456ee8c897602090815260408083208054600160ff1991821681179092557f911c709b9be4f1ae2b9f62542e6578f826afdf6fa62cd1aac81cae683df0add2845282852080548216831790557f427c45ac74610ad1057fc3524fc3fecaa3416dd8aa9baf907541659f889ee436909352922080549091169091179055565b6001600160a01b038116811461058957600080fd5b600080604083850312156109cc57600080fd5b8235915060208301356109de816109a4565b809150509250929050565b6000602082840312156109fb57600080fd5b813561044e816109a4565b600060208284031215610a1857600080fd5b5035919050565b600060208284031215610a3157600080fd5b815161044e816109a456fea2646970667358221220df8a267b97a47fb672406d4352b538f48dbdc28002ce5d838ddfbd494e7469c464736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/contract_config/configs.yml b/contract_config/configs.yml index bf62d92..6f43f53 100644 --- a/contract_config/configs.yml +++ b/contract_config/configs.yml @@ -5,6 +5,7 @@ initiator_registration_topic: 3ed0a993c042af686c0f93773269df3a1874729d2b4fc3f71f initiator_minipool_created_topic: d37d31a5a66d534ce3b071e3ee6cf8b7d36a6dd20aa4f08e9cec322b27bd7704 initiator_minipool_ready_topic: c474edb44e2d7e6c7f20261d61afed24712bcbd6a0799ae5b0786626c47da63c initiator_removal_topic: 34ecedfa430a18df6cda4bc2313d74d224c6742df8e6a98eca2fda96b69a050d -fee_recipient_set_topic: a2ae9b7eef58d6d2779721289e4e6fecb49b0134e40d787b3bed6743ed325497 +fee_recipient_set_topic: 05d899f2c039ff7edfb413f1f4fe82889c2efaace7f08d7ac5aebad905b27ced safestake_network_abi_path: contract_config/SafeStakeNetwork.json safestake_registry_abi_path: contract_config/SafeStakeRegistry.json +safestake_config_abi_path: contract_config/SafeStakeOperatorConfig.json \ No newline at end of file diff --git a/docker-compose-operator-mev.yml b/docker-compose-operator-mev.yml index 8373377..04587ed 100644 --- a/docker-compose-operator-mev.yml +++ b/docker-compose-operator-mev.yml @@ -89,7 +89,7 @@ services: - /bin/sh - -c - | - dvf validator_client --builder-proposals --metrics --debug-level=info --network=${OPERATOR_NETWORK} --beacon-nodes=${BEACON_NODE_ENDPOINT} --api=${API_SERVER} --ws-url=${WS_URL} --ip=${NODE_IP} --id=${OPERATOR_ID} --registry-contract=${REGISTRY_CONTRACT_ADDRESS} --network-contract=${NETWORK_CONTRACT_ADDRESS} --base-port=26000 2>&1 + dvf validator_client --builder-proposals --metrics --debug-level=info --network=${OPERATOR_NETWORK} --beacon-nodes=${BEACON_NODE_ENDPOINT} --api=${API_SERVER} --ws-url=${WS_URL} --ip=${NODE_IP} --id=${OPERATOR_ID} --registry-contract=${REGISTRY_CONTRACT_ADDRESS} --network-contract=${NETWORK_CONTRACT_ADDRESS} --config-contract=${CONFIG_CONTRACT_ADDRESS} --base-port=26000 2>&1 expose: - "26000" - "26001" diff --git a/docker-compose-operator.yml b/docker-compose-operator.yml index 8bae598..26efde8 100644 --- a/docker-compose-operator.yml +++ b/docker-compose-operator.yml @@ -80,7 +80,7 @@ services: - /bin/sh - -c - | - dvf validator_client --metrics --debug-level=info --network=${OPERATOR_NETWORK} --beacon-nodes=${BEACON_NODE_ENDPOINT} --api=${API_SERVER} --ws-url=${WS_URL} --ip=${NODE_IP} --id=${OPERATOR_ID} --registry-contract=${REGISTRY_CONTRACT_ADDRESS} --network-contract=${NETWORK_CONTRACT_ADDRESS} --base-port=26000 2>&1 + dvf validator_client --metrics --debug-level=info --network=${OPERATOR_NETWORK} --beacon-nodes=${BEACON_NODE_ENDPOINT} --api=${API_SERVER} --ws-url=${WS_URL} --ip=${NODE_IP} --id=${OPERATOR_ID} --registry-contract=${REGISTRY_CONTRACT_ADDRESS} --network-contract=${NETWORK_CONTRACT_ADDRESS} --config-contract=${CONFIG_CONTRACT_ADDRESS} --base-port=26000 2>&1 expose: - "26000" - "26001" diff --git a/docs/safestake-running-an-operator-node.md b/docs/safestake-running-an-operator-node.md index 932aaa8..4e3ab91 100644 --- a/docs/safestake-running-an-operator-node.md +++ b/docs/safestake-running-an-operator-node.md @@ -259,6 +259,10 @@ Some description of the folders and files under `/data/operator/v1/mainnet/`: ├── validators # data files of the validators that the operator is serving, inherited from the native folder of lighthouse validator client, including slashing_protection.sqlite, etc. ``` +## Monitoring + +You can use prometheus to fetch metrics from port `5064` of operator and monitor if the metric `vc_signed_attestations_total{status="success"}` is increasing to know if your operator is active. + ## Common issues troubleshooting ```mermaid graph TD; diff --git a/hotstuff/network/src/dvf_message.rs b/hotstuff/network/src/dvf_message.rs index b6db944..9ed5610 100644 --- a/hotstuff/network/src/dvf_message.rs +++ b/hotstuff/network/src/dvf_message.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; -pub use dvf_version::{VERSION}; +pub use dvf_version::VERSION; #[derive(Debug, Serialize, Deserialize)] pub struct DvfMessage { diff --git a/hotstuff/network/src/receiver.rs b/hotstuff/network/src/receiver.rs index 78f3a41..c9aeb80 100644 --- a/hotstuff/network/src/receiver.rs +++ b/hotstuff/network/src/receiver.rs @@ -10,8 +10,8 @@ use std::net::SocketAddr; use tokio::net::{TcpListener, TcpStream}; use tokio_util::codec::{Framed, LengthDelimitedCodec}; use std::collections::HashMap; -use std::sync::{Arc}; -use tokio::sync::{RwLock}; +use std::sync::Arc; +use tokio::sync::RwLock; use crate::dvf_message::{DvfMessage, VERSION}; use futures::SinkExt; use tokio::time::{sleep, Duration}; @@ -89,7 +89,7 @@ impl Receiver { let version = dvf_message.version; if version != VERSION { let _ = writer.send(Bytes::from("Version mismatch")).await; - error!("[VA {}] Version mismatch: got ({}), expected ({})", validator_id, version, VERSION); + error!("[VA {}] Version mismatch: got ({}), expected version ({})", validator_id, version, VERSION); sleep(Duration::from_secs(INVALID_MESSAGE_DELAY)).await; // [zico] Should we kill the connection here? // If we kill it, then a reliable sender can resend the message because the ACK is not normal, but it may cause the diff --git a/src/bin/dvf_monitor_tool.rs b/src/bin/dvf_monitor_tool.rs new file mode 100644 index 0000000..12caa81 --- /dev/null +++ b/src/bin/dvf_monitor_tool.rs @@ -0,0 +1,98 @@ +use network::{DvfMessage, ReliableSender}; +use dvf_version::VERSION; +use std::fs::File; +use dvf::node::config::{BOOT_ENRS_CONFIG_FILE, base_to_signature_addr, base_to_duties_addr, base_to_active_addr}; +use lighthouse_network::discv5::enr::{Enr, CombinedKey}; +use std::net::{IpAddr, SocketAddr}; +use bytes::Bytes; +use log::{error, info}; +use std::net::TcpStream; + +fn all_equal(array: &[T]) -> bool { + if let Some(first) = array.first() { + array.iter().all(|x| x == first) + } else { + true + } +} + +async fn query_socket_address_from_boot(enrs: Vec>, op_pk: Vec) -> Vec { + // query socket address of the operator + let dvf_message = DvfMessage { + version: VERSION, + validator_id: 0, + message: op_pk.clone(), + }; + let serialized_msg = bincode::serialize(&dvf_message).unwrap(); + let boot_socketaddrs: Vec = enrs.iter().map(|e| { + SocketAddr::new( + IpAddr::V4(e.ip4().expect("boot enr ip should not be empty")), + e.udp4().expect("boot enr port should not be empty")) + }).collect(); + let network_sender = ReliableSender::new(); + let mut result = Vec::new(); + for addr in boot_socketaddrs { + match network_sender + .send(addr, Bytes::from(serialized_msg.clone())) + .await.await { + Ok(data) => { + let op_base_socketaddr = bincode::deserialize::(&data).unwrap(); + result.push(op_base_socketaddr); + }, + Err(e) => { + error!("failed to query op socket addr from boot {}", e) + } + } + } + result +} + +fn check_all_ports(base_addr: SocketAddr) -> bool { + TcpStream::connect(base_to_signature_addr(base_addr)).is_ok() && + TcpStream::connect(base_to_duties_addr(base_addr)).is_ok() && + TcpStream::connect(base_to_active_addr(base_addr)).is_ok() +} + +#[tokio::main] +async fn main() { + let mut logger = + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")); + logger.format_timestamp_millis(); + logger.init(); + log::info!("------dvf_monitor_tool------"); + + let op_pk_str = std::env::args() + .nth(1) + .expect("ERROR: there is no valid operator public key argument"); + + let op_pk = hex::decode(&op_pk_str[2..]).expect("decode failed, op pk should be in hex format"); + + let file = File::options() + .read(true) + .write(false) + .create(false) + .open(BOOT_ENRS_CONFIG_FILE) + .expect( + format!( + "Unable to open the boot enrs config file: {:?}", + BOOT_ENRS_CONFIG_FILE + ) + .as_str(), + ); + let boot_enrs: Vec> = + serde_yaml::from_reader(file).expect("Unable to parse boot enr"); + + let op_addrs = query_socket_address_from_boot(boot_enrs, op_pk).await; + info!("node addre {:?}", op_addrs); + if op_addrs.is_empty() { + panic!("failed to query op socket address from boot node"); + } + if !all_equal(&op_addrs) { + panic!("the op addresses from different boot nodes are not same {:?}", op_addrs) + } + let base_addr = op_addrs.first().unwrap(); + if !check_all_ports(base_addr.clone()) { + panic!("ports checking failed") + } + info!("op {} is ready", op_pk_str); +} \ No newline at end of file diff --git a/src/node/config.rs b/src/node/config.rs index 489ef04..e08a0be 100644 --- a/src/node/config.rs +++ b/src/node/config.rs @@ -30,7 +30,7 @@ pub const VALIDATOR_PK_URL: &str = "validator_pk"; pub const PRESTAKE_SIGNATURE_URL: &str = "prestake_signature"; pub const STAKE_SIGNATURE_URL: &str = "stake_signature"; pub const TOPIC_NODE_INFO: &str = "dvf/topic_node_info"; -const BOOT_ENRS_CONFIG_FILE: &str = "boot_config/boot_enrs.yaml"; +pub const BOOT_ENRS_CONFIG_FILE: &str = "boot_config/boot_enrs.yaml"; lazy_static! { // [Issue] SocketAddr::new is not yet a const fn in stable release. diff --git a/src/node/contract.rs b/src/node/contract.rs index d6d1243..ee9f018 100644 --- a/src/node/contract.rs +++ b/src/node/contract.rs @@ -33,12 +33,12 @@ const CONTRACT_INI_REG_EVENT_NAME: &str = "InitiatorRegistration"; const CONTRACT_MINIPOOL_CREATED_EVENT_NAME: &str = "InitiatorMiniPoolCreated"; const CONTRACT_MINIPOOL_READY_EVENT_NAME: &str = "InitiatorMiniPoolReady"; const CONTRACT_INI_RM_EVENT_NAME: &str = "InitiatorRemoval"; -const CONTRACT_FEE_RECIPIENT_SET_EVENT_NAME: &str = "FeeReceiptAddressSet"; +const CONTRACT_FEE_RECIPIENT_SET_EVENT_NAME: &str = "FeeRecipientAddressChanged"; pub static SELF_OPERATOR_ID: OnceCell = OnceCell::const_new(); pub static DEFAULT_TRANSPORT_URL: OnceCell = OnceCell::const_new(); pub static REGISTRY_CONTRACT: OnceCell = OnceCell::const_new(); pub static NETWORK_CONTRACT: OnceCell = OnceCell::const_new(); -// pub static EXTRA_CONTRACT: OnceCell = OnceCell::const_new(); +pub static CONFIG_CONTRACT: OnceCell = OnceCell::const_new(); pub static DATABASE: OnceCell = OnceCell::const_new(); const QUERY_LOGS_INTERVAL: u64 = 60; const QUERY_BLOCK_INTERVAL: u64 = 500; @@ -139,6 +139,7 @@ pub enum ContractCommand { OperatorPublicKeys, SharedPublicKeys, EncryptedSecretKeys, + Address ), RemoveValidator(Validator), ActivateValidator(Validator), @@ -159,7 +160,7 @@ pub enum ContractCommand { Address, ), RemoveInitiator(Initiator, OperatorPublicKeys), - SetFeeRecipient(ValidatorPublicKey, Address), + SetFeeRecipient(u64, ValidatorPublicKey, Address), } #[derive(Clone)] @@ -316,10 +317,10 @@ impl TopicHandler for FeeRecipientSetHandler { db: &Database, _operator_pk_base64: &String, _config: &ContractConfig, - _web3: &Web3, + web3: &Web3, ) -> Result<(), ContractError> { - process_fee_recipient_set(log, db).await.map_err(|e| { - error!("error happens when process initiator removal"); + process_fee_recipient_set(log, db, web3).await.map_err(|e| { + error!("error happens when process set fee recipient"); e }) } @@ -336,6 +337,7 @@ pub struct ContractConfig { pub fee_recipient_set_topic: String, pub safestake_network_abi_path: String, pub safestake_registry_abi_path: String, + pub safestake_config_abi_path: String } impl FromFile for ContractConfig {} @@ -455,7 +457,7 @@ impl Contract { let va_filter_builder = FilterBuilder::default() .address(vec![ Address::from_slice(&hex::decode(NETWORK_CONTRACT.get().unwrap()).unwrap()), - // Address::from_slice(&hex::decode(EXTRA_CONTRACT.get().unwrap()).unwrap()), + Address::from_slice(&hex::decode(CONFIG_CONTRACT.get().unwrap()).unwrap()), ]) .topics( Some(vec![va_reg_topic, va_rm_topic, fee_receipient_set_topic]), @@ -805,6 +807,7 @@ pub async fn process_validator_registration( .into_iter() .map(|s| base64::decode(s).unwrap()) .collect(); + let fee_recipient_address = query_owner_fee_recipient(config, address.clone(), web3).await?; //send command to node let validator = Validator { id: validator_id, @@ -818,7 +821,7 @@ pub async fn process_validator_registration( // save validator in local database db.insert_validator(validator.clone(), registration_timestamp) .await; - let cmd = ContractCommand::StartValidator(validator, op_pk_bn, shared_pks, encrypted_sks); + let cmd = ContractCommand::StartValidator(validator, op_pk_bn, shared_pks, encrypted_sks, fee_recipient_address); db.insert_contract_command(validator_id, serde_json::to_string(&cmd).unwrap()) .await; } @@ -1162,7 +1165,7 @@ pub async fn process_minipool_ready(raw_log: Log, db: &Database) -> Result<(), C } } -pub async fn process_fee_recipient_set(raw_log: Log, db: &Database) -> Result<(), ContractError> { +pub async fn process_fee_recipient_set(raw_log: Log, db: &Database, web3: &Web3,) -> Result<(), ContractError> { info!("process_fee_recipient_set"); let fee_recipient_set_event = Event { name: CONTRACT_FEE_RECIPIENT_SET_EVENT_NAME.to_string(), @@ -1170,21 +1173,11 @@ pub async fn process_fee_recipient_set(raw_log: Log, db: &Database) -> Result<() EventParam { name: "owner".to_string(), kind: ParamType::Address, - indexed: true, - }, - EventParam { - name: "pubkey".to_string(), - kind: ParamType::Bytes, indexed: false, }, EventParam { - name: "feeReceiptAddress".to_string(), + name: "newAddress".to_string(), kind: ParamType::Address, - indexed: true, - }, - EventParam { - name: "updateCount".to_string(), - kind: ParamType::Uint(32), indexed: false, }, ], @@ -1201,39 +1194,21 @@ pub async fn process_fee_recipient_set(raw_log: Log, db: &Database) -> Result<() .clone() .into_address() .ok_or(ContractError::LogParseError)?; - let pubkey = log.params[1] - .value - .clone() - .into_bytes() - .ok_or(ContractError::LogParseError)?; - let fee_recipient_address = log.params[2] + let fee_recipient_address = log.params[1] .value .clone() .into_address() .ok_or(ContractError::LogParseError)?; + let block_number = raw_log.block_number.unwrap(); + let registration_timestamp = query_block_number_timestamp(block_number, web3).await?; + db.upsert_owner_fee_recipient(owner, fee_recipient_address).await; - if pubkey.iter().all(|&x| x == 0) { - // public key is zero - for v in db.query_validator_by_address(owner).await.unwrap().iter() { - let cmd = ContractCommand::SetFeeRecipient(v.public_key.clone(), fee_recipient_address); - db.insert_contract_command(v.id, serde_json::to_string(&cmd).unwrap()) - .await; - } - } else { - match db - .query_validator_by_public_key(hex::encode(pubkey.clone())) - .await - .unwrap() - { - Some(v) => { - let cmd = ContractCommand::SetFeeRecipient(pubkey, fee_recipient_address); - db.insert_contract_command(v.id, serde_json::to_string(&cmd).unwrap()) - .await; - } - None => { - info!("set fee recipient not releated to this operator"); - } - } + // public key is zero + for v in db.query_validator_by_address(owner).await.unwrap().iter() { + db.update_validator_registration_timestamp(v.public_key.clone(), registration_timestamp).await; + let cmd = ContractCommand::SetFeeRecipient(v.id, v.public_key.clone(), fee_recipient_address); + db.insert_contract_command(v.id, serde_json::to_string(&cmd).unwrap()) + .await; } Ok(()) @@ -1257,6 +1232,37 @@ pub async fn query_block_number_timestamp( } } +pub async fn query_owner_fee_recipient( + config: &ContractConfig, + owner: Address, + web3: &Web3 +) -> Result { + let raw_abi = std::fs::read_to_string(&config.safestake_config_abi_path) + .or_else(|e| { + error!( + "Can't read from {} {}", + &config.safestake_config_abi_path, e + ); + Err(ContractError::FileError) + }) + .unwrap(); + let raw_json: Value = serde_json::from_str(&raw_abi).unwrap(); + let abi = raw_json["abi"].to_string(); + let address = Address::from_slice(&hex::decode(CONFIG_CONTRACT.get().unwrap()).unwrap()); + let contract = EthContract::from_json(web3.eth(), address, abi.as_bytes()) + .or_else(|e| { + error!("Can't create contract from json {}", e); + Err(ContractError::ContractParseError) + }) + .unwrap(); + let fee_recipient: Address = contract.query("getFeeRecipientAddress", (owner,), None, Options::default(), None).await + .or_else(|e| { + error!("Can't query from contract {}", e); + Err(ContractError::QueryError) + })?; + Ok(fee_recipient) +} + pub async fn query_operator_from_contract( config: &ContractConfig, id: u32, diff --git a/src/node/db.rs b/src/node/db.rs index 0f6309c..06b4ad3 100644 --- a/src/node/db.rs +++ b/src/node/db.rs @@ -41,6 +41,10 @@ pub enum DbCommand { QueryInitiatorStore(u32, oneshot::Sender>>), QueryAllValidatorPublicKeys(oneshot::Sender>>), QueryValidatorRegistrationTimestamp(String, oneshot::Sender>), + UpsertOwnerFeeRecipient(Address, Address), + QueryOwnerFeeRecipient(Address, oneshot::Sender>>), + CheckValidatorFeeRecipient(Vec, Address, oneshot::Sender>), + UpdateValidatorRegistrationTimestamp(Vec, u64) } #[derive(Clone, Debug)] @@ -134,6 +138,11 @@ impl Database { CONSTRAINT initiator_store_constraint FOREIGN KEY (record_id) REFERENCES initiator_store_record(id) ON DELETE CASCADE )"; + let create_owner_fee_recipient_sql = "CREATE TABLE IF NOT EXISTS owner_fee_recipient( + owner CHARACTER(40) NOT NULL PRIMARY KEY, + fee_recipient CHARACTER(40) NOT NULL + )"; + conn.execute(create_operators_sql, [])?; conn.execute(create_validators_sql, [])?; conn.execute(create_releation_sql, [])?; @@ -145,6 +154,7 @@ impl Database { conn.execute(create_initiator_store_sql, [])?; conn.execute(create_initiator_store_oppk_sql, [])?; conn.execute(create_validators_registration_timestamp_sql, [])?; + conn.execute(create_owner_fee_recipient_sql, [])?; let (tx, mut rx) = channel(1000); tokio::spawn(async move { @@ -242,6 +252,20 @@ impl Database { query_validator_registration_timestamp(&mut conn, &public_key); let _ = sender.send(response); } + DbCommand::UpsertOwnerFeeRecipient(owner, fee_recipient) => { + upsert_owner_fee_recipient(&mut conn, owner, fee_recipient); + } + DbCommand::QueryOwnerFeeRecipient(owner, sender) => { + let response = query_owner_fee_recipient(&mut conn, owner); + let _ = sender.send(response); + } + DbCommand::CheckValidatorFeeRecipient(pubkey, fee_recipient, sender) => { + let response = check_validator_fee_recipient(&mut conn, pubkey, fee_recipient); + let _ = sender.send(response); + } + DbCommand::UpdateValidatorRegistrationTimestamp(pubkey, timestamp) => { + update_validator_registration_timestamp(&conn, pubkey, timestamp); + } } } }); @@ -617,6 +641,67 @@ impl Database { .await .expect("Failed to receive reply of query validators registration timestamp from db") } + + pub async fn upsert_owner_fee_recipient( + &self, + owner: Address, + fee_recipient: Address + ) { + + if let Err(e) = self + .channel + .send(DbCommand::UpsertOwnerFeeRecipient(owner, fee_recipient)) + .await + { + panic!("Failed to send enable validator command to store: {}", e); + } + } + + pub async fn query_owner_fee_recipient( + &self, + owner: Address, + ) -> DbResult>{ + let (sender, receiver) = oneshot::channel(); + if let Err(e) = self.channel.send(DbCommand::QueryOwnerFeeRecipient(owner, sender)).await { + panic!( + "Failed to send query fee recipient address to store: {}", + e + ); + } + receiver + .await + .expect("Failed to receive reply of query fee recipient address from db") + } + + pub async fn check_validator_fee_recipient( + &self, + pubkey: Vec, + fee_recipient: Address + ) -> DbResult { + let (sender, receiver) = oneshot::channel(); + if let Err(e) = self.channel.send(DbCommand::CheckValidatorFeeRecipient(pubkey, fee_recipient, sender)).await { + panic!( + "Failed to send check validator fee recipient address to store: {}", + e + ); + } + receiver + .await + .expect("Failed to receive reply of check validator fee recipient address from db") + } + + pub async fn update_validator_registration_timestamp( + &self, + pubkey: Vec, + timestamp: u64 + ) { + if let Err(e) = self.channel.send(DbCommand::UpdateValidatorRegistrationTimestamp(pubkey, timestamp)).await { + panic!( + "Failed to send update validator registration timestamp {}", + e + ); + } + } } fn insert_operator(conn: &Connection, operator: Operator) { @@ -1209,6 +1294,122 @@ pub fn query_all_validator_publickeys(conn: &Connection) -> DbResult Ok(public_keys) } +pub fn upsert_owner_fee_recipient(conn: &Connection, owner: Address, fee_recipient: Address) { + let owner = format!("{0:0x}", owner); + let fee_recipient = format!("{0:0x}", fee_recipient); + if let Err(e) = conn.execute("insert into owner_fee_recipient(owner, fee_recipient) values(?1, ?2) ON conflict(owner) do update set fee_recipient = (?3)", params![owner, fee_recipient, fee_recipient]) { + error!("Can't insert into owner fee recipient, error: {}", e); + } +} + +pub fn query_owner_fee_recipient(conn: &Connection, owner: Address) -> DbResult> { + let owner = format!("{0:0x}", owner); + match conn.prepare("select fee_recipient from owner_fee_recipient where owner = (?)") { + Ok(mut stmt) => { + let mut rows = stmt.query([owner])?; + while let Some(row) = rows.next()? { + let fee_recipient: String = row.get(0)?; + return Ok(Some(Address::from_slice(&hex::decode(&fee_recipient).unwrap()))); + } + return Ok(None); + } + Err(e) => { + error!("Can't prepare statement {}", e); + return Err(e); + } + } +} + +pub fn check_validator_fee_recipient(conn: &Connection, pubkey: Vec, fee_recipient: Address) -> DbResult { + let pk = hex::encode(pubkey); + match conn.prepare("select owner_fee_recipient.fee_recipient from validators join owner_fee_recipient on validators.owner_address = owner_fee_recipient.owner where validators.public_key = (?)") { + Ok(mut stmt) => { + let mut rows = stmt.query([pk.clone()])?; + while let Some(row) = rows.next()? { + let res: String = row.get(0)?; + return Ok(res == format!("{0:0x}", fee_recipient)); + } + } + Err(e) => { + error!("Can't prepare statement {}", e); + return Err(e); + } + } + match conn.prepare("select owner_address from validators where public_key = (?)") { + Ok(mut stmt) => { + let mut rows = stmt.query([pk])?; + while let Some(row) = rows.next()? { + let res: String = row.get(0)?; + return Ok(res == format!("{0:0x}", fee_recipient)); + } + } + Err(e) => { + error!("Can't prepare statement {}", e); + return Err(e); + } + } + + Ok(false) +} + +fn update_validator_registration_timestamp(conn: &Connection, pubkey: Vec, timestamp: u64) { + let pk = hex::encode(pubkey); + if let Err(e) = conn.execute("UPDATE validators_registration_timestamp SET registration_timestamp = ?1 WHERE public_key = ?2", params![timestamp, pk]) { + error!("Can't insert into owner fee recipient, error: {}", e); + } +} + +#[tokio::test] +async fn test_check_fee_recipient() { + let mut logger = + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")); + logger.format_timestamp_millis(); + logger.init(); + let _ = Database::new("/tmp/contract_database.db").unwrap(); + let conn = Connection::open("/tmp/contract_database.db").unwrap(); + let va_pk = hex::decode("82664f099bbd1a81ad878c2c2a3feb3c7caa3a623b5859018f6601da574e0ebd29432169610ed89b048f195f1fdcc024").unwrap(); + let address: Address = Address::from_slice(&hex::decode("331d29ac25cb8b7e52b5a7de0ba8e863682eca80").unwrap()); + assert_eq!(check_validator_fee_recipient(&conn, va_pk, address).unwrap(), true); +} + +#[tokio::test] +async fn test_fee_recipient() { + use rand::RngCore; + let mut logger = + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")); + logger.format_timestamp_millis(); + logger.init(); + let _ = Database::new("/tmp/test.db").unwrap(); + let mut conn = Connection::open("/tmp/test.db").unwrap(); + let owner = Address::random(); + let mut rng = rand::thread_rng(); + let mut dest = [0u8; 48]; + rng.fill_bytes(&mut dest); + let pubkey = dest.to_vec(); + let validator = Validator { + id: 1, + owner_address: owner.clone(), + public_key: pubkey.clone(), + releated_operators: vec![], + active: true + }; + insert_validator(&mut conn, validator, 0); + assert_eq!(check_validator_fee_recipient(&conn, pubkey.clone(), owner).unwrap(), true); + let fee_recipient = Address::random(); + let new_fee_recipient = Address::random(); + assert_eq!(check_validator_fee_recipient(&conn, pubkey.clone(), fee_recipient).unwrap(), false); + assert_eq!(query_owner_fee_recipient(&conn, owner), Ok(None)); + upsert_owner_fee_recipient(&conn, owner, fee_recipient); + assert_eq!(check_validator_fee_recipient(&conn, pubkey.clone(), fee_recipient).unwrap(), true); + assert_eq!(query_owner_fee_recipient(&conn, owner), Ok(Some(fee_recipient))); + upsert_owner_fee_recipient(&conn, owner, new_fee_recipient); + assert_eq!(query_owner_fee_recipient(&conn, owner), Ok(Some(new_fee_recipient))); + assert_eq!(check_validator_fee_recipient(&conn, pubkey.clone(), new_fee_recipient).unwrap(), true); + update_validator_registration_timestamp(&conn, pubkey.clone(), 10); + let pk_str = hex::encode(pubkey.clone()); + assert_eq!(query_validator_registration_timestamp(&conn, &pk_str).unwrap(), 10); +} + #[tokio::test] async fn test_database() { use crate::crypto::ThresholdSignature; diff --git a/src/node/dvfcore.rs b/src/node/dvfcore.rs index 303eaf0..b55fb4d 100644 --- a/src/node/dvfcore.rs +++ b/src/node/dvfcore.rs @@ -33,8 +33,10 @@ use store::Store; use tokio::sync::RwLock; use types::{ AbstractExecPayload, AttestationData, BeaconBlock, BlindedPayload, EthSpec, FullPayload, - Keypair, + Keypair, ExecPayload }; +use crate::node::db::Database; + #[derive(Serialize, Deserialize, Clone)] pub struct DvfInfo { pub validator_id: u64, @@ -167,6 +169,7 @@ pub struct DvfDutyCheckHandler { pub validator_pk: BlsPublicKey, pub operator_pks: HashMap, pub keypair: Keypair, + pub db: Database, _phantom: PhantomData, } @@ -412,6 +415,20 @@ impl MessageHandler for DvfDutyCheckHandler { return Ok(()); } }; + let fee_recipient = block.body().execution_payload().unwrap().fee_recipient(); + let pubkey = self.validator_pk.serialize().to_vec(); + info!("block proposal full block, va pubic key {}, fee recipient {}", hex::encode(&pubkey), format!("{0:0x}", fee_recipient)); + if !self.db.check_validator_fee_recipient(pubkey, fee_recipient).await.unwrap() { + reply( + writer, + DutySafety::Invalid, + format!("fee recipient is not consistent"), + ) + .await; + error!("fee recipient is not consistent"); + return Ok(()); + } + self.sign_block(writer, block, check_msg.domain_hash).await; } BlockType::Blinded => { @@ -522,6 +539,7 @@ impl DvfSigner { validator_pk: operator_committee.get_validator_pk(), operator_pks, keypair: keypair.clone(), + db: node.db.clone(), _phantom: PhantomData, }, ); @@ -589,7 +607,7 @@ impl DvfSigner { domain_hash, check_type, data: data.to_vec(), - sign_hex: None, + sign_hex: None }; match msg.sign_digest(&self.node_secret) { Ok(sign_hex) => msg.sign_hex = Some(sign_hex), diff --git a/src/node/node.rs b/src/node/node.rs index bc5b9d2..9c746e1 100644 --- a/src/node/node.rs +++ b/src/node/node.rs @@ -50,7 +50,7 @@ use tokio::sync::RwLock; use tokio::time::{sleep, Duration}; use types::EthSpec; use validator_dir::insecure_keys::INSECURE_PASSWORD; -use web3::types::H160; +use web3::types::{H160, Address}; const THRESHOLD: u64 = 3; pub const COMMITTEE_IP_HEARTBEAT_INTERVAL: u64 = 1800; @@ -228,6 +228,7 @@ impl Node { operator_pks, shared_pks, encrypted_sks, + fee_recipient ) => { let va_id = validator.id; info!("StartValidator"); @@ -237,6 +238,7 @@ impl Node { operator_pks, shared_pks, encrypted_sks, + fee_recipient ) .await { @@ -387,9 +389,10 @@ impl Node { } } } - ContractCommand::SetFeeRecipient(va_pk, fee_recipient_address) => { + ContractCommand::SetFeeRecipient(va_id, va_pk, fee_recipient_address) => { match set_validator_fee_recipient( node.clone(), + va_id, va_pk, fee_recipient_address, ) @@ -530,6 +533,7 @@ pub async fn add_validator( operator_public_keys: OperatorPublicKeys, shared_public_keys: SharedPublicKeys, encrypted_secret_keys: EncryptedSecretKeys, + fee_recipient: Address ) -> Result<(), String> { let node = node.read().await; let validator_dir = node.config.validator_dir.clone(); @@ -651,6 +655,8 @@ pub async fn add_validator( default_keystore_share_path(&keystore_share, validator_dir.clone()); let voting_keystore_share_password_path = default_keystore_share_password_path(&keystore_share, secret_dir.clone()); + + match &node.validator_store { Some(validator_store) => { let _ = validator_store @@ -659,7 +665,7 @@ pub async fn add_validator( voting_keystore_share_password_path, true, None, - Some(validator.owner_address), + Some(fee_recipient), None, Some(node.config.builder_proposals), node.config.builder_boost_factor, @@ -813,7 +819,7 @@ pub async fn start_initiator( secp256k1::SecretKey::from_slice(&secret.0).expect("Unable to load secret key"); let node_public_key = secp256k1::PublicKey::from_secret_key(&secp, &node_secret_key); if operator_addrs.iter().any(|x| x.is_none()) { - sleep(Duration::from_secs(10)).await; + sleep(Duration::from_secs(5)).await; return Err("StartInitiator: Insufficient operators discovered for DKG".to_string()); } let operator_addrs: Vec = operator_addrs.iter().map(|x| x.unwrap()).collect(); @@ -1086,6 +1092,7 @@ pub async fn restart_validator( pub async fn set_validator_fee_recipient( node: Arc>>, + _validator_id: u64, validator_pk: Vec, fee_recipient_address: H160, ) -> Result<(), DvfError> { @@ -1100,9 +1107,10 @@ pub async fn set_validator_fee_recipient( }; match validator_store { Some(validator_store) => { + let validator_pk = BlsPublicKey::deserialize(&validator_pk).unwrap(); validator_store .set_fee_recipient_for_validator( - &BlsPublicKey::deserialize(&validator_pk).unwrap(), + &validator_pk, fee_recipient_address, ) .await; diff --git a/src/node/status_report.rs b/src/node/status_report.rs index 7d1c0f6..4f5f0f6 100644 --- a/src/node/status_report.rs +++ b/src/node/status_report.rs @@ -50,7 +50,7 @@ impl StatusReport { &metrics::SIGNED_ATTESTATIONS_TOTAL, &[metrics::SUCCESS], ) as usize, - version: dvf_version::VERSION as usize, + version: dvf_version::SOFTWARE_VERSION as usize, connected_nodes: metrics::int_counter(&metrics::DVT_VC_CONNECTED_NODES) as usize, sign_hex: None, diff --git a/src/validation/cli.rs b/src/validation/cli.rs index aa437ee..20b07d1 100644 --- a/src/validation/cli.rs +++ b/src/validation/cli.rs @@ -154,17 +154,17 @@ pub fn cli_app() -> Command { .display_order(0) .required(true) ) - // .arg( - // Arg::new("extra-contract") - // .long("extra-contract") - // .value_name("EXTRA_CONTRACT") - // .help( - // "This is the address of extra contract" - // ) - // .action(ArgAction::Set) - // .display_order(0) - // .required(true) - // ) + .arg( + Arg::new("config-contract") + .long("config-contract") + .value_name("CONFIG_CONTRACT") + .help( + "This is the address of config contract" + ) + .action(ArgAction::Set) + .display_order(0) + .required(true) + ) .arg( Arg::new("init-slashing-protection") .long("init-slashing-protection") diff --git a/src/validation/config.rs b/src/validation/config.rs index da5230f..a2d66d2 100644 --- a/src/validation/config.rs +++ b/src/validation/config.rs @@ -1,6 +1,6 @@ use crate::node::config::{NodeConfig, API_ADDRESS}; use crate::node::contract::{ - DEFAULT_TRANSPORT_URL, NETWORK_CONTRACT, REGISTRY_CONTRACT, SELF_OPERATOR_ID, + DEFAULT_TRANSPORT_URL, NETWORK_CONTRACT, REGISTRY_CONTRACT, SELF_OPERATOR_ID, CONFIG_CONTRACT }; use crate::validation::beacon_node_fallback::ApiTopic; use crate::validation::graffiti_file::GraffitiFile; @@ -163,9 +163,9 @@ impl Config { info!(log, "read network contract"; "network-contract" => &network_contract); NETWORK_CONTRACT.set(network_contract).unwrap(); - // let extra_contract: String = parse_required(cli_args, "extra-contract")?; - // info!(log, "read extra contract"; "extra-contract" => &extra_contract); - // EXTRA_CONTRACT.set(extra_contract).unwrap(); + let config_contract: String = parse_required(cli_args, "config-contract")?; + info!(log, "read config contract"; "config-contract" => &config_contract); + CONFIG_CONTRACT.set(config_contract).unwrap(); let self_ip: Ipv4Addr = parse_required(cli_args, "ip")?; info!(log, "read node ip"; "ip" => &self_ip.to_string()); diff --git a/src/validation/preparation_service.rs b/src/validation/preparation_service.rs index 89b01ad..9522e9a 100644 --- a/src/validation/preparation_service.rs +++ b/src/validation/preparation_service.rs @@ -26,7 +26,7 @@ const PROPOSER_PREPARATION_LOOKAHEAD_EPOCHS: u64 = 2; const EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION: u64 = 1; /// The number of validator registrations to include per request to the beacon node. -const VALIDATOR_REGISTRATION_BATCH_SIZE: usize = 500; +const VALIDATOR_REGISTRATION_BATCH_SIZE: usize = 50; /// Builds an `PreparationService`. pub struct PreparationServiceBuilder { @@ -385,37 +385,6 @@ impl PreparationService { // Check if any have changed or it's been `EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION`. if let Some(slot) = self.slot_clock.now() { - // let epoch = slot.epoch(E::slots_per_epoch()); - // let start_slot = epoch.start_slot(E::slots_per_epoch()); - // let registration_duration = self.slot_clock.start_of(start_slot); - // match registration_duration { - // Some(duration) => { - // let registartion_timestamp = duration.as_secs(); - // if slot % (E::slots_per_epoch() * EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION) - // == 0 - // { - // self.publish_validator_registration_data( - // registration_keys, - // registartion_timestamp, - // epoch, - // ) - // .await?; - // } else if !changed_keys.is_empty() { - // self.publish_validator_registration_data( - // changed_keys, - // registartion_timestamp, - // epoch, - // ) - // .await?; - // } - // } - // _ => { - // error!( - // log, - // "Unable to calculate the regitration timestamp"; - // ) - // } - // } let epoch = slot.epoch(E::slots_per_epoch()); if slot % (E::slots_per_epoch() * EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION) == 0 { self.publish_validator_registration_data(registration_keys, epoch) diff --git a/src/validation/validator_store.rs b/src/validation/validator_store.rs index e306252..a26a755 100644 --- a/src/validation/validator_store.rs +++ b/src/validation/validator_store.rs @@ -1176,7 +1176,7 @@ impl ValidatorStore { "pubkey" => format!("{:?}", pubkey)); self.stop_validator_keystore(pubkey).await; // Cooling down - tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; self.start_validator_keystore(pubkey).await; }