├── .github └── workflows │ ├── cd.yaml │ ├── deploy.yaml │ ├── erc20Transfer.yaml │ ├── transfer.yaml │ └── uniswap.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── benchmark.md ├── bridge ├── abi │ └── backend_abi.go ├── checker │ └── checker.go ├── client │ └── rdoclient.go ├── contract │ ├── ChildBridgeCoreFacet.abi │ ├── ChildBridgeCoreFacet.go │ ├── DownwardMessageDispatcherFacet.abi │ ├── DownwardMessageDispatcherFacet.go │ ├── ParentBridgeCoreFacet.abi │ ├── ParentBridgeCoreFacet.go │ ├── UpwardMessageDispatcherFacet.abi │ └── UpwardMessageDispatcherFacet.go ├── controller │ ├── api │ │ ├── api.go │ │ ├── controller.go │ │ ├── l2unclaimed_withdrawals_by_address.go │ │ └── txs_by_address.go │ ├── l1_eventsWatcher.go │ ├── l2_eventsWatcher.go │ ├── l2_eventsWatcherTripod.go │ └── route │ │ └── route.go ├── logic │ ├── api_logic.go │ ├── l1_event_parser.go │ ├── l1_event_parser_test.go │ ├── l2_event_parser.go │ ├── l2_watcher_logic.go │ └── metrics.go ├── orm │ ├── cross_message.go │ ├── cross_message_test.go │ └── raw_bridge_events.go ├── relayer │ ├── .env.example │ ├── l1Relayer.go │ ├── l2Relayer.go │ └── l2Relayer_test.go ├── test │ ├── bindings │ │ ├── ChildBridgeCoreFacet.abi │ │ ├── ChildBridgeCoreFacet.go │ │ ├── ChildTokenMessageTransmitterFacet.abi │ │ ├── ChildTokenMessageTransmitterFacet.go │ │ ├── ERC1155Token.abi │ │ ├── ERC1155Token.go │ │ ├── ERC20Token.abi │ │ ├── ERC20Token.go │ │ ├── ERC721Token.abi │ │ ├── ERC721Token.go │ │ ├── ParentTokenMessageTransmitterFacet.abi │ │ └── ParentTokenMessageTransmitterFacet.go │ └── integration_test.go ├── types │ └── schema.go └── utils │ ├── database │ ├── config.go │ ├── db.go │ └── db_test.go │ ├── utils.go │ └── utils_test.go ├── cmd └── node │ ├── app │ └── app.go │ ├── main.go │ └── testrequest.go ├── conf ├── config.toml ├── evm.toml ├── poa.toml └── yu.toml ├── config └── config.go ├── contract └── const.go ├── docker-compose.yml ├── docs └── images │ ├── reddio_arch.png │ └── reddio_running.jpg ├── evm ├── cfg.go ├── config │ └── config.go ├── errors.go ├── eth.go ├── ethrpc │ ├── addrlock.go │ ├── api.go │ ├── api_backend.go │ ├── backend.go │ ├── gasprice.go │ ├── log_filter.go │ ├── metrics.go │ ├── rpc.go │ └── transaction_args.go ├── genesis.go ├── genesis_alloc.go ├── pending_state │ ├── pending_ctx.go │ ├── pending_state_wrapper.go │ ├── ps_wrapper_conflict.go │ └── statedb_wrapper.go ├── state.go ├── types.go ├── util.go └── util_test.go ├── go.mod ├── go.sum ├── metrics ├── api_metrics.go ├── grafana.json ├── metrics.go └── solidity_metrics.go ├── parallel ├── evm_process.go ├── evm_state.go ├── evm_util.go ├── parallel_evm.go └── serial_evm.go ├── test ├── cmd │ ├── benchmark │ │ └── main.go │ ├── erc20 │ │ └── main.go │ ├── transfer │ │ └── main.go │ ├── uniswap │ │ └── main.go │ └── uniswap_benchmark │ │ └── main.go ├── conf │ ├── conf.go │ └── evm_cfg.toml ├── contracts │ ├── ERC20T.abi │ ├── ERC20T.bin │ ├── ERC20T.go │ ├── Token.abi │ ├── Token.bin │ ├── Token.go │ ├── UniswapV2Factory.abi │ ├── UniswapV2Factory.bin │ ├── UniswapV2Factory.go │ ├── UniswapV2Router01.abi │ ├── UniswapV2Router01.bin │ ├── UniswapV2Router01.go │ ├── WETH9.abi │ ├── WETH9.bin │ └── WETH9.go ├── erc20 │ ├── eth.go │ └── testcase.go ├── pkg │ ├── block.go │ ├── case.go │ ├── case_erc20.go │ ├── case_test.go │ ├── test │ │ └── data.json │ ├── util.go │ ├── wallet.go │ └── wallet_test.go ├── testx │ └── config.go ├── transfer │ ├── benchmark_case.go │ ├── conflict_transfer.go │ ├── eth.go │ ├── state_root_testcase.go │ └── testcase.go └── uniswap │ ├── eth.go │ ├── testcase.go │ ├── uniswapTPS_testcase.go │ ├── uniswapV2Accuracy_testcase.go │ └── utils.go └── utils ├── pprof.go ├── ratelimit.go └── s3 └── s3_client.go /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: cd 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths-ignore: 8 | - '**.md' 9 | tags: 10 | - '*' 11 | 12 | jobs: 13 | docker: 14 | runs-on: ubuntu-latest 15 | name: Build and release docker images 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | submodules: 'recursive' 22 | 23 | - name: Login to GitHub Container Registry 24 | uses: docker/login-action@v3 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.repository_owner }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Docker metadata 31 | id: meta 32 | uses: docker/metadata-action@v4 33 | with: 34 | images: | 35 | ghcr.io/${{ github.event.repository.full_name }} 36 | 37 | - name: Get tag name 38 | if: github.ref_name != 'main' 39 | uses: olegtarasov/get-tag@v2.1 40 | id: gitTag 41 | 42 | - name: Set the image tag as output if not main branch 43 | if: github.ref_name != 'main' 44 | id: imageTag 45 | run: | 46 | IMAGE_TAG=$(echo ${{ steps.gitTag.outputs.tag }}); \ 47 | echo "::set-output name=tag::$IMAGE_TAG"; \ 48 | 49 | - name: Set up Docker Buildx 50 | uses: docker/setup-buildx-action@v3 51 | 52 | - name: Build and push 53 | uses: docker/build-push-action@v6 54 | with: 55 | context: . 56 | platforms: linux/amd64 57 | push: true 58 | tags: ${{ steps.meta.outputs.tags }} 59 | labels: ${{ steps.meta.outputs.labels }} 60 | 61 | - name: Deploy tagged image to devnet 62 | uses: appleboy/ssh-action@master 63 | if: startsWith(github.ref, 'refs/tags/') 64 | with: 65 | host: ${{ secrets.DEVNET_IP }} 66 | username: root 67 | key: ${{ secrets.API_SSH_KEY }} 68 | script: | 69 | docker login ghcr.io -u reddio-com -p ${{ secrets.GITHUB_TOKEN }} 70 | 71 | docker pull ghcr.io/${{ github.event.repository.full_name }}:${{ steps.imageTag.outputs.tag }} 72 | 73 | cd ${{ secrets.DEVNET_PATH }} 74 | 75 | # Change Tag to ${{ steps.imageTag.outputs.tag }} 76 | sed -E -i'' "s|(.*reddio-com/reddio:).*|\\1${{ steps.imageTag.outputs.tag }}|" 'docker-compose.yml' 77 | 78 | docker-compose up -d -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy devnet 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | reddio-devnet-version: 7 | description: 'Version of devnet,e.g: devnet-v2.1.13' 8 | required: true 9 | default: 'devnet-v2.1.13' 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | name: Deploy tagged image to devnet 14 | 15 | steps: 16 | - uses: tspascoal/get-user-teams-membership@v3 17 | id: checkUserMember 18 | with: 19 | username: ${{ github.actor }} 20 | team: 'reddio-com' 21 | GITHUB_TOKEN: ${{ secrets.PAT }} 22 | 23 | - name: Stop on non-member 24 | if: steps.checkUserMember.outputs.isMember == 'false' 25 | run: exit 1 26 | 27 | - name: Deploy tagged image to devnet 28 | uses: appleboy/ssh-action@master 29 | if: startsWith(github.ref, 'refs/tags/') 30 | with: 31 | host: ${{ secrets.DEVNET_IP }} 32 | username: root 33 | key: ${{ secrets.API_SSH_KEY }} 34 | script: | 35 | docker login ghcr.io -u reddio-com -p ${{ secrets.GITHUB_TOKEN }} 36 | 37 | cd ${{ secrets.DEVNET_PATH }} 38 | 39 | # Change Tag to ${{ github.event.inputs.reddio-devnet-version }} 40 | sed -E -i'' "s|(.*reddio-com/reddio:).*|\\1${{ github.event.inputs.reddio-devnet-version }}|" 'docker-compose.yml' 41 | 42 | docker-compose up -d -------------------------------------------------------------------------------- /.github/workflows/erc20Transfer.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: ERC20Transfer 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | paths-ignore: 10 | - "**/docs/**" 11 | - "**/README.md" 12 | pull_request: 13 | branches: ["main"] 14 | paths-ignore: 15 | - "**/docs/**" 16 | - "**/README.md" 17 | 18 | jobs: 19 | transfer: 20 | if: github.event.pull_request.draft == false 21 | runs-on: ubuntu-latest 22 | services: 23 | mysql: 24 | image: mysql:5.7 25 | env: 26 | MYSQL_ROOT_PASSWORD: root 27 | MYSQL_DATABASE: test 28 | ports: 29 | - 3306:3306 30 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Set up Go 35 | uses: actions/setup-go@v4 36 | with: 37 | go-version: "1.23" 38 | 39 | - name: Run Makefile check-mod-tidy 40 | run: make check-mod-tidy 41 | - name: Build 42 | run: make build 43 | - name: Build Test 44 | run: make build_transfer_erc20_test_race 45 | - name: Parallel Transfer Test 46 | run: make ci_parallel_transfer_erc20_test 47 | - name: Serial erc20 Transfer Test 48 | run: make ci_serial_transfer_erc20_test -------------------------------------------------------------------------------- /.github/workflows/transfer.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Transfer 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | paths-ignore: 10 | - "**/docs/**" 11 | - "**/README.md" 12 | pull_request: 13 | branches: ["main"] 14 | paths-ignore: 15 | - "**/docs/**" 16 | - "**/README.md" 17 | 18 | jobs: 19 | transfer: 20 | if: github.event.pull_request.draft == false 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v4 27 | with: 28 | go-version: "1.23" 29 | 30 | - name: Run Makefile check-mod-tidy 31 | run: make check-mod-tidy 32 | - name: Build 33 | run: make build 34 | - name: Build Test 35 | run: make build_transfer_test_race 36 | - name: Parallel Transfer Test 37 | run: make ci_parallel_transfer_test 38 | - name: Serial Transfer Test 39 | run: make ci_serial_transfer_test -------------------------------------------------------------------------------- /.github/workflows/uniswap.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Uniswap 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | paths-ignore: 10 | - "**/docs/**" 11 | - "**/README.md" 12 | pull_request: 13 | branches: ["main"] 14 | paths-ignore: 15 | - "**/docs/**" 16 | - "**/README.md" 17 | 18 | jobs: 19 | uniswap: 20 | if: github.event.pull_request.draft == false 21 | runs-on: ubuntu-latest 22 | services: 23 | mysql: 24 | image: mysql:5.7 25 | env: 26 | MYSQL_ROOT_PASSWORD: root 27 | MYSQL_DATABASE: test 28 | ports: 29 | - 3306:3306 30 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Set up Go 35 | uses: actions/setup-go@v4 36 | with: 37 | go-version: "1.23" 38 | - name: Build Uniswap Test 39 | run: make build_uniswap_test_race 40 | - name: Parallel Uniswap Test 41 | run: make ci_parallel_uniswap_test 42 | - name: Serial Uniswap Test 43 | run: make ci_serial_uniswap_test 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/**/yu 3 | **/**/reddio_db 4 | **/**/evm_bridge_db 5 | .vscode 6 | 7 | /test/tmp/ 8 | data 9 | **/.env 10 | **/.*.env 11 | bin/ 12 | reddio -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # docker build . -t ghcr.io/reddio-com/reddio:latest 2 | FROM golang:1.23-bookworm as builder 3 | 4 | RUN mkdir /build 5 | COPY . /build 6 | RUN cd /build && git submodule init && git submodule update --recursive --checkout && make build 7 | 8 | FROM debian:bookworm-slim 9 | 10 | RUN apt-get update && apt-get install -y ca-certificates && apt-get clean 11 | 12 | # COPY ./conf /conf 13 | RUN mkdir /reddio_db /yu 14 | COPY --from=builder /build/reddio /reddio 15 | 16 | CMD ["/reddio"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reddio 2 | [Reddio](https://www.reddio.com/) is a high performance parallel Ethereum-compatible Layer 2, leveraging 3 | zero-knowledge technology to achieve unrivaled computation scale with 4 | Ethereum-level security. 5 | 6 | ## Build & Run 7 | 8 | ### Prerequisites 9 | 10 | - go 1.23.0 11 | 12 | ### Ethereum compatible 13 | 14 | - go-ethereum v1.14.0 15 | 16 | ### Source code Build & Run 17 | 18 | ```shell 19 | git clone git@github.com:reddio-com/reddio.git 20 | cd reddio && make build 21 | 22 | ./reddio 23 | ``` 24 | 25 | ### Docker Pull & Run 26 | 27 | ```shell 28 | docker pull ghcr.io/reddio-com/reddio:latest 29 | docker-compose up 30 | ``` 31 | 32 | ### Check pprof 33 | 34 | http://localhost:10199/debug/pprof -------------------------------------------------------------------------------- /benchmark.md: -------------------------------------------------------------------------------- 1 | ### Transfer benchmark Tutorial 2 | 3 | 1. Start the reddio server 4 | 2. build the benchmark cmd by: `make build_benchmark_test` 5 | 3. prepare the benchmark data by: `./benchmark_test --action=prepare --preCreateWallets=` 6 | 4. run the benchmark test by: `./benchmark_test --action=run --qps=10` -------------------------------------------------------------------------------- /bridge/client/rdoclient.go: -------------------------------------------------------------------------------- 1 | package rdoclient 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/HyperService-Consortium/go-hexutil" 11 | "github.com/ethereum/go-ethereum" 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/ethereum/go-ethereum/core/types" 14 | "github.com/ethereum/go-ethereum/ethclient" 15 | "github.com/ethereum/go-ethereum/rpc" 16 | ) 17 | 18 | type Client struct { 19 | *ethclient.Client // ethclient.Client 20 | } 21 | 22 | func NewClient(rpcURL string) (*Client, error) { 23 | client, err := ethclient.Dial(rpcURL) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &Client{Client: client}, nil 28 | } 29 | 30 | func (rc *Client) HeaderByNumberNoType(ctx context.Context, number *big.Int) (*map[string]interface{}, error) { 31 | var head map[string]interface{} 32 | err := rc.Client.Client().CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) 33 | if err == nil && head == nil { 34 | err = ethereum.NotFound 35 | } 36 | return &head, err 37 | } 38 | 39 | // func (rc *Client) BlockByNumberNoType(ctx context.Context, number *big.Int) (map[string]interface{}, error) { 40 | // var block map[string]interface{} 41 | // err := rc.Client.Client().CallContext(ctx, &block, "eth_getBlockByNumber", toBlockNumArg(number), true) 42 | // if err == nil && block == nil { 43 | // err = ethereum.NotFound 44 | // } 45 | // return block, err 46 | // } 47 | 48 | // BlockByNumber returns a block from the current canonical chain. If number is nil, the 49 | // latest known block is returned. 50 | // 51 | // Note that loading full blocks requires two requests. Use HeaderByNumber 52 | // if you don't need all transactions or uncle headers. 53 | func (ec *Client) RdoBlockByNumber(ctx context.Context, number *big.Int) (*RdoBlock, error) { 54 | return ec.getRdoBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), true) 55 | } 56 | 57 | type rpcTransaction struct { 58 | tx *types.Transaction 59 | txExtraInfo 60 | } 61 | 62 | type txExtraInfo struct { 63 | BlockNumber *string `json:"blockNumber,omitempty"` 64 | BlockHash *common.Hash `json:"blockHash,omitempty"` 65 | From *common.Address `json:"from,omitempty"` 66 | } 67 | type rpcBlock struct { 68 | Hash common.Hash `json:"hash"` 69 | Transactions []rpcTransaction `json:"transactions"` 70 | UncleHashes []common.Hash `json:"uncles"` 71 | Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` 72 | } 73 | 74 | func (rc *Client) BlockByHashNoType(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { 75 | var block map[string]interface{} 76 | err := rc.Client.Client().CallContext(ctx, &block, "eth_getBlockByHash", hash, true) 77 | if err == nil && block == nil { 78 | err = ethereum.NotFound 79 | } 80 | return block, err 81 | } 82 | 83 | func toBlockNumArg(number *big.Int) string { 84 | if number == nil { 85 | return "latest" 86 | } 87 | return "0x" + number.Text(16) 88 | } 89 | 90 | // func (rc *Client) HeaderByNumberNoType(ctx context.Context, number *big.Int) (*map[string]interface{}, error) { 91 | 92 | // // Call eth_getBlockByNumber to get the block details 93 | // var head map[string]interface{} 94 | // err := rc.ethclient.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) 95 | // if err == nil && head == nil { 96 | // err = ethereum.NotFound 97 | // } 98 | // return header, nil 99 | // } 100 | func (rc *Client) getRdoBlock(ctx context.Context, method string, args ...interface{}) (*RdoBlock, error) { 101 | var raw json.RawMessage 102 | err := rc.Client.Client().CallContext(ctx, &raw, method, args...) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | // Decode header and transactions. 108 | var head *types.Header 109 | if err := json.Unmarshal(raw, &head); err != nil { 110 | return nil, err 111 | } 112 | // When the block is not found, the API returns JSON null. 113 | if head == nil { 114 | return nil, ethereum.NotFound 115 | } 116 | 117 | var body rpcBlock 118 | if err := json.Unmarshal(raw, &body); err != nil { 119 | return nil, err 120 | } 121 | // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. 122 | if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 { 123 | return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles") 124 | } 125 | if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { 126 | return nil, errors.New("server returned empty uncle list but block header indicates uncles") 127 | } 128 | if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 { 129 | return nil, errors.New("server returned non-empty transaction list but block header indicates no transactions") 130 | } 131 | if head.TxHash != types.EmptyTxsHash && len(body.Transactions) == 0 { 132 | return nil, errors.New("server returned empty transaction list but block header indicates transactions") 133 | } 134 | // Load uncles because they are not included in the block response. 135 | var uncles []*types.Header 136 | if len(body.UncleHashes) > 0 { 137 | uncles = make([]*types.Header, len(body.UncleHashes)) 138 | reqs := make([]rpc.BatchElem, len(body.UncleHashes)) 139 | for i := range reqs { 140 | reqs[i] = rpc.BatchElem{ 141 | Method: "eth_getUncleByBlockHashAndIndex", 142 | Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))}, 143 | Result: &uncles[i], 144 | } 145 | } 146 | if err := rc.Client.Client().BatchCallContext(ctx, reqs); err != nil { 147 | return nil, err 148 | } 149 | for i := range reqs { 150 | if reqs[i].Error != nil { 151 | return nil, reqs[i].Error 152 | } 153 | if uncles[i] == nil { 154 | return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:]) 155 | } 156 | } 157 | } 158 | // Fill the sender cache of transactions in the block. 159 | txs := make([]*types.Transaction, len(body.Transactions)) 160 | block := types.NewBlockWithHeader(head).WithBody(txs, uncles).WithWithdrawals(body.Withdrawals) 161 | rdoBlock := &RdoBlock{Block: block} 162 | rdoBlock.SetHash(body.Hash) 163 | 164 | return rdoBlock, nil 165 | } 166 | 167 | type RdoBlock struct { 168 | *types.Block 169 | hash common.Hash 170 | } 171 | 172 | func (rb *RdoBlock) SetHash(hash common.Hash) { 173 | rb.hash = hash 174 | } 175 | 176 | func (rb *RdoBlock) Hash() common.Hash { 177 | if rb.hash != (common.Hash{}) { 178 | return rb.hash 179 | } 180 | return rb.Block.Hash() 181 | } 182 | -------------------------------------------------------------------------------- /bridge/contract/ChildBridgeCoreFacet.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint256", 8 | "name": "index", 9 | "type": "uint256" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "bytes32", 14 | "name": "messageHash", 15 | "type": "bytes32" 16 | } 17 | ], 18 | "name": "AppendMessageEvent", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "bytes32", 27 | "name": "xDomainCalldataHash", 28 | "type": "bytes32" 29 | }, 30 | { 31 | "indexed": false, 32 | "internalType": "uint256", 33 | "name": "nonce", 34 | "type": "uint256" 35 | }, 36 | { 37 | "indexed": false, 38 | "internalType": "uint32", 39 | "name": "payloadType", 40 | "type": "uint32" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "bytes", 45 | "name": "payload", 46 | "type": "bytes" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "uint256", 51 | "name": "gasLimit", 52 | "type": "uint256" 53 | } 54 | ], 55 | "name": "SentMessage", 56 | "type": "event" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": false, 63 | "internalType": "uint32", 64 | "name": "payloadType", 65 | "type": "uint32" 66 | }, 67 | { 68 | "indexed": false, 69 | "internalType": "bytes", 70 | "name": "payload", 71 | "type": "bytes" 72 | } 73 | ], 74 | "name": "UpwardMessage", 75 | "type": "event" 76 | }, 77 | { 78 | "inputs": [ 79 | { 80 | "internalType": "address", 81 | "name": "erc1155Address", 82 | "type": "address" 83 | } 84 | ], 85 | "name": "getBridgedERC1155TokenChild", 86 | "outputs": [ 87 | { 88 | "internalType": "address", 89 | "name": "", 90 | "type": "address" 91 | } 92 | ], 93 | "stateMutability": "view", 94 | "type": "function" 95 | }, 96 | { 97 | "inputs": [ 98 | { 99 | "internalType": "address", 100 | "name": "erc20Address", 101 | "type": "address" 102 | } 103 | ], 104 | "name": "getBridgedERC20TokenChild", 105 | "outputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "", 109 | "type": "address" 110 | } 111 | ], 112 | "stateMutability": "view", 113 | "type": "function" 114 | }, 115 | { 116 | "inputs": [ 117 | { 118 | "internalType": "address", 119 | "name": "erc721Address", 120 | "type": "address" 121 | } 122 | ], 123 | "name": "getBridgedERC721TokenChild", 124 | "outputs": [ 125 | { 126 | "internalType": "address", 127 | "name": "", 128 | "type": "address" 129 | } 130 | ], 131 | "stateMutability": "view", 132 | "type": "function" 133 | }, 134 | { 135 | "inputs": [ 136 | { 137 | "internalType": "address", 138 | "name": "bridgedERC1155TokenAddress", 139 | "type": "address" 140 | } 141 | ], 142 | "name": "getERC1155TokenChild", 143 | "outputs": [ 144 | { 145 | "internalType": "address", 146 | "name": "", 147 | "type": "address" 148 | } 149 | ], 150 | "stateMutability": "view", 151 | "type": "function" 152 | }, 153 | { 154 | "inputs": [ 155 | { 156 | "internalType": "address", 157 | "name": "bridgedERC20Address", 158 | "type": "address" 159 | } 160 | ], 161 | "name": "getERC20TokenChild", 162 | "outputs": [ 163 | { 164 | "internalType": "address", 165 | "name": "", 166 | "type": "address" 167 | } 168 | ], 169 | "stateMutability": "view", 170 | "type": "function" 171 | }, 172 | { 173 | "inputs": [], 174 | "name": "gettL1RedTokenAddress", 175 | "outputs": [ 176 | { 177 | "internalType": "address", 178 | "name": "", 179 | "type": "address" 180 | } 181 | ], 182 | "stateMutability": "view", 183 | "type": "function" 184 | }, 185 | { 186 | "inputs": [], 187 | "name": "initialize", 188 | "outputs": [], 189 | "stateMutability": "nonpayable", 190 | "type": "function" 191 | }, 192 | { 193 | "inputs": [], 194 | "name": "pauseBridge", 195 | "outputs": [ 196 | { 197 | "internalType": "bool", 198 | "name": "", 199 | "type": "bool" 200 | } 201 | ], 202 | "stateMutability": "nonpayable", 203 | "type": "function" 204 | }, 205 | { 206 | "inputs": [], 207 | "name": "pauseStatusBridge", 208 | "outputs": [ 209 | { 210 | "internalType": "bool", 211 | "name": "", 212 | "type": "bool" 213 | } 214 | ], 215 | "stateMutability": "view", 216 | "type": "function" 217 | }, 218 | { 219 | "inputs": [ 220 | { 221 | "internalType": "uint32", 222 | "name": "payloadType", 223 | "type": "uint32" 224 | }, 225 | { 226 | "internalType": "bytes", 227 | "name": "payload", 228 | "type": "bytes" 229 | } 230 | ], 231 | "name": "sendUpwardMessage", 232 | "outputs": [], 233 | "stateMutability": "nonpayable", 234 | "type": "function" 235 | }, 236 | { 237 | "inputs": [ 238 | { 239 | "internalType": "address", 240 | "name": "l1RedTokenAddress", 241 | "type": "address" 242 | } 243 | ], 244 | "name": "setRedTokenAddress", 245 | "outputs": [], 246 | "stateMutability": "nonpayable", 247 | "type": "function" 248 | }, 249 | { 250 | "inputs": [], 251 | "name": "unpauseBridge", 252 | "outputs": [ 253 | { 254 | "internalType": "bool", 255 | "name": "", 256 | "type": "bool" 257 | } 258 | ], 259 | "stateMutability": "nonpayable", 260 | "type": "function" 261 | } 262 | ] -------------------------------------------------------------------------------- /bridge/contract/DownwardMessageDispatcherFacet.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "bytes32", 8 | "name": "messageHash", 9 | "type": "bytes32" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint32", 14 | "name": "payloadType", 15 | "type": "uint32" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "bytes", 20 | "name": "payload", 21 | "type": "bytes" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256", 26 | "name": "nonce", 27 | "type": "uint256" 28 | } 29 | ], 30 | "name": "RelayedMessage", 31 | "type": "event" 32 | }, 33 | { 34 | "inputs": [ 35 | { 36 | "internalType": "bytes32", 37 | "name": "hash", 38 | "type": "bytes32" 39 | } 40 | ], 41 | "name": "isL1MessageExecuted", 42 | "outputs": [ 43 | { 44 | "internalType": "bool", 45 | "name": "", 46 | "type": "bool" 47 | } 48 | ], 49 | "stateMutability": "view", 50 | "type": "function" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "components": [ 56 | { 57 | "internalType": "uint32", 58 | "name": "payloadType", 59 | "type": "uint32" 60 | }, 61 | { 62 | "internalType": "bytes", 63 | "name": "payload", 64 | "type": "bytes" 65 | }, 66 | { 67 | "internalType": "uint256", 68 | "name": "nonce", 69 | "type": "uint256" 70 | } 71 | ], 72 | "internalType": "struct DownwardMessage[]", 73 | "name": "downwardMessages", 74 | "type": "tuple[]" 75 | } 76 | ], 77 | "name": "receiveDownwardMessages", 78 | "outputs": [], 79 | "stateMutability": "nonpayable", 80 | "type": "function" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "uint32", 86 | "name": "payloadType", 87 | "type": "uint32" 88 | }, 89 | { 90 | "internalType": "bytes", 91 | "name": "payload", 92 | "type": "bytes" 93 | }, 94 | { 95 | "internalType": "uint256", 96 | "name": "nonce", 97 | "type": "uint256" 98 | } 99 | ], 100 | "name": "relayMessage", 101 | "outputs": [], 102 | "stateMutability": "nonpayable", 103 | "type": "function" 104 | } 105 | ] -------------------------------------------------------------------------------- /bridge/contract/ParentBridgeCoreFacet.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint32", 8 | "name": "payloadType", 9 | "type": "uint32" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "bytes", 14 | "name": "payload", 15 | "type": "bytes" 16 | } 17 | ], 18 | "name": "DownwardMessage", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "bytes32", 27 | "name": "hash", 28 | "type": "bytes32" 29 | }, 30 | { 31 | "indexed": true, 32 | "internalType": "uint64", 33 | "name": "queueIndex", 34 | "type": "uint64" 35 | }, 36 | { 37 | "indexed": false, 38 | "internalType": "uint32", 39 | "name": "payloadType", 40 | "type": "uint32" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "bytes", 45 | "name": "payload", 46 | "type": "bytes" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "uint256", 51 | "name": "gasLimit", 52 | "type": "uint256" 53 | } 54 | ], 55 | "name": "QueueTransaction", 56 | "type": "event" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": true, 63 | "internalType": "bytes32", 64 | "name": "messageHash", 65 | "type": "bytes32" 66 | } 67 | ], 68 | "name": "RelayedMessage", 69 | "type": "event" 70 | }, 71 | { 72 | "inputs": [ 73 | { 74 | "internalType": "uint256", 75 | "name": "_gasLimit", 76 | "type": "uint256" 77 | } 78 | ], 79 | "name": "estimateCrossMessageFee", 80 | "outputs": [ 81 | { 82 | "internalType": "uint256", 83 | "name": "", 84 | "type": "uint256" 85 | } 86 | ], 87 | "stateMutability": "view", 88 | "type": "function" 89 | }, 90 | { 91 | "inputs": [], 92 | "name": "gettL1RedTokenAddress", 93 | "outputs": [ 94 | { 95 | "internalType": "address", 96 | "name": "", 97 | "type": "address" 98 | } 99 | ], 100 | "stateMutability": "view", 101 | "type": "function" 102 | }, 103 | { 104 | "inputs": [], 105 | "name": "nextCrossDomainMessageIndex", 106 | "outputs": [ 107 | { 108 | "internalType": "uint256", 109 | "name": "", 110 | "type": "uint256" 111 | } 112 | ], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [], 118 | "name": "pauseBridge", 119 | "outputs": [ 120 | { 121 | "internalType": "bool", 122 | "name": "", 123 | "type": "bool" 124 | } 125 | ], 126 | "stateMutability": "nonpayable", 127 | "type": "function" 128 | }, 129 | { 130 | "inputs": [], 131 | "name": "pauseStatusBridge", 132 | "outputs": [ 133 | { 134 | "internalType": "bool", 135 | "name": "", 136 | "type": "bool" 137 | } 138 | ], 139 | "stateMutability": "view", 140 | "type": "function" 141 | }, 142 | { 143 | "inputs": [ 144 | { 145 | "internalType": "uint32", 146 | "name": "payloadType", 147 | "type": "uint32" 148 | }, 149 | { 150 | "internalType": "bytes", 151 | "name": "payload", 152 | "type": "bytes" 153 | }, 154 | { 155 | "internalType": "uint256", 156 | "name": "ethAmount", 157 | "type": "uint256" 158 | }, 159 | { 160 | "internalType": "uint256", 161 | "name": "gasLimit", 162 | "type": "uint256" 163 | }, 164 | { 165 | "internalType": "uint256", 166 | "name": "value", 167 | "type": "uint256" 168 | } 169 | ], 170 | "name": "sendDownwardMessage", 171 | "outputs": [], 172 | "stateMutability": "nonpayable", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [ 177 | { 178 | "internalType": "address", 179 | "name": "l1RedTokenAddress", 180 | "type": "address" 181 | } 182 | ], 183 | "name": "setL1RedTokenAddress", 184 | "outputs": [], 185 | "stateMutability": "nonpayable", 186 | "type": "function" 187 | }, 188 | { 189 | "inputs": [], 190 | "name": "unpauseBridge", 191 | "outputs": [ 192 | { 193 | "internalType": "bool", 194 | "name": "", 195 | "type": "bool" 196 | } 197 | ], 198 | "stateMutability": "nonpayable", 199 | "type": "function" 200 | } 201 | ] -------------------------------------------------------------------------------- /bridge/contract/UpwardMessageDispatcherFacet.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "bytes32", 8 | "name": "messageHash", 9 | "type": "bytes32" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint32", 14 | "name": "payloadType", 15 | "type": "uint32" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "bytes", 20 | "name": "payload", 21 | "type": "bytes" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256", 26 | "name": "nonce", 27 | "type": "uint256" 28 | } 29 | ], 30 | "name": "RelayedMessage", 31 | "type": "event" 32 | }, 33 | { 34 | "inputs": [ 35 | { 36 | "internalType": "bytes32", 37 | "name": "hash", 38 | "type": "bytes32" 39 | } 40 | ], 41 | "name": "isL2MessageExecuted", 42 | "outputs": [ 43 | { 44 | "internalType": "bool", 45 | "name": "", 46 | "type": "bool" 47 | } 48 | ], 49 | "stateMutability": "view", 50 | "type": "function" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "components": [ 56 | { 57 | "internalType": "uint32", 58 | "name": "payloadType", 59 | "type": "uint32" 60 | }, 61 | { 62 | "internalType": "bytes", 63 | "name": "payload", 64 | "type": "bytes" 65 | }, 66 | { 67 | "internalType": "uint256", 68 | "name": "nonce", 69 | "type": "uint256" 70 | } 71 | ], 72 | "internalType": "struct UpwardMessage[]", 73 | "name": "upwardMessages", 74 | "type": "tuple[]" 75 | }, 76 | { 77 | "internalType": "bytes[]", 78 | "name": "signaturesArray", 79 | "type": "bytes[]" 80 | } 81 | ], 82 | "name": "receiveUpwardMessages", 83 | "outputs": [], 84 | "stateMutability": "nonpayable", 85 | "type": "function" 86 | }, 87 | { 88 | "inputs": [ 89 | { 90 | "internalType": "uint32", 91 | "name": "payloadType", 92 | "type": "uint32" 93 | }, 94 | { 95 | "internalType": "bytes", 96 | "name": "payload", 97 | "type": "bytes" 98 | }, 99 | { 100 | "internalType": "uint256", 101 | "name": "nonce", 102 | "type": "uint256" 103 | } 104 | ], 105 | "name": "relayMessageWithProof", 106 | "outputs": [], 107 | "stateMutability": "nonpayable", 108 | "type": "function" 109 | } 110 | ] -------------------------------------------------------------------------------- /bridge/controller/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/reddio-com/reddio/bridge/logic" 8 | "github.com/reddio-com/reddio/bridge/types" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | type BridgeAPI struct { 13 | historyLogic *logic.HistoryLogic 14 | } 15 | 16 | func NewBridgeAPI(db *gorm.DB) *BridgeAPI { 17 | return &BridgeAPI{ 18 | historyLogic: logic.NewHistoryLogic(db), 19 | } 20 | } 21 | 22 | func (s *BridgeAPI) GetStatus(c *gin.Context) { 23 | c.JSON(http.StatusOK, gin.H{"status": "Bridge service is running"}) 24 | } 25 | 26 | // GetL2UnclaimedWithdrawalsByAddress defines the http get method behavior 27 | func (s *BridgeAPI) GetL2UnclaimedWithdrawalsByAddress(ctx *gin.Context) { 28 | var req types.QueryByAddressRequest 29 | if err := ctx.ShouldBind(&req); err != nil { 30 | types.RenderFailure(ctx, types.ErrParameterInvalidNo, err) 31 | return 32 | } 33 | 34 | pagedTxs, total, err := s.historyLogic.GetL2UnclaimedWithdrawalsByAddress(ctx, req.Address, req.Page, req.PageSize) 35 | if err != nil { 36 | types.RenderFailure(ctx, types.ErrGetL2ClaimableWithdrawalsError, err) 37 | return 38 | } 39 | 40 | resultData := &types.ResultData{Results: pagedTxs, Total: total} 41 | types.RenderSuccess(ctx, resultData) 42 | } 43 | -------------------------------------------------------------------------------- /bridge/controller/api/controller.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "sync" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | var ( 10 | 11 | // L2UnclaimedWithdrawalsByAddressCtl the L2UnclaimedWithdrawalsByAddressController instance 12 | L2UnclaimedWithdrawalsByAddressCtl *L2UnclaimedWithdrawalsByAddressController 13 | // TxsByAddressCtl the TxsByAddressController instance 14 | TxsByAddressCtl *TxsByAddressController 15 | 16 | // L2WithdrawalsByAddressCtl the L2WithdrawalsByAddressController instance 17 | initControllerOnce sync.Once 18 | ) 19 | 20 | // InitController inits Controller with database 21 | func InitController(db *gorm.DB) { 22 | initControllerOnce.Do(func() { 23 | TxsByAddressCtl = NewTxsByAddressController(db) 24 | L2UnclaimedWithdrawalsByAddressCtl = NewL2UnclaimedWithdrawalsByAddressController(db) 25 | 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /bridge/controller/api/l2unclaimed_withdrawals_by_address.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | 7 | "github.com/reddio-com/reddio/bridge/logic" 8 | "github.com/reddio-com/reddio/bridge/types" 9 | ) 10 | 11 | // L2UnclaimedWithdrawalsByAddressController the controller of GetL2UnclaimedWithdrawalsByAddress 12 | type L2UnclaimedWithdrawalsByAddressController struct { 13 | historyLogic *logic.HistoryLogic 14 | } 15 | 16 | // NewL2UnclaimedWithdrawalsByAddressController create new L2UnclaimedWithdrawalsByAddressController 17 | func NewL2UnclaimedWithdrawalsByAddressController(db *gorm.DB) *L2UnclaimedWithdrawalsByAddressController { 18 | return &L2UnclaimedWithdrawalsByAddressController{ 19 | historyLogic: logic.NewHistoryLogic(db), 20 | } 21 | } 22 | 23 | // GetL2UnclaimedWithdrawalsByAddress defines the http get method behavior 24 | func (c *L2UnclaimedWithdrawalsByAddressController) GetL2UnclaimedWithdrawalsByAddress(ctx *gin.Context) { 25 | var req types.QueryByAddressRequest 26 | if err := ctx.ShouldBind(&req); err != nil { 27 | types.RenderFailure(ctx, types.ErrParameterInvalidNo, err) 28 | return 29 | } 30 | 31 | pagedTxs, total, err := c.historyLogic.GetL2UnclaimedWithdrawalsByAddress(ctx, req.Address, req.Page, req.PageSize) 32 | if err != nil { 33 | types.RenderFailure(ctx, types.ErrGetL2ClaimableWithdrawalsError, err) 34 | return 35 | } 36 | 37 | resultData := &types.ResultData{Results: pagedTxs, Total: total} 38 | types.RenderSuccess(ctx, resultData) 39 | } 40 | -------------------------------------------------------------------------------- /bridge/controller/api/txs_by_address.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/reddio-com/reddio/bridge/types" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/reddio-com/reddio/bridge/logic" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | // TxsByAddressController the controller of GetTxsByAddress 12 | type TxsByAddressController struct { 13 | historyLogic *logic.HistoryLogic 14 | } 15 | 16 | // NewTxsByAddressController create new TxsByAddressController 17 | func NewTxsByAddressController(db *gorm.DB) *TxsByAddressController { 18 | return &TxsByAddressController{ 19 | historyLogic: logic.NewHistoryLogic(db), 20 | } 21 | } 22 | 23 | // GetTxsByAddress defines the http get method behavior 24 | func (c *TxsByAddressController) GetTxsByAddress(ctx *gin.Context) { 25 | var req types.QueryByAddressRequest 26 | if err := ctx.ShouldBind(&req); err != nil { 27 | types.RenderFailure(ctx, types.ErrParameterInvalidNo, err) 28 | return 29 | } 30 | 31 | pagedTxs, total, err := c.historyLogic.GetTxsByAddress(ctx, req.Address, req.Page, req.PageSize) 32 | if err != nil { 33 | types.RenderFailure(ctx, types.ErrGetTxsError, err) 34 | return 35 | } 36 | 37 | resultData := &types.ResultData{Results: pagedTxs, Total: total} 38 | types.RenderSuccess(ctx, resultData) 39 | } 40 | -------------------------------------------------------------------------------- /bridge/controller/l2_eventsWatcherTripod.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/yu-org/yu/core/tripod" 10 | yutypes "github.com/yu-org/yu/core/types" 11 | "gorm.io/gorm" 12 | 13 | "github.com/reddio-com/reddio/bridge/logic" 14 | "github.com/reddio-com/reddio/bridge/orm" 15 | "github.com/reddio-com/reddio/evm" 16 | ) 17 | 18 | type L2EventsWatcherTripod struct { 19 | //ctx context.Context 20 | cfg *evm.GethConfig 21 | //ethClient *ethclient.Client 22 | l2WatcherLogic *logic.L2WatcherLogic 23 | *tripod.Tripod 24 | solidity *evm.Solidity `tripod:"solidity"` 25 | rawBridgeEventsOrm *orm.RawBridgeEvent 26 | db *gorm.DB 27 | } 28 | 29 | func NewL2EventsWatcherTripod(cfg *evm.GethConfig, db *gorm.DB) *L2EventsWatcherTripod { 30 | tri := tripod.NewTripod() 31 | c := &L2EventsWatcherTripod{ 32 | //ctx: ctx, 33 | cfg: cfg, 34 | Tripod: tri, 35 | db: db, 36 | } 37 | return c 38 | } 39 | 40 | func (w *L2EventsWatcherTripod) WatchL2BridgeEvent(ctx context.Context, block *yutypes.Block, Solidity *evm.Solidity) error { 41 | l2WithdrawMessages, l2RelayedMessages, _, err := w.l2WatcherLogic.L2FetcherBridgeEventsFromLogs(ctx, block, w.cfg.L2BlockCollectionDepth) 42 | if err != nil { 43 | return fmt.Errorf("failed to fetch upward message from logs: %v", err) 44 | } 45 | if len(l2WithdrawMessages) != 0 { 46 | err = w.savel2BridgeEvents(l2WithdrawMessages) 47 | if err != nil { 48 | return fmt.Errorf("failed to save l2WithdrawMessages: %v", err) 49 | } 50 | } 51 | if len(l2RelayedMessages) != 0 { 52 | err = w.savel2BridgeEvents(l2RelayedMessages) 53 | if err != nil { 54 | return fmt.Errorf("failed to save l2RelayedMessages: %v", err) 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (w *L2EventsWatcherTripod) InitChain(block *yutypes.Block) { 62 | if w.cfg.EnableBridge { 63 | w.rawBridgeEventsOrm = orm.NewRawBridgeEvent(w.db, w.cfg) 64 | 65 | l2WatcherLogic, err := logic.NewL2WatcherLogic(w.cfg, w.solidity) 66 | if err != nil { 67 | logrus.Fatal("init l2WatcherLogic failed: ", err) 68 | } 69 | w.l2WatcherLogic = l2WatcherLogic 70 | } 71 | } 72 | 73 | func (w *L2EventsWatcherTripod) StartBlock(block *yutypes.Block) { 74 | } 75 | 76 | func (w *L2EventsWatcherTripod) EndBlock(block *yutypes.Block) { 77 | } 78 | 79 | func (w *L2EventsWatcherTripod) FinalizeBlock(block *yutypes.Block) { 80 | if w.cfg.EnableBridge { 81 | //watch upward message 82 | blockHeightBigInt := big.NewInt(int64(block.Header.Height)) 83 | if big.NewInt(0).Mod(blockHeightBigInt, w.cfg.L2BlockCollectionDepth).Cmp(big.NewInt(0)) == 0 { 84 | go func() { 85 | err := w.WatchL2BridgeEvent(context.Background(), block, w.solidity) 86 | if err != nil { 87 | logrus.Errorf("WatchUpwardMessage error: %v", err) 88 | } 89 | }() 90 | } 91 | } 92 | } 93 | func (w *L2EventsWatcherTripod) savel2BridgeEvents( 94 | rawBridgeEvents []*orm.RawBridgeEvent, 95 | ) error { 96 | //fmt.Println("savel2BridgeEvents rawBridgeEvents: ", rawBridgeEvents) 97 | if len(rawBridgeEvents) == 0 { 98 | return nil 99 | } 100 | err := w.rawBridgeEventsOrm.InsertRawBridgeEvents(context.Background(), w.cfg.L2_RawBridgeEventsTableName, rawBridgeEvents) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /bridge/controller/route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-contrib/cors" 7 | "github.com/gin-gonic/gin" 8 | 9 | "github.com/reddio-com/reddio/bridge/controller/api" 10 | ) 11 | 12 | // Route routes the APIs 13 | func Route(router *gin.Engine) { 14 | router.Use(cors.New(cors.Config{ 15 | AllowOrigins: []string{"*"}, 16 | AllowMethods: []string{"GET", "POST"}, 17 | AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, 18 | AllowCredentials: true, 19 | MaxAge: 12 * time.Hour, 20 | })) 21 | 22 | r := router.Group("bridge/") 23 | // bridgeApi := api.NewBridgeAPI(db) 24 | // r.GET("/test", bridgeApi.GetStatus) 25 | r.POST("/withdrawals", api.L2UnclaimedWithdrawalsByAddressCtl.GetL2UnclaimedWithdrawalsByAddress) 26 | r.POST("/txsbyaddress", api.TxsByAddressCtl.GetTxsByAddress) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bridge/logic/api_logic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/sirupsen/logrus" 10 | "golang.org/x/sync/singleflight" 11 | "gorm.io/gorm" 12 | 13 | "github.com/reddio-com/reddio/bridge/orm" 14 | "github.com/reddio-com/reddio/bridge/types" 15 | "github.com/reddio-com/reddio/bridge/utils" 16 | ) 17 | 18 | // HistoryLogic services. 19 | type HistoryLogic struct { 20 | crossMessageOrm *orm.CrossMessage 21 | 22 | singleFlight singleflight.Group 23 | } 24 | 25 | // NewHistoryLogic returns bridge history services. 26 | func NewHistoryLogic(db *gorm.DB) *HistoryLogic { 27 | if err := db.AutoMigrate(&orm.CrossMessage{}); err != nil { 28 | logrus.Error("Failed to auto migrate: %v", err) 29 | } 30 | logic := &HistoryLogic{ 31 | crossMessageOrm: orm.NewCrossMessage(db), 32 | } 33 | return logic 34 | } 35 | 36 | // GetL2UnclaimedWithdrawalsByAddress gets all unclaimed withdrawal txs under given address. 37 | func (h *HistoryLogic) GetL2UnclaimedWithdrawalsByAddress(ctx context.Context, address string, page, pageSize uint64) ([]*types.TxHistoryInfo, uint64, error) { 38 | cacheKey := fmt.Sprintf("unclaimed_withdrawals_%s_%d_%d", address, page, pageSize) 39 | logrus.Info("cache miss", "cache key", cacheKey) 40 | var total uint64 41 | result, err, _ := h.singleFlight.Do(cacheKey, func() (interface{}, error) { 42 | var txHistoryInfos []*types.TxHistoryInfo 43 | crossMessages, totalCount, getErr := h.crossMessageOrm.GetL2UnclaimedWithdrawalsByAddress(ctx, address, page, pageSize) 44 | if getErr != nil { 45 | return nil, getErr 46 | } 47 | for _, message := range crossMessages { 48 | txHistoryInfos = append(txHistoryInfos, getTxHistoryInfoFromCrossMessage(message)) 49 | } 50 | total = totalCount 51 | return txHistoryInfos, nil 52 | }) 53 | if err != nil { 54 | logrus.Error("failed to get L2 claimable withdrawals by address", "address", address, "error", err) 55 | return nil, 0, err 56 | } 57 | 58 | txHistoryInfos, ok := result.([]*types.TxHistoryInfo) 59 | if !ok { 60 | logrus.Error("unexpected type", "expected", "[]*types.TxHistoryInfo", "got", reflect.TypeOf(result), "address", address) 61 | return nil, 0, errors.New("unexpected error") 62 | } 63 | 64 | return txHistoryInfos, uint64(total), nil 65 | } 66 | 67 | // GetL2UnclaimedWithdrawalsByAddress gets all unclaimed withdrawal txs under given address. 68 | func (h *HistoryLogic) GetTxsByAddress(ctx context.Context, address string, page, pageSize uint64) ([]*types.TxHistoryInfo, uint64, error) { 69 | cacheKey := fmt.Sprintf("txs_by_address_%s_%d_%d", address, page, pageSize) 70 | logrus.Info("cache miss", "cache key", cacheKey) 71 | var total uint64 72 | result, err, _ := h.singleFlight.Do(cacheKey, func() (interface{}, error) { 73 | var txHistoryInfos []*types.TxHistoryInfo 74 | crossMessages, totalCount, getErr := h.crossMessageOrm.GetTxsByAddress(ctx, address, page, pageSize) 75 | if getErr != nil { 76 | return nil, getErr 77 | } 78 | for _, message := range crossMessages { 79 | txHistoryInfos = append(txHistoryInfos, getTxHistoryInfoFromCrossMessage(message)) 80 | } 81 | total = totalCount 82 | return txHistoryInfos, nil 83 | }) 84 | if err != nil { 85 | logrus.Error("failed to get L2 claimable withdrawals by address", "address", address, "error", err) 86 | return nil, 0, err 87 | } 88 | 89 | txHistoryInfos, ok := result.([]*types.TxHistoryInfo) 90 | if !ok { 91 | logrus.Error("unexpected type", "expected", "[]*types.TxHistoryInfo", "got", reflect.TypeOf(result), "address", address) 92 | return nil, 0, errors.New("unexpected error") 93 | } 94 | 95 | return txHistoryInfos, uint64(total), nil 96 | } 97 | 98 | func getTxHistoryInfoFromCrossMessage(message *orm.CrossMessage) *types.TxHistoryInfo { 99 | txHistory := &types.TxHistoryInfo{ 100 | MessageHash: message.MessageHash, 101 | TokenType: types.TokenType(message.TokenType), 102 | TokenIDs: utils.ConvertStringToStringArray(message.TokenIDs), 103 | TokenAmounts: utils.ConvertStringToStringArray(message.TokenAmounts), 104 | L1TokenAddress: message.L1TokenAddress, 105 | L2TokenAddress: message.L2TokenAddress, 106 | MessageType: types.MessageType(message.MessageType), 107 | TxStatus: types.TxStatusType(message.TxStatus), 108 | TxType: types.TxType(message.TxType), 109 | BlockTimestamp: message.BlockTimestamp, 110 | } 111 | 112 | txHistory.Hash = message.L2TxHash 113 | txHistory.BlockNumber = message.L2BlockNumber 114 | txHistory.ClaimInfo = &types.ClaimInfo{ 115 | From: message.MessageFrom, 116 | To: message.MessageTo, 117 | Value: message.MessageValue, 118 | 119 | Message: types.Message{ 120 | PayloadType: uint32(message.MessagePayloadType), 121 | Payload: message.MessagePayload, 122 | Nonce: message.MessageNonce, 123 | }, 124 | Proof: types.L2MessageProof{ 125 | MultiSignProof: message.MultiSignProof, 126 | }, 127 | } 128 | 129 | return txHistory 130 | } 131 | -------------------------------------------------------------------------------- /bridge/logic/l2_watcher_logic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | "slices" 7 | "time" 8 | 9 | "github.com/ethereum/go-ethereum" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/core/types" 12 | "github.com/sirupsen/logrus" 13 | yucommon "github.com/yu-org/yu/common" 14 | yutypes "github.com/yu-org/yu/core/types" 15 | 16 | backendabi "github.com/reddio-com/reddio/bridge/abi" 17 | "github.com/reddio-com/reddio/bridge/orm" 18 | "github.com/reddio-com/reddio/evm" 19 | ) 20 | 21 | type L2WatcherLogic struct { 22 | cfg *evm.GethConfig 23 | addressList []common.Address 24 | parser *L2EventParser 25 | solidity *evm.Solidity `tripod:"solidity"` 26 | } 27 | 28 | func NewL2WatcherLogic(cfg *evm.GethConfig, solidity *evm.Solidity) (*L2WatcherLogic, error) { 29 | contractAddressList := []common.Address{ 30 | common.HexToAddress(cfg.ChildLayerContractAddress), 31 | } 32 | f := &L2WatcherLogic{ 33 | cfg: cfg, 34 | addressList: contractAddressList, 35 | parser: NewL2EventParser(cfg), 36 | solidity: solidity, 37 | } 38 | 39 | return f, nil 40 | } 41 | 42 | // L2FetcherUpwardMessageFromLogs collects upward messages from the logs of the current block 43 | // and the previous l2BlockCollectionDepth blocks. 44 | func (f *L2WatcherLogic) L2FetcherBridgeEventsFromLogs(ctx context.Context, block *yutypes.Block, l2BlockCollectionDepth *big.Int) ([]*orm.RawBridgeEvent, []*orm.RawBridgeEvent, map[uint64]uint64, error) { 45 | var l2WithdrawMessagesAll []*orm.RawBridgeEvent 46 | var l2RelayedMessagesAll []*orm.RawBridgeEvent 47 | 48 | depth := int(l2BlockCollectionDepth.Int64()) 49 | blockHeight := block.Height 50 | blockTimestampsMap := make(map[uint64]uint64) 51 | 52 | var err error 53 | startBlockHeight := int(blockHeight) - depth 54 | endBlockHeight := int(blockHeight) - 2*depth 55 | 56 | for height := startBlockHeight; height > endBlockHeight; height-- { 57 | //fmt.Println("Watcher GetCompactBlock startBlockHeight: ", startBlockHeight) 58 | //fmt.Println("Watcher GetCompactBlock endBlockHeight: ", endBlockHeight) 59 | block, err = f.GetBlockWithRetry(yucommon.BlockNum(height), 5, 1*time.Second) 60 | if err != nil { 61 | //fmt.Println("Watcher GetCompactBlock error: ", err) 62 | logrus.Error("Watcher GetCompactBlock ,Height:", height, "error:", err) 63 | return nil, nil, nil, err 64 | } 65 | blockTimestampsMap[uint64(height)] = block.Timestamp 66 | query := ethereum.FilterQuery{ 67 | // FromBlock: new(big.Int).SetUint64(from), // inclusive 68 | // ToBlock: new(big.Int).SetUint64(to), // inclusive 69 | Addresses: f.addressList, 70 | Topics: make([][]common.Hash, 1), 71 | } 72 | query.Topics[0] = make([]common.Hash, 2) 73 | query.Topics[0][0] = backendabi.L2SentMessageEventSig 74 | query.Topics[0][1] = backendabi.L2RelayedMessageEventSig 75 | 76 | eventLogs, err := f.FilterLogs(ctx, block, query) 77 | if err != nil { 78 | logrus.Error("FilterLogs err:", err) 79 | return nil, nil, nil, err 80 | } 81 | if len(eventLogs) == 0 { 82 | continue 83 | } 84 | l2WithdrawMessages, l2RelayedMessages, err := f.parser.ParseL2EventLogs(ctx, eventLogs) 85 | if err != nil { 86 | logrus.Error("Failed to parse L2 event logs 3", "err", err) 87 | return nil, nil, nil, err 88 | } 89 | l2WithdrawMessagesAll = append(l2WithdrawMessagesAll, l2WithdrawMessages...) 90 | l2RelayedMessagesAll = append(l2RelayedMessagesAll, l2RelayedMessages...) 91 | blockHeight-- 92 | } 93 | return l2WithdrawMessagesAll, l2RelayedMessagesAll, blockTimestampsMap, nil 94 | } 95 | func (f *L2WatcherLogic) GetBlockWithRetry(height yucommon.BlockNum, retries int, delay time.Duration) (*yutypes.Block, error) { 96 | var block *yutypes.Block 97 | var err error 98 | for i := 0; i < retries; i++ { 99 | block, err = f.solidity.Chain.GetBlockByHeight(height) 100 | if err == nil { 101 | return block, nil 102 | } 103 | logrus.Warnf("Retrying to get block, attempt %d/%d, height: %d, error: %v", i+1, retries, height, err) 104 | time.Sleep(delay) 105 | } 106 | return nil, err 107 | } 108 | func (f *L2WatcherLogic) FilterLogs(ctx context.Context, block *yutypes.Block, criteria ethereum.FilterQuery) ([]types.Log, error) { 109 | logs, err := f.getLogs(ctx, block) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | result := make([]types.Log, 0) 115 | var logIdx uint 116 | for i, txLogs := range logs { 117 | for _, vLog := range txLogs { 118 | vLog.BlockHash = common.Hash(block.Hash) 119 | vLog.BlockNumber = uint64(block.Height) 120 | vLog.TxIndex = uint(i) 121 | vLog.Index = logIdx 122 | logIdx++ 123 | 124 | //TODO 125 | if f.checkMatches(ctx, vLog) { 126 | result = append(result, *vLog) 127 | } 128 | } 129 | } 130 | 131 | return result, nil 132 | } 133 | 134 | func (f *L2WatcherLogic) checkMatches(ctx context.Context, vLog *types.Log) bool { 135 | if len(f.addressList) > 0 { 136 | if !slices.Contains(f.addressList, vLog.Address) { 137 | return false 138 | } 139 | } 140 | 141 | // TODO: The logic for topic filtering is a bit complex; it will not be implemented for now. 142 | //if len(f.topics) > len(vLog.Topics) { 143 | // return false 144 | //} 145 | //for i, sub := range f.topics { 146 | // if len(sub) == 0 { 147 | // continue // empty rule set == wildcard 148 | // } 149 | // if !slices.Contains(sub, vLog.Topics[i]) { 150 | // return false 151 | // } 152 | //} 153 | 154 | return true 155 | } 156 | func (f *L2WatcherLogic) getLogs(ctx context.Context, block *yutypes.Block) ([][]*types.Log, error) { 157 | 158 | receipts, err := f.getReceipts(ctx, block) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | result := [][]*types.Log{} 164 | for _, receipt := range receipts { 165 | logs := []*types.Log{} 166 | logs = append(logs, receipt.Logs...) 167 | result = append(result, logs) 168 | } 169 | 170 | return result, nil 171 | } 172 | 173 | // param hash just for test , its gonna be removed in the final version 174 | func (f *L2WatcherLogic) getReceipts(ctx context.Context, block *yutypes.Block) (types.Receipts, error) { 175 | 176 | var receipts []*types.Receipt 177 | for _, tx := range block.Txns { 178 | receipt, err := f.GetEthReceiptWithRetry(common.Hash(tx.TxnHash), 5, 1*time.Second) 179 | if err != nil { 180 | return nil, err 181 | } 182 | receipts = append(receipts, receipt) 183 | } 184 | 185 | return receipts, nil 186 | } 187 | func (f *L2WatcherLogic) GetEthReceiptWithRetry(txHash common.Hash, retries int, delay time.Duration) (*types.Receipt, error) { 188 | var receipt *types.Receipt 189 | var err error 190 | receipt, err = f.solidity.GetEthReceipt(txHash) 191 | if err == nil { 192 | return receipt, nil 193 | } 194 | return nil, err 195 | } 196 | -------------------------------------------------------------------------------- /bridge/logic/metrics.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | const ( 6 | TypeFunc = "func" 7 | TypeTri = "tri" 8 | ) 9 | 10 | var ( 11 | L2WatchCounterCounter = prometheus.NewCounterVec( 12 | prometheus.CounterOpts{ 13 | Namespace: "reddio", 14 | Subsystem: "logic_l2_watch", 15 | Name: "counter", 16 | Help: "Total Counter for l2 watcher", 17 | }, 18 | []string{TypeFunc, TypeTri}, 19 | ) 20 | ) 21 | 22 | func init() { 23 | prometheus.MustRegister(L2WatchCounterCounter) 24 | } 25 | -------------------------------------------------------------------------------- /bridge/relayer/.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY=your_private_key_here -------------------------------------------------------------------------------- /bridge/relayer/l2Relayer_test.go: -------------------------------------------------------------------------------- 1 | package relayer 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/ethereum/go-ethereum/crypto" 9 | "github.com/reddio-com/reddio/bridge/contract" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | /** 14 | * TestGenerateUpwardMessageMultiSignatures tests the generateUpwardMessageMultiSignatures function. 15 | * 16 | * Arrange: 17 | * - Create a mock list of upwardMessages. 18 | * - Create a mock list of privateKeys. 19 | * 20 | * Act: 21 | * - Call the generateUpwardMessageMultiSignatures function to generate signatures. 22 | * 23 | * Assert: 24 | * - Ensure no errors occurred. 25 | * - Ensure the number of generated signatures matches the expected count. 26 | * - Verify each signature is correct. 27 | */ 28 | func TestGenerateUpwardMessageMultiSignatures(t *testing.T) { 29 | //Arrange: 30 | // Create mock upward messages 31 | upwardMessages := []contract.UpwardMessage{ 32 | { 33 | PayloadType: 0, 34 | Payload: hexDecode("0x0000000000000000000000007888b7b844b4b16c03f8dacacef7dda0f51886450000000000000000000000007888b7b844b4b16c03f8dacacef7dda0f5188645000000000000000000000000000000000000000000000000000000000000006f"), 35 | Nonce: big.NewInt(0), 36 | }, 37 | { 38 | PayloadType: 0, 39 | Payload: hexDecode("0x0000000000000000000000007888b7b844b4b16c03f8dacacef7dda0f51886450000000000000000000000007888b7b844b4b16c03f8dacacef7dda0f5188645000000000000000000000000000000000000000000000000000000000000006f"), 40 | Nonce: big.NewInt(0), 41 | }, 42 | } 43 | // Create mock private keys 44 | privateKeys := []string{ 45 | "32e3b56c9f2763d2332e6e4188e4755815ac96441e899de121969845e343c2ff", 46 | "78740b0ee70f3e8fda88f90da06d3852043c70235b6cd8b3a2337ddd37423dc5", 47 | } 48 | 49 | // Call the function to test 50 | signatures, err := generateUpwardMessageMultiSignatures(upwardMessages, privateKeys) 51 | if err != nil { 52 | t.Fatalf("Failed to generate multi-signatures: %v", err) 53 | } 54 | 55 | assert.NoError(t, err) 56 | 57 | // Assert the length of signatures 58 | assert.Equal(t, len(privateKeys), len(signatures)) 59 | 60 | for i, sig := range signatures { 61 | privateKey, err := crypto.HexToECDSA(privateKeys[i]) 62 | assert.NoError(t, err) 63 | t.Logf("signature[%d]: %x\n", i, sig) 64 | dataHash, err := generateUpwardMessageToHash(upwardMessages) 65 | if err != nil { 66 | t.Fatalf("Failed to generate data hash: %v", err) 67 | } 68 | t.Logf("dataHash: %s\n", dataHash) 69 | pubKey, err := crypto.Ecrecover(dataHash.Bytes(), sig) 70 | if err != nil { 71 | t.Fatalf("Failed to recover public key: %v", err) 72 | } 73 | 74 | if pubKey == nil { 75 | t.Fatalf("Recovered public key is nil") 76 | } 77 | // Convert public key to address 78 | publicKeyECDSA, err := crypto.UnmarshalPubkey(pubKey) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | recoveredAddr := crypto.PubkeyToAddress(*publicKeyECDSA) 83 | expectedAddr := crypto.PubkeyToAddress(privateKey.PublicKey) 84 | 85 | assert.Equal(t, expectedAddr, recoveredAddr) 86 | } 87 | } 88 | 89 | // hexDecode decodes a hex string to a byte slice 90 | func hexDecode(hexStr string) []byte { 91 | bytes, err := hex.DecodeString(hexStr[2:]) // Skip the "0x" prefix 92 | if err != nil { 93 | panic(err) 94 | } 95 | return bytes 96 | } 97 | -------------------------------------------------------------------------------- /bridge/test/bindings/ChildBridgeCoreFacet.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint256", 8 | "name": "index", 9 | "type": "uint256" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "bytes32", 14 | "name": "messageHash", 15 | "type": "bytes32" 16 | } 17 | ], 18 | "name": "AppendMessageEvent", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "bytes32", 27 | "name": "xDomainCalldataHash", 28 | "type": "bytes32" 29 | }, 30 | { 31 | "indexed": false, 32 | "internalType": "uint256", 33 | "name": "nonce", 34 | "type": "uint256" 35 | }, 36 | { 37 | "indexed": false, 38 | "internalType": "uint32", 39 | "name": "payloadType", 40 | "type": "uint32" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "bytes", 45 | "name": "payload", 46 | "type": "bytes" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "uint256", 51 | "name": "gasLimit", 52 | "type": "uint256" 53 | } 54 | ], 55 | "name": "SentMessage", 56 | "type": "event" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": false, 63 | "internalType": "uint32", 64 | "name": "payloadType", 65 | "type": "uint32" 66 | }, 67 | { 68 | "indexed": false, 69 | "internalType": "bytes", 70 | "name": "payload", 71 | "type": "bytes" 72 | } 73 | ], 74 | "name": "UpwardMessage", 75 | "type": "event" 76 | }, 77 | { 78 | "inputs": [ 79 | { 80 | "internalType": "address", 81 | "name": "erc1155Address", 82 | "type": "address" 83 | } 84 | ], 85 | "name": "getBridgedERC1155TokenChild", 86 | "outputs": [ 87 | { 88 | "internalType": "address", 89 | "name": "", 90 | "type": "address" 91 | } 92 | ], 93 | "stateMutability": "view", 94 | "type": "function" 95 | }, 96 | { 97 | "inputs": [ 98 | { 99 | "internalType": "address", 100 | "name": "erc20Address", 101 | "type": "address" 102 | } 103 | ], 104 | "name": "getBridgedERC20TokenChild", 105 | "outputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "", 109 | "type": "address" 110 | } 111 | ], 112 | "stateMutability": "view", 113 | "type": "function" 114 | }, 115 | { 116 | "inputs": [ 117 | { 118 | "internalType": "address", 119 | "name": "erc721Address", 120 | "type": "address" 121 | } 122 | ], 123 | "name": "getBridgedERC721TokenChild", 124 | "outputs": [ 125 | { 126 | "internalType": "address", 127 | "name": "", 128 | "type": "address" 129 | } 130 | ], 131 | "stateMutability": "view", 132 | "type": "function" 133 | }, 134 | { 135 | "inputs": [ 136 | { 137 | "internalType": "address", 138 | "name": "bridgedERC1155TokenAddress", 139 | "type": "address" 140 | } 141 | ], 142 | "name": "getERC1155TokenChild", 143 | "outputs": [ 144 | { 145 | "internalType": "address", 146 | "name": "", 147 | "type": "address" 148 | } 149 | ], 150 | "stateMutability": "view", 151 | "type": "function" 152 | }, 153 | { 154 | "inputs": [ 155 | { 156 | "internalType": "address", 157 | "name": "bridgedERC20Address", 158 | "type": "address" 159 | } 160 | ], 161 | "name": "getERC20TokenChild", 162 | "outputs": [ 163 | { 164 | "internalType": "address", 165 | "name": "", 166 | "type": "address" 167 | } 168 | ], 169 | "stateMutability": "view", 170 | "type": "function" 171 | }, 172 | { 173 | "inputs": [], 174 | "name": "gettL1RedTokenAddress", 175 | "outputs": [ 176 | { 177 | "internalType": "address", 178 | "name": "", 179 | "type": "address" 180 | } 181 | ], 182 | "stateMutability": "view", 183 | "type": "function" 184 | }, 185 | { 186 | "inputs": [], 187 | "name": "initialize", 188 | "outputs": [], 189 | "stateMutability": "nonpayable", 190 | "type": "function" 191 | }, 192 | { 193 | "inputs": [], 194 | "name": "pauseBridge", 195 | "outputs": [ 196 | { 197 | "internalType": "bool", 198 | "name": "", 199 | "type": "bool" 200 | } 201 | ], 202 | "stateMutability": "nonpayable", 203 | "type": "function" 204 | }, 205 | { 206 | "inputs": [], 207 | "name": "pauseStatusBridge", 208 | "outputs": [ 209 | { 210 | "internalType": "bool", 211 | "name": "", 212 | "type": "bool" 213 | } 214 | ], 215 | "stateMutability": "view", 216 | "type": "function" 217 | }, 218 | { 219 | "inputs": [ 220 | { 221 | "internalType": "uint32", 222 | "name": "payloadType", 223 | "type": "uint32" 224 | }, 225 | { 226 | "internalType": "bytes", 227 | "name": "payload", 228 | "type": "bytes" 229 | } 230 | ], 231 | "name": "sendUpwardMessage", 232 | "outputs": [], 233 | "stateMutability": "nonpayable", 234 | "type": "function" 235 | }, 236 | { 237 | "inputs": [ 238 | { 239 | "internalType": "address", 240 | "name": "l1RedTokenAddress", 241 | "type": "address" 242 | } 243 | ], 244 | "name": "setRedTokenAddress", 245 | "outputs": [], 246 | "stateMutability": "nonpayable", 247 | "type": "function" 248 | }, 249 | { 250 | "inputs": [], 251 | "name": "unpauseBridge", 252 | "outputs": [ 253 | { 254 | "internalType": "bool", 255 | "name": "", 256 | "type": "bool" 257 | } 258 | ], 259 | "stateMutability": "nonpayable", 260 | "type": "function" 261 | } 262 | ] -------------------------------------------------------------------------------- /bridge/test/bindings/ChildTokenMessageTransmitterFacet.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "recipient", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "uint256", 11 | "name": "amount", 12 | "type": "uint256" 13 | } 14 | ], 15 | "name": "withdrawETH", 16 | "outputs": [], 17 | "stateMutability": "nonpayable", 18 | "type": "function" 19 | }, 20 | { 21 | "inputs": [ 22 | { 23 | "internalType": "address", 24 | "name": "tokenAddress", 25 | "type": "address" 26 | }, 27 | { 28 | "internalType": "address", 29 | "name": "recipient", 30 | "type": "address" 31 | }, 32 | { 33 | "internalType": "uint256[]", 34 | "name": "tokenIds", 35 | "type": "uint256[]" 36 | }, 37 | { 38 | "internalType": "uint256[]", 39 | "name": "amounts", 40 | "type": "uint256[]" 41 | } 42 | ], 43 | "name": "withdrawErc1155BatchToken", 44 | "outputs": [], 45 | "stateMutability": "nonpayable", 46 | "type": "function" 47 | }, 48 | { 49 | "inputs": [ 50 | { 51 | "internalType": "address", 52 | "name": "tokenAddress", 53 | "type": "address" 54 | }, 55 | { 56 | "internalType": "address", 57 | "name": "recipient", 58 | "type": "address" 59 | }, 60 | { 61 | "internalType": "uint256", 62 | "name": "amount", 63 | "type": "uint256" 64 | } 65 | ], 66 | "name": "withdrawErc20Token", 67 | "outputs": [], 68 | "stateMutability": "nonpayable", 69 | "type": "function" 70 | }, 71 | { 72 | "inputs": [ 73 | { 74 | "internalType": "address", 75 | "name": "tokenAddress", 76 | "type": "address" 77 | }, 78 | { 79 | "internalType": "address", 80 | "name": "recipient", 81 | "type": "address" 82 | }, 83 | { 84 | "internalType": "uint256", 85 | "name": "tokenId", 86 | "type": "uint256" 87 | } 88 | ], 89 | "name": "withdrawErc721Token", 90 | "outputs": [], 91 | "stateMutability": "nonpayable", 92 | "type": "function" 93 | }, 94 | { 95 | "inputs": [ 96 | { 97 | "internalType": "address", 98 | "name": "recipient", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "withdrawRED", 103 | "outputs": [], 104 | "stateMutability": "payable", 105 | "type": "function" 106 | } 107 | ] -------------------------------------------------------------------------------- /bridge/utils/database/config.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | // Config db config 4 | type Config struct { 5 | // data source name 6 | DSN string `json:"dsn"` 7 | DriverName string `json:"driverName"` 8 | 9 | MaxOpenNum int `json:"maxOpenNum"` 10 | MaxIdleNum int `json:"maxIdleNum"` 11 | } 12 | -------------------------------------------------------------------------------- /bridge/utils/database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/ethereum/go-ethereum/log" 10 | bridge_utils "github.com/reddio-com/reddio/bridge/utils" 11 | "gorm.io/driver/mysql" 12 | "gorm.io/gorm" 13 | "gorm.io/gorm/logger" 14 | "gorm.io/gorm/utils" 15 | ) 16 | 17 | type gormLogger struct { 18 | gethLogger log.Logger 19 | } 20 | 21 | func (g *gormLogger) LogMode(level logger.LogLevel) logger.Interface { 22 | return g 23 | } 24 | 25 | func (g *gormLogger) Info(_ context.Context, msg string, data ...interface{}) { 26 | infoMsg := fmt.Sprintf(msg, data...) 27 | g.gethLogger.Info("gorm", "info message", infoMsg) 28 | } 29 | 30 | func (g *gormLogger) Warn(_ context.Context, msg string, data ...interface{}) { 31 | warnMsg := fmt.Sprintf(msg, data...) 32 | g.gethLogger.Warn("gorm", "warn message", warnMsg) 33 | } 34 | 35 | func (g *gormLogger) Error(_ context.Context, msg string, data ...interface{}) { 36 | errMsg := fmt.Sprintf(msg, data...) 37 | g.gethLogger.Error("gorm", "err message", errMsg) 38 | } 39 | 40 | func (g *gormLogger) Trace(_ context.Context, begin time.Time, fc func() (string, int64), err error) { 41 | elapsed := time.Since(begin) 42 | sql, rowsAffected := fc() 43 | g.gethLogger.Debug("gorm", "line", utils.FileWithLineNum(), "cost", elapsed, "sql", sql, "rowsAffected", rowsAffected, "err", err) 44 | } 45 | 46 | // InitDB init the db handler 47 | func InitDB(config *Config) (*gorm.DB, error) { 48 | tmpGormLogger := gormLogger{ 49 | gethLogger: log.Root(), 50 | } 51 | var dialector gorm.Dialector 52 | switch config.DriverName { 53 | case "mysql": 54 | dialector = mysql.Open(config.DSN) 55 | default: 56 | return nil, fmt.Errorf("unsupported driver: %s", config.DriverName) 57 | } 58 | db, err := gorm.Open(dialector, &gorm.Config{ 59 | Logger: &tmpGormLogger, 60 | NowFunc: func() time.Time { 61 | return bridge_utils.NowUTC() 62 | }, 63 | }) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | sqlDB, pingErr := Ping(db) 69 | if pingErr != nil { 70 | return nil, pingErr 71 | } 72 | 73 | sqlDB.SetConnMaxLifetime(time.Minute * 10) 74 | sqlDB.SetConnMaxIdleTime(time.Minute * 5) 75 | 76 | sqlDB.SetMaxOpenConns(config.MaxOpenNum) 77 | sqlDB.SetMaxIdleConns(config.MaxIdleNum) 78 | 79 | return db, nil 80 | } 81 | 82 | // CloseDB close the db handler. notice the db handler only can close when then program exit. 83 | func CloseDB(db *gorm.DB) error { 84 | sqlDB, err := db.DB() 85 | if err != nil { 86 | return err 87 | } 88 | if err := sqlDB.Close(); err != nil { 89 | return err 90 | } 91 | return nil 92 | } 93 | 94 | // Ping check db status 95 | func Ping(db *gorm.DB) (*sql.DB, error) { 96 | sqlDB, err := db.DB() 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | if err = sqlDB.Ping(); err != nil { 102 | return nil, err 103 | } 104 | return sqlDB, nil 105 | } 106 | -------------------------------------------------------------------------------- /bridge/utils/database/db_test.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | "time" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var MockConfig = &Config{ 12 | DSN: "testuser:123456@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local", 13 | DriverName: "mysql", 14 | MaxOpenNum: 10, 15 | MaxIdleNum: 5, 16 | } 17 | 18 | func MockPing(db *gorm.DB) (*sql.DB, error) { 19 | sqlDB, err := db.DB() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return sqlDB, sqlDB.Ping() 24 | } 25 | 26 | func TestInitDB(t *testing.T) { 27 | db, err := InitDB(MockConfig) 28 | if err != nil { 29 | t.Fatalf("Failed to initialize database: %v", err) 30 | } 31 | defer func() { 32 | if err := CloseDB(db); err != nil { 33 | t.Fatalf("Failed to close database: %v", err) 34 | } 35 | }() 36 | 37 | sqlDB, err := db.DB() 38 | if err != nil { 39 | t.Fatalf("Failed to get sql.DB from gorm.DB: %v", err) 40 | } 41 | time.Sleep(2 * time.Second) 42 | 43 | if sqlDB.Stats().MaxOpenConnections != MockConfig.MaxOpenNum { 44 | t.Errorf("Expected MaxOpenConnections to be %d, got %d", MockConfig.MaxOpenNum, sqlDB.Stats().MaxOpenConnections) 45 | } 46 | 47 | if err := sqlDB.Ping(); err != nil { 48 | t.Fatalf("Failed to ping database: %v", err) 49 | } 50 | } 51 | 52 | type User_test struct { 53 | gorm.Model 54 | Name string 55 | Email string 56 | } 57 | 58 | func TestCreateAndGetUser(t *testing.T) { 59 | db, err := InitDB(MockConfig) 60 | if err != nil { 61 | t.Fatalf("Failed to initialize database: %v", err) 62 | } 63 | defer func() { 64 | if err := CloseDB(db); err != nil { 65 | t.Fatalf("Failed to close database: %v", err) 66 | } 67 | }() 68 | 69 | if err := db.AutoMigrate(&User_test{}); err != nil { 70 | t.Fatalf("Failed to auto migrate: %v", err) 71 | } 72 | 73 | user := User_test{Name: "John Doe", Email: "john.doe@example.com"} 74 | if err := db.Create(&user).Error; err != nil { 75 | t.Fatalf("Failed to create user: %v", err) 76 | } 77 | 78 | var retrievedUser User_test 79 | if err := db.First(&retrievedUser, user.ID).Error; err != nil { 80 | t.Fatalf("Failed to get user: %v", err) 81 | } 82 | 83 | if retrievedUser.Name != user.Name { 84 | t.Errorf("Expected user name to be %s, got %s", user.Name, retrievedUser.Name) 85 | } 86 | if retrievedUser.Email != user.Email { 87 | t.Errorf("Expected user email to be %s, got %s", user.Email, retrievedUser.Email) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /bridge/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/ethereum/go-ethereum/accounts/abi" 14 | "github.com/ethereum/go-ethereum/common" 15 | "github.com/ethereum/go-ethereum/core/types" 16 | "github.com/ethereum/go-ethereum/crypto" 17 | "github.com/ethereum/go-ethereum/ethclient" 18 | "github.com/ethereum/go-ethereum/log" 19 | "github.com/holiman/uint256" 20 | "github.com/joho/godotenv" 21 | rdoclient "github.com/reddio-com/reddio/bridge/client" 22 | "github.com/sirupsen/logrus" 23 | yu_common "github.com/yu-org/yu/common" 24 | "golang.org/x/sync/errgroup" 25 | ) 26 | 27 | // Loop Run the f func periodically. 28 | func Loop(ctx context.Context, period time.Duration, f func()) { 29 | tick := time.NewTicker(period) 30 | defer tick.Stop() 31 | for ; ; <-tick.C { 32 | select { 33 | case <-ctx.Done(): 34 | return 35 | default: 36 | f() 37 | } 38 | } 39 | } 40 | 41 | // UnpackLog unpacks a retrieved log into the provided output structure. 42 | // @todo: add unit test. 43 | func UnpackLog(c *abi.ABI, out interface{}, event string, log types.Log) error { 44 | if log.Topics[0] != c.Events[event].ID { 45 | return errors.New("event signature mismatch") 46 | } 47 | if len(log.Data) > 0 { 48 | if err := c.UnpackIntoInterface(out, event, log.Data); err != nil { 49 | logrus.Error("unpack event data error:", err) 50 | return err 51 | } 52 | } 53 | var indexed abi.Arguments 54 | for _, arg := range c.Events[event].Inputs { 55 | if arg.Indexed { 56 | indexed = append(indexed, arg) 57 | } 58 | } 59 | return abi.ParseTopics(out, indexed, log.Topics[1:]) 60 | } 61 | 62 | func ConvertHashToYuHash(hash common.Hash) (yu_common.Hash, error) { 63 | var yuHash [yu_common.HashLen]byte 64 | if len(hash.Bytes()) == yu_common.HashLen { 65 | copy(yuHash[:], hash.Bytes()) 66 | return yuHash, nil 67 | } else { 68 | return yu_common.Hash{}, errors.New(fmt.Sprintf("Expected hash to be 32 bytes long, but got %d bytes", len(hash.Bytes()))) 69 | } 70 | } 71 | 72 | func ConvertBigIntToUint256(b *big.Int) *uint256.Int { 73 | if b == nil { 74 | return nil 75 | } 76 | u, _ := uint256.FromBig(b) 77 | return u 78 | } 79 | 80 | func ObjToJson(obj interface{}) string { 81 | byt, err := json.Marshal(obj) 82 | if err != nil { 83 | fmt.Printf("Error marshalling obj to json: %v\n", err) 84 | return "" 85 | } 86 | return string(byt) 87 | } 88 | func LoadPrivateKey(envFilePath string, envPramName string) (string, error) { 89 | err := godotenv.Load(envFilePath) 90 | if err != nil { 91 | return "", err 92 | } 93 | 94 | privateKey := os.Getenv(envPramName) 95 | if privateKey == "" { 96 | return "", fmt.Errorf("PRIVATE_KEY not set in %s", envFilePath) 97 | } 98 | 99 | return privateKey, nil 100 | } 101 | 102 | func GenerateNonce() *big.Int { 103 | return big.NewInt(time.Now().UnixNano()) 104 | } 105 | 106 | func NowUTC() time.Time { 107 | utc, _ := time.LoadLocation("") 108 | return time.Now().In(utc) 109 | } 110 | 111 | // ConvertStringToStringArray takes a string with values separated by commas and returns a slice of strings 112 | func ConvertStringToStringArray(s string) []string { 113 | if s == "" { 114 | return []string{} 115 | } 116 | stringParts := strings.Split(s, ",") 117 | for i, part := range stringParts { 118 | stringParts[i] = strings.TrimSpace(part) 119 | } 120 | return stringParts 121 | } 122 | 123 | func ComputeMessageHash(payloadType uint32, payload []byte, nonce *big.Int) (common.Hash, error) { 124 | packedData, err := abi.Arguments{ 125 | {Type: abi.Type{T: abi.UintTy, Size: 32}}, // Use UintTy with size 32 for uint32 126 | {Type: abi.Type{T: abi.BytesTy}}, 127 | {Type: abi.Type{T: abi.UintTy, Size: 256}}, // Use UintTy with size 256 for *big.Int 128 | }.Pack(payloadType, payload, nonce) 129 | if err != nil { 130 | logrus.Fatalf("Failed to pack data: %v", err) 131 | } 132 | 133 | dataHash := crypto.Keccak256Hash(packedData) 134 | return dataHash, nil 135 | } 136 | 137 | func GetBlockNumber(ctx context.Context, client *ethclient.Client, confirmations uint64) (uint64, error) { 138 | number, err := client.BlockNumber(ctx) 139 | if err != nil || number <= confirmations { 140 | return 0, err 141 | } 142 | number = number - confirmations 143 | return number, nil 144 | } 145 | 146 | // GetBlocksInRange gets a batch of blocks for a block range [start, end] inclusive. 147 | func GetBlocksInRange(ctx context.Context, cli *ethclient.Client, start, end uint64) ([]*types.Block, error) { 148 | var ( 149 | eg errgroup.Group 150 | blocks = make([]*types.Block, end-start+1) 151 | concurrency = 32 152 | sem = make(chan struct{}, concurrency) 153 | ) 154 | 155 | for i := start; i <= end; i++ { 156 | sem <- struct{}{} // Acquire a slot in the semaphore 157 | blockNum := int64(i) 158 | index := i - start 159 | eg.Go(func() error { 160 | defer func() { <-sem }() // Release the slot when done 161 | block, err := cli.BlockByNumber(ctx, big.NewInt(blockNum)) 162 | if err != nil { 163 | log.Error("Failed to fetch block number", "number", blockNum, "error", err) 164 | return err 165 | } 166 | blocks[index] = block 167 | return nil 168 | }) 169 | } 170 | 171 | if err := eg.Wait(); err != nil { 172 | log.Error("Error waiting for block fetching routines", "error", err) 173 | return nil, err 174 | } 175 | return blocks, nil 176 | } 177 | func GetRdoBlockNumber(ctx context.Context, client *rdoclient.Client, confirmations uint64) (uint64, error) { 178 | number, err := client.BlockNumber(ctx) 179 | if err != nil || number <= confirmations { 180 | return 0, err 181 | } 182 | number = number - confirmations 183 | return number, nil 184 | } 185 | 186 | // GetBlocksInRange gets a batch of blocks for a block range [start, end] inclusive. 187 | func GetRdoBlocksInRange(ctx context.Context, cli *rdoclient.Client, start, end uint64) ([]*rdoclient.RdoBlock, error) { 188 | var ( 189 | eg errgroup.Group 190 | blocks = make([]*rdoclient.RdoBlock, end-start+1) 191 | concurrency = 32 192 | sem = make(chan struct{}, concurrency) 193 | ) 194 | 195 | for i := start; i <= end; i++ { 196 | sem <- struct{}{} // Acquire a slot in the semaphore 197 | blockNum := int64(i) 198 | index := i - start 199 | eg.Go(func() error { 200 | defer func() { <-sem }() // Release the slot when done 201 | block, err := cli.RdoBlockByNumber(ctx, big.NewInt(blockNum)) 202 | if err != nil { 203 | log.Error("Failed to fetch block number", "number", blockNum, "error", err) 204 | return err 205 | } 206 | blocks[index] = block 207 | return nil 208 | }) 209 | } 210 | 211 | if err := eg.Wait(); err != nil { 212 | log.Error("Error waiting for block fetching routines", "error", err) 213 | return nil, err 214 | } 215 | return blocks, nil 216 | } 217 | -------------------------------------------------------------------------------- /bridge/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "math/big" 7 | "os" 8 | "testing" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/stretchr/testify/assert" 12 | "golang.org/x/crypto/sha3" 13 | ) 14 | 15 | // TestLoadPrivateKey tests the loadPrivateKey function 16 | func TestLoadPrivateKey(t *testing.T) { 17 | wd, err := os.Getwd() 18 | if err != nil { 19 | t.Fatalf("Failed to get current working directory: %v", err) 20 | } 21 | t.Logf("Current working directory: %s", wd) 22 | // Create a temporary .env file 23 | envFilePath := ".env.test" 24 | privateKey := "test_private_key" 25 | envContent := fmt.Sprintf("PRIVATE_KEY=%s\n", privateKey) 26 | err = os.WriteFile(envFilePath, []byte(envContent), 0644) 27 | assert.NoError(t, err) 28 | 29 | // Ensure the temporary .env file is removed after the test 30 | defer os.Remove(envFilePath) 31 | 32 | // Test case: Successfully load private key 33 | t.Run("Success", func(t *testing.T) { 34 | loadedKey, err := LoadPrivateKey(envFilePath, "PRIVATE_KEY") 35 | assert.NoError(t, err) 36 | assert.Equal(t, privateKey, loadedKey) 37 | }) 38 | t.Run("Success2", func(t *testing.T) { 39 | _, err := LoadPrivateKey("../relayer/.sepolia.env", "PRIVATE_KEY") 40 | assert.NoError(t, err) 41 | 42 | }) 43 | // Test case: .env file does not exist 44 | t.Run("FileNotExist", func(t *testing.T) { 45 | _, err := LoadPrivateKey("nonexistent.env", "PRIVATE_KEY") 46 | assert.Error(t, err) 47 | }) 48 | 49 | // Test case: RELAYER_PRIVATE_KEY not set in .env file 50 | } 51 | func TestComputeMessageHash(t *testing.T) { 52 | payloadType := uint32(4) 53 | payloadHex := "0000000000000000000000002655fc00139e0274dd0d84270fd80150b5f25426000000000000000000000000994314a99177eee8554fb0d0a246f3a3ea4ef56c000000000000000000000000994314a99177eee8554fb0d0a246f3a3ea4ef56c0000000000000000000000000000000000000000000000000de0b6b3a7640000" 54 | payload, err := hex.DecodeString(payloadHex) 55 | if err != nil { 56 | t.Fatalf("Failed to decode hex string: %v", err) 57 | } 58 | nonce := big.NewInt(1579416) 59 | 60 | expectedHash := common.HexToHash("0xb68fe48d80c53ad8794b9f8a147d14ca9f9e8f181a8a2eecd5d8e239a74b34fa") 61 | 62 | hash, err := ComputeMessageHash(payloadType, payload, nonce) 63 | if err != nil { 64 | t.Fatalf("ComputeMessageHash failed: %v", err) 65 | } 66 | if hash != expectedHash { 67 | t.Errorf("Expected hash %s, got %s", expectedHash.Hex(), hash.Hex()) 68 | } 69 | } 70 | func TestStorage(t *testing.T) { 71 | key := "child.bridge.core.storage" 72 | 73 | hash := sha3.NewLegacyKeccak256() 74 | hash.Write([]byte(key)) 75 | result := hash.Sum(nil) 76 | 77 | address := fmt.Sprintf("%x", result) 78 | t.Logf("Address: %s", address) 79 | expectedAddress := "some_incorrect_address" 80 | if address != expectedAddress { 81 | t.Errorf("Expected address %s, but got %s", expectedAddress, address) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cmd/node/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/sirupsen/logrus" 8 | 9 | "github.com/reddio-com/reddio/cmd/node/app" 10 | "github.com/reddio-com/reddio/utils/s3" 11 | ) 12 | 13 | var ( 14 | evmConfigPath string 15 | yuConfigPath string 16 | PoaConfigPath string 17 | ReddioConfigPath string 18 | 19 | loadConfigType string 20 | folder string 21 | Bucket string 22 | ) 23 | 24 | func init() { 25 | flag.StringVar(&evmConfigPath, "evm-config", "./conf/evm.toml", "path to evm-config file") 26 | flag.StringVar(&yuConfigPath, "yu-config", "./conf/yu.toml", "path to yu-config file") 27 | flag.StringVar(&PoaConfigPath, "poa-config", "./conf/poa.toml", "path to poa-config file") 28 | flag.StringVar(&ReddioConfigPath, "reddio-config", "./conf/config.toml", "path to reddio-config file") 29 | flag.StringVar(&loadConfigType, "load-config-type", "file", "load-config-type json") 30 | flag.StringVar(&folder, "folder", "", "path to bucket folder") 31 | flag.StringVar(&Bucket, "bucket", "", "s3 bucket name") 32 | } 33 | 34 | func main() { 35 | flag.Parse() 36 | switch loadConfigType { 37 | case "s3": 38 | logrus.Info("load config from s3") 39 | if len(Bucket) < 1 { 40 | panic(fmt.Errorf("s3 bucket name is required")) 41 | } 42 | s3Config, err := s3.InitS3Config(folder, Bucket) 43 | if err != nil { 44 | panic(err) 45 | } 46 | app.StartByCfgData(s3Config.GetConfig()) 47 | case "file": 48 | logrus.Info("load config from file") 49 | app.Start(evmConfigPath, yuConfigPath, PoaConfigPath, ReddioConfigPath) 50 | default: 51 | logrus.Info("load config from file") 52 | app.Start(evmConfigPath, yuConfigPath, PoaConfigPath, ReddioConfigPath) 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /conf/config.toml: -------------------------------------------------------------------------------- 1 | isParallel = true 2 | enableAsyncCommit = false 3 | maxConcurrency = 4 4 | isBenchmarkMode = false 5 | ignoreConflict = false 6 | 7 | [rateLimitConfig] 8 | getReceipt = 20000 9 | -------------------------------------------------------------------------------- /conf/evm.toml: -------------------------------------------------------------------------------- 1 | chain_id = 50341 2 | is_reddio_mainnet = false 3 | enable_eth_rpc = true 4 | eth_host = "0.0.0.0" 5 | eth_port = "9092" 6 | 7 | 8 | # [Module:Watcher] 9 | enable_bridge = false 10 | # note: need websocket rpc to listen to the L1 event 11 | ## use your own node address 12 | l1_client_address = "" 13 | l2_client_address = "" 14 | parentlayer_contract_address = "" 15 | childlayer_contract_address = "" 16 | 17 | # seconds l1 block time 18 | # Used to collect L2->L1 cross-chain messages from L2 blocks. 'n' indicates collecting once every 'n' L2 blocks, collecting cross-chain messages from 'n' L2 blocks at a time. 19 | l2_block_collection_depth = 5 20 | 21 | #[bridge_api] 22 | bridge_port = "8888" 23 | bridge_host = "0.0.0.0" 24 | 25 | #[relayer_config] 26 | relayer_batch_size = 500 27 | multisig_env_file = "" 28 | multisig_env_var = "" 29 | relayer_env_file = "" 30 | relayer_env_var = "" 31 | l1_raw_bridge_events_table_name = "" 32 | l2_raw_bridge_events_table_name = "" 33 | 34 | 35 | #checker 36 | enable_bridge_checker = false 37 | 38 | [l1_watcher_config] 39 | confirmation = 5 40 | fetch_limit = 16 41 | start_height = 7678638 42 | block_time = 12 43 | chain_id = 11155111 44 | 45 | [l2_watcher_config] 46 | confirmation = 5 47 | fetch_limit = 16 48 | start_height = 180 49 | block_time = 12 50 | chain_id = 50341 51 | 52 | [bridge_checker_config] 53 | check_l1_contract_address = "" 54 | check_l2_contract_address = "" 55 | enable_l1_check_step1 = false 56 | enable_l1_check_step2 = false 57 | enable_l2_check_step1 = false 58 | enable_l2_check_step2 = false 59 | checker_batch_size = 500 60 | sepolia_ticker_interval = 10 #seconds 61 | reddio_ticker_interval = 15 62 | 63 | 64 | [bridge_db_config] 65 | dsn = "testuser:123456@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local" 66 | driverName = "mysql" 67 | maxOpenNum = 10 68 | maxIdleNum = 5 69 | -------------------------------------------------------------------------------- /conf/poa.toml: -------------------------------------------------------------------------------- 1 | key_type = "sr25519" 2 | my_secret = "node1" 3 | block_interval = 3000 4 | pack_num = 30000 5 | pretty_log = false 6 | 7 | [[validators]] 8 | pubkey = "0x31f40d8bd828cdd35a172571639ac309a673935a5298a28096b842eccc84aeab17" 9 | p2p_ip = "12D3KooWHHzSeKaY8xuZVzkLbKFfvNgPPeKhFBGrMbNzbm5akpqu" 10 | 11 | [[validators]] 12 | pubkey = "0x31123ce471d94b1b9eaf3aaf95d287d70544ec3326c0b98459efebd8b7b9e48223" 13 | p2p_ip = "12D3KooWSKPs95miv8wzj3fa5HkJ1tH7oEGumsEiD92n2MYwRtQG" 14 | 15 | [[validators]] 16 | pubkey = "0x31fc4636448ab2470bb8d9f2ebbba1e9f702d8d26121067f022089cbd8b38bf322" 17 | p2p_ip = "12D3KooWRuwP7nXaRhZrmoFJvPPGat2xPafVmGpQpZs5zKMtwqPH" 18 | -------------------------------------------------------------------------------- /conf/yu.toml: -------------------------------------------------------------------------------- 1 | node_type = 0 2 | data_dir = "yu" 3 | sync_mode = 0 4 | run_mode = 0 5 | grpc_port = "" 6 | http_port = "7999" 7 | ws_port = "8999" 8 | log_level = "info" 9 | #log_output = "yu/yu.log" 10 | lei_limit = 50000 11 | statedb_type = "no" 12 | 13 | is_admin = false 14 | max_block_num = 0 15 | enable_pprof = true 16 | pprof_port = "10199" 17 | 18 | [kvdb] 19 | kv_type = "pebble" 20 | path = "yu.db" 21 | 22 | [block_chain] 23 | chain_id = 0 24 | cache_size = 10 25 | [block_chain.chain_db] 26 | sql_db_type = "sqlite" 27 | dsn = "chain.db" 28 | 29 | [txpool] 30 | pool_size = 20480 31 | txn_max_size = 1024000 32 | 33 | [p2p] 34 | p2p_listen_addrs = ["/ip4/127.0.0.1/tcp/8887"] 35 | protocol_id = "yu" 36 | node_key_type = 1 37 | node_key_rand_seed = 1 38 | node_key = "" 39 | node_key_bits = 0 40 | node_key_file = "" 41 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | 7 | "github.com/BurntSushi/toml" 8 | ) 9 | 10 | type Config struct { 11 | IsParallel bool `yaml:"isParallel"` 12 | MaxConcurrency int `yaml:"maxConcurrency"` 13 | IsBenchmarkMode bool `yaml:"isBenchmarkMode"` 14 | AsyncCommit bool `yaml:"asyncCommit"` 15 | IgnoreConflict bool `yaml:"ignoreConflict"` 16 | RateLimitConfig RateLimitConfig `yaml:"rateLimitConfig"` 17 | } 18 | 19 | type RateLimitConfig struct { 20 | GetReceipt int64 `yaml:"getReceipt"` 21 | } 22 | 23 | func defaultConfig() *Config { 24 | return &Config{ 25 | AsyncCommit: false, 26 | IsParallel: true, 27 | MaxConcurrency: runtime.NumCPU(), 28 | RateLimitConfig: RateLimitConfig{ 29 | GetReceipt: 2000, 30 | }, 31 | } 32 | } 33 | 34 | var GlobalConfig *Config 35 | 36 | func init() { 37 | GlobalConfig = defaultConfig() 38 | } 39 | 40 | func GetGlobalConfig() *Config { 41 | return GlobalConfig 42 | } 43 | 44 | func LoadConfig(path string) error { 45 | if path == "" { 46 | return nil 47 | } 48 | content, err := os.ReadFile(path) 49 | if err != nil { 50 | return err 51 | } 52 | c := &Config{} 53 | err = toml.Unmarshal(content, c) 54 | if err != nil { 55 | return err 56 | } 57 | GlobalConfig = c 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /contract/const.go: -------------------------------------------------------------------------------- 1 | package contract 2 | 3 | import ( 4 | common2 "github.com/ethereum/go-ethereum/common" 5 | ) 6 | 7 | var ( 8 | //0xeC054c6ee2DbbeBC9EbCA50CdBF94A94B02B2E40 9 | TestBridgeContractAddress = common2.HexToAddress("0xA3ED8915aE346bF85E56B6BB6b723091716f58b4") 10 | TestStorageSlotHash = common2.HexToHash("0x65d63ba7ddf496eb3f7a10c642f6d20487aee1873bcad4890f640b167eab1069") 11 | ) 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | reddio: 5 | image: ghcr.io/reddio-com/reddio:latest 6 | restart: always 7 | volumes: 8 | # config 9 | - ./data/conf:/conf 10 | # database 11 | - ./data/reddio_db:/reddio_db 12 | - ./data/yu:/yu 13 | ports: 14 | # p2p port 15 | - 8887:8887 16 | # http port 17 | - 7999:7999 18 | # websocket port 19 | - 8999:8999 20 | # ETH RPC port 21 | - 9092:9092 22 | # pprof port 23 | - 8080:8080 24 | -------------------------------------------------------------------------------- /docs/images/reddio_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reddio-com/reddio/04f62cfe49d6c2c3ad801c6a33dc8c664047dc35/docs/images/reddio_arch.png -------------------------------------------------------------------------------- /docs/images/reddio_running.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reddio-com/reddio/04f62cfe49d6c2c3ad801c6a33dc8c664047dc35/docs/images/reddio_running.jpg -------------------------------------------------------------------------------- /evm/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "time" 4 | 5 | type Config struct { 6 | // Enables VM tracing 7 | VMTrace string `toml:"vm_trace"` 8 | VMTraceConfig string `toml:"vm_trace_config"` 9 | // Enables tracking of SHA3 preimages in the VM 10 | EnablePreimageRecording bool `toml:"enable_preimage_recording"` 11 | 12 | // snapshots config 13 | Recovery bool `toml:"recovery"` 14 | NoBuild bool `toml:"no_build"` 15 | SnapshotWait bool `toml:"snapshot_wait"` 16 | SnapshotCache int `toml:"snapshot_cache"` 17 | 18 | // cache config 19 | TrieCleanCache int `toml:"trie_clean_cache"` 20 | TrieDirtyCache int `toml:"trie_dirty_cache"` 21 | TrieTimeout time.Duration `toml:"trie_timeout"` 22 | Preimages bool `toml:"preimages"` 23 | NoPruning bool `toml:"no_pruning"` 24 | NoPrefetch bool `toml:"no_prefetch"` 25 | StateHistory uint64 `toml:"state_history"` 26 | 27 | StateScheme string `toml:"state_scheme"` 28 | 29 | // database 30 | DbPath string `toml:"db_path"` 31 | DbType string `toml:"db_type"` // pebble 32 | NameSpace string `toml:"name_space"` // eth/db/chaindata/ 33 | Ancient string `toml:"ancient"` 34 | Cache int `toml:"cache"` 35 | Handles int `toml:"handles"` 36 | } 37 | -------------------------------------------------------------------------------- /evm/errors.go: -------------------------------------------------------------------------------- 1 | package evm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | "github.com/ethereum/go-ethereum/core/vm" 10 | ) 11 | 12 | // From geth/core/txpool/errors 13 | var ( 14 | // ErrAlreadyKnown is returned if the transaction is already contained 15 | // within the pool. 16 | ErrAlreadyKnown = errors.New("already known") 17 | 18 | ErrNotFoundReceipt = errors.New("receipt not found") 19 | ) 20 | 21 | // RevertError is an API error that encompasses an EVM revert with JSON error 22 | // code and a binary data blob. 23 | type RevertError struct { 24 | error 25 | reason string // revert reason hex encoded 26 | } 27 | 28 | // ErrorCode returns the JSON error code for a revert. 29 | // See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal 30 | func (e *RevertError) ErrorCode() int { 31 | return 3 32 | } 33 | 34 | // ErrorData returns the hex encoded revert reason. 35 | func (e *RevertError) ErrorData() interface{} { 36 | return e.reason 37 | } 38 | 39 | // NewRevertError creates a RevertError instance with the provided revert data. 40 | func NewRevertError(revert []byte) *RevertError { 41 | err := vm.ErrExecutionReverted 42 | 43 | reason, errUnpack := abi.UnpackRevert(revert) 44 | if errUnpack == nil { 45 | err = fmt.Errorf("%w: %v", vm.ErrExecutionReverted, reason) 46 | } 47 | return &RevertError{ 48 | error: err, 49 | reason: hexutil.Encode(revert), 50 | } 51 | } 52 | 53 | // TxIndexingError is an API error that indicates the transaction indexing is not 54 | // fully finished yet with JSON error code and a binary data blob. 55 | type TxIndexingError struct{} 56 | 57 | // NewTxIndexingError creates a TxIndexingError instance. 58 | func NewTxIndexingError() *TxIndexingError { return &TxIndexingError{} } 59 | 60 | // Error implement error interface, returning the error message. 61 | func (e *TxIndexingError) Error() string { 62 | return "transaction indexing is in progress" 63 | } 64 | 65 | // ErrorCode returns the JSON error code for a revert. 66 | // See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal 67 | func (e *TxIndexingError) ErrorCode() int { 68 | return -32000 // to be decided 69 | } 70 | 71 | // ErrorData returns the hex encoded revert reason. 72 | func (e *TxIndexingError) ErrorData() interface{} { return "transaction indexing is in progress" } 73 | -------------------------------------------------------------------------------- /evm/ethrpc/addrlock.go: -------------------------------------------------------------------------------- 1 | package ethrpc 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | ) 8 | 9 | type AddrLocker struct { 10 | mu sync.Mutex 11 | locks map[common.Address]*sync.Mutex 12 | } 13 | 14 | // lock returns the lock of the given address. 15 | func (l *AddrLocker) lock(address common.Address) *sync.Mutex { 16 | l.mu.Lock() 17 | defer l.mu.Unlock() 18 | if l.locks == nil { 19 | l.locks = make(map[common.Address]*sync.Mutex) 20 | } 21 | if _, ok := l.locks[address]; !ok { 22 | l.locks[address] = new(sync.Mutex) 23 | } 24 | return l.locks[address] 25 | } 26 | 27 | // LockAddr locks an account's mutex. This is used to prevent another tx getting the 28 | // same nonce until the lock is released. The mutex prevents the (an identical nonce) from 29 | // being read again during the time that the first transaction is being signed. 30 | func (l *AddrLocker) LockAddr(address common.Address) { 31 | l.lock(address).Lock() 32 | } 33 | 34 | // UnlockAddr unlocks the mutex of the given account. 35 | func (l *AddrLocker) UnlockAddr(address common.Address) { 36 | l.lock(address).Unlock() 37 | } 38 | -------------------------------------------------------------------------------- /evm/ethrpc/backend.go: -------------------------------------------------------------------------------- 1 | package ethrpc 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | "time" 7 | 8 | "github.com/ethereum/go-ethereum" 9 | "github.com/ethereum/go-ethereum/accounts" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/common/hexutil" 12 | "github.com/ethereum/go-ethereum/consensus" 13 | "github.com/ethereum/go-ethereum/core" 14 | "github.com/ethereum/go-ethereum/core/bloombits" 15 | "github.com/ethereum/go-ethereum/core/state" 16 | "github.com/ethereum/go-ethereum/core/types" 17 | "github.com/ethereum/go-ethereum/core/vm" 18 | "github.com/ethereum/go-ethereum/ethdb" 19 | "github.com/ethereum/go-ethereum/event" 20 | "github.com/ethereum/go-ethereum/params" 21 | "github.com/ethereum/go-ethereum/rpc" 22 | yutypes "github.com/yu-org/yu/core/types" 23 | ) 24 | 25 | // Backend interface provides the common API services (that are provided by 26 | // both full and light clients) with access to necessary functions. 27 | type Backend interface { 28 | // General Ethereum API 29 | SyncProgress() ethereum.SyncProgress 30 | 31 | SuggestGasTipCap(ctx context.Context) (*big.Int, error) 32 | FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) 33 | BlobBaseFee(ctx context.Context) *big.Int 34 | ChainDb() ethdb.Database 35 | AccountManager() *accounts.Manager 36 | ExtRPCEnabled() bool 37 | RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection 38 | RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection 39 | RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs 40 | UnprotectedAllowed() bool // allows only for EIP155 transactions. 41 | 42 | // Blockchain API 43 | SetHead(number uint64) 44 | HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, *yutypes.Header, error) 45 | HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, *yutypes.Header, error) 46 | HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, *yutypes.Header, error) 47 | CurrentHeader() *types.Header 48 | CurrentBlock() *types.Header 49 | BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, *yutypes.Block, error) 50 | BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, *yutypes.Block, error) 51 | BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, *yutypes.Block, error) 52 | StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) 53 | StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) 54 | Pending() (*types.Block, types.Receipts, *state.StateDB) 55 | GetReceipt(ctx context.Context, txnHash common.Hash) (*types.Receipt, error) 56 | GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) 57 | GetTd(ctx context.Context, hash common.Hash) *big.Int 58 | GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM 59 | SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription 60 | SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription 61 | SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription 62 | 63 | // Transaction pool API 64 | SendTx(ctx context.Context, signedTx *types.Transaction) error 65 | Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) 66 | GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) 67 | GetPoolTransactions() (types.Transactions, error) 68 | GetPoolTransaction(txHash common.Hash) (*types.Transaction, error) 69 | GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) 70 | Stats() (pending int, queued int) 71 | TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) 72 | TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) 73 | SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription 74 | 75 | ChainConfig() *params.ChainConfig 76 | Engine() consensus.Engine 77 | 78 | // This is copied from filters.Backend 79 | // eth/filters needs to be initialized from this backend type, so methods needed by 80 | // it must also be included here. 81 | GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) 82 | GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) 83 | SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription 84 | SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 85 | BloomStatus() (uint64, uint64) 86 | ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) 87 | } 88 | 89 | func GetAPIs(apiBackend Backend) []rpc.API { 90 | nonceLock := new(AddrLocker) 91 | return []rpc.API{ 92 | { 93 | Namespace: "eth", 94 | Service: NewEthereumAPI(apiBackend), 95 | }, { 96 | Namespace: "eth", 97 | Service: NewBlockChainAPI(apiBackend), 98 | }, { 99 | Namespace: "eth", 100 | Service: NewTransactionAPI(apiBackend, nonceLock), 101 | }, { 102 | Namespace: "debug", 103 | Service: NewDebugAPI(apiBackend), 104 | }, { 105 | Namespace: "net", 106 | Service: NewNetAPI(nil, 0), 107 | }, { 108 | Namespace: "web3", 109 | Service: NewWeb3API(apiBackend), 110 | }, 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /evm/ethrpc/metrics.go: -------------------------------------------------------------------------------- 1 | package ethrpc 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | const ( 6 | TypeLbl = "type" 7 | TypeCountLbl = "count" 8 | TypeStatusLbl = "status" 9 | ) 10 | 11 | var ( 12 | EthApiBackendCounter = prometheus.NewCounterVec( 13 | prometheus.CounterOpts{ 14 | Namespace: "reddio", 15 | Subsystem: "eth_api_backend", 16 | Name: "op_counter", 17 | Help: "Total Operator number of counter", 18 | }, 19 | []string{TypeLbl}, 20 | ) 21 | 22 | EthApiBackendDuration = prometheus.NewHistogramVec( 23 | prometheus.HistogramOpts{ 24 | Namespace: "reddio", 25 | Subsystem: "eth_api_backend", 26 | Name: "op_duration_microseconds", 27 | Help: "Operation Duration", 28 | Buckets: prometheus.ExponentialBuckets(10, 2, 20), // 10us ~ 5s 29 | }, 30 | []string{TypeLbl}, 31 | ) 32 | 33 | TransactionAPICounter = prometheus.NewCounterVec( 34 | prometheus.CounterOpts{ 35 | Namespace: "reddio", 36 | Subsystem: "transaction_api", 37 | Name: "counter", 38 | Help: "Total Operator number of counter", 39 | }, 40 | []string{TypeLbl}, 41 | ) 42 | ) 43 | 44 | func init() { 45 | prometheus.MustRegister(EthApiBackendCounter) 46 | prometheus.MustRegister(EthApiBackendDuration) 47 | prometheus.MustRegister(TransactionAPICounter) 48 | } 49 | -------------------------------------------------------------------------------- /evm/ethrpc/rpc.go: -------------------------------------------------------------------------------- 1 | package ethrpc 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "net" 9 | "net/http" 10 | "strings" 11 | "time" 12 | 13 | "github.com/ethereum/go-ethereum/rpc" 14 | "github.com/rs/cors" 15 | "github.com/sirupsen/logrus" 16 | "github.com/sourcegraph/conc" 17 | "github.com/yu-org/yu/core/kernel" 18 | 19 | "github.com/reddio-com/reddio/evm" 20 | ) 21 | 22 | const SolidityTripod = "solidity" 23 | 24 | type EthRPC struct { 25 | chain *kernel.Kernel 26 | cfg *evm.GethConfig 27 | srv *http.Server 28 | rpcServer *rpc.Server 29 | } 30 | 31 | func StartupEthRPC(chain *kernel.Kernel, cfg *evm.GethConfig) { 32 | if cfg.EnableEthRPC { 33 | rpcSrv, err := NewEthRPC(chain, cfg) 34 | if err != nil { 35 | logrus.Fatalf("init EthRPC server failed, %v", err) 36 | } 37 | ctx, cancel := context.WithCancel(context.Background()) 38 | go func() { 39 | defer cancel() 40 | err = rpcSrv.Serve(ctx) 41 | if err != nil { 42 | logrus.Errorf("starknetRPC serves failed, %v", err) 43 | } 44 | }() 45 | } 46 | } 47 | 48 | func NewEthRPC(chain *kernel.Kernel, cfg *evm.GethConfig) (*EthRPC, error) { 49 | s := &EthRPC{ 50 | chain: chain, 51 | cfg: cfg, 52 | rpcServer: rpc.NewServer(), 53 | } 54 | logrus.Debug("Start EthRpc at ", net.JoinHostPort(cfg.EthHost, cfg.EthPort)) 55 | backend := &EthAPIBackend{ 56 | allowUnprotectedTxs: true, 57 | chain: chain, 58 | ethChainCfg: cfg.ChainConfig, 59 | } 60 | backend.gasPriceCache = NewEthGasPrice(backend) 61 | 62 | apis := GetAPIs(backend) 63 | for _, api := range apis { 64 | err := s.rpcServer.RegisterName(api.Namespace, api.Service) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } 69 | 70 | mux := http.NewServeMux() 71 | mux.Handle("/", logRequestResponse(s.rpcServer)) 72 | 73 | s.srv = &http.Server{ 74 | Addr: net.JoinHostPort(cfg.EthHost, cfg.EthPort), 75 | Handler: cors.Default().Handler(mux), 76 | ReadTimeout: 30 * time.Second, 77 | } 78 | 79 | return s, nil 80 | } 81 | 82 | func (s *EthRPC) Serve(ctx context.Context) error { 83 | errCh := make(chan error) 84 | defer close(errCh) 85 | 86 | var wg conc.WaitGroup 87 | defer wg.Wait() 88 | wg.Go(func() { 89 | if err := s.srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { 90 | errCh <- err 91 | } 92 | }) 93 | 94 | select { 95 | case <-ctx.Done(): 96 | return s.srv.Shutdown(context.Background()) 97 | case err := <-errCh: 98 | return err 99 | } 100 | } 101 | 102 | func logRequestResponse(next http.Handler) http.Handler { 103 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 104 | //ip := getIP(r) 105 | bodyBytes, err := io.ReadAll(r.Body) 106 | if err == nil { 107 | r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) 108 | } 109 | //logrus.Infof("[API] IP,request: %s, %s", ip, string(bodyBytes)) 110 | 111 | rec := &responseRecorder{ResponseWriter: w, body: &bytes.Buffer{}} 112 | next.ServeHTTP(rec, r) 113 | logrus.Debugf("[API] Request: %s", string(bodyBytes)) 114 | //logrus.Debugf("[API] IP: %s", ip) 115 | logrus.Debugf("[API] Response: %s", rec.body.String()) 116 | }) 117 | } 118 | 119 | type responseRecorder struct { 120 | http.ResponseWriter 121 | status int 122 | body *bytes.Buffer 123 | } 124 | 125 | func (r *responseRecorder) WriteHeader(status int) { 126 | r.status = status 127 | r.ResponseWriter.WriteHeader(status) 128 | } 129 | 130 | func (r *responseRecorder) Write(b []byte) (int, error) { 131 | r.body.Write(b) 132 | return r.ResponseWriter.Write(b) 133 | } 134 | 135 | func getIP(r *http.Request) string { 136 | cfConnectingIP := r.Header.Get("CF-Connecting-IP") 137 | if cfConnectingIP != "" { 138 | return cfConnectingIP 139 | } 140 | 141 | xff := r.Header.Get("X-Forwarded-For") 142 | if xff != "" { 143 | ips := strings.Split(xff, ",") 144 | if len(ips) > 0 { 145 | return strings.TrimSpace(ips[0]) 146 | } 147 | } 148 | 149 | xri := r.Header.Get("X-Real-Ip") 150 | if xri != "" { 151 | return xri 152 | } 153 | 154 | ip := r.RemoteAddr 155 | if colon := strings.LastIndex(ip, ":"); colon != -1 { 156 | ip = ip[:colon] 157 | } 158 | return ip 159 | } 160 | -------------------------------------------------------------------------------- /evm/pending_state/ps_wrapper_conflict.go: -------------------------------------------------------------------------------- 1 | package pending_state 2 | 3 | import "github.com/ethereum/go-ethereum/common" 4 | 5 | func (sctx *StateContext) WriteConflict(addr common.Address, txnID int64) bool { 6 | wa := sctx.Write.Address[addr] 7 | ra := sctx.Read.Address[addr] 8 | if checkVisitedTxnConflict(txnID, wa) || checkVisitedTxnConflict(txnID, ra) { 9 | return true 10 | } 11 | return false 12 | } 13 | 14 | func (sctx *StateContext) ReadConflict(addr common.Address, txnID int64) bool { 15 | ra := sctx.Read.Address[addr] 16 | return checkVisitedTxnConflict(txnID, ra) 17 | } 18 | 19 | func checkVisitedTxnConflict(txnID int64, w VisitTxnID) bool { 20 | if len(w) < 1 { 21 | return false 22 | } 23 | if len(w) == 1 { 24 | _, ok := w[txnID] 25 | if ok { 26 | return false 27 | } 28 | } 29 | return true 30 | } 31 | -------------------------------------------------------------------------------- /evm/pending_state/statedb_wrapper.go: -------------------------------------------------------------------------------- 1 | package pending_state 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/core/state" 8 | "github.com/ethereum/go-ethereum/core/tracing" 9 | "github.com/ethereum/go-ethereum/core/types" 10 | "github.com/ethereum/go-ethereum/params" 11 | "github.com/holiman/uint256" 12 | ) 13 | 14 | // StateDBWrapper provides a pending state for a transaction. 15 | type StateDBWrapper struct { 16 | sync.RWMutex 17 | state *state.StateDB 18 | } 19 | 20 | func NewStateDBWrapper(db *state.StateDB) *StateDBWrapper { 21 | return &StateDBWrapper{ 22 | state: db, 23 | } 24 | } 25 | 26 | func (s *StateDBWrapper) SetTxContext(txHash common.Hash, txIndex int) { 27 | s.Lock() 28 | defer s.Unlock() 29 | s.state.SetTxContext(txHash, txIndex) 30 | } 31 | 32 | func (s *StateDBWrapper) GetStateDB() *state.StateDB { 33 | s.RLock() 34 | defer s.RUnlock() 35 | return s.state 36 | } 37 | 38 | func (s *StateDBWrapper) CreateAccount(address common.Address) { 39 | s.Lock() 40 | defer s.Unlock() 41 | s.state.CreateAccount(address) 42 | } 43 | 44 | func (s *StateDBWrapper) SubBalance(address common.Address, u *uint256.Int, reason tracing.BalanceChangeReason) { 45 | s.Lock() 46 | defer s.Unlock() 47 | s.state.SubBalance(address, u, reason) 48 | } 49 | 50 | func (s *StateDBWrapper) AddBalance(address common.Address, u *uint256.Int, reason tracing.BalanceChangeReason) { 51 | s.Lock() 52 | defer s.Unlock() 53 | s.state.AddBalance(address, u, reason) 54 | } 55 | 56 | func (s *StateDBWrapper) GetBalance(address common.Address) *uint256.Int { 57 | s.RLock() 58 | defer s.RUnlock() 59 | return s.state.GetBalance(address) 60 | } 61 | 62 | func (s *StateDBWrapper) GetNonce(address common.Address) uint64 { 63 | s.RLock() 64 | defer s.RUnlock() 65 | return s.state.GetNonce(address) 66 | } 67 | 68 | func (s *StateDBWrapper) SetNonce(address common.Address, u uint64) { 69 | s.Lock() 70 | defer s.Unlock() 71 | s.state.SetNonce(address, u) 72 | } 73 | 74 | func (s *StateDBWrapper) GetCodeHash(address common.Address) common.Hash { 75 | s.RLock() 76 | defer s.RUnlock() 77 | return s.state.GetCodeHash(address) 78 | } 79 | 80 | func (s *StateDBWrapper) GetCode(address common.Address) []byte { 81 | s.RLock() 82 | defer s.RUnlock() 83 | return s.state.GetCode(address) 84 | } 85 | 86 | func (s *StateDBWrapper) SetCode(address common.Address, bytes []byte) { 87 | s.Lock() 88 | defer s.Unlock() 89 | s.state.SetCode(address, bytes) 90 | } 91 | 92 | func (s *StateDBWrapper) GetCodeSize(address common.Address) int { 93 | s.RLock() 94 | defer s.RUnlock() 95 | return s.state.GetCodeSize(address) 96 | } 97 | 98 | func (s *StateDBWrapper) AddRefund(u uint64) { 99 | s.Lock() 100 | defer s.Unlock() 101 | s.state.AddRefund(u) 102 | } 103 | 104 | func (s *StateDBWrapper) SubRefund(u uint64) { 105 | s.Lock() 106 | defer s.Unlock() 107 | s.state.SubRefund(u) 108 | } 109 | 110 | func (s *StateDBWrapper) GetRefund() uint64 { 111 | s.RLock() 112 | defer s.RUnlock() 113 | return s.state.GetRefund() 114 | } 115 | 116 | func (s *StateDBWrapper) GetCommittedState(address common.Address, hash common.Hash) common.Hash { 117 | s.RLock() 118 | defer s.RUnlock() 119 | return s.state.GetCommittedState(address, hash) 120 | } 121 | 122 | func (s *StateDBWrapper) GetState(address common.Address, key common.Hash) common.Hash { 123 | s.RLock() 124 | defer s.RUnlock() 125 | return s.state.GetState(address, key) 126 | } 127 | 128 | func (s *StateDBWrapper) SetState(address common.Address, key common.Hash, value common.Hash) { 129 | s.Lock() 130 | defer s.Unlock() 131 | s.state.SetState(address, key, value) 132 | } 133 | 134 | func (s *StateDBWrapper) GetStorageRoot(addr common.Address) common.Hash { 135 | s.RLock() 136 | defer s.RUnlock() 137 | return s.state.GetStorageRoot(addr) 138 | } 139 | 140 | func (s *StateDBWrapper) GetTransientState(addr common.Address, key common.Hash) common.Hash { 141 | s.RLock() 142 | defer s.RUnlock() 143 | return s.state.GetTransientState(addr, key) 144 | } 145 | 146 | func (s *StateDBWrapper) SetTransientState(addr common.Address, key, value common.Hash) { 147 | s.Lock() 148 | defer s.Unlock() 149 | s.state.SetTransientState(addr, key, value) 150 | } 151 | 152 | func (s *StateDBWrapper) SelfDestruct(address common.Address) { 153 | s.Lock() 154 | defer s.Unlock() 155 | s.state.SelfDestruct(address) 156 | } 157 | 158 | func (s *StateDBWrapper) HasSelfDestructed(address common.Address) bool { 159 | s.RLock() 160 | defer s.RUnlock() 161 | return s.state.HasSelfDestructed(address) 162 | } 163 | 164 | func (s *StateDBWrapper) Selfdestruct6780(address common.Address) { 165 | s.Lock() 166 | defer s.Unlock() 167 | s.state.Selfdestruct6780(address) 168 | } 169 | 170 | func (s *StateDBWrapper) Exist(address common.Address) bool { 171 | s.RLock() 172 | defer s.RUnlock() 173 | return s.state.Exist(address) 174 | } 175 | 176 | func (s *StateDBWrapper) Empty(address common.Address) bool { 177 | s.RLock() 178 | defer s.RUnlock() 179 | return s.state.Empty(address) 180 | } 181 | 182 | func (s *StateDBWrapper) AddressInAccessList(addr common.Address) bool { 183 | s.RLock() 184 | defer s.RUnlock() 185 | return s.state.AddressInAccessList(addr) 186 | } 187 | 188 | func (s *StateDBWrapper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { 189 | s.RLock() 190 | defer s.RUnlock() 191 | return s.state.SlotInAccessList(addr, slot) 192 | } 193 | 194 | func (s *StateDBWrapper) AddAddressToAccessList(addr common.Address) { 195 | s.Lock() 196 | defer s.Unlock() 197 | s.state.AddAddressToAccessList(addr) 198 | } 199 | 200 | func (s *StateDBWrapper) AddSlotToAccessList(addr common.Address, slot common.Hash) { 201 | s.Lock() 202 | defer s.Unlock() 203 | s.state.AddSlotToAccessList(addr, slot) 204 | } 205 | 206 | func (s *StateDBWrapper) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) { 207 | s.Lock() 208 | defer s.Unlock() 209 | s.state.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses) 210 | } 211 | 212 | func (s *StateDBWrapper) RevertToSnapshot(i int) { 213 | s.Lock() 214 | defer s.Unlock() 215 | s.state.RevertToSnapshot(i) 216 | } 217 | 218 | func (s *StateDBWrapper) Snapshot() int { 219 | s.Lock() 220 | defer s.Unlock() 221 | return s.state.Snapshot() 222 | } 223 | 224 | func (s *StateDBWrapper) AddLog(log *types.Log) { 225 | s.Lock() 226 | defer s.Unlock() 227 | s.state.AddLog(log) 228 | } 229 | 230 | func (s *StateDBWrapper) AddPreimage(hash common.Hash, bytes []byte) { 231 | s.Lock() 232 | defer s.Unlock() 233 | s.state.AddPreimage(hash, bytes) 234 | } 235 | 236 | func (s *StateDBWrapper) AllPreimages() map[common.Hash][]byte { 237 | s.RLock() 238 | defer s.RUnlock() 239 | return s.state.Preimages() 240 | } 241 | -------------------------------------------------------------------------------- /evm/types.go: -------------------------------------------------------------------------------- 1 | package evm 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | "github.com/ethereum/go-ethereum/core/types" 9 | "github.com/ethereum/go-ethereum/crypto/kzg4844" 10 | "github.com/holiman/uint256" 11 | ) 12 | 13 | type CallRequest struct { 14 | Input []byte `json:"input"` 15 | Address common.Address `json:"address"` 16 | Origin common.Address `json:"origin"` 17 | GasLimit uint64 `json:"gasLimit"` 18 | GasPrice *big.Int `json:"gasPrice"` 19 | Value *big.Int `json:"value"` 20 | } 21 | 22 | type CallResponse struct { 23 | Ret []byte `json:"ret"` 24 | LeftOverGas uint64 `json:"leftOverGas"` 25 | Err error `json:"err"` 26 | } 27 | 28 | type TxRequest struct { 29 | Input []byte `json:"input"` 30 | Address *common.Address `json:"address"` 31 | Origin common.Address `json:"origin"` 32 | GasLimit uint64 `json:"gasLimit"` 33 | GasPrice *big.Int `json:"gasPrice"` 34 | Value *big.Int `json:"value"` 35 | Hash common.Hash `json:"hash"` 36 | Nonce uint64 `json:"nonce"` 37 | V *big.Int `json:"v"` 38 | R *big.Int `json:"r"` 39 | S *big.Int `json:"s"` 40 | 41 | OriginArgs []byte `json:"originArgs"` 42 | 43 | IsInternalCall bool `json:"is_internal_call"` 44 | } 45 | 46 | type CreateRequest struct { 47 | Input []byte `json:"input"` 48 | Origin common.Address `json:"origin"` 49 | GasLimit uint64 `json:"gasLimit"` 50 | GasPrice *big.Int `json:"gasPrice"` 51 | Value *big.Int `json:"value"` 52 | } 53 | 54 | // Temp 55 | 56 | // TransactionArgs represents the arguments to construct a new transaction 57 | // or a message call. 58 | type TempTransactionArgs struct { 59 | From *common.Address `json:"from"` 60 | To *common.Address `json:"to"` 61 | Gas *hexutil.Uint64 `json:"gas"` 62 | GasPrice *hexutil.Big `json:"gasPrice"` 63 | MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` 64 | MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` 65 | Value *hexutil.Big `json:"value"` 66 | Nonce *hexutil.Uint64 `json:"nonce"` 67 | 68 | // We accept "data" and "input" for backwards-compatibility reasons. 69 | // "input" is the newer name and should be preferred by clients. 70 | // Issue detail: https://github.com/ethereum/go-ethereum/issues/15628 71 | Data *hexutil.Bytes `json:"data"` 72 | Input *hexutil.Bytes `json:"input"` 73 | 74 | // Introduced by AccessListTxType transaction. 75 | AccessList *types.AccessList `json:"accessList,omitempty"` 76 | ChainID *hexutil.Big `json:"chainId,omitempty"` 77 | 78 | // For BlobTxType 79 | BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"` 80 | BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"` 81 | 82 | // For BlobTxType transactions with blob sidecar 83 | Blobs []kzg4844.Blob `json:"blobs"` 84 | Commitments []kzg4844.Commitment `json:"commitments"` 85 | Proofs []kzg4844.Proof `json:"proofs"` 86 | 87 | // This configures whether blobs are allowed to be passed. 88 | blobSidecarAllowed bool 89 | } 90 | 91 | // ToTransaction converts the arguments to a transaction. 92 | // This assumes that setDefaults has been called. 93 | func (args *TempTransactionArgs) ToTransaction(v, r, s *big.Int) *types.Transaction { 94 | var data types.TxData 95 | switch { 96 | case args.BlobHashes != nil: 97 | al := types.AccessList{} 98 | if args.AccessList != nil { 99 | al = *args.AccessList 100 | } 101 | data = &types.BlobTx{ 102 | To: *args.To, 103 | ChainID: uint256.MustFromBig((*big.Int)(args.ChainID)), 104 | Nonce: uint64(*args.Nonce), 105 | Gas: uint64(*args.Gas), 106 | GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)), 107 | GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)), 108 | Value: uint256.MustFromBig((*big.Int)(args.Value)), 109 | Data: args.data(), 110 | AccessList: al, 111 | BlobHashes: args.BlobHashes, 112 | BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)), 113 | V: ConvertBigIntToUint256(v), 114 | R: ConvertBigIntToUint256(r), 115 | S: ConvertBigIntToUint256(s), 116 | } 117 | if args.Blobs != nil { 118 | data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{ 119 | Blobs: args.Blobs, 120 | Commitments: args.Commitments, 121 | Proofs: args.Proofs, 122 | } 123 | } 124 | 125 | case args.MaxFeePerGas != nil: 126 | al := types.AccessList{} 127 | if args.AccessList != nil { 128 | al = *args.AccessList 129 | } 130 | data = &types.DynamicFeeTx{ 131 | To: args.To, 132 | ChainID: (*big.Int)(args.ChainID), 133 | Nonce: uint64(*args.Nonce), 134 | Gas: uint64(*args.Gas), 135 | GasFeeCap: (*big.Int)(args.MaxFeePerGas), 136 | GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), 137 | Value: (*big.Int)(args.Value), 138 | Data: args.data(), 139 | AccessList: al, 140 | V: v, 141 | R: r, 142 | S: s, 143 | } 144 | 145 | case args.AccessList != nil: 146 | data = &types.AccessListTx{ 147 | To: args.To, 148 | ChainID: (*big.Int)(args.ChainID), 149 | Nonce: uint64(*args.Nonce), 150 | Gas: uint64(*args.Gas), 151 | GasPrice: (*big.Int)(args.GasPrice), 152 | Value: (*big.Int)(args.Value), 153 | Data: args.data(), 154 | AccessList: *args.AccessList, 155 | V: v, 156 | R: r, 157 | S: s, 158 | } 159 | 160 | default: 161 | data = &types.LegacyTx{ 162 | To: args.To, 163 | Nonce: uint64(*args.Nonce), 164 | Gas: uint64(*args.Gas), 165 | GasPrice: (*big.Int)(args.GasPrice), 166 | Value: (*big.Int)(args.Value), 167 | Data: args.data(), 168 | V: v, 169 | R: r, 170 | S: s, 171 | } 172 | } 173 | return types.NewTx(data) 174 | } 175 | 176 | // data retrieves the transaction calldata. Input field is preferred. 177 | func (args *TempTransactionArgs) data() []byte { 178 | if args.Input != nil { 179 | return *args.Input 180 | } 181 | if args.Data != nil { 182 | return *args.Data 183 | } 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /evm/util.go: -------------------------------------------------------------------------------- 1 | package evm 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "math/big" 8 | "strings" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/holiman/uint256" 12 | yu_common "github.com/yu-org/yu/common" 13 | ) 14 | 15 | func ConvertHashToYuHash(hash common.Hash) (yu_common.Hash, error) { 16 | var yuHash [yu_common.HashLen]byte 17 | if len(hash.Bytes()) == yu_common.HashLen { 18 | copy(yuHash[:], hash.Bytes()) 19 | return yuHash, nil 20 | } else { 21 | return yu_common.Hash{}, errors.New(fmt.Sprintf("Expected hash to be 32 bytes long, but got %d bytes", len(hash.Bytes()))) 22 | } 23 | } 24 | 25 | func ConvertBigIntToUint256(b *big.Int) *uint256.Int { 26 | if b == nil { 27 | return nil 28 | } 29 | u, _ := uint256.FromBig(b) 30 | return u 31 | } 32 | 33 | func ObjToJson(obj interface{}) string { 34 | byt, err := json.Marshal(obj) 35 | if err != nil { 36 | return "" 37 | } 38 | return string(byt) 39 | } 40 | 41 | func ValidateTxHash(hash string) bool { 42 | if len(hash) != 66 || !strings.HasPrefix(hash, "0x") { 43 | return false 44 | } 45 | 46 | if isAllZero(hash) { 47 | return false 48 | } 49 | 50 | if countLeadingZeros(hash) > 16 { 51 | return false 52 | } 53 | 54 | return true 55 | } 56 | 57 | func isAllZero(hash string) bool { 58 | for _, c := range hash[2:] { 59 | if c != '0' { 60 | return false 61 | } 62 | } 63 | return true 64 | } 65 | 66 | func countLeadingZeros(hash string) int { 67 | count := 0 68 | for _, c := range hash[2:] { 69 | if c != '0' { 70 | break 71 | } 72 | count++ 73 | } 74 | return count 75 | } 76 | -------------------------------------------------------------------------------- /evm/util_test.go: -------------------------------------------------------------------------------- 1 | package evm 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | yu_common "github.com/yu-org/yu/common" 8 | ) 9 | 10 | func TestConvertHashToYuHash(t *testing.T) { 11 | hash := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000006835c46") 12 | 13 | yuHash, err := ConvertHashToYuHash(hash) 14 | if err != nil { 15 | t.Fatalf("Expected no error, but got %v", err) 16 | } 17 | if len(yuHash) != yu_common.HashLen { 18 | t.Fatalf("Expected hash length %d, but got %d", yu_common.HashLen, len(yuHash)) 19 | } 20 | 21 | invalidHash := common.HexToHash("0x123") 22 | yuHash, err = ConvertHashToYuHash(invalidHash) 23 | if err == nil { 24 | t.Log("yuHash", yuHash.String()) 25 | t.Fatalf("Expected error, but got nil") 26 | } 27 | 28 | leadingZerosHash := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000123") 29 | yuHash, err = ConvertHashToYuHash(leadingZerosHash) 30 | if err == nil { 31 | t.Log("yuHash", yuHash.String()) 32 | t.Fatalf("Expected error for leading zeros, but got nil") 33 | } 34 | } 35 | 36 | func TestValidateTxHash(t *testing.T) { 37 | validHash := "0x3d169ccf2d92ede8d507540c40a158cf7bf0aff09239b1966d5dda6a01ad965a" 38 | if !ValidateTxHash(validHash) { 39 | t.Fatalf("Expected valid hash, but got invalid: %s", validHash) 40 | } 41 | 42 | invalidHashShort := "0x123" 43 | if ValidateTxHash(invalidHashShort) { 44 | t.Fatalf("Expected invalid hash due to short length, but got valid: %s", invalidHashShort) 45 | } 46 | 47 | invalidHashNoPrefix := "0000000000000000000000000000000000000000000000000000000006835c46" 48 | if ValidateTxHash(invalidHashNoPrefix) { 49 | t.Fatalf("Expected invalid hash due to missing prefix, but got valid: %s", invalidHashNoPrefix) 50 | } 51 | 52 | allZeroHash := "0x0000000000000000000000000000000000000000000000000000000000000000" 53 | if ValidateTxHash(allZeroHash) { 54 | t.Fatalf("Expected invalid hash due to all zeros, but got valid: %s", allZeroHash) 55 | } 56 | 57 | leadingZerosHash := "0x0000000000000000000000000000000000000000000000000000000000000123" 58 | if ValidateTxHash(leadingZerosHash) { 59 | t.Fatalf("Expected invalid hash due to leading zeros, but got valid: %s", leadingZerosHash) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /metrics/api_metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | var ( 6 | EthereumAPICounter = prometheus.NewCounterVec( 7 | prometheus.CounterOpts{ 8 | Namespace: "reddio", 9 | Subsystem: "ethereum_api", 10 | Name: "op_counter", 11 | Help: "Total Operator number of counter", 12 | }, 13 | []string{TypeLbl, TypeStatusLbl}, 14 | ) 15 | 16 | EthereumAPICounterHist = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 17 | Namespace: "reddio", 18 | Subsystem: "ethereum_api", 19 | Name: "op_execute_hist", 20 | Help: "operation execute duration distribution.", 21 | Buckets: prometheus.ExponentialBuckets(10, 2, 20), // 10us ~ 5s 22 | }, []string{TypeLbl, TypeStatusLbl}) 23 | ) 24 | 25 | func init() { 26 | prometheus.MustRegister(EthereumAPICounter) 27 | prometheus.MustRegister(EthereumAPICounterHist) 28 | } 29 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | const ( 6 | TypeLbl = "type" 7 | TypeCountLbl = "count" 8 | TypeStatusLbl = "status" 9 | ) 10 | 11 | var ( 12 | TxnCounter = prometheus.NewCounterVec( 13 | prometheus.CounterOpts{ 14 | Namespace: "reddio", 15 | Subsystem: "txn", 16 | Name: "total_count", 17 | Help: "Total number of count for txn", 18 | }, 19 | []string{TypeLbl}, 20 | ) 21 | 22 | BatchTxnCounter = prometheus.NewCounterVec( 23 | prometheus.CounterOpts{ 24 | Namespace: "reddio", 25 | Subsystem: "batch_txn", 26 | Name: "total_count", 27 | Help: "Total number of redo count for batch txn", 28 | }, 29 | []string{TypeLbl}, 30 | ) 31 | 32 | BlockTxnCommitDurationGauge = prometheus.NewGaugeVec( 33 | prometheus.GaugeOpts{ 34 | Namespace: "reddio", 35 | Subsystem: "block_txn", 36 | Name: "commit_duration_seconds", 37 | Help: "txn commit duration seconds", 38 | }, 39 | []string{}, 40 | ) 41 | 42 | BatchTxnDuration = prometheus.NewHistogramVec( 43 | prometheus.HistogramOpts{ 44 | Namespace: "reddio", 45 | Subsystem: "batch_txn", 46 | Name: "execute_duration_seconds", 47 | Help: "txn execute duration distribution.", 48 | Buckets: prometheus.ExponentialBuckets(10, 2, 20), // 10us ~ 5s 49 | }, 50 | []string{TypeLbl}, 51 | ) 52 | 53 | BatchTxnSplitCounter = prometheus.NewCounterVec( 54 | prometheus.CounterOpts{ 55 | Namespace: "reddio", 56 | Subsystem: "batch_txn", 57 | Name: "split_txn_count", 58 | Help: "split sub batch txn count", 59 | }, 60 | []string{TypeCountLbl}, 61 | ) 62 | 63 | BlockExecuteTxnDurationGauge = prometheus.NewGaugeVec( 64 | prometheus.GaugeOpts{ 65 | Namespace: "reddio", 66 | Subsystem: "block", 67 | Name: "execute_duration_seconds", 68 | Help: "block execute txn duration", 69 | }, 70 | []string{}, 71 | ) 72 | 73 | BlockExecuteTxnCountGauge = prometheus.NewGaugeVec( 74 | prometheus.GaugeOpts{ 75 | Namespace: "reddio", 76 | Subsystem: "block", 77 | Name: "execute_txn_count", 78 | Help: "txn count for each block", 79 | }, []string{}) 80 | 81 | BlockTxnPrepareDurationGauge = prometheus.NewGaugeVec( 82 | prometheus.GaugeOpts{ 83 | Namespace: "reddio", 84 | Subsystem: "block_txn", 85 | Name: "prepare_txn_duration_seconds", 86 | Help: "split batch txn duration", 87 | }, 88 | []string{}, 89 | ) 90 | 91 | BlockTxnAllExecuteDurationGauge = prometheus.NewGaugeVec( 92 | prometheus.GaugeOpts{ 93 | Namespace: "reddio", 94 | Subsystem: "block_txn", 95 | Name: "execute_all_duration_seconds", 96 | Help: "split batch txn duration", 97 | }, 98 | []string{}, 99 | ) 100 | DownwardMessageSuccessCounter = prometheus.NewCounterVec( 101 | prometheus.CounterOpts{ 102 | Namespace: "reddio", 103 | Subsystem: "bridge", 104 | Name: "downward_success_total", 105 | Help: "Total number of successfully processed downward messages", 106 | }, 107 | []string{TypeLbl}, 108 | ) 109 | 110 | DownwardMessageFailureCounter = prometheus.NewCounterVec( 111 | prometheus.CounterOpts{ 112 | Namespace: "reddio", 113 | Subsystem: "bridge", 114 | Name: "downward_failure_total", 115 | Help: "Total number of failed downward message processing attempts", 116 | }, 117 | []string{TypeLbl}, 118 | ) 119 | 120 | DownwardMessageReceivedCounter = prometheus.NewCounterVec( 121 | prometheus.CounterOpts{ 122 | Namespace: "reddio", 123 | Subsystem: "bridge", 124 | Name: "downward_received_total", 125 | Help: "Total number of received downward messages", 126 | }, 127 | []string{TypeLbl}, 128 | ) 129 | 130 | UpwardMessageSuccessCounter = prometheus.NewCounterVec( 131 | prometheus.CounterOpts{ 132 | Namespace: "reddio", 133 | Subsystem: "bridge", 134 | Name: "upward_success_total", 135 | Help: "Total number of successfully processed upward messages", 136 | }, 137 | []string{TypeLbl}, 138 | ) 139 | 140 | UpwardMessageFailureCounter = prometheus.NewCounterVec( 141 | prometheus.CounterOpts{ 142 | Namespace: "reddio", 143 | Subsystem: "bridge", 144 | Name: "upward_failure_total", 145 | Help: "Total number of failed upward message processing attempts", 146 | }, 147 | []string{TypeLbl}, 148 | ) 149 | 150 | UpwardMessageReceivedCounter = prometheus.NewCounterVec( 151 | prometheus.CounterOpts{ 152 | Namespace: "reddio", 153 | Subsystem: "bridge", 154 | Name: "upward_received_total", 155 | Help: "Total number of received upward messages", 156 | }, 157 | []string{TypeLbl}, 158 | ) 159 | L1EventWatcherFailureCounter = prometheus.NewCounter( 160 | prometheus.CounterOpts{ 161 | Namespace: "reddio", 162 | Subsystem: "l1_event_watcher", 163 | Name: "failure_total", 164 | Help: "Total number of L1 event watcher failures", 165 | }, 166 | ) 167 | 168 | L1EventWatcherRetryCounter = prometheus.NewCounter( 169 | prometheus.CounterOpts{ 170 | Namespace: "reddio", 171 | Subsystem: "l1_event_watcher", 172 | Name: "retry_total", 173 | Help: "Total number of L1 event watcher retries", 174 | }, 175 | ) 176 | 177 | WithdrawMessageNonceGauge = prometheus.NewGaugeVec( 178 | prometheus.GaugeOpts{ 179 | Namespace: "reddio", 180 | Subsystem: "bridge", 181 | Name: "withdraw_message_nonce", 182 | Help: "The current value of the bridge event nonce.", 183 | }, 184 | []string{"table"}, 185 | ) 186 | 187 | WithdrawMessageNonceGap = prometheus.NewCounterVec( 188 | prometheus.CounterOpts{ 189 | Namespace: "reddio", 190 | Subsystem: "bridge", 191 | Name: "withdraw_message_nonce_gap", 192 | Help: "The gap between the current value of the bridge event nonce and the expected value.", 193 | }, 194 | []string{TypeLbl, TypeStatusLbl}, 195 | ) 196 | ) 197 | 198 | func init() { 199 | prometheus.MustRegister(TxnCounter) 200 | 201 | prometheus.MustRegister(BlockExecuteTxnCountGauge) 202 | prometheus.MustRegister(BlockTxnPrepareDurationGauge) 203 | prometheus.MustRegister(BlockTxnAllExecuteDurationGauge) 204 | prometheus.MustRegister(BlockTxnCommitDurationGauge) 205 | prometheus.MustRegister(BlockExecuteTxnDurationGauge) 206 | 207 | prometheus.MustRegister(BatchTxnCounter) 208 | prometheus.MustRegister(BatchTxnSplitCounter) 209 | prometheus.MustRegister(BatchTxnDuration) 210 | 211 | prometheus.MustRegister(DownwardMessageSuccessCounter) 212 | prometheus.MustRegister(DownwardMessageFailureCounter) 213 | prometheus.MustRegister(DownwardMessageReceivedCounter) 214 | prometheus.MustRegister(UpwardMessageSuccessCounter) 215 | prometheus.MustRegister(UpwardMessageFailureCounter) 216 | prometheus.MustRegister(UpwardMessageReceivedCounter) 217 | 218 | prometheus.MustRegister(L1EventWatcherFailureCounter) 219 | prometheus.MustRegister(L1EventWatcherRetryCounter) 220 | prometheus.MustRegister(WithdrawMessageNonceGauge) 221 | prometheus.MustRegister(WithdrawMessageNonceGap) 222 | 223 | } 224 | 225 | var TxnBuckets = []float64{.00005, .0001, .00025, .0005, .001, .0025, .005, 0.01, 0.025, 0.05, 0.1} 226 | -------------------------------------------------------------------------------- /metrics/solidity_metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | var ( 6 | SolidityCounter = prometheus.NewCounterVec( 7 | prometheus.CounterOpts{ 8 | Namespace: "reddio", 9 | Subsystem: "solidity", 10 | Name: "op_counter", 11 | Help: "Total Operator number of counter", 12 | }, 13 | []string{TypeLbl, TypeStatusLbl}, 14 | ) 15 | 16 | SolidityHist = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 17 | Namespace: "reddio", 18 | Subsystem: "solidity", 19 | Name: "op_execute_hist", 20 | Help: "solidity operation execute duration distribution.", 21 | Buckets: prometheus.ExponentialBuckets(10, 2, 22), // 10us ~ 20s 22 | }, []string{TypeLbl}) 23 | ) 24 | 25 | func init() { 26 | prometheus.MustRegister(SolidityCounter) 27 | prometheus.MustRegister(SolidityHist) 28 | } 29 | -------------------------------------------------------------------------------- /parallel/evm_process.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "time" 5 | 6 | common2 "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/core/state" 8 | "github.com/yu-org/yu/core/tripod" 9 | 10 | "github.com/yu-org/yu/common" 11 | "github.com/yu-org/yu/core/types" 12 | 13 | "github.com/reddio-com/reddio/config" 14 | "github.com/reddio-com/reddio/evm" 15 | ) 16 | 17 | const ( 18 | txnLabelRedoExecute = "redo" 19 | txnLabelExecuteSuccess = "success" 20 | txnLabelErrExecute = "err" 21 | 22 | batchTxnLabelSuccess = "success" 23 | batchTxnLabelRedo = "redo" 24 | ) 25 | 26 | type ParallelEVM struct { 27 | *tripod.Tripod 28 | db *state.StateDB 29 | Solidity *evm.Solidity `tripod:"solidity"` 30 | statManager *BlockTxnStatManager 31 | objectInc map[common2.Address]int 32 | processor EvmProcessor 33 | } 34 | 35 | func NewParallelEVM() *ParallelEVM { 36 | evm := &ParallelEVM{ 37 | Tripod: tripod.NewTripod(), 38 | } 39 | return evm 40 | } 41 | 42 | func (k *ParallelEVM) setupProcessor() { 43 | if config.GetGlobalConfig().IsParallel { 44 | k.processor = NewParallelEvmExecutor(k) 45 | } else { 46 | k.processor = NewSerialEvmExecutor(k) 47 | } 48 | } 49 | 50 | func (k *ParallelEVM) Execute(block *types.Block) error { 51 | k.statManager = &BlockTxnStatManager{TxnCount: len(block.Txns)} 52 | k.db = k.Solidity.StateDB() 53 | k.setupProcessor() 54 | start := time.Now() 55 | defer func() { 56 | k.statManager.ExecuteDuration = time.Since(start) 57 | k.statManager.UpdateMetrics() 58 | }() 59 | k.processor.Prepare(block) 60 | k.processor.Execute(block) 61 | receipts := k.processor.Receipts(block) 62 | return k.Commit(block, receipts) 63 | } 64 | 65 | func (k *ParallelEVM) Commit(block *types.Block, receipts map[common.Hash]*types.Receipt) error { 66 | commitStart := time.Now() 67 | defer func() { 68 | k.statManager.CommitDuration = time.Since(commitStart) 69 | }() 70 | return k.PostExecute(block, receipts) 71 | } 72 | 73 | type EvmProcessor interface { 74 | Prepare(block *types.Block) 75 | Execute(block *types.Block) 76 | Receipts(block *types.Block) map[common.Hash]*types.Receipt 77 | } 78 | -------------------------------------------------------------------------------- /parallel/evm_state.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/sirupsen/logrus" 7 | 8 | "github.com/reddio-com/reddio/config" 9 | "github.com/reddio-com/reddio/metrics" 10 | ) 11 | 12 | type BlockTxnStatManager struct { 13 | TxnCount int 14 | TxnBatchCount int 15 | TxnBatchRedoCount int 16 | ConflictCount int 17 | ExecuteDuration time.Duration 18 | ExecuteTxnDuration time.Duration 19 | PrepareDuration time.Duration 20 | CommitDuration time.Duration 21 | CopyDuration time.Duration 22 | } 23 | 24 | func (stat *BlockTxnStatManager) UpdateMetrics() { 25 | metrics.BlockExecuteTxnCountGauge.WithLabelValues().Set(float64(stat.TxnCount)) 26 | metrics.BlockExecuteTxnDurationGauge.WithLabelValues().Set(float64(stat.ExecuteDuration.Seconds())) 27 | metrics.BlockTxnAllExecuteDurationGauge.WithLabelValues().Set(float64(stat.ExecuteTxnDuration.Seconds())) 28 | metrics.BlockTxnPrepareDurationGauge.WithLabelValues().Set(float64(stat.PrepareDuration.Seconds())) 29 | metrics.BlockTxnCommitDurationGauge.WithLabelValues().Set(float64(stat.CommitDuration.Seconds())) 30 | if config.GlobalConfig.IsBenchmarkMode { 31 | logrus.Infof("execute %v txn, total:%v, execute cost:%v, prepare:%v, copy:%v, commit:%v, txnBatch:%v, conflict:%v, redoBatch:%v", 32 | stat.TxnCount, stat.ExecuteDuration.String(), stat.ExecuteTxnDuration.String(), 33 | stat.PrepareDuration.String(), stat.CopyDuration.String(), stat.CommitDuration.String(), stat.TxnBatchCount, stat.ConflictCount, stat.TxnBatchRedoCount) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /parallel/evm_util.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "time" 5 | 6 | common2 "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/core/state" 8 | "github.com/yu-org/yu/common" 9 | "github.com/yu-org/yu/core/context" 10 | "github.com/yu-org/yu/core/tripod/dev" 11 | "github.com/yu-org/yu/core/types" 12 | 13 | "github.com/reddio-com/reddio/config" 14 | "github.com/reddio-com/reddio/evm" 15 | "github.com/reddio-com/reddio/evm/pending_state" 16 | "github.com/reddio-com/reddio/metrics" 17 | ) 18 | 19 | func checkAddressConflict(curTxn *txnCtx, curList []*txnCtx) bool { 20 | for _, compare := range curList { 21 | 22 | if curTxn.req.Address != nil && compare.req.Address != nil { 23 | if *compare.req.Address == *curTxn.req.Address { 24 | return true 25 | } 26 | } 27 | 28 | if compare.req.Address != nil { 29 | if *compare.req.Address == curTxn.req.Origin { 30 | return true 31 | } 32 | } 33 | 34 | if curTxn.req.Address != nil { 35 | if compare.req.Origin == *curTxn.req.Address { 36 | return true 37 | } 38 | } 39 | 40 | if compare.req.Origin == curTxn.req.Origin { 41 | return true 42 | } 43 | 44 | } 45 | return false 46 | } 47 | 48 | type txnCtx struct { 49 | ctx *context.WriteContext 50 | txn *types.SignedTxn 51 | writing dev.Writing 52 | req *evm.TxRequest 53 | err error 54 | ps *pending_state.PendingStateWrapper 55 | receipt *types.Receipt 56 | } 57 | 58 | func (k *ParallelEVM) handleTxnError(err error, ctx *context.WriteContext, block *types.Block, stxn *types.SignedTxn) *types.Receipt { 59 | metrics.TxnCounter.WithLabelValues(txnLabelErrExecute).Inc() 60 | return k.HandleError(err, ctx, block, stxn) 61 | } 62 | 63 | func (k *ParallelEVM) handleTxnEvent(ctx *context.WriteContext, block *types.Block, stxn *types.SignedTxn, isRedo bool) *types.Receipt { 64 | metrics.TxnCounter.WithLabelValues(txnLabelExecuteSuccess).Inc() 65 | if isRedo { 66 | metrics.TxnCounter.WithLabelValues(txnLabelRedoExecute).Inc() 67 | } 68 | return k.HandleEvent(ctx, block, stxn) 69 | } 70 | 71 | func (k *ParallelEVM) prepareExecute() { 72 | if config.GetGlobalConfig().AsyncCommit { 73 | //k.cpdb.ClearPendingCommitMark() 74 | k.clearObjInc() 75 | } 76 | } 77 | 78 | func (k *ParallelEVM) clearObjInc() { 79 | k.objectInc = make(map[common2.Address]int) 80 | } 81 | 82 | func (k *ParallelEVM) updateTxnObjSub(txns []*txnCtx) { 83 | if !config.GetGlobalConfig().AsyncCommit { 84 | return 85 | } 86 | sub := func(key common2.Address) { 87 | v, ok := k.objectInc[key] 88 | if ok { 89 | if v == 1 { 90 | delete(k.objectInc, key) 91 | return 92 | } 93 | k.objectInc[key] = v - 1 94 | } 95 | } 96 | for _, txn := range txns { 97 | addr1 := txn.req.Address 98 | if addr1 != nil { 99 | sub(*addr1) 100 | } 101 | sub(txn.req.Origin) 102 | } 103 | } 104 | 105 | func (k *ParallelEVM) updateTxnObjInc(txns []*txnCtx) { 106 | if !config.GetGlobalConfig().AsyncCommit { 107 | return 108 | } 109 | inc := func(key common2.Address) { 110 | v, ok := k.objectInc[key] 111 | if ok { 112 | k.objectInc[key] = v + 1 113 | return 114 | } 115 | k.objectInc[key] = 1 116 | } 117 | for _, txn := range txns { 118 | addr1 := txn.req.Address 119 | if addr1 != nil { 120 | inc(*addr1) 121 | } 122 | inc(txn.req.Origin) 123 | } 124 | } 125 | 126 | func (k *ParallelEVM) prepareTxnList(block *types.Block) ([]*txnCtx, map[common.Hash]*types.Receipt) { 127 | start := time.Now() 128 | defer func() { 129 | k.statManager.PrepareDuration = time.Since(start) 130 | }() 131 | stxns := block.Txns 132 | receipts := make(map[common.Hash]*types.Receipt) 133 | txnCtxList := make([]*txnCtx, 0) 134 | for index, stxn := range stxns { 135 | wrCall := stxn.Raw.WrCall 136 | ctx, err := context.NewWriteContext(stxn, block, index) 137 | if err != nil { 138 | receipt := k.handleTxnError(err, ctx, block, stxn) 139 | receipts[stxn.TxnHash] = receipt 140 | continue 141 | } 142 | req := &evm.TxRequest{} 143 | if err := ctx.BindJson(req); err != nil { 144 | receipt := k.handleTxnError(err, ctx, block, stxn) 145 | receipts[stxn.TxnHash] = receipt 146 | continue 147 | } 148 | writing, _ := k.Land.GetWriting(wrCall.TripodName, wrCall.FuncName) 149 | stxnCtx := &txnCtx{ 150 | ctx: ctx, 151 | txn: stxn, 152 | writing: writing, 153 | req: req, 154 | } 155 | txnCtxList = append(txnCtxList, stxnCtx) 156 | } 157 | return txnCtxList, receipts 158 | } 159 | 160 | func (k *ParallelEVM) executeTxnCtxListInOrder(sdb *state.StateDB, list []*txnCtx, isRedo bool) []*txnCtx { 161 | for index, tctx := range list { 162 | if tctx.err != nil { 163 | tctx.receipt = k.handleTxnError(tctx.err, tctx.ctx, tctx.ctx.Block, tctx.txn) 164 | continue 165 | } 166 | tctx.ctx.ExtraInterface = pending_state.NewPendingStateWrapper(pending_state.NewStateDBWrapper(sdb), pending_state.NewStateContext(false), int64(index)) 167 | err := tctx.writing(tctx.ctx) 168 | if err != nil { 169 | tctx.err = err 170 | tctx.receipt = k.handleTxnError(err, tctx.ctx, tctx.ctx.Block, tctx.txn) 171 | } else { 172 | tctx.receipt = k.handleTxnEvent(tctx.ctx, tctx.ctx.Block, tctx.txn, isRedo) 173 | } 174 | tctx.ps = tctx.ctx.ExtraInterface.(*pending_state.PendingStateWrapper) 175 | list[index] = tctx 176 | } 177 | k.gcCopiedStateDB(nil, list) 178 | return list 179 | } 180 | 181 | func (k *ParallelEVM) gcCopiedStateDB(copiedStateDBList []*pending_state.PendingStateWrapper, list []*txnCtx) { 182 | copiedStateDBList = nil 183 | for _, ctx := range list { 184 | ctx.ctx.ExtraInterface = nil 185 | ctx.ps = nil 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /parallel/parallel_evm.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "sync" 7 | "time" 8 | 9 | common2 "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/core/state" 11 | "github.com/yu-org/yu/common" 12 | "github.com/yu-org/yu/core/types" 13 | 14 | "github.com/reddio-com/reddio/config" 15 | "github.com/reddio-com/reddio/evm/pending_state" 16 | "github.com/reddio-com/reddio/metrics" 17 | ) 18 | 19 | type ParallelEvmExecutor struct { 20 | k *ParallelEVM 21 | cpdb *state.StateDB 22 | receipts map[common.Hash]*types.Receipt 23 | subTxnList [][]*txnCtx 24 | } 25 | 26 | func NewParallelEvmExecutor(evm *ParallelEVM) *ParallelEvmExecutor { 27 | return &ParallelEvmExecutor{ 28 | k: evm, 29 | cpdb: evm.db, 30 | } 31 | } 32 | 33 | func (e *ParallelEvmExecutor) Prepare(block *types.Block) { 34 | e.k.prepareExecute() 35 | txnCtxList, receipts := e.k.prepareTxnList(block) 36 | e.receipts = receipts 37 | e.k.updateTxnObjInc(txnCtxList) 38 | e.subTxnList = e.splitTxnCtxList(txnCtxList) 39 | } 40 | 41 | func (e *ParallelEvmExecutor) Execute(block *types.Block) { 42 | got := e.executeAllTxn(e.subTxnList) 43 | for _, subList := range got { 44 | for _, c := range subList { 45 | e.receipts[c.txn.TxnHash] = c.receipt 46 | } 47 | } 48 | } 49 | 50 | func (e *ParallelEvmExecutor) Receipts(block *types.Block) map[common.Hash]*types.Receipt { 51 | return e.receipts 52 | } 53 | 54 | func (e *ParallelEvmExecutor) executeAllTxn(got [][]*txnCtx) [][]*txnCtx { 55 | start := time.Now() 56 | defer func() { 57 | e.k.statManager.ExecuteTxnDuration = time.Since(start) 58 | }() 59 | for index, subList := range got { 60 | e.executeTxnCtxListInParallel(subList) 61 | got[index] = subList 62 | } 63 | e.k.Solidity.SetStateDB(e.cpdb) 64 | return got 65 | } 66 | 67 | func (e *ParallelEvmExecutor) splitTxnCtxList(list []*txnCtx) [][]*txnCtx { 68 | cur := 0 69 | curList := make([]*txnCtx, 0) 70 | got := make([][]*txnCtx, 0) 71 | for cur < len(list) { 72 | curTxnCtx := list[cur] 73 | if checkAddressConflict(curTxnCtx, curList) { 74 | got = append(got, curList) 75 | curList = make([]*txnCtx, 0) 76 | continue 77 | } 78 | curList = append(curList, curTxnCtx) 79 | if len(curList) >= config.GetGlobalConfig().MaxConcurrency { 80 | got = append(got, curList) 81 | curList = make([]*txnCtx, 0) 82 | } 83 | cur++ 84 | } 85 | if len(curList) > 0 { 86 | got = append(got, curList) 87 | } 88 | e.k.statManager.TxnBatchCount = len(got) 89 | return got 90 | } 91 | 92 | func (e *ParallelEvmExecutor) executeTxnCtxListInParallel(list []*txnCtx) []*txnCtx { 93 | defer func() { 94 | e.cpdb.Finalise(true) 95 | if config.GetGlobalConfig().AsyncCommit { 96 | e.k.updateTxnObjSub(list) 97 | //e.cpdb.PendingCommit(true, e.k.objectInc) 98 | } 99 | }() 100 | metrics.BatchTxnSplitCounter.WithLabelValues(strconv.FormatInt(int64(len(list)), 10)).Inc() 101 | return e.executeTxnCtxListInConcurrency(list) 102 | } 103 | 104 | func (e *ParallelEvmExecutor) executeTxnCtxListInConcurrency(list []*txnCtx) []*txnCtx { 105 | conflict := false 106 | start := time.Now() 107 | defer func() { 108 | end := time.Now() 109 | metrics.BatchTxnDuration.WithLabelValues(fmt.Sprintf("%v", conflict)).Observe(end.Sub(start).Seconds()) 110 | }() 111 | copiedStateDBList := e.CopyStateDb(list) 112 | wg := sync.WaitGroup{} 113 | for i, c := range list { 114 | wg.Add(1) 115 | go func(index int, tctx *txnCtx, cpDb *pending_state.PendingStateWrapper) { 116 | defer func() { 117 | wg.Done() 118 | }() 119 | tctx.ctx.ExtraInterface = cpDb 120 | err := tctx.writing(tctx.ctx) 121 | if err != nil { 122 | tctx.err = err 123 | tctx.receipt = e.k.handleTxnError(err, tctx.ctx, tctx.ctx.Block, tctx.txn) 124 | } else { 125 | tctx.receipt = e.k.handleTxnEvent(tctx.ctx, tctx.ctx.Block, tctx.txn, false) 126 | } 127 | tctx.ps = tctx.ctx.ExtraInterface.(*pending_state.PendingStateWrapper) 128 | list[index] = tctx 129 | }(i, c, copiedStateDBList[i]) 130 | } 131 | wg.Wait() 132 | curtCtx := pending_state.NewStateContext(false) 133 | for _, tctx := range list { 134 | if curtCtx.IsConflict(tctx.ps.GetCtx()) { 135 | conflict = true 136 | e.k.statManager.ConflictCount++ 137 | break 138 | } 139 | } 140 | if conflict && !config.GetGlobalConfig().IgnoreConflict { 141 | e.k.statManager.TxnBatchRedoCount++ 142 | metrics.BatchTxnCounter.WithLabelValues(batchTxnLabelRedo).Inc() 143 | return e.k.executeTxnCtxListInOrder(e.cpdb, list, true) 144 | } 145 | metrics.BatchTxnCounter.WithLabelValues(batchTxnLabelSuccess).Inc() 146 | e.mergeStateDB(list) 147 | e.k.gcCopiedStateDB(copiedStateDBList, list) 148 | return list 149 | } 150 | 151 | func (e *ParallelEvmExecutor) mergeStateDB(list []*txnCtx) { 152 | for _, tctx := range list { 153 | tctx.ps.MergeInto(e.cpdb, tctx.req.Origin) 154 | } 155 | } 156 | 157 | func (e *ParallelEvmExecutor) CopyStateDb(list []*txnCtx) []*pending_state.PendingStateWrapper { 158 | copiedStateDBList := make([]*pending_state.PendingStateWrapper, 0) 159 | start := time.Now() 160 | defer func() { 161 | e.k.statManager.CopyDuration += time.Since(start) 162 | }() 163 | for i := 0; i < len(list); i++ { 164 | needCopy := make(map[common2.Address]struct{}) 165 | if list[i].req.Address != nil { 166 | needCopy[*list[i].req.Address] = struct{}{} 167 | } 168 | needCopy[list[i].req.Origin] = struct{}{} 169 | //copiedStateDBList = append(copiedStateDBList, pending_state.NewPendingStateWrapper(pending_state.NewStateDBWrapper(e.cpdb.SimpleCopy(needCopy)), pending_state.NewStateContext(false), int64(i))) 170 | copiedStateDBList = append(copiedStateDBList, pending_state.NewPendingStateWrapper(pending_state.NewStateDBWrapper(e.cpdb.Copy()), pending_state.NewStateContext(false), int64(i))) 171 | 172 | } 173 | return copiedStateDBList 174 | } 175 | -------------------------------------------------------------------------------- /parallel/serial_evm.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "math/big" 5 | "time" 6 | 7 | "github.com/ethereum/go-ethereum/core/state" 8 | "github.com/yu-org/yu/common" 9 | "github.com/yu-org/yu/core/types" 10 | 11 | "github.com/reddio-com/reddio/config" 12 | ) 13 | 14 | type SerialEvmExecutor struct { 15 | db *state.StateDB 16 | k *ParallelEVM 17 | receipts map[common.Hash]*types.Receipt 18 | txnCtxList []*txnCtx 19 | startNonce *big.Int 20 | endNonce *big.Int 21 | } 22 | 23 | func NewSerialEvmExecutor(evm *ParallelEVM) *SerialEvmExecutor { 24 | return &SerialEvmExecutor{ 25 | k: evm, 26 | db: evm.db, 27 | } 28 | } 29 | 30 | func (s *SerialEvmExecutor) Prepare(block *types.Block) { 31 | s.k.prepareExecute() 32 | s.txnCtxList, s.receipts = s.k.prepareTxnList(block) 33 | s.k.updateTxnObjInc(s.txnCtxList) 34 | } 35 | 36 | func (s *SerialEvmExecutor) Execute(block *types.Block) { 37 | start := time.Now() 38 | defer func() { 39 | s.k.statManager.ExecuteTxnDuration = time.Since(start) 40 | }() 41 | got := s.executeTxnCtxListInSerial(s.txnCtxList) 42 | for _, c := range got { 43 | s.receipts[c.txn.TxnHash] = c.receipt 44 | } 45 | } 46 | 47 | func (s *SerialEvmExecutor) Receipts(block *types.Block) map[common.Hash]*types.Receipt { 48 | return s.receipts 49 | } 50 | 51 | func (s *SerialEvmExecutor) executeTxnCtxListInSerial(list []*txnCtx) []*txnCtx { 52 | defer func() { 53 | if config.GetGlobalConfig().AsyncCommit { 54 | s.k.updateTxnObjSub(list) 55 | //s.cpdb.PendingCommit(true, s.k.objectInc) 56 | } 57 | }() 58 | return s.k.executeTxnCtxListInOrder(s.db, list, false) 59 | } 60 | -------------------------------------------------------------------------------- /test/cmd/benchmark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "os" 8 | "time" 9 | 10 | "golang.org/x/time/rate" 11 | 12 | "github.com/reddio-com/reddio/evm" 13 | "github.com/reddio-com/reddio/test/conf" 14 | "github.com/reddio-com/reddio/test/pkg" 15 | "github.com/reddio-com/reddio/test/transfer" 16 | ) 17 | 18 | var ( 19 | configPath string 20 | evmConfigPath string 21 | qps int 22 | duration time.Duration 23 | action string 24 | preCreateWallets int 25 | nodeUrl string 26 | genesisPrivateKey string 27 | ) 28 | 29 | const benchmarkDataPath = "./bin/eth_benchmark_data.json" 30 | 31 | func init() { 32 | flag.StringVar(&configPath, "configPath", "", "") 33 | flag.StringVar(&evmConfigPath, "evmConfigPath", "./conf/evm.toml", "") 34 | flag.IntVar(&qps, "qps", 10000, "") 35 | flag.DurationVar(&duration, "duration", 5*time.Minute, "") 36 | flag.StringVar(&action, "action", "run", "") 37 | flag.IntVar(&preCreateWallets, "preCreateWallets", 100, "") 38 | flag.StringVar(&nodeUrl, "nodeUrl", "http://localhost:9092", "") 39 | flag.StringVar(&genesisPrivateKey, "key", "32e3b56c9f2763d2332e6e4188e4755815ac96441e899de121969845e343c2ff", "") 40 | 41 | } 42 | 43 | func main() { 44 | flag.Parse() 45 | if err := conf.LoadConfig(configPath); err != nil { 46 | panic(err) 47 | } 48 | evmConfig := evm.LoadEvmConfig(evmConfigPath) 49 | switch action { 50 | case "prepare": 51 | prepareBenchmark(evmConfig.ChainConfig.ChainID.Int64()) 52 | case "run": 53 | blockBenchmark(evmConfig.ChainConfig.ChainID.Int64(), qps) 54 | } 55 | } 56 | 57 | func prepareBenchmark(chainID int64) error { 58 | ethManager := &transfer.EthManager{} 59 | cfg := conf.Config.EthCaseConf 60 | ethManager.Configure(cfg, nodeUrl, genesisPrivateKey, chainID) 61 | wallets, err := ethManager.PreCreateWallets(preCreateWallets, cfg.InitialEthCount) 62 | if err != nil { 63 | return err 64 | } 65 | _, err = os.Stat(benchmarkDataPath) 66 | if err == nil { 67 | os.Remove(benchmarkDataPath) 68 | } 69 | file, err := os.Create(benchmarkDataPath) 70 | if err != nil { 71 | return err 72 | } 73 | defer file.Close() 74 | d, err := json.Marshal(wallets) 75 | if err != nil { 76 | return err 77 | } 78 | _, err = file.Write(d) 79 | return err 80 | } 81 | 82 | func loadWallets() ([]*pkg.EthWallet, error) { 83 | d, err := os.ReadFile(benchmarkDataPath) 84 | if err != nil { 85 | return nil, err 86 | } 87 | exp := make([]*pkg.EthWallet, 0) 88 | if err := json.Unmarshal(d, &exp); err != nil { 89 | return nil, err 90 | } 91 | return exp, nil 92 | } 93 | 94 | func blockBenchmark(chainID int64, qps int) error { 95 | wallets, err := loadWallets() 96 | if err != nil { 97 | return err 98 | } 99 | ethManager := &transfer.EthManager{} 100 | cfg := conf.Config.EthCaseConf 101 | ethManager.Configure(cfg, nodeUrl, genesisPrivateKey, chainID) 102 | limiter := rate.NewLimiter(rate.Limit(qps), qps) 103 | ethManager.AddTestCase(transfer.NewRandomBenchmarkTest("[rand_test 1000 transfer]", cfg.InitialEthCount, wallets, limiter)) 104 | runBenchmark(ethManager) 105 | return nil 106 | } 107 | 108 | func runBenchmark(manager *transfer.EthManager) { 109 | after := time.After(duration) 110 | for { 111 | select { 112 | case <-after: 113 | return 114 | default: 115 | } 116 | manager.Run(context.Background()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/cmd/erc20/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/reddio-com/reddio/cmd/node/app" 11 | config2 "github.com/reddio-com/reddio/config" 12 | "github.com/reddio-com/reddio/evm" 13 | "github.com/reddio-com/reddio/test/conf" 14 | "github.com/reddio-com/reddio/test/erc20" 15 | ) 16 | 17 | var ( 18 | evmConfigPath string 19 | yuConfigPath string 20 | poaConfigPath string 21 | isParallel bool 22 | nodeUrl string 23 | genesisPrivateKey string 24 | ) 25 | 26 | func init() { 27 | flag.StringVar(&evmConfigPath, "evmConfigPath", "./conf/evm.toml", "") 28 | flag.StringVar(&yuConfigPath, "yuConfigPath", "./conf/yu.toml", "") 29 | flag.StringVar(&poaConfigPath, "poaConfigPath", "./conf/poa.toml", "") 30 | flag.BoolVar(&isParallel, "parallel", true, "") 31 | flag.StringVar(&nodeUrl, "nodeUrl", "http://localhost:9092", "") 32 | flag.StringVar(&genesisPrivateKey, "key", "32e3b56c9f2763d2332e6e4188e4755815ac96441e899de121969845e343c2ff", "") 33 | 34 | } 35 | 36 | func main() { 37 | flag.Parse() 38 | evmConfig := evm.LoadEvmConfig(evmConfigPath) 39 | config := config2.GetGlobalConfig() 40 | config.IsBenchmarkMode = true 41 | config.IsParallel = isParallel 42 | config.AsyncCommit = true 43 | go func() { 44 | if config.IsParallel { 45 | log.Println("start transfer test in parallel") 46 | } else { 47 | log.Println("start transfer test in serial") 48 | } 49 | app.Start(evmConfigPath, yuConfigPath, poaConfigPath, "") 50 | }() 51 | time.Sleep(5 * time.Second) 52 | log.Println("finish start reddio") 53 | if err := assertErc20Transfer(context.Background(), evmConfig); err != nil { 54 | log.Println(err) 55 | os.Exit(1) 56 | } 57 | log.Println("assert success") 58 | os.Exit(0) 59 | } 60 | 61 | func assertErc20Transfer(ctx context.Context, evmCfg *evm.GethConfig) error { 62 | log.Println("start asserting transfer eth") 63 | ethManager := &erc20.EthManager{} 64 | cfg := conf.Config.EthCaseConf 65 | ethManager.Configure(cfg, nodeUrl, genesisPrivateKey, evmCfg.ChainConfig.ChainID.Int64()) 66 | ethManager.AddTestCase( 67 | erc20.NewRandomTest("[rand_test 2 account, 1 transfer]", nodeUrl, 2, cfg.InitialEthCount, 1, evmCfg.ChainID), 68 | ) 69 | return ethManager.Run(ctx) 70 | } 71 | -------------------------------------------------------------------------------- /test/cmd/transfer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | 11 | "github.com/reddio-com/reddio/cmd/node/app" 12 | "github.com/reddio-com/reddio/test/conf" 13 | "github.com/reddio-com/reddio/test/testx" 14 | "github.com/reddio-com/reddio/test/transfer" 15 | ) 16 | 17 | var ( 18 | evmConfigPath string 19 | yuConfigPath string 20 | poaConfigPath string 21 | isParallel bool 22 | nodeUrl string 23 | genesisPrivateKey string 24 | AsClient bool 25 | chainID int64 26 | ) 27 | 28 | func init() { 29 | flag.StringVar(&evmConfigPath, "evmConfigPath", "./conf/evm.toml", "") 30 | flag.StringVar(&yuConfigPath, "yuConfigPath", "./conf/yu.toml", "") 31 | flag.StringVar(&poaConfigPath, "poaConfigPath", "./conf/poa.toml", "") 32 | flag.BoolVar(&isParallel, "parallel", true, "") 33 | 34 | flag.StringVar(&nodeUrl, "nodeUrl", "http://localhost:9092", "") 35 | flag.StringVar(&genesisPrivateKey, "key", "32e3b56c9f2763d2332e6e4188e4755815ac96441e899de121969845e343c2ff", "") 36 | 37 | flag.BoolVar(&AsClient, "as-client", false, "") 38 | flag.Int64Var(&chainID, "chainId", 0, "") 39 | 40 | } 41 | 42 | func main() { 43 | flag.Parse() 44 | if !AsClient { 45 | yuCfg, poaCfg, evmConfig, config := testx.GenerateConfig(yuConfigPath, evmConfigPath, poaConfigPath, isParallel) 46 | go func() { 47 | if config.IsParallel { 48 | logrus.Info("start transfer test in parallel") 49 | } else { 50 | logrus.Info("start transfer test in serial") 51 | } 52 | app.StartByConfig(yuCfg, poaCfg, evmConfig) 53 | }() 54 | time.Sleep(5 * time.Second) 55 | logrus.Info("finish start reddio") 56 | chainID = evmConfig.ChainConfig.ChainID.Int64() 57 | } 58 | if err := assertEthTransfer(context.Background(), chainID); err != nil { 59 | logrus.Info(err) 60 | os.Exit(1) 61 | } 62 | logrus.Info("assert success") 63 | os.Exit(0) 64 | } 65 | 66 | func assertEthTransfer(ctx context.Context, chainID int64) error { 67 | logrus.Info("start asserting transfer eth") 68 | ethManager := &transfer.EthManager{} 69 | cfg := conf.Config.EthCaseConf 70 | ethManager.Configure(cfg, nodeUrl, genesisPrivateKey, chainID) 71 | ethManager.AddTestCase( 72 | transfer.NewRandomTest("[rand_test 2 account, 1 transfer]", 2, cfg.InitialEthCount, 1), 73 | transfer.NewRandomTest("[rand_test 20 account, 100 transfer]", 20, cfg.InitialEthCount, 100), 74 | transfer.NewConflictTest("[conflict_test 20 account, 50 transfer]", 20, cfg.InitialEthCount, 50), 75 | ) 76 | return ethManager.Run(ctx) 77 | } 78 | -------------------------------------------------------------------------------- /test/cmd/uniswap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os" 7 | "runtime" 8 | "time" 9 | 10 | "github.com/sirupsen/logrus" 11 | 12 | "github.com/reddio-com/reddio/cmd/node/app" 13 | "github.com/reddio-com/reddio/test/conf" 14 | "github.com/reddio-com/reddio/test/testx" 15 | "github.com/reddio-com/reddio/test/transfer" 16 | "github.com/reddio-com/reddio/test/uniswap" 17 | ) 18 | 19 | var ( 20 | evmConfigPath string 21 | yuConfigPath string 22 | poaConfigPath string 23 | isParallel bool 24 | nodeUrl string 25 | genesisPrivateKey string 26 | ) 27 | 28 | func init() { 29 | flag.StringVar(&evmConfigPath, "evmConfigPath", "./conf/evm.toml", "") 30 | flag.StringVar(&yuConfigPath, "yuConfigPath", "./conf/yu.toml", "") 31 | flag.StringVar(&poaConfigPath, "poaConfigPath", "./conf/poa.toml", "") 32 | flag.BoolVar(&isParallel, "parallel", true, "") 33 | flag.StringVar(&nodeUrl, "nodeUrl", "http://localhost:9092", "") 34 | flag.StringVar(&genesisPrivateKey, "key", "32e3b56c9f2763d2332e6e4188e4755815ac96441e899de121969845e343c2ff", "") 35 | 36 | } 37 | 38 | func main() { 39 | flag.Parse() 40 | yuCfg, poaCfg, evmConfig, config := testx.GenerateConfig(yuConfigPath, evmConfigPath, poaConfigPath, isParallel) 41 | go func() { 42 | logrus.Infof("Number of goroutines after app.Start: %d", runtime.NumGoroutine()) 43 | if config.IsParallel { 44 | logrus.Info("start uniswap test in parallel") 45 | } else { 46 | logrus.Info("start uniswap test in serial") 47 | } 48 | app.StartByConfig(yuCfg, poaCfg, evmConfig) 49 | }() 50 | time.Sleep(5 * time.Second) 51 | logrus.Info("finish start reddio") 52 | if err := assertUniswapV2(context.Background(), evmConfig.ChainConfig.ChainID.Int64()); err != nil { 53 | logrus.Info(err) 54 | os.Exit(1) 55 | } 56 | logrus.Info("assert success") 57 | os.Exit(0) 58 | } 59 | 60 | func assertUniswapV2(ctx context.Context, chainID int64) error { 61 | ethManager := &transfer.EthManager{} 62 | cfg := conf.Config.EthCaseConf 63 | ethManager.Configure(cfg, nodeUrl, genesisPrivateKey, chainID) 64 | ethManager.AddTestCase( 65 | uniswap.NewUniswapV2AccuracyTestCase("UniswapV2 Accuracy TestCase", nodeUrl, 2, cfg.InitialEthCount, chainID), 66 | ) 67 | return ethManager.Run(ctx) 68 | } 69 | -------------------------------------------------------------------------------- /test/cmd/uniswap_benchmark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/yu-org/yu/core/startup" 10 | "golang.org/x/time/rate" 11 | 12 | "github.com/reddio-com/reddio/evm" 13 | "github.com/reddio-com/reddio/test/conf" 14 | "github.com/reddio-com/reddio/test/uniswap" 15 | ) 16 | 17 | var ( 18 | configPath string 19 | evmConfigPath string 20 | maxBlock int 21 | qps int 22 | action string 23 | duration time.Duration 24 | deployUsers int 25 | testUsers int 26 | nonConflict bool 27 | maxUsers int 28 | nodeUrl string 29 | genesisPrivateKey string 30 | ) 31 | 32 | func init() { 33 | flag.StringVar(&configPath, "configPath", "", "") 34 | flag.StringVar(&evmConfigPath, "evmConfigPath", "./conf/evm.toml", "") 35 | flag.IntVar(&maxBlock, "maxBlock", 500, "") 36 | flag.IntVar(&qps, "qps", 1500, "") 37 | flag.StringVar(&action, "action", "run", "") 38 | flag.DurationVar(&duration, "duration", time.Minute*5, "") 39 | flag.IntVar(&deployUsers, "deployUsers", 10, "") 40 | flag.IntVar(&testUsers, "testUsers", 100, "") 41 | flag.BoolVar(&nonConflict, "nonConflict", false, "") 42 | flag.IntVar(&maxUsers, "maxUsers", 0, "") 43 | flag.StringVar(&nodeUrl, "nodeUrl", "http://localhost:9092", "") 44 | flag.StringVar(&genesisPrivateKey, "key", "32e3b56c9f2763d2332e6e4188e4755815ac96441e899de121969845e343c2ff", "") 45 | 46 | } 47 | 48 | func main() { 49 | flag.Parse() 50 | if err := conf.LoadConfig(configPath); err != nil { 51 | panic(err) 52 | } 53 | yuCfg := startup.InitDefaultKernelConfig() 54 | yuCfg.IsAdmin = true 55 | yuCfg.Txpool.PoolSize = 10000000 56 | evmConfig := evm.LoadEvmConfig(evmConfigPath) 57 | ethManager := &uniswap.EthManager{} 58 | cfg := conf.Config.EthCaseConf 59 | limiter := rate.NewLimiter(rate.Limit(qps), qps) 60 | ethManager.Configure(cfg, nodeUrl, genesisPrivateKey, evmConfig.ChainConfig.ChainID.Int64()) 61 | ethManager.AddTestCase( 62 | uniswap.NewUniswapV2TPSStatisticsTestCase("UniswapV2 TPS StatisticsTestCase", nodeUrl, deployUsers, testUsers, maxUsers, limiter, action == "run", nonConflict, evmConfig.ChainID)) 63 | switch action { 64 | case "prepare": 65 | prepareBenchmark(context.Background(), ethManager) 66 | case "run": 67 | blockBenchmark(ethManager) 68 | } 69 | } 70 | 71 | func blockBenchmark(ethManager *uniswap.EthManager) { 72 | after := time.After(duration) 73 | ctx, cancel := context.WithCancel(context.Background()) 74 | defer cancel() 75 | go runBenchmark(ctx, ethManager) 76 | for { 77 | select { 78 | case <-after: 79 | return 80 | } 81 | } 82 | } 83 | 84 | func prepareBenchmark(ctx context.Context, manager *uniswap.EthManager) { 85 | err := manager.Prepare(ctx) 86 | if err != nil { 87 | logrus.Infof("err:%v", err.Error()) 88 | } 89 | } 90 | 91 | func runBenchmark(ctx context.Context, manager *uniswap.EthManager) { 92 | for { 93 | select { 94 | case <-ctx.Done(): 95 | return 96 | default: 97 | } 98 | manager.Run(ctx) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "os" 5 | 6 | "gopkg.in/yaml.v3" 7 | ) 8 | 9 | var Config *TestConfig 10 | 11 | func init() { 12 | Config = NewDefaultConfig() 13 | } 14 | 15 | func LoadConfig(path string) error { 16 | if len(path) < 1 { 17 | return nil 18 | } 19 | d, err := os.ReadFile(path) 20 | if err != nil { 21 | return err 22 | } 23 | return yaml.Unmarshal(d, Config) 24 | } 25 | 26 | type TestConfig struct { 27 | EthCaseConf *EthCaseConf `yaml:"ethCaseConf"` 28 | } 29 | 30 | type EthCaseConf struct { 31 | HostUrl string `yaml:"hostUrl"` 32 | GenWalletCount int `yaml:"genWalletCount"` 33 | InitialEthCount uint64 `yaml:"initialEthCount"` 34 | TestSteps int `yaml:"testSteps"` 35 | RetryCount int `yaml:"retryCount"` 36 | } 37 | 38 | func NewDefaultConfig() *TestConfig { 39 | tc := &TestConfig{} 40 | tc.EthCaseConf = DefaultEthCaseConf() 41 | return tc 42 | } 43 | 44 | func DefaultEthCaseConf() *EthCaseConf { 45 | return &EthCaseConf{ 46 | GenWalletCount: 2, 47 | InitialEthCount: 100 * 100 * 100, 48 | TestSteps: 1, 49 | RetryCount: 3, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/conf/evm_cfg.toml: -------------------------------------------------------------------------------- 1 | enable_eth_rpc = true 2 | eth_host = "localhost" 3 | eth_port = "9092" -------------------------------------------------------------------------------- /test/contracts/ERC20T.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockNumberT","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/contracts/Token.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/contracts/UniswapV2Factory.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":false,"internalType":"address","name":"pair","type":"address"},{"indexed":false,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"constant":true,"inputs":[],"name":"INIT_CODE_PAIR_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_feeTo","type":"address"}],"name":"setFeeTo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"name":"setFeeToSetter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /test/contracts/WETH9.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}] -------------------------------------------------------------------------------- /test/contracts/WETH9.bin: -------------------------------------------------------------------------------- 1 | 60606040526040805190810160405280600d81526020017f57726170706564204574686572000000000000000000000000000000000000008152506000908051906020019061004f9291906100c8565b506040805190810160405280600481526020017f57455448000000000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100c8565b506012600260006101000a81548160ff021916908360ff16021790555034156100c357600080fd5b61016d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010957805160ff1916838001178555610137565b82800160010185558215610137579182015b8281111561013657825182559160200191906001019061011b565b5b5090506101449190610148565b5090565b61016a91905b8082111561016657600081600090555060010161014e565b5090565b90565b610c348061017c6000396000f3006060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a723058202f73adbbe8c248080bcd14d941d76d8abeccbe8232a73d9b9f21fd4fb9421ee80029 -------------------------------------------------------------------------------- /test/erc20/eth.go: -------------------------------------------------------------------------------- 1 | package erc20 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/reddio-com/reddio/test/conf" 9 | "github.com/reddio-com/reddio/test/pkg" 10 | ) 11 | 12 | type EthManager struct { 13 | config *conf.EthCaseConf 14 | wm *pkg.WalletManager 15 | // tm *pkg.TransferManager 16 | testcases []TestCase 17 | } 18 | 19 | func (m *EthManager) Configure(cfg *conf.EthCaseConf, nodeUrl, pk string, chainID int64) { 20 | m.config = cfg 21 | m.wm = pkg.NewWalletManager(chainID, nodeUrl, pk) 22 | m.testcases = []TestCase{} 23 | } 24 | 25 | func (m *EthManager) PreCreateWallets(walletCount int, initCount uint64) ([]*pkg.EthWallet, error) { 26 | wallets, err := m.wm.GenerateRandomWallets(walletCount, initCount) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return wallets, nil 31 | } 32 | 33 | func (m *EthManager) AddTestCase(tc ...TestCase) { 34 | m.testcases = append(m.testcases, tc...) 35 | } 36 | 37 | func (m *EthManager) Run(ctx context.Context) error { 38 | for _, tc := range m.testcases { 39 | log.Println(fmt.Sprintf("start to test %v", tc.Name())) 40 | if err := tc.Run(ctx, m.wm); err != nil { 41 | return fmt.Errorf("%s failed, err:%v", tc.Name(), err) 42 | } 43 | log.Println(fmt.Sprintf("test %v success", tc.Name())) 44 | } 45 | return nil 46 | } 47 | 48 | func (m *EthManager) Prepare(ctx context.Context) error { 49 | // for _, tc := range m.testcases { 50 | // log.Println(fmt.Sprintf("start to prepare %v", tc.Name())) 51 | // if _, err := tc.Prepare(ctx, m.wm); err != nil { 52 | // return fmt.Errorf("%s failed, err:%v", tc.Name(), err) 53 | // } 54 | // log.Println(fmt.Sprintf("prepare %v success", tc.Name())) 55 | // } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /test/pkg/block.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | "github.com/sirupsen/logrus" 11 | "github.com/yu-org/yu/common/yerror" 12 | "github.com/yu-org/yu/core/types" 13 | ) 14 | 15 | func GetDefaultBlockManager() *BlockManager { 16 | return &BlockManager{ 17 | hostUrl: "localhost:7999", 18 | } 19 | } 20 | 21 | type BlockManager struct { 22 | hostUrl string 23 | } 24 | 25 | func (bm *BlockManager) StopBlockChain() { 26 | _, err := http.Get(fmt.Sprintf("http://%s/api/admin/stop", bm.hostUrl)) 27 | if err != nil { 28 | logrus.Error("Failed to stop blockchain", err) 29 | } 30 | } 31 | 32 | func (bm *BlockManager) GetBlockTxnCountByIndex(index int) (bool, int, error) { 33 | resp, err := http.Get(fmt.Sprintf("http://%s/api/receipts_count?block_number=%v", bm.hostUrl, index)) 34 | if err != nil { 35 | return false, 0, err 36 | } 37 | defer resp.Body.Close() 38 | d, err := io.ReadAll(resp.Body) 39 | if err != nil { 40 | return false, 0, err 41 | } 42 | r := &txCountResp{} 43 | err = json.Unmarshal(d, &r) 44 | if err != nil { 45 | return false, 0, err 46 | } 47 | if r.ErrMsg == yerror.ErrBlockNotFound.Error() { 48 | return false, 0, nil 49 | } 50 | return true, r.Data, nil 51 | } 52 | 53 | func (bm *BlockManager) GetCurrentBlock() (*types.Block, error) { 54 | resp, err := http.Get(fmt.Sprintf("http://%s/api/block", bm.hostUrl)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer resp.Body.Close() 59 | d, err := io.ReadAll(resp.Body) 60 | if err != nil { 61 | return nil, err 62 | } 63 | r := &blockResp{} 64 | err = json.Unmarshal(d, &r) 65 | if err != nil { 66 | return nil, err 67 | } 68 | if r.ErrMsg == yerror.ErrBlockNotFound.Error() { 69 | return nil, err 70 | } 71 | return r.Data, nil 72 | } 73 | 74 | func (bm *BlockManager) GetBlockByIndex(id uint64) (*types.Block, error) { 75 | resp, err := http.Get(fmt.Sprintf("http://%s/api/block?number=%s", bm.hostUrl, hexutil.EncodeUint64(id))) 76 | if err != nil { 77 | return nil, err 78 | } 79 | defer resp.Body.Close() 80 | d, err := io.ReadAll(resp.Body) 81 | if err != nil { 82 | return nil, err 83 | } 84 | r := &blockResp{} 85 | err = json.Unmarshal(d, &r) 86 | if err != nil { 87 | return nil, err 88 | } 89 | if r.ErrMsg == yerror.ErrBlockNotFound.Error() { 90 | return nil, err 91 | } 92 | return r.Data, nil 93 | } 94 | 95 | type txCountResp struct { 96 | Code int `json:"code"` 97 | ErrMsg string `json:"err_msg"` 98 | Data int `json:"data"` 99 | } 100 | 101 | type blockResp struct { 102 | Code int `json:"code"` 103 | ErrMsg string `json:"err_msg"` 104 | Data *types.Block `json:"data"` 105 | } 106 | -------------------------------------------------------------------------------- /test/pkg/case.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | type CaseEthWallet struct { 9 | *EthWallet 10 | EthCount uint64 `json:"ethCount"` 11 | } 12 | 13 | func (c *CaseEthWallet) Copy() *CaseEthWallet { 14 | return &CaseEthWallet{ 15 | EthWallet: c.EthWallet.Copy(), 16 | EthCount: c.EthCount, 17 | } 18 | } 19 | 20 | type TransferManager struct{} 21 | 22 | func NewTransferManager() *TransferManager { 23 | return &TransferManager{} 24 | } 25 | 26 | func GenerateCaseWallets(initialEthCount uint64, wallets []*EthWallet) []*CaseEthWallet { 27 | c := make([]*CaseEthWallet, 0) 28 | for _, w := range wallets { 29 | c = append(c, &CaseEthWallet{ 30 | EthWallet: w, 31 | EthCount: initialEthCount, 32 | }) 33 | } 34 | return c 35 | } 36 | 37 | func (m *TransferManager) GenerateRandomTransferSteps(stepCount int, wallets []*CaseEthWallet) *TransferCase { 38 | t := &TransferCase{ 39 | Original: getCopy(wallets), 40 | Expect: getCopy(wallets), 41 | } 42 | steps := make([]*Step, 0) 43 | r := rand.New(rand.NewSource(time.Now().Unix())) 44 | curTransfer := 1 45 | for i := 0; i < stepCount; i++ { 46 | steps = append(steps, generateRandomStep(r, wallets, curTransfer)) 47 | curTransfer++ 48 | } 49 | t.Steps = steps 50 | calculateExpect(t) 51 | return t 52 | } 53 | 54 | func (m *TransferManager) GenerateTransferSteps(wallets []*CaseEthWallet) *TransferCase { 55 | t := &TransferCase{ 56 | Original: getCopy(wallets), 57 | Expect: getCopy(wallets), 58 | } 59 | steps := make([]*Step, 0) 60 | curTransfer := 1 61 | for i := 0; i < len(wallets); i += 2 { 62 | steps = append(steps, generateStep(wallets[i], wallets[i+1], curTransfer)) 63 | curTransfer++ 64 | } 65 | t.Steps = steps 66 | calculateExpect(t) 67 | return t 68 | } 69 | 70 | func (m *TransferManager) GenerateSameTargetTransferSteps(stepCount int, wallets []*CaseEthWallet, target *CaseEthWallet) *TransferCase { 71 | t := &TransferCase{ 72 | Original: getCopy(wallets), 73 | Expect: getCopy(wallets), 74 | } 75 | steps := make([]*Step, 0) 76 | cur := 0 77 | curTransfer := 1 78 | for i := 0; i < stepCount; i++ { 79 | from := wallets[cur] 80 | steps = append(steps, generateTransferStep(from, target, curTransfer)) 81 | cur++ 82 | if cur >= len(wallets) { 83 | cur = 0 84 | } 85 | curTransfer++ 86 | } 87 | t.Steps = steps 88 | calculateExpect(t) 89 | return t 90 | } 91 | 92 | func (tc *TransferCase) Run(m *WalletManager) error { 93 | nonceMap := make(map[string]uint64) 94 | for _, step := range tc.Steps { 95 | if _, ok := nonceMap[step.From.Address]; ok { 96 | nonceMap[step.From.Address]++ 97 | } 98 | if err := m.TransferEth(step.From, step.To, step.Count, nonceMap[step.From.Address]); err != nil { 99 | return err 100 | } 101 | } 102 | return nil 103 | } 104 | 105 | func (tc *TransferCase) AssertExpect(m *WalletManager, wallets []*EthWallet) (map[string]*CaseEthWallet, bool, error) { 106 | got := make(map[string]*CaseEthWallet) 107 | for _, w := range wallets { 108 | c, err := m.QueryEth(w) 109 | if err != nil { 110 | return nil, false, err 111 | } 112 | got[w.Address] = &CaseEthWallet{ 113 | EthWallet: w, 114 | EthCount: c, 115 | } 116 | } 117 | if len(tc.Expect) != len(got) { 118 | return got, false, nil 119 | } 120 | for key, value := range got { 121 | e, ok := tc.Expect[key] 122 | if !ok { 123 | return got, false, nil 124 | } 125 | if e.EthCount != value.EthCount { 126 | return got, false, nil 127 | } 128 | } 129 | return got, true, nil 130 | } 131 | 132 | func calculateExpect(tc *TransferCase) { 133 | for _, step := range tc.Steps { 134 | calculate(step, tc.Expect) 135 | } 136 | } 137 | 138 | func calculate(step *Step, expect map[string]*CaseEthWallet) { 139 | fromWallet := expect[step.From.Address] 140 | toWallet := expect[step.To.Address] 141 | fromWallet.EthCount = fromWallet.EthCount - step.Count 142 | toWallet.EthCount = toWallet.EthCount + step.Count 143 | expect[step.From.Address] = fromWallet 144 | expect[step.To.Address] = toWallet 145 | } 146 | 147 | func generateRandomStep(r *rand.Rand, wallets []*CaseEthWallet, transfer int) *Step { 148 | from := r.Intn(len(wallets)) 149 | to := from + 1 150 | if to >= len(wallets) { 151 | to = 0 152 | } 153 | return &Step{ 154 | From: wallets[from].EthWallet, 155 | To: wallets[to].EthWallet, 156 | Count: uint64(transfer), 157 | } 158 | } 159 | 160 | func generateStep(from, to *CaseEthWallet, transfer int) *Step { 161 | return &Step{ 162 | From: from.EthWallet, 163 | To: to.EthWallet, 164 | Count: uint64(transfer), 165 | } 166 | } 167 | 168 | func generateTransferStep(from, to *CaseEthWallet, transferCount int) *Step { 169 | return &Step{ 170 | From: from.EthWallet, 171 | To: to.EthWallet, 172 | Count: uint64(transferCount), 173 | } 174 | } 175 | 176 | func getCopy(wallets []*CaseEthWallet) map[string]*CaseEthWallet { 177 | m := make(map[string]*CaseEthWallet) 178 | for _, w := range wallets { 179 | m[w.Address] = w.Copy() 180 | } 181 | return m 182 | } 183 | 184 | type TransferCase struct { 185 | Steps []*Step `json:"steps"` 186 | // address to wallet 187 | Original map[string]*CaseEthWallet `json:"original"` 188 | Expect map[string]*CaseEthWallet `json:"expect"` 189 | } 190 | 191 | type Step struct { 192 | From *EthWallet `json:"from"` 193 | To *EthWallet `json:"to"` 194 | Count uint64 `json:"count"` 195 | } 196 | -------------------------------------------------------------------------------- /test/pkg/case_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | "github.com/tendermint/tendermint/libs/os" 9 | ) 10 | 11 | func TestTransferStep(t *testing.T) { 12 | data, err := os.ReadFile("./test/data.json") 13 | require.NoError(t, err) 14 | tc := &TransferCase{} 15 | require.NoError(t, json.Unmarshal(data, tc)) 16 | require.Equal(t, calculateSteps(tc.Steps, copyOrigin(tc.Original)), tc.Expect) 17 | ntc := &TransferCase{ 18 | Steps: tc.Steps, 19 | Original: tc.Original, 20 | Expect: tc.Original, 21 | } 22 | calculateExpect(ntc) 23 | require.Equal(t, tc.Expect, ntc.Expect) 24 | } 25 | 26 | func copyOrigin(origin map[string]*CaseEthWallet) map[string]*CaseEthWallet { 27 | m := make(map[string]*CaseEthWallet) 28 | for k, v := range origin { 29 | m[k] = v.Copy() 30 | } 31 | return m 32 | } 33 | 34 | func calculateSteps(steps []*Step, origin map[string]*CaseEthWallet) map[string]*CaseEthWallet { 35 | for _, step := range steps { 36 | calculateStep(step, origin) 37 | } 38 | return origin 39 | } 40 | 41 | func calculateStep(step *Step, origin map[string]*CaseEthWallet) { 42 | origin[step.From.Address].EthCount = origin[step.From.Address].EthCount - step.Count 43 | origin[step.To.Address].EthCount = origin[step.To.Address].EthCount + step.Count 44 | } 45 | -------------------------------------------------------------------------------- /test/pkg/test/data.json: -------------------------------------------------------------------------------- 1 | {"steps":[{"from":{"pk":"33d4bce78c7ca6a6b5277d760c610b546fa31a93cb4b0833bf1804a98243e083","address":"0x260849EA376C93ca1cEd0F22c5ca360369d39143"},"to":{"pk":"772121a8e8227349ffc0671c985c483d4b4199acd8e478759cfd4da0737e542c","address":"0x0Fe412F7ea80A594966FB31baFd7afED2F70794a"},"count":54},{"from":{"pk":"9014fc6c2b8c6783c449ff9781de945c3e728a407a6931b6b0dc2bc6dad73ced","address":"0x8964C4F47e59EDAf87aa00ce4b8C44D13bc2729e"},"to":{"pk":"ad34c860310afdb49b410a64557306684d22b6fb30038ddad302ab4e91a2092b","address":"0x4091064339854c176488e8FFaA70ed62ce1d27fC"},"count":9},{"from":{"pk":"96e767362be08999a84193ecc1df17c877418426274b4c9233638e8837d591d9","address":"0xDb9A5Ed568bE5F1F417e9fB59772454826B063AA"},"to":{"pk":"557e718aa78e268e6227ec6de017dbb958bca8e4970714e12c0f2d66fcd8d1eb","address":"0x6d4e5384987fD468900863Eb201F6b20Ac5983E6"},"count":34},{"from":{"pk":"ad34c860310afdb49b410a64557306684d22b6fb30038ddad302ab4e91a2092b","address":"0x4091064339854c176488e8FFaA70ed62ce1d27fC"},"to":{"pk":"0fd57655db3be165cc7a95c27e21a7936a4013e901c27ae31d78848135282d23","address":"0x6216c6b8B47E6974b1C9E77f80784b2377629Cfb"},"count":52},{"from":{"pk":"dc8f004a00c411c1693bcf831fd138e58ad116f9552b16033d93efebca04990b","address":"0xca073d10Dde33f34c3F866b9FdBbCFaC559914Bf"},"to":{"pk":"ad34c860310afdb49b410a64557306684d22b6fb30038ddad302ab4e91a2092b","address":"0x4091064339854c176488e8FFaA70ed62ce1d27fC"},"count":63}],"original":{"0x0Fe412F7ea80A594966FB31baFd7afED2F70794a":{"pk":"772121a8e8227349ffc0671c985c483d4b4199acd8e478759cfd4da0737e542c","address":"0x0Fe412F7ea80A594966FB31baFd7afED2F70794a","ethCount":10000},"0x260849EA376C93ca1cEd0F22c5ca360369d39143":{"pk":"33d4bce78c7ca6a6b5277d760c610b546fa31a93cb4b0833bf1804a98243e083","address":"0x260849EA376C93ca1cEd0F22c5ca360369d39143","ethCount":10000},"0x4091064339854c176488e8FFaA70ed62ce1d27fC":{"pk":"ad34c860310afdb49b410a64557306684d22b6fb30038ddad302ab4e91a2092b","address":"0x4091064339854c176488e8FFaA70ed62ce1d27fC","ethCount":10000},"0x5C13105ba0c01d4c1bC3f3e30eEE899510820148":{"pk":"eef35eb59bd75827de709b269243794ba0e20d3ce6386bee7f5c4e54d3e0b555","address":"0x5C13105ba0c01d4c1bC3f3e30eEE899510820148","ethCount":10000},"0x6216c6b8B47E6974b1C9E77f80784b2377629Cfb":{"pk":"0fd57655db3be165cc7a95c27e21a7936a4013e901c27ae31d78848135282d23","address":"0x6216c6b8B47E6974b1C9E77f80784b2377629Cfb","ethCount":10000},"0x6d4e5384987fD468900863Eb201F6b20Ac5983E6":{"pk":"557e718aa78e268e6227ec6de017dbb958bca8e4970714e12c0f2d66fcd8d1eb","address":"0x6d4e5384987fD468900863Eb201F6b20Ac5983E6","ethCount":10000},"0x8964C4F47e59EDAf87aa00ce4b8C44D13bc2729e":{"pk":"9014fc6c2b8c6783c449ff9781de945c3e728a407a6931b6b0dc2bc6dad73ced","address":"0x8964C4F47e59EDAf87aa00ce4b8C44D13bc2729e","ethCount":10000},"0xDb9A5Ed568bE5F1F417e9fB59772454826B063AA":{"pk":"96e767362be08999a84193ecc1df17c877418426274b4c9233638e8837d591d9","address":"0xDb9A5Ed568bE5F1F417e9fB59772454826B063AA","ethCount":10000},"0xb2828628FC3C141FFaC10E89f8C20E1b08130551":{"pk":"9c188a6e22678c6a5ba18722c629ddcaef9d17b45b8c04b8f25d41200fad7dbf","address":"0xb2828628FC3C141FFaC10E89f8C20E1b08130551","ethCount":10000},"0xca073d10Dde33f34c3F866b9FdBbCFaC559914Bf":{"pk":"dc8f004a00c411c1693bcf831fd138e58ad116f9552b16033d93efebca04990b","address":"0xca073d10Dde33f34c3F866b9FdBbCFaC559914Bf","ethCount":10000}},"expect":{"0x0Fe412F7ea80A594966FB31baFd7afED2F70794a":{"pk":"772121a8e8227349ffc0671c985c483d4b4199acd8e478759cfd4da0737e542c","address":"0x0Fe412F7ea80A594966FB31baFd7afED2F70794a","ethCount":10054},"0x260849EA376C93ca1cEd0F22c5ca360369d39143":{"pk":"33d4bce78c7ca6a6b5277d760c610b546fa31a93cb4b0833bf1804a98243e083","address":"0x260849EA376C93ca1cEd0F22c5ca360369d39143","ethCount":9946},"0x4091064339854c176488e8FFaA70ed62ce1d27fC":{"pk":"ad34c860310afdb49b410a64557306684d22b6fb30038ddad302ab4e91a2092b","address":"0x4091064339854c176488e8FFaA70ed62ce1d27fC","ethCount":10020},"0x5C13105ba0c01d4c1bC3f3e30eEE899510820148":{"pk":"eef35eb59bd75827de709b269243794ba0e20d3ce6386bee7f5c4e54d3e0b555","address":"0x5C13105ba0c01d4c1bC3f3e30eEE899510820148","ethCount":10000},"0x6216c6b8B47E6974b1C9E77f80784b2377629Cfb":{"pk":"0fd57655db3be165cc7a95c27e21a7936a4013e901c27ae31d78848135282d23","address":"0x6216c6b8B47E6974b1C9E77f80784b2377629Cfb","ethCount":10052},"0x6d4e5384987fD468900863Eb201F6b20Ac5983E6":{"pk":"557e718aa78e268e6227ec6de017dbb958bca8e4970714e12c0f2d66fcd8d1eb","address":"0x6d4e5384987fD468900863Eb201F6b20Ac5983E6","ethCount":10034},"0x8964C4F47e59EDAf87aa00ce4b8C44D13bc2729e":{"pk":"9014fc6c2b8c6783c449ff9781de945c3e728a407a6931b6b0dc2bc6dad73ced","address":"0x8964C4F47e59EDAf87aa00ce4b8C44D13bc2729e","ethCount":9991},"0xDb9A5Ed568bE5F1F417e9fB59772454826B063AA":{"pk":"96e767362be08999a84193ecc1df17c877418426274b4c9233638e8837d591d9","address":"0xDb9A5Ed568bE5F1F417e9fB59772454826B063AA","ethCount":9966},"0xb2828628FC3C141FFaC10E89f8C20E1b08130551":{"pk":"9c188a6e22678c6a5ba18722c629ddcaef9d17b45b8c04b8f25d41200fad7dbf","address":"0xb2828628FC3C141FFaC10E89f8C20E1b08130551","ethCount":10000},"0xca073d10Dde33f34c3F866b9FdBbCFaC559914Bf":{"pk":"dc8f004a00c411c1693bcf831fd138e58ad116f9552b16033d93efebca04990b","address":"0xca073d10Dde33f34c3F866b9FdBbCFaC559914Bf","ethCount":9937}}} -------------------------------------------------------------------------------- /test/pkg/util.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "crypto/rand" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/ethereum/go-ethereum/common/hexutil" 13 | "github.com/ethereum/go-ethereum/crypto" 14 | "github.com/sirupsen/logrus" 15 | ) 16 | 17 | func generatePrivateKey() (string, string) { 18 | privateKey, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) 19 | if err != nil { 20 | return "", "" 21 | } 22 | privateKeyBytes := crypto.FromECDSA(privateKey) 23 | 24 | publicKey := privateKey.Public() 25 | publicKeyECDSA, _ := publicKey.(*ecdsa.PublicKey) 26 | address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() 27 | 28 | return hexutil.Encode(privateKeyBytes)[2:], address 29 | } 30 | 31 | func sendRequest(hostAddress string, dataString string) ([]byte, error) { 32 | resp, err := sendSingleRequest(hostAddress, dataString) 33 | if err == nil { 34 | return resp, nil 35 | } 36 | logrus.Infof("send request got Err:%v", err) 37 | for { 38 | time.Sleep(10 * time.Millisecond) 39 | resp, err = sendSingleRequest(hostAddress, dataString) 40 | if err == nil { 41 | break 42 | } 43 | } 44 | return resp, nil 45 | } 46 | 47 | func sendSingleRequest(hostAddress string, dataString string) ([]byte, error) { 48 | req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s", hostAddress), bytes.NewBuffer([]byte(dataString))) 49 | if err != nil { 50 | return nil, err 51 | } 52 | req.Header.Set("Content-Type", "application/json") 53 | client := &http.Client{} 54 | resp, err := client.Do(req) 55 | if err != nil { 56 | return nil, fmt.Errorf("error sending request: %v", err) 57 | } 58 | defer resp.Body.Close() 59 | data, err := io.ReadAll(resp.Body) 60 | if err != nil { 61 | return nil, fmt.Errorf("error sending request: %v", err) 62 | } 63 | 64 | if resp.StatusCode != http.StatusOK { 65 | return nil, fmt.Errorf("status code err: %v", resp.StatusCode) 66 | } 67 | return data, nil 68 | } 69 | -------------------------------------------------------------------------------- /test/pkg/wallet_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestParse(t *testing.T) { 10 | v, err := parse("0x2710") 11 | require.NoError(t, err) 12 | require.Equal(t, uint64(10000), v) 13 | } 14 | -------------------------------------------------------------------------------- /test/testx/config.go: -------------------------------------------------------------------------------- 1 | package testx 2 | 3 | import ( 4 | "github.com/yu-org/yu/apps/poa" 5 | yuConfig "github.com/yu-org/yu/config" 6 | "github.com/yu-org/yu/core/startup" 7 | 8 | config2 "github.com/reddio-com/reddio/config" 9 | "github.com/reddio-com/reddio/evm" 10 | ) 11 | 12 | func GenerateConfig(yuConfigPath, evmConfigPath, poaConfigPath string, isParallel bool) (yuCfg *yuConfig.KernelConf, poaCfg *poa.PoaConfig, evmConfig *evm.GethConfig, config *config2.Config) { 13 | yuCfg = startup.InitKernelConfigFromPath(yuConfigPath) 14 | evmConfig = evm.LoadEvmConfig(evmConfigPath) 15 | config = config2.GetGlobalConfig() 16 | config.IsBenchmarkMode = true 17 | config.IsParallel = isParallel 18 | config.AsyncCommit = false 19 | config.RateLimitConfig.GetReceipt = 0 20 | poaCfg = poa.LoadCfgFromPath(poaConfigPath) 21 | return yuCfg, poaCfg, evmConfig, config 22 | } 23 | -------------------------------------------------------------------------------- /test/transfer/benchmark_case.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | "golang.org/x/time/rate" 9 | 10 | "github.com/reddio-com/reddio/test/pkg" 11 | ) 12 | 13 | type RandomBenchmarkTest struct { 14 | CaseName string 15 | initialCount uint64 16 | tm *pkg.TransferManager 17 | wallets []*pkg.EthWallet 18 | rm *rate.Limiter 19 | } 20 | 21 | func NewRandomBenchmarkTest(name string, initial uint64, wallets []*pkg.EthWallet, rm *rate.Limiter) *RandomBenchmarkTest { 22 | return &RandomBenchmarkTest{ 23 | CaseName: name, 24 | initialCount: initial, 25 | tm: pkg.NewTransferManager(), 26 | wallets: wallets, 27 | rm: rm, 28 | } 29 | } 30 | 31 | func (tc *RandomBenchmarkTest) Name() string { 32 | return tc.CaseName 33 | } 34 | 35 | func (tc *RandomBenchmarkTest) Run(ctx context.Context, m *pkg.WalletManager) error { 36 | transferCase := tc.tm.GenerateTransferSteps(pkg.GenerateCaseWallets(tc.initialCount, tc.wallets)) 37 | for i, step := range transferCase.Steps { 38 | if err := tc.rm.Wait(ctx); err != nil { 39 | return err 40 | } 41 | if err := m.TransferEth(step.From, step.To, step.Count, uint64(i)+uint64(time.Now().UnixNano())); err != nil { 42 | logrus.Error("Failed to transfer step: from:%v, to:%v", step.From, step.To) 43 | } 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /test/transfer/conflict_transfer.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sirupsen/logrus" 7 | 8 | "github.com/reddio-com/reddio/test/pkg" 9 | ) 10 | 11 | type ConflictTransfer struct { 12 | CaseName string 13 | walletCount int 14 | initialCount uint64 15 | steps int 16 | tm *pkg.TransferManager 17 | } 18 | 19 | func (c *ConflictTransfer) Run(ctx context.Context, m *pkg.WalletManager) error { 20 | wallets, err := m.GenerateRandomWallets(c.walletCount, c.initialCount) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | logrus.Info("%s create wallets finish", c.CaseName) 26 | 27 | cwallets := pkg.GenerateCaseWallets(c.initialCount, wallets) 28 | transferCase := c.tm.GenerateSameTargetTransferSteps(c.steps, cwallets, cwallets[0]) 29 | return runAndAssert(transferCase, m, wallets) 30 | } 31 | 32 | func (c *ConflictTransfer) Name() string { 33 | return c.CaseName 34 | } 35 | 36 | func NewConflictTest(name string, count int, initial uint64, steps int) *ConflictTransfer { 37 | return &ConflictTransfer{ 38 | CaseName: name, 39 | walletCount: count, 40 | initialCount: initial, 41 | steps: steps, 42 | tm: pkg.NewTransferManager(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/transfer/eth.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sirupsen/logrus" 8 | 9 | "github.com/reddio-com/reddio/test/conf" 10 | "github.com/reddio-com/reddio/test/pkg" 11 | ) 12 | 13 | type EthManager struct { 14 | config *conf.EthCaseConf 15 | wm *pkg.WalletManager 16 | // tm *pkg.TransferManager 17 | testcases []TestCase 18 | } 19 | 20 | func (m *EthManager) Configure(cfg *conf.EthCaseConf, nodeUrl, pk string, chainID int64) { 21 | m.config = cfg 22 | m.wm = pkg.NewWalletManager(chainID, nodeUrl, pk) 23 | m.testcases = []TestCase{} 24 | } 25 | 26 | func (m *EthManager) GetWalletManager() *pkg.WalletManager { 27 | return m.wm 28 | } 29 | 30 | func (m *EthManager) PreCreateWallets(walletCount int, initCount uint64) ([]*pkg.EthWallet, error) { 31 | wallets, err := m.wm.BatchGenerateRandomWallets(walletCount, initCount) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return wallets, nil 36 | } 37 | 38 | func (m *EthManager) AddTestCase(tc ...TestCase) { 39 | m.testcases = append(m.testcases, tc...) 40 | } 41 | 42 | func (m *EthManager) Run(ctx context.Context) error { 43 | for _, tc := range m.testcases { 44 | logrus.Infof("start to test %v", tc.Name()) 45 | if err := tc.Run(ctx, m.wm); err != nil { 46 | return fmt.Errorf("%s failed, err:%v", tc.Name(), err) 47 | } 48 | logrus.Infof("test %v success", tc.Name()) 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /test/transfer/state_root_testcase.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | yucommon "github.com/yu-org/yu/common" 10 | 11 | "github.com/reddio-com/reddio/test/pkg" 12 | ) 13 | 14 | var resultJson = "stateRootTestResult.json" 15 | 16 | type StateRootTestCase struct { 17 | *RandomTransferTestCase 18 | } 19 | 20 | func NewStateRootTestCase(name string, count int, initial uint64, steps int) *StateRootTestCase { 21 | return &StateRootTestCase{ 22 | RandomTransferTestCase: NewRandomTest(name, count, initial, steps), 23 | } 24 | } 25 | 26 | func (st *StateRootTestCase) Name() string { 27 | return "StateRootTestCase" 28 | } 29 | 30 | func (st *StateRootTestCase) Run(ctx context.Context, m *pkg.WalletManager) error { 31 | if err := st.RandomTransferTestCase.Run(ctx, m); err != nil { 32 | return err 33 | } 34 | hash, err := getStateRoot() 35 | if err != nil { 36 | return err 37 | } 38 | result := StateRootTestResult{ 39 | Wallets: st.wallets, 40 | TransferCase: st.transCase, 41 | StateRoot: hash, 42 | } 43 | content, _ := json.Marshal(result) 44 | 45 | if _, err = os.Stat("stateRootTestResult.json"); err == nil { 46 | if err = os.Remove("stateRootTestResult.json"); err != nil { 47 | return err 48 | } 49 | } else if !os.IsNotExist(err) { 50 | return err 51 | } 52 | file, err := os.Create(resultJson) 53 | if err != nil { 54 | return err 55 | } 56 | defer func() { 57 | _ = file.Close() 58 | }() 59 | _, err = file.Write(content) 60 | if err != nil { 61 | return err 62 | } 63 | return nil 64 | } 65 | 66 | type StateRootTestResult struct { 67 | Wallets []*pkg.CaseEthWallet `json:"wallets"` 68 | TransferCase *pkg.TransferCase `json:"transferCase"` 69 | StateRoot yucommon.Hash `json:"stateRoot"` 70 | } 71 | 72 | func getStateRoot() (yucommon.Hash, error) { 73 | b, err := pkg.GetDefaultBlockManager().GetBlockByIndex(3) 74 | if err != nil { 75 | return [32]byte{}, err 76 | } 77 | return b.StateRoot, nil 78 | } 79 | 80 | type StateRootAssertTestCase struct { 81 | content []byte 82 | initial uint64 83 | } 84 | 85 | func NewStateRootAssertTestCase(content []byte, initial uint64) *StateRootAssertTestCase { 86 | return &StateRootAssertTestCase{content: content, initial: initial} 87 | } 88 | 89 | func (s *StateRootAssertTestCase) Run(ctx context.Context, m *pkg.WalletManager) error { 90 | result := &StateRootTestResult{} 91 | if err := json.Unmarshal(s.content, result); err != nil { 92 | return err 93 | } 94 | var lastWallet *pkg.EthWallet 95 | var err error 96 | for _, wallet := range result.Wallets { 97 | lastWallet, err = m.CreateEthWalletByAddress(s.initial, wallet.PK, wallet.Address) 98 | if err != nil { 99 | return err 100 | } 101 | } 102 | m.AssertWallet(lastWallet, s.initial) 103 | if err := runAndAssert(result.TransferCase, m, getWallets(result.Wallets)); err != nil { 104 | return err 105 | } 106 | stateRoot, err := getStateRoot() 107 | if err != nil { 108 | return err 109 | } 110 | if result.StateRoot != stateRoot { 111 | return fmt.Errorf("expected stateRoot %s, got %s", stateRoot.String(), result.StateRoot.String()) 112 | } 113 | return nil 114 | } 115 | 116 | func (s *StateRootAssertTestCase) Name() string { 117 | return "StateRootAssertTestCase" 118 | } 119 | 120 | func getWallets(ws []*pkg.CaseEthWallet) []*pkg.EthWallet { 121 | got := make([]*pkg.EthWallet, 0) 122 | for _, w := range ws { 123 | got = append(got, w.EthWallet) 124 | } 125 | return got 126 | } 127 | -------------------------------------------------------------------------------- /test/transfer/testcase.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | 10 | "github.com/reddio-com/reddio/test/pkg" 11 | ) 12 | 13 | type TestCase interface { 14 | Run(ctx context.Context, m *pkg.WalletManager) error 15 | Name() string 16 | } 17 | 18 | type RandomTransferTestCase struct { 19 | CaseName string 20 | walletCount int 21 | initialCount uint64 22 | steps int 23 | tm *pkg.TransferManager 24 | 25 | wallets []*pkg.CaseEthWallet 26 | transCase *pkg.TransferCase 27 | } 28 | 29 | func NewRandomTest(name string, count int, initial uint64, steps int) *RandomTransferTestCase { 30 | return &RandomTransferTestCase{ 31 | CaseName: name, 32 | walletCount: count, 33 | initialCount: initial, 34 | steps: steps, 35 | tm: pkg.NewTransferManager(), 36 | } 37 | } 38 | 39 | func (tc *RandomTransferTestCase) Name() string { 40 | return tc.CaseName 41 | } 42 | 43 | func (tc *RandomTransferTestCase) Run(ctx context.Context, m *pkg.WalletManager) error { 44 | var wallets []*pkg.EthWallet 45 | var err error 46 | wallets, err = m.GenerateRandomWallets(tc.walletCount, tc.initialCount) 47 | if err != nil { 48 | return err 49 | } 50 | logrus.Infof("%s create wallets finish", tc.CaseName) 51 | tc.wallets = pkg.GenerateCaseWallets(tc.initialCount, wallets) 52 | tc.transCase = tc.tm.GenerateRandomTransferSteps(tc.steps, tc.wallets) 53 | return runAndAssert(tc.transCase, m, wallets) 54 | } 55 | 56 | func runAndAssert(transferCase *pkg.TransferCase, m *pkg.WalletManager, wallets []*pkg.EthWallet) error { 57 | if err := transferCase.Run(m); err != nil { 58 | return err 59 | } 60 | logrus.Info("wait transfer transaction done") 61 | time.Sleep(5 * time.Second) 62 | success, err := assert(transferCase, m, wallets) 63 | if err != nil { 64 | return err 65 | } 66 | if !success { 67 | return errors.New("transfer manager assert failed") 68 | } 69 | 70 | bm := pkg.GetDefaultBlockManager() 71 | block, err := bm.GetCurrentBlock() 72 | if err != nil { 73 | return err 74 | } 75 | logrus.Infof("Block(%d) StateRoot: %s", block.Height, block.StateRoot.String()) 76 | return nil 77 | } 78 | 79 | func assert(transferCase *pkg.TransferCase, walletsManager *pkg.WalletManager, wallets []*pkg.EthWallet) (bool, error) { 80 | var got map[string]*pkg.CaseEthWallet 81 | var success bool 82 | var err error 83 | for i := 0; i < 20; i++ { 84 | got, success, err = transferCase.AssertExpect(walletsManager, wallets) 85 | if err != nil { 86 | return false, err 87 | } 88 | if success { 89 | return true, nil 90 | } else { 91 | // wait block 92 | time.Sleep(4 * time.Second) 93 | continue 94 | } 95 | } 96 | 97 | printChange(got, transferCase.Expect, transferCase) 98 | return false, nil 99 | } 100 | 101 | func printChange(got, expect map[string]*pkg.CaseEthWallet, transferCase *pkg.TransferCase) { 102 | for _, step := range transferCase.Steps { 103 | logrus.Infof("%v transfer %v eth to %v", step.From.Address, step.Count, step.To.Address) 104 | } 105 | for k, v := range got { 106 | ev, ok := expect[k] 107 | if ok { 108 | if v.EthCount != ev.EthCount { 109 | logrus.Infof("%v got:%v expect:%v", k, v.EthCount, ev.EthCount) 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/uniswap/eth.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sirupsen/logrus" 8 | 9 | "github.com/reddio-com/reddio/test/conf" 10 | "github.com/reddio-com/reddio/test/pkg" 11 | ) 12 | 13 | type EthManager struct { 14 | config *conf.EthCaseConf 15 | wm *pkg.WalletManager 16 | // tm *pkg.TransferManager 17 | testcases []TestCase 18 | } 19 | 20 | func (m *EthManager) Configure(cfg *conf.EthCaseConf, nodeUrl, pk string, chainID int64) { 21 | m.config = cfg 22 | m.wm = pkg.NewWalletManager(chainID, nodeUrl, pk) 23 | m.testcases = []TestCase{} 24 | } 25 | 26 | func (m *EthManager) PreCreateWallets(walletCount int, initCount uint64) ([]*pkg.EthWallet, error) { 27 | wallets, err := m.wm.GenerateRandomWallets(walletCount, initCount) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return wallets, nil 32 | } 33 | 34 | func (m *EthManager) AddTestCase(tc ...TestCase) { 35 | m.testcases = append(m.testcases, tc...) 36 | } 37 | 38 | func (m *EthManager) Run(ctx context.Context) error { 39 | for _, tc := range m.testcases { 40 | logrus.Infof("start to test %v", tc.Name()) 41 | if err := tc.Run(ctx, m.wm); err != nil { 42 | return fmt.Errorf("%s failed, err:%v", tc.Name(), err) 43 | } 44 | logrus.Infof("test %v success", tc.Name()) 45 | } 46 | return nil 47 | } 48 | 49 | func (m *EthManager) Prepare(ctx context.Context) error { 50 | for _, tc := range m.testcases { 51 | logrus.Infof("start to prepare %v", tc.Name()) 52 | if err := tc.Prepare(ctx, m.wm); err != nil { 53 | return fmt.Errorf("%s failed, err:%v", tc.Name(), err) 54 | } 55 | logrus.Infof("prepare %v success", tc.Name()) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /test/uniswap/testcase.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reddio-com/reddio/test/pkg" 7 | ) 8 | 9 | type TestCase interface { 10 | Prepare(ctx context.Context, m *pkg.WalletManager) error 11 | Run(ctx context.Context, m *pkg.WalletManager) error 12 | Name() string 13 | } 14 | -------------------------------------------------------------------------------- /utils/pprof.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | //import ( 4 | // "fmt" 5 | // "github.com/reddio-com/reddio/cairo/config" 6 | // "net/http" 7 | // _ "net/http/pprof" 8 | //) 9 | // 10 | //func StartUpPprof(cfg *config.Config) { 11 | // if cfg.EnablePprof { 12 | // go func() { 13 | // fmt.Println(http.ListenAndServe(cfg.PprofAddr, nil)) 14 | // }() 15 | // } 16 | //} 17 | -------------------------------------------------------------------------------- /utils/ratelimit.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "golang.org/x/time/rate" 6 | 7 | "github.com/reddio-com/reddio/config" 8 | ) 9 | 10 | var ( 11 | GetReceiptRateLimiter *rate.Limiter 12 | ) 13 | 14 | func IniLimiter() { 15 | GetReceiptRateLimiter = GenGetReceiptRateLimiter() 16 | logrus.Infof("GetReceipt Limit %v qps", config.GetGlobalConfig().RateLimitConfig.GetReceipt) 17 | } 18 | 19 | func GenGetReceiptRateLimiter() *rate.Limiter { 20 | qps := config.GetGlobalConfig().RateLimitConfig.GetReceipt 21 | if qps < 1 { 22 | return nil 23 | } 24 | limiter := rate.NewLimiter(rate.Limit(qps), 1) 25 | return limiter 26 | } 27 | -------------------------------------------------------------------------------- /utils/s3/s3_client.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "path/filepath" 8 | 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/config" 11 | "github.com/aws/aws-sdk-go-v2/service/s3" 12 | ) 13 | 14 | func InitS3Config(folder, bucket string) (*S3ConfigClient, error) { 15 | s := &S3ConfigClient{} 16 | if err := s.Init(folder, bucket); err != nil { 17 | return nil, fmt.Errorf("init s3 client err: %v", err) 18 | } 19 | if err := s.LoadAllConfig(); err != nil { 20 | return nil, fmt.Errorf("load config from s3 err: %v", err) 21 | } 22 | return s, nil 23 | } 24 | 25 | type S3ConfigClient struct { 26 | Folder string 27 | Bucket string 28 | client *s3.Client 29 | cd *ConfigData 30 | } 31 | 32 | func (s *S3ConfigClient) Init(Folder, Bucket string) error { 33 | cfg, err := config.LoadDefaultConfig(context.TODO()) 34 | if err != nil { 35 | return err 36 | } 37 | client := s3.NewFromConfig(cfg) 38 | s.client = client 39 | s.Folder = Folder 40 | s.Bucket = Bucket 41 | s.cd = &ConfigData{} 42 | return nil 43 | } 44 | 45 | func (s *S3ConfigClient) GetConfig() *ConfigData { 46 | return s.cd 47 | } 48 | 49 | func (s *S3ConfigClient) LoadAllConfig() error { 50 | var err error 51 | s.cd.EvmCfg, err = s.LoadConfig(filepath.Join(s.Folder, "evm.toml")) 52 | if err != nil { 53 | return err 54 | } 55 | s.cd.YuCfg, err = s.LoadConfig(filepath.Join(s.Folder, "yu.toml")) 56 | if err != nil { 57 | return err 58 | } 59 | s.cd.PoaCfg, err = s.LoadConfig(filepath.Join(s.Folder, "poa.toml")) 60 | if err != nil { 61 | return err 62 | } 63 | s.cd.ConfigCfg, err = s.LoadConfig(filepath.Join(s.Folder, "config.toml")) 64 | if err != nil { 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | func (s *S3ConfigClient) LoadConfig(key string) ([]byte, error) { 71 | resp, err := s.client.GetObject(context.TODO(), &s3.GetObjectInput{ 72 | Bucket: aws.String(s.Bucket), 73 | Key: aws.String(key), 74 | }) 75 | if err != nil { 76 | return nil, err 77 | } 78 | data, err := io.ReadAll(resp.Body) 79 | if err != nil { 80 | return nil, fmt.Errorf("load %v error %v", key, err) 81 | } 82 | return data, nil 83 | } 84 | 85 | type ConfigData struct { 86 | EvmCfg []byte 87 | YuCfg []byte 88 | PoaCfg []byte 89 | ConfigCfg []byte 90 | } 91 | --------------------------------------------------------------------------------