Merge remote-tracking branch 'origin/main' into oc_llvm_c++17_only
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"cdt":{
|
||||
"target":"4",
|
||||
"prerelease":false
|
||||
},
|
||||
"eossystemcontracts":{
|
||||
"ref":"release/3.1"
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,12 @@ RUN apt-get update && apt-get upgrade -y && \
|
||||
cmake \
|
||||
git \
|
||||
jq \
|
||||
libboost-all-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libgmp-dev \
|
||||
libssl-dev \
|
||||
llvm-11-dev \
|
||||
ninja-build \
|
||||
python3-numpy \
|
||||
file \
|
||||
zlib1g-dev \
|
||||
zstd
|
||||
|
||||
@@ -6,11 +6,12 @@ RUN apt-get update && apt-get upgrade -y && \
|
||||
cmake \
|
||||
git \
|
||||
jq \
|
||||
libboost-all-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libgmp-dev \
|
||||
libssl-dev \
|
||||
llvm-11-dev \
|
||||
ninja-build \
|
||||
python3-numpy \
|
||||
file \
|
||||
zlib1g-dev \
|
||||
zstd
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,8 +12,10 @@ const log_tarball_prefix = core.getInput('log-tarball-prefix', {required: true})
|
||||
const tests_label = core.getInput('tests-label', {required: true});
|
||||
const test_timeout = core.getInput('test-timeout', {required: true});
|
||||
|
||||
const repo_name = process.env.GITHUB_REPOSITORY.split('/')[1];
|
||||
|
||||
try {
|
||||
if(child_process.spawnSync("docker", ["run", "--name", "base", "-v", `${process.cwd()}/build.tar.zst:/build.tar.zst`, "--workdir", "/__w/leap/leap", container, "sh", "-c", "zstdcat /build.tar.zst | tar x"], {stdio:"inherit"}).status)
|
||||
if(child_process.spawnSync("docker", ["run", "--name", "base", "-v", `${process.cwd()}/build.tar.zst:/build.tar.zst`, "--workdir", `/__w/${repo_name}/${repo_name}`, container, "sh", "-c", "zstdcat /build.tar.zst | tar x"], {stdio:"inherit"}).status)
|
||||
throw new Error("Failed to create base container");
|
||||
if(child_process.spawnSync("docker", ["commit", "base", "baseimage"], {stdio:"inherit"}).status)
|
||||
throw new Error("Failed to create base image");
|
||||
@@ -45,13 +47,13 @@ try {
|
||||
let packer = tar.pack();
|
||||
|
||||
extractor.on('entry', (header, stream, next) => {
|
||||
if(!header.name.startsWith(`__w/leap/leap/build`)) {
|
||||
if(!header.name.startsWith(`__w/${repo_name}/${repo_name}/build`)) {
|
||||
stream.on('end', () => next());
|
||||
stream.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
header.name = header.name.substring(`__w/leap/leap/`.length);
|
||||
header.name = header.name.substring(`__w/${repo_name}/${repo_name}/`.length);
|
||||
if(header.name !== "build/" && error_log_paths.filter(p => header.name.startsWith(p)).length === 0) {
|
||||
stream.on('end', () => next());
|
||||
stream.resume();
|
||||
|
||||
+160
-80
@@ -7,6 +7,20 @@ on:
|
||||
- "release/*"
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
override-cdt:
|
||||
description: 'Override cdt target'
|
||||
type: string
|
||||
override-cdt-prerelease:
|
||||
type: choice
|
||||
description: Override cdt prelease
|
||||
options:
|
||||
- default
|
||||
- true
|
||||
- false
|
||||
override-eos-system-contracts:
|
||||
description: 'Override eos-system-contracts ref'
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
packages: read
|
||||
@@ -17,87 +31,59 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
d:
|
||||
name: Discover Platforms
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
missing-platforms: ${{steps.discover.outputs.missing-platforms}}
|
||||
p: ${{steps.discover.outputs.platforms}}
|
||||
steps:
|
||||
- name: Discover Platforms
|
||||
id: discover
|
||||
uses: AntelopeIO/discover-platforms-action@v1
|
||||
with:
|
||||
platform-file: .cicd/platforms.json
|
||||
password: ${{secrets.GITHUB_TOKEN}}
|
||||
package-name: builders
|
||||
build-platforms:
|
||||
name: Build Platforms
|
||||
needs: d
|
||||
if: needs.d.outputs.missing-platforms != '[]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{fromJSON(needs.d.outputs.missing-platforms)}}
|
||||
runs-on: ["self-hosted", "enf-x86-beefy"]
|
||||
build-base:
|
||||
name: Run Build Workflow
|
||||
uses: ./.github/workflows/build_base.yaml
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{github.repository_owner}}
|
||||
password: ${{secrets.GITHUB_TOKEN}}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
tags: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
file: ${{fromJSON(needs.d.outputs.p)[matrix.platform].dockerfile}}
|
||||
|
||||
Build:
|
||||
needs: [d, build-platforms]
|
||||
if: always() && needs.d.result == 'success' && (needs.build-platforms.result == 'success' || needs.build-platforms.result == 'skipped')
|
||||
v:
|
||||
name: Discover Versions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cdt-target: ${{steps.versions.outputs.cdt-target}}
|
||||
cdt-prerelease: ${{steps.versions.outputs.cdt-prerelease}}
|
||||
eos-system-contracts-ref: ${{steps.versions.outputs.eos-system-contracts-ref}}
|
||||
steps:
|
||||
- name: Setup cdt and eos-system-contracts versions
|
||||
id: versions
|
||||
env:
|
||||
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
run: |
|
||||
DEFAULTS_JSON=$(curl -sSfL $(gh api https://api.github.com/repos/${{github.repository}}/contents/.cicd/defaults.json?ref=${{github.sha}} --jq .download_url))
|
||||
echo cdt-target=$(echo "$DEFAULTS_JSON" | jq -r '.cdt.target') >> $GITHUB_OUTPUT
|
||||
echo cdt-prerelease=$(echo "$DEFAULTS_JSON" | jq -r '.cdt.prerelease') >> $GITHUB_OUTPUT
|
||||
echo eos-system-contracts-ref=$(echo "$DEFAULTS_JSON" | jq -r '.eossystemcontracts.ref') >> $GITHUB_OUTPUT
|
||||
|
||||
if [[ "${{inputs.override-cdt}}" != "" ]]; then
|
||||
echo cdt-target=${{inputs.override-cdt}} >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if [[ "${{inputs.override-cdt-prerelease}}" == +(true|false) ]]; then
|
||||
echo cdt-prerelease=${{inputs.override-cdt-prerelease}} >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if [[ "${{inputs.override-eos-system-contracts}}" != "" ]]; then
|
||||
echo eos-system-contracts-ref=${{inputs.override-eos-system-contracts}} >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
dev-package:
|
||||
name: Build leap-dev package
|
||||
needs: [build-base]
|
||||
if: always() && needs.build-base.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu20, ubuntu22]
|
||||
runs-on: ["self-hosted", "enf-x86-beefy"]
|
||||
container: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
# https://github.com/actions/runner/issues/2033
|
||||
chown -R $(id -u):$(id -g) $PWD
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -GNinja
|
||||
cmake --build build
|
||||
tar -pc --exclude "*.o" build | zstd --long -T0 -9 > build.tar.zst
|
||||
- name: Upload builddir
|
||||
uses: AntelopeIO/upload-artifact-large-chunks-action@v1
|
||||
with:
|
||||
name: ${{matrix.platform}}-build
|
||||
path: build.tar.zst
|
||||
|
||||
dev-package:
|
||||
name: Build leap-dev package
|
||||
needs: [d, Build]
|
||||
if: always() && needs.Build.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{fromJSON(needs.d.outputs.p)['ubuntu20'].image}}
|
||||
container: ${{fromJSON(needs.build-base.outputs.p)[matrix.platform].image}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- name: Download builddir
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ubuntu20-build
|
||||
name: ${{matrix.platform}}-build
|
||||
- name: Build dev package
|
||||
run: |
|
||||
zstdcat build.tar.zst | tar x
|
||||
@@ -105,27 +91,28 @@ jobs:
|
||||
cpack
|
||||
- name: Install dev package
|
||||
run: |
|
||||
apt update && apt upgrade -y
|
||||
apt install -y ./build/leap_*.deb ./build/leap-dev*.deb
|
||||
apt-get update && apt-get upgrade -y
|
||||
apt-get install -y ./build/leap_*.deb ./build/leap-dev*.deb
|
||||
- name: Test using TestHarness
|
||||
run: |
|
||||
python3 -c "from TestHarness import Cluster"
|
||||
- name: Upload dev package
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: leap-dev-ubuntu20-amd64
|
||||
name: leap-dev-${{matrix.platform}}-amd64
|
||||
path: build/leap-dev*.deb
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
needs: [d, Build]
|
||||
if: always() && needs.Build.result == 'success'
|
||||
needs: [build-base]
|
||||
if: always() && needs.build-base.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu20, ubuntu22]
|
||||
runs-on: ["self-hosted", "enf-x86-hightier"]
|
||||
container:
|
||||
image: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
image: ${{fromJSON(needs.build-base.outputs.p)[matrix.platform].image}}
|
||||
options: --security-opt seccomp=unconfined
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -143,8 +130,8 @@ jobs:
|
||||
|
||||
np-tests:
|
||||
name: NP Tests
|
||||
needs: [d, Build]
|
||||
if: always() && needs.Build.result == 'success'
|
||||
needs: [build-base]
|
||||
if: always() && needs.build-base.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -159,7 +146,7 @@ jobs:
|
||||
- name: Run tests in parallel containers
|
||||
uses: ./.github/actions/parallel-ctest-containers
|
||||
with:
|
||||
container: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
container: ${{fromJSON(needs.build-base.outputs.p)[matrix.platform].image}}
|
||||
error-log-paths: '["build/etc", "build/var", "build/leap-ignition-wd", "build/TestLogs"]'
|
||||
log-tarball-prefix: ${{matrix.platform}}
|
||||
tests-label: nonparallelizable_tests
|
||||
@@ -173,8 +160,8 @@ jobs:
|
||||
|
||||
lr-tests:
|
||||
name: LR Tests
|
||||
needs: [d, Build]
|
||||
if: always() && needs.Build.result == 'success'
|
||||
needs: [build-base]
|
||||
if: always() && needs.build-base.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -189,7 +176,7 @@ jobs:
|
||||
- name: Run tests in parallel containers
|
||||
uses: ./.github/actions/parallel-ctest-containers
|
||||
with:
|
||||
container: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
container: ${{fromJSON(needs.build-base.outputs.p)[matrix.platform].image}}
|
||||
error-log-paths: '["build/etc", "build/var", "build/leap-ignition-wd", "build/TestLogs"]'
|
||||
log-tarball-prefix: ${{matrix.platform}}
|
||||
tests-label: long_running_tests
|
||||
@@ -201,11 +188,104 @@ jobs:
|
||||
name: ${{matrix.platform}}-lr-logs
|
||||
path: '*-logs.tar.gz'
|
||||
|
||||
libtester-tests:
|
||||
name: libtester tests
|
||||
needs: [build-base, v, dev-package]
|
||||
if: always() && needs.v.result == 'success' && needs.dev-package.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu20, ubuntu22]
|
||||
test: [build-tree, make-dev-install, deb-install]
|
||||
runs-on: ["self-hosted", "enf-x86-midtier"]
|
||||
container: ${{ matrix.test != 'deb-install' && fromJSON(needs.build-base.outputs.p)[matrix.platform].image || matrix.platform == 'ubuntu20' && 'ubuntu:focal' || 'ubuntu:jammy' }}
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
TZ: Etc/UTC
|
||||
steps:
|
||||
- name: Update Package Index & Upgrade Packages
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get upgrade -y
|
||||
|
||||
# LEAP
|
||||
- if: ${{ matrix.test != 'deb-install' }}
|
||||
name: Clone leap
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- if: ${{ matrix.test != 'deb-install' }}
|
||||
name: Download leap builddir
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{matrix.platform}}-build
|
||||
- if: ${{ matrix.test != 'deb-install' }}
|
||||
name: Extract leap build
|
||||
run: |
|
||||
zstdcat build.tar.zst | tar x
|
||||
- if: ${{ matrix.test == 'build-tree' }}
|
||||
name: Set leap_DIR env var
|
||||
run: |
|
||||
echo "leap_DIR=$PWD/build/lib/cmake/leap" >> "$GITHUB_ENV"
|
||||
- if: ${{ matrix.test == 'make-dev-install' }}
|
||||
name: leap dev-install
|
||||
run: |
|
||||
cmake --install build
|
||||
cmake --install build --component dev
|
||||
- if: ${{ matrix.test == 'make-dev-install' }}
|
||||
name: Delete leap artifacts
|
||||
run: |
|
||||
rm -r *
|
||||
- if: ${{ matrix.test == 'deb-install' }}
|
||||
name: Download leap-dev
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: leap-dev-${{matrix.platform}}-amd64
|
||||
- if: ${{ matrix.test == 'deb-install' }}
|
||||
name: Install leap-dev Package
|
||||
run: |
|
||||
apt-get install -y ./*.deb
|
||||
rm ./*.deb
|
||||
|
||||
# CDT
|
||||
- name: Download cdt
|
||||
uses: AntelopeIO/asset-artifact-download-action@v2
|
||||
with:
|
||||
owner: AntelopeIO
|
||||
repo: cdt
|
||||
file: 'cdt_.*amd64.deb'
|
||||
target: '${{needs.v.outputs.cdt-target}}'
|
||||
prereleases: ${{fromJSON(needs.v.outputs.cdt-prerelease)}}
|
||||
artifact-name: cdt_ubuntu_package_amd64
|
||||
token: ${{github.token}}
|
||||
- name: Install cdt Packages
|
||||
run: |
|
||||
apt-get install -y ./*.deb
|
||||
rm ./*.deb
|
||||
|
||||
# Reference Contracts
|
||||
- name: checkout eos-system-contracts
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: eosnetworkfoundation/eos-system-contracts
|
||||
path: eos-system-contracts
|
||||
ref: '${{needs.v.outputs.eos-system-contracts-ref}}'
|
||||
- if: ${{ matrix.test == 'deb-install' }}
|
||||
name: Install eos-system-contracts deps
|
||||
run: |
|
||||
apt-get -y install cmake build-essential
|
||||
- name: Build & Test eos-system-contracts
|
||||
run: |
|
||||
cmake -S eos-system-contracts -B eos-system-contracts/build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=On -DSYSTEM_ENABLE_LEAP_VERSION_CHECK=Off -DSYSTEM_ENABLE_CDT_VERSION_CHECK=Off
|
||||
cmake --build eos-system-contracts/build -- -j $(nproc)
|
||||
cd eos-system-contracts/build/tests
|
||||
ctest --output-on-failure -j $(nproc)
|
||||
|
||||
all-passing:
|
||||
name: All Required Tests Passed
|
||||
needs: [dev-package, tests, np-tests]
|
||||
needs: [dev-package, tests, np-tests, libtester-tests]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: needs.dev-package.result != 'success' || needs.tests.result != 'success' || needs.np-tests.result != 'success'
|
||||
- if: needs.dev-package.result != 'success' || needs.tests.result != 'success' || needs.np-tests.result != 'success' || needs.libtester-tests.result != 'success'
|
||||
run: false
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
name: "Build leap"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
outputs:
|
||||
p:
|
||||
description: "Discovered Build Platforms"
|
||||
value: ${{ jobs.d.outputs.p }}
|
||||
|
||||
permissions:
|
||||
packages: read
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
d:
|
||||
name: Discover Platforms
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
missing-platforms: ${{steps.discover.outputs.missing-platforms}}
|
||||
p: ${{steps.discover.outputs.platforms}}
|
||||
steps:
|
||||
- name: Discover Platforms
|
||||
id: discover
|
||||
uses: AntelopeIO/discover-platforms-action@v1
|
||||
with:
|
||||
platform-file: .cicd/platforms.json
|
||||
password: ${{secrets.GITHUB_TOKEN}}
|
||||
package-name: builders
|
||||
|
||||
build-platforms:
|
||||
name: Build Platforms
|
||||
needs: d
|
||||
if: needs.d.outputs.missing-platforms != '[]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{fromJSON(needs.d.outputs.missing-platforms)}}
|
||||
runs-on: ["self-hosted", "enf-x86-beefy"]
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{github.repository_owner}}
|
||||
password: ${{secrets.GITHUB_TOKEN}}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
tags: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
file: ${{fromJSON(needs.d.outputs.p)[matrix.platform].dockerfile}}
|
||||
|
||||
Build:
|
||||
name: Build leap
|
||||
needs: [d, build-platforms]
|
||||
if: always() && needs.d.result == 'success' && (needs.build-platforms.result == 'success' || needs.build-platforms.result == 'skipped')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu20, ubuntu22]
|
||||
runs-on: ["self-hosted", "enf-x86-beefy"]
|
||||
container: ${{fromJSON(needs.d.outputs.p)[matrix.platform].image}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
# https://github.com/actions/runner/issues/2033
|
||||
chown -R $(id -u):$(id -g) $PWD
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_LEAP_DEV_DEB=On -GNinja
|
||||
cmake --build build
|
||||
tar -pc --exclude "*.o" build | zstd --long -T0 -9 > build.tar.zst
|
||||
- name: Upload builddir
|
||||
uses: AntelopeIO/upload-artifact-large-chunks-action@v1
|
||||
with:
|
||||
name: ${{matrix.platform}}-build
|
||||
path: build.tar.zst
|
||||
@@ -0,0 +1,21 @@
|
||||
name: "Performance Harness Run"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
packages: read
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
tmp:
|
||||
name: Stub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Workflow Stub
|
||||
run: |
|
||||
echo "Workflow Stub"
|
||||
@@ -0,0 +1,70 @@
|
||||
name: "Performance Harness Backwards Compatibility"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
packages: read
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build-base:
|
||||
name: Run Build Workflow
|
||||
uses: ./.github/workflows/build_base.yaml
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
needs: [build-base]
|
||||
if: always() && needs.build-base.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu20, ubuntu22]
|
||||
release: [3.1, 3.2, 4.0]
|
||||
runs-on: ["self-hosted", "enf-x86-lowtier"]
|
||||
container:
|
||||
image: ${{fromJSON(needs.build-base.outputs.p)[matrix.platform].image}}
|
||||
options: --security-opt seccomp=unconfined
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download builddir
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{matrix.platform}}-build
|
||||
- name: Extract Build Directory
|
||||
run: |
|
||||
zstdcat build.tar.zst | tar x
|
||||
- name: Download Prev Leap Version
|
||||
uses: AntelopeIO/asset-artifact-download-action@v2
|
||||
with:
|
||||
owner: AntelopeIO
|
||||
repo: leap
|
||||
file: '(leap).*${{matrix.platform}}.04.*(x86_64|amd64).deb'
|
||||
target: '${{matrix.release}}'
|
||||
token: ${{github.token}}
|
||||
- name: Install leap & replace binaries for PH use
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y ./leap*.deb
|
||||
rm build/bin/nodeos
|
||||
rm build/bin/cleos
|
||||
cp /usr/bin/nodeos build/bin
|
||||
cp /usr/bin/cleos build/bin
|
||||
./build/bin/nodeos --version
|
||||
- if: ${{ matrix.release == '3.1' || matrix.release == '3.2' }}
|
||||
name: Run Performance Tests (<v4.0)
|
||||
run: |
|
||||
cd build
|
||||
ctest --output-on-failure -R performance_test -E read_only --timeout 480
|
||||
- if: ${{ matrix.release != '3.1' && matrix.release != '3.2' }}
|
||||
name: Run Performance Tests (>=v4.0)
|
||||
run: |
|
||||
cd build
|
||||
ctest --output-on-failure -R performance_test --timeout 480
|
||||
@@ -0,0 +1,51 @@
|
||||
name: "Pinned Build"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
packages: read
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
name: Build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu20, ubuntu22]
|
||||
runs-on: ["self-hosted", "enf-x86-beefy-long"]
|
||||
container: ${{ matrix.platform == 'ubuntu20' && 'ubuntu:focal' || 'ubuntu:jammy' }}
|
||||
steps:
|
||||
- name: Update and Install git
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git
|
||||
git --version
|
||||
- name: Clone leap
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# https://github.com/actions/runner/issues/2033
|
||||
chown -R $(id -u):$(id -g) $PWD
|
||||
./scripts/install_deps.sh
|
||||
- name: Build Pinned Build
|
||||
env:
|
||||
LEAP_PINNED_INSTALL_PREFIX: /usr
|
||||
run: |
|
||||
./scripts/pinned_build.sh deps build "$(nproc)"
|
||||
- name: Upload package
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: leap-${{matrix.platform}}-pinned-amd64
|
||||
path: build/leap_*.deb
|
||||
- name: Run Parallel Tests
|
||||
run: |
|
||||
cd build
|
||||
ctest --output-on-failure -j $(nproc) -LE "(nonparallelizable_tests|long_running_tests)" --timeout 420
|
||||
@@ -75,6 +75,8 @@ witness_node_data_dir
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
*.gdb_history
|
||||
|
||||
Testing/*
|
||||
build.tar.gz
|
||||
[Bb]uild*/*
|
||||
|
||||
@@ -31,3 +31,6 @@
|
||||
[submodule "libraries/cli11/cli11"]
|
||||
path = libraries/cli11/cli11
|
||||
url = https://github.com/AntelopeIO/CLI11.git
|
||||
[submodule "libraries/boost"]
|
||||
path = libraries/boost
|
||||
url = https://github.com/boostorg/boost.git
|
||||
|
||||
+18
-10
@@ -101,12 +101,6 @@ else()
|
||||
set(no_whole_archive_flag "--no-whole-archive")
|
||||
endif()
|
||||
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" )
|
||||
# Most boost deps get implictly picked up via fc, as just about everything links to fc. In addition we pick up
|
||||
# the pthread dependency through fc.
|
||||
find_package(Boost 1.71 REQUIRED COMPONENTS program_options unit_test_framework system)
|
||||
|
||||
if( APPLE AND UNIX )
|
||||
# Apple Specific Options Here
|
||||
message( STATUS "Configuring Leap on macOS" )
|
||||
@@ -174,9 +168,10 @@ endif()
|
||||
|
||||
message( STATUS "Using '${EOSIO_ROOT_KEY}' as public key for 'eosio' account" )
|
||||
|
||||
find_package( Gperftools QUIET )
|
||||
if( GPERFTOOLS_FOUND )
|
||||
message( STATUS "Found gperftools; compiling Leap with TCMalloc")
|
||||
option(ENABLE_TCMALLOC "use tcmalloc (requires gperftools)" OFF)
|
||||
if( ENABLE_TCMALLOC )
|
||||
find_package( Gperftools REQUIRED )
|
||||
message( STATUS "Compiling Leap with TCMalloc")
|
||||
#if doing this by the book, simply link_libraries( ${GPERFTOOLS_TCMALLOC} ) here. That will
|
||||
#give the performance benefits of tcmalloc but since it won't be linked last
|
||||
#the heap profiler & checker may not be accurate. This here is rather undocumented behavior
|
||||
@@ -247,7 +242,6 @@ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests/TestHarness DESTINATION ${CM
|
||||
PATTERN "*.json"
|
||||
PATTERN "__pycache__" EXCLUDE
|
||||
PATTERN "CMakeFiles" EXCLUDE)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/tests/launcher.py DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/leap_testing/tests COMPONENT dev EXCLUDE_FROM_ALL)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.21)
|
||||
# Cmake versions < 3.21 did not support installing symbolic links to a directory via install(FILES ...)
|
||||
@@ -278,6 +272,18 @@ configure_file(${CMAKE_SOURCE_DIR}/libraries/cli11/bash-completion/completions/c
|
||||
install(FILES libraries/cli11/bash-completion/completions/leap-util DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/bash-completion/completions COMPONENT base)
|
||||
install(FILES libraries/cli11/bash-completion/completions/cleos DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/bash-completion/completions COMPONENT base)
|
||||
|
||||
# Add the boost submodule we used to build to our install package, so headers can be found for libtester
|
||||
install(DIRECTORY "${CMAKE_SOURCE_DIR}/libraries/boost/"
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/leap_boost
|
||||
COMPONENT dev EXCLUDE_FROM_ALL
|
||||
PATTERN ".git/*" EXCLUDE
|
||||
PATTERN "example/*" EXCLUDE
|
||||
PATTERN "bench/*" EXCLUDE
|
||||
PATTERN "doc/*" EXCLUDE
|
||||
PATTERN "libs/*/test" EXCLUDE
|
||||
PATTERN "tools/*/test" EXCLUDE
|
||||
)
|
||||
|
||||
add_custom_target(dev-install
|
||||
COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}"
|
||||
COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}"
|
||||
@@ -287,5 +293,7 @@ add_custom_target(dev-install
|
||||
|
||||
include(doxygen)
|
||||
|
||||
option(ENABLE_LEAP_DEV_DEB "Enable building the leap-dev .deb package" OFF)
|
||||
|
||||
include(package.cmake)
|
||||
include(CPack)
|
||||
|
||||
@@ -14,12 +14,6 @@ if (LLVM_DIR STREQUAL "" OR NOT LLVM_DIR)
|
||||
set(LLVM_DIR @LLVM_DIR@)
|
||||
endif()
|
||||
|
||||
find_package( Gperftools QUIET )
|
||||
if( GPERFTOOLS_FOUND )
|
||||
message( STATUS "Found gperftools; compiling tests with TCMalloc")
|
||||
list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )
|
||||
endif()
|
||||
|
||||
if(NOT "@LLVM_FOUND@" STREQUAL "")
|
||||
find_package(LLVM @LLVM_VERSION@ EXACT REQUIRED CONFIG)
|
||||
llvm_map_components_to_libnames(LLVM_LIBS support core passes mcjit native DebugInfoDWARF orcjit)
|
||||
@@ -41,14 +35,10 @@ else ( APPLE )
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -Wall")
|
||||
endif ( APPLE )
|
||||
|
||||
set( Boost_USE_MULTITHREADED ON )
|
||||
set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" )
|
||||
find_package(Boost @Boost_MAJOR_VERSION@.@Boost_MINOR_VERSION@ EXACT REQUIRED COMPONENTS
|
||||
date_time
|
||||
filesystem
|
||||
system
|
||||
chrono
|
||||
iostreams
|
||||
unit_test_framework)
|
||||
|
||||
add_subdirectory( @CMAKE_INSTALL_FULL_DATAROOTDIR@/leap_boost ${PROJECT_BINARY_DIR}/libraries/boost EXCLUDE_FROM_ALL)
|
||||
|
||||
find_library(libtester eosio_testing @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH)
|
||||
find_library(libchain eosio_chain @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH)
|
||||
@@ -94,12 +84,18 @@ macro(add_eosio_test_executable test_name)
|
||||
${libbn256}
|
||||
@GMP_LIBRARY@
|
||||
|
||||
${Boost_FILESYSTEM_LIBRARY}
|
||||
${Boost_SYSTEM_LIBRARY}
|
||||
${Boost_CHRONO_LIBRARY}
|
||||
${Boost_IOSTREAMS_LIBRARY}
|
||||
Boost::date_time
|
||||
Boost::filesystem
|
||||
Boost::system
|
||||
Boost::chrono
|
||||
Boost::multi_index
|
||||
Boost::multiprecision
|
||||
Boost::interprocess
|
||||
Boost::asio
|
||||
Boost::signals2
|
||||
Boost::iostreams
|
||||
"-lz" # Needed by Boost iostreams
|
||||
${Boost_DATE_TIME_LIBRARY}
|
||||
Boost::unit_test_framework
|
||||
|
||||
${LLVM_LIBS}
|
||||
|
||||
@@ -115,7 +111,6 @@ macro(add_eosio_test_executable test_name)
|
||||
endif()
|
||||
|
||||
target_include_directories( ${test_name} PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
@OPENSSL_INCLUDE_DIR@
|
||||
@CMAKE_INSTALL_PREFIX@
|
||||
@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
|
||||
@@ -12,12 +12,6 @@ if (LLVM_DIR STREQUAL "" OR NOT LLVM_DIR)
|
||||
set(LLVM_DIR @LLVM_DIR@)
|
||||
endif()
|
||||
|
||||
find_package( Gperftools QUIET )
|
||||
if( GPERFTOOLS_FOUND )
|
||||
message( STATUS "Found gperftools; compiling tests with TCMalloc")
|
||||
list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )
|
||||
endif()
|
||||
|
||||
if(NOT "@LLVM_FOUND@" STREQUAL "")
|
||||
find_package(LLVM @LLVM_VERSION@ EXACT REQUIRED CONFIG)
|
||||
llvm_map_components_to_libnames(LLVM_LIBS support core passes mcjit native DebugInfoDWARF orcjit)
|
||||
@@ -38,14 +32,10 @@ else ( APPLE )
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -Wall")
|
||||
endif ( APPLE )
|
||||
|
||||
set( Boost_USE_MULTITHREADED ON )
|
||||
set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" )
|
||||
find_package(Boost @Boost_MAJOR_VERSION@.@Boost_MINOR_VERSION@ EXACT REQUIRED COMPONENTS
|
||||
date_time
|
||||
filesystem
|
||||
system
|
||||
chrono
|
||||
iostreams
|
||||
unit_test_framework)
|
||||
|
||||
add_subdirectory( @CMAKE_SOURCE_DIR@/libraries/boost ${PROJECT_BINARY_DIR}/libraries/boost EXCLUDE_FROM_ALL)
|
||||
|
||||
find_library(libtester eosio_testing @CMAKE_BINARY_DIR@/libraries/testing NO_DEFAULT_PATH)
|
||||
find_library(libchain eosio_chain @CMAKE_BINARY_DIR@/libraries/chain NO_DEFAULT_PATH)
|
||||
@@ -91,12 +81,18 @@ macro(add_eosio_test_executable test_name)
|
||||
${libbn256}
|
||||
@GMP_LIBRARY@
|
||||
|
||||
${Boost_FILESYSTEM_LIBRARY}
|
||||
${Boost_SYSTEM_LIBRARY}
|
||||
${Boost_CHRONO_LIBRARY}
|
||||
${Boost_IOSTREAMS_LIBRARY}
|
||||
Boost::date_time
|
||||
Boost::filesystem
|
||||
Boost::system
|
||||
Boost::chrono
|
||||
Boost::multi_index
|
||||
Boost::multiprecision
|
||||
Boost::interprocess
|
||||
Boost::asio
|
||||
Boost::signals2
|
||||
Boost::iostreams
|
||||
"-lz" # Needed by Boost iostreams
|
||||
${Boost_DATE_TIME_LIBRARY}
|
||||
Boost::unit_test_framework
|
||||
|
||||
${LLVM_LIBS}
|
||||
|
||||
@@ -112,7 +108,6 @@ macro(add_eosio_test_executable test_name)
|
||||
endif()
|
||||
|
||||
target_include_directories( ${test_name} PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
@OPENSSL_INCLUDE_DIR@
|
||||
@CMAKE_SOURCE_DIR@/libraries/chain/include
|
||||
@CMAKE_BINARY_DIR@/libraries/chain/include
|
||||
|
||||
@@ -132,12 +132,13 @@ sudo apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libboost-all-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libgmp-dev \
|
||||
libssl-dev \
|
||||
llvm-11-dev \
|
||||
python3-numpy
|
||||
python3-numpy \
|
||||
file \
|
||||
zlib1g-dev
|
||||
```
|
||||
To build, make sure you are in the root of the `leap` repo, then run the following command:
|
||||
```bash
|
||||
|
||||
@@ -131,8 +131,7 @@ Config Options for eosio::chain_plugin:
|
||||
received via the P2P network are not
|
||||
relayed and transactions cannot be
|
||||
pushed via the chain API.
|
||||
In "speculative" mode: (DEPRECATED:
|
||||
head mode recommended) database
|
||||
In "speculative" mode: database
|
||||
contains state changes by transactions
|
||||
in the blockchain up to the head block
|
||||
as well as some transactions not yet
|
||||
@@ -179,7 +178,16 @@ Config Options for eosio::chain_plugin:
|
||||
code cache
|
||||
--eos-vm-oc-compile-threads arg (=1) Number of threads to use for EOS VM OC
|
||||
tier-up
|
||||
--eos-vm-oc-enable Enable EOS VM OC tier-up runtime
|
||||
--eos-vm-oc-enable arg (=auto) Enable EOS VM OC tier-up runtime
|
||||
('auto', 'all', 'none').
|
||||
'auto' - EOS VM OC tier-up is enabled
|
||||
for eosio.* accounts, read-only trxs,
|
||||
and applying blocks.
|
||||
'all' - EOS VM OC tier-up is enabled
|
||||
for all contract execution.
|
||||
'none' - EOS VM OC tier-up is
|
||||
completely disabled.
|
||||
|
||||
--enable-account-queries arg (=0) enable queries to find accounts by
|
||||
various metadata.
|
||||
--max-nonprivileged-inline-action-size arg (=4096)
|
||||
@@ -192,13 +200,17 @@ Config Options for eosio::chain_plugin:
|
||||
feature. Setting above 0 enables this
|
||||
feature.
|
||||
--transaction-retry-interval-sec arg (=20)
|
||||
How often, in seconds, to resend an
|
||||
incoming transaction to network if not
|
||||
How often, in seconds, to resend an
|
||||
incoming transaction to network if not
|
||||
seen in a block.
|
||||
Needs to be at least twice as large as
|
||||
p2p-dedup-cache-expire-time-sec.
|
||||
--transaction-retry-max-expiration-sec arg (=120)
|
||||
Maximum allowed transaction expiration
|
||||
for retry transactions, will retry
|
||||
Maximum allowed transaction expiration
|
||||
for retry transactions, will retry
|
||||
transactions up to this value.
|
||||
Should be larger than
|
||||
transaction-retry-interval-sec.
|
||||
--transaction-finality-status-max-storage-size-gb arg
|
||||
Maximum size (in GiB) allowed to be
|
||||
allocated for the Transaction Finality
|
||||
|
||||
@@ -122,8 +122,6 @@ Config Options for eosio::producer_plugin:
|
||||
transaction queue. Exceeding this value
|
||||
will subjectively drop transaction with
|
||||
resource exhaustion.
|
||||
--disable-subjective-billing arg (=1) Disable subjective CPU billing for
|
||||
API/P2P transactions
|
||||
--disable-subjective-account-billing arg
|
||||
Account which is excluded from
|
||||
subjective CPU billing
|
||||
|
||||
@@ -5,6 +5,10 @@ set(SOFTFLOAT_INSTALL_COMPONENT "dev")
|
||||
set(EOSVM_INSTALL_COMPONENT "dev")
|
||||
set(BN256_INSTALL_COMPONENT "dev")
|
||||
|
||||
set( Boost_USE_MULTITHREADED ON )
|
||||
set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" )
|
||||
add_subdirectory( boost EXCLUDE_FROM_ALL )
|
||||
|
||||
add_subdirectory( libfc )
|
||||
add_subdirectory( builtins )
|
||||
add_subdirectory( softfloat )
|
||||
|
||||
+1
-1
Submodule libraries/appbase updated: 9c57bbbee4...b75b31e14f
Submodule
+1
Submodule libraries/boost added at b6928ae5c9
@@ -105,6 +105,7 @@ add_library( eosio_chain
|
||||
|
||||
wast_to_wasm.cpp
|
||||
wasm_interface.cpp
|
||||
wasm_interface_collection.cpp
|
||||
wasm_eosio_validation.cpp
|
||||
wasm_eosio_injection.cpp
|
||||
wasm_config.cpp
|
||||
@@ -127,6 +128,7 @@ add_library( eosio_chain
|
||||
protocol_feature_manager.cpp
|
||||
producer_schedule.cpp
|
||||
genesis_intrinsics.cpp
|
||||
symbol.cpp
|
||||
whitelisted_intrinsics.cpp
|
||||
thread_utils.cpp
|
||||
platform_timer_accuracy.cpp
|
||||
@@ -134,8 +136,17 @@ add_library( eosio_chain
|
||||
${HEADERS}
|
||||
)
|
||||
|
||||
## Boost::accumulators depends on Boost::numeric_ublas, which is still missing cmake support (see
|
||||
## https://github.com/boostorg/cmake/issues/39). Until this is fixed, manually add Boost::numeric_ublas
|
||||
## as an interface library
|
||||
## ----------------------------------------------------------------------------------------------------
|
||||
add_library(boost_numeric_ublas INTERFACE)
|
||||
add_library(Boost::numeric_ublas ALIAS boost_numeric_ublas)
|
||||
|
||||
target_link_libraries( eosio_chain PUBLIC bn256 fc chainbase eosio_rapidjson Logging IR WAST WASM
|
||||
softfloat builtins ${CHAIN_EOSVM_LIBRARIES} ${LLVM_LIBS} ${CHAIN_RT_LINKAGE}
|
||||
Boost::signals2 Boost::hana Boost::property_tree Boost::multi_index Boost::asio Boost::lockfree
|
||||
Boost::assign Boost::accumulators
|
||||
)
|
||||
target_include_directories( eosio_chain
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <fc/io/raw.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <fc/io/varint.hpp>
|
||||
#include <fc/time.hpp>
|
||||
|
||||
namespace eosio { namespace chain {
|
||||
|
||||
@@ -23,11 +24,11 @@ namespace eosio { namespace chain {
|
||||
|
||||
template <typename T>
|
||||
inline fc::variant variant_from_stream(fc::datastream<const char*>& stream, const abi_serializer::yield_function_t& yield) {
|
||||
fc::yield_function_t y = [&yield](){ yield(0); }; // create yield function matching fc::variant requirements, 0 for recursive depth
|
||||
T temp;
|
||||
fc::raw::unpack( stream, temp );
|
||||
y();
|
||||
return fc::variant( temp, y );
|
||||
yield(0);
|
||||
// create yield function matching fc::variant requirements, 0 for recursive depth
|
||||
return fc::variant( temp, [yield](){ yield(0); } );
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -129,7 +130,7 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
void abi_serializer::set_abi(abi_def abi, const yield_function_t& yield) {
|
||||
impl::abi_traverse_context ctx(yield);
|
||||
impl::abi_traverse_context ctx(yield, fc::microseconds{});
|
||||
|
||||
EOS_ASSERT(starts_with(abi.version, "eosio::abi/1."), unsupported_abi_version_exception, "ABI has an unsupported version");
|
||||
|
||||
@@ -235,7 +236,7 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
bool abi_serializer::is_type(const std::string_view& type, const yield_function_t& yield)const {
|
||||
impl::abi_traverse_context ctx(yield);
|
||||
impl::abi_traverse_context ctx(yield, fc::microseconds{});
|
||||
return _is_type(type, ctx);
|
||||
}
|
||||
|
||||
@@ -464,23 +465,27 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
fc::variant abi_serializer::binary_to_variant( const std::string_view& type, const bytes& binary, const yield_function_t& yield, bool short_path )const {
|
||||
impl::binary_to_variant_context ctx(*this, yield, type);
|
||||
impl::binary_to_variant_context ctx(*this, yield, fc::microseconds{}, type);
|
||||
ctx.short_path = short_path;
|
||||
return _binary_to_variant(type, binary, ctx);
|
||||
}
|
||||
|
||||
fc::variant abi_serializer::binary_to_variant( const std::string_view& type, const bytes& binary, const fc::microseconds& max_serialization_time, bool short_path )const {
|
||||
return binary_to_variant( type, binary, create_yield_function(max_serialization_time), short_path );
|
||||
fc::variant abi_serializer::binary_to_variant( const std::string_view& type, const bytes& binary, const fc::microseconds& max_action_data_serialization_time, bool short_path )const {
|
||||
impl::binary_to_variant_context ctx(*this, create_depth_yield_function(), max_action_data_serialization_time, type);
|
||||
ctx.short_path = short_path;
|
||||
return _binary_to_variant(type, binary, ctx);
|
||||
}
|
||||
|
||||
fc::variant abi_serializer::binary_to_variant( const std::string_view& type, fc::datastream<const char*>& binary, const yield_function_t& yield, bool short_path )const {
|
||||
impl::binary_to_variant_context ctx(*this, yield, type);
|
||||
impl::binary_to_variant_context ctx(*this, yield, fc::microseconds{}, type);
|
||||
ctx.short_path = short_path;
|
||||
return _binary_to_variant(type, binary, ctx);
|
||||
}
|
||||
|
||||
fc::variant abi_serializer::binary_to_variant( const std::string_view& type, fc::datastream<const char*>& binary, const fc::microseconds& max_serialization_time, bool short_path )const {
|
||||
return binary_to_variant( type, binary, create_yield_function(max_serialization_time), short_path );
|
||||
fc::variant abi_serializer::binary_to_variant( const std::string_view& type, fc::datastream<const char*>& binary, const fc::microseconds& max_action_data_serialization_time, bool short_path )const {
|
||||
impl::binary_to_variant_context ctx(*this, create_depth_yield_function(), max_action_data_serialization_time, type);
|
||||
ctx.short_path = short_path;
|
||||
return _binary_to_variant(type, binary, ctx);
|
||||
}
|
||||
|
||||
void abi_serializer::_variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char *>& ds, impl::variant_to_binary_context& ctx )const
|
||||
@@ -543,14 +548,15 @@ namespace eosio { namespace chain {
|
||||
bool disallow_additional_fields = false;
|
||||
for( uint32_t i = 0; i < st.fields.size(); ++i ) {
|
||||
const auto& field = st.fields[i];
|
||||
if( vo.contains( string(field.name).c_str() ) ) {
|
||||
bool present = vo.contains(string(field.name).c_str());
|
||||
if( present || is_optional(field.type) ) {
|
||||
if( disallow_additional_fields )
|
||||
EOS_THROW( pack_exception, "Unexpected field '${f}' found in input object while processing struct '${p}'",
|
||||
("f", ctx.maybe_shorten(field.name))("p", ctx.get_path_string()) );
|
||||
{
|
||||
auto h1 = ctx.push_to_path( impl::field_path_item{ .parent_struct_itr = s_itr, .field_ordinal = i } );
|
||||
auto h2 = ctx.disallow_extensions_unless( &field == &st.fields.back() );
|
||||
_variant_to_binary(_remove_bin_extension(field.type), vo[field.name], ds, ctx);
|
||||
_variant_to_binary(_remove_bin_extension(field.type), present ? vo[field.name] : fc::variant(nullptr), ds, ctx);
|
||||
}
|
||||
} else if( ends_with(field.type, "$") && ctx.extensions_allowed() ) {
|
||||
disallow_additional_fields = true;
|
||||
@@ -603,23 +609,27 @@ namespace eosio { namespace chain {
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
bytes abi_serializer::variant_to_binary( const std::string_view& type, const fc::variant& var, const yield_function_t& yield, bool short_path )const {
|
||||
impl::variant_to_binary_context ctx(*this, yield, type);
|
||||
impl::variant_to_binary_context ctx(*this, yield, fc::microseconds{}, type);
|
||||
ctx.short_path = short_path;
|
||||
return _variant_to_binary(type, var, ctx);
|
||||
}
|
||||
|
||||
bytes abi_serializer::variant_to_binary( const std::string_view& type, const fc::variant& var, const fc::microseconds& max_serialization_time, bool short_path ) const {
|
||||
return variant_to_binary( type, var, create_yield_function(max_serialization_time), short_path );
|
||||
bytes abi_serializer::variant_to_binary( const std::string_view& type, const fc::variant& var, const fc::microseconds& max_action_data_serialization_time, bool short_path ) const {
|
||||
impl::variant_to_binary_context ctx(*this, create_depth_yield_function(), max_action_data_serialization_time, type);
|
||||
ctx.short_path = short_path;
|
||||
return _variant_to_binary(type, var, ctx);
|
||||
}
|
||||
|
||||
void abi_serializer::variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char*>& ds, const yield_function_t& yield, bool short_path )const {
|
||||
impl::variant_to_binary_context ctx(*this, yield, type);
|
||||
impl::variant_to_binary_context ctx(*this, yield, fc::microseconds{}, type);
|
||||
ctx.short_path = short_path;
|
||||
_variant_to_binary(type, var, ds, ctx);
|
||||
}
|
||||
|
||||
void abi_serializer::variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char*>& ds, const fc::microseconds& max_serialization_time, bool short_path ) const {
|
||||
variant_to_binary( type, var, create_yield_function(max_serialization_time), short_path );
|
||||
void abi_serializer::variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char*>& ds, const fc::microseconds& max_action_data_serialization_time, bool short_path ) const {
|
||||
impl::variant_to_binary_context ctx(*this, create_depth_yield_function(), max_action_data_serialization_time, type);
|
||||
ctx.short_path = short_path;
|
||||
_variant_to_binary(type, var, ds, ctx);
|
||||
}
|
||||
|
||||
type_name abi_serializer::get_action_type(name action)const {
|
||||
@@ -650,9 +660,7 @@ namespace eosio { namespace chain {
|
||||
namespace impl {
|
||||
|
||||
fc::scoped_exit<std::function<void()>> abi_traverse_context::enter_scope() {
|
||||
std::function<void()> callback = [old_recursion_depth=recursion_depth, this](){
|
||||
recursion_depth = old_recursion_depth;
|
||||
};
|
||||
std::function<void()> callback = [this](){ --recursion_depth; };
|
||||
|
||||
++recursion_depth;
|
||||
yield( recursion_depth );
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <eosio/chain/controller.hpp>
|
||||
#include <eosio/chain/transaction_context.hpp>
|
||||
#include <eosio/chain/exceptions.hpp>
|
||||
#include <eosio/chain/wasm_interface.hpp>
|
||||
#include <eosio/chain/wasm_interface_collection.hpp>
|
||||
#include <eosio/chain/generated_transaction_object.hpp>
|
||||
#include <eosio/chain/authorization_manager.hpp>
|
||||
#include <eosio/chain/resource_limits.hpp>
|
||||
@@ -1094,4 +1094,19 @@ action_name apply_context::get_sender() const {
|
||||
return action_name();
|
||||
}
|
||||
|
||||
// Context | OC?
|
||||
//-------------------------------------------------------------------------------
|
||||
// Building block | baseline, OC for eosio.*
|
||||
// Applying block | OC unless a producer, OC for eosio.* including producers
|
||||
// Speculative API trx | baseline, OC for eosio.*
|
||||
// Speculative P2P trx | baseline, OC for eosio.*
|
||||
// Compute trx | baseline, OC for eosio.*
|
||||
// Read only trx | OC
|
||||
bool apply_context::should_use_eos_vm_oc()const {
|
||||
return receiver.prefix() == config::system_account_name // "eosio"_n, all cases use OC
|
||||
|| (is_applying_block() && !control.is_producer_node()) // validating/applying block
|
||||
|| trx_context.is_read_only();
|
||||
}
|
||||
|
||||
|
||||
} } /// eosio::chain
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <string>
|
||||
#include <eosio/chain/asset.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <fc/reflect/variant.hpp>
|
||||
#include <fc/safe.hpp>
|
||||
|
||||
@@ -22,11 +24,11 @@ int64_t asset::precision()const {
|
||||
string asset::to_string()const {
|
||||
string sign = amount < 0 ? "-" : "";
|
||||
int64_t abs_amount = std::abs(amount);
|
||||
string result = fc::to_string( static_cast<int64_t>(abs_amount) / precision());
|
||||
string result = std::to_string( static_cast<int64_t>(abs_amount) / precision());
|
||||
if( decimals() )
|
||||
{
|
||||
auto fract = static_cast<int64_t>(abs_amount) % precision();
|
||||
result += "." + fc::to_string(precision() + fract).erase(0,1);
|
||||
result += "." + std::to_string(precision() + fract).erase(0,1);
|
||||
}
|
||||
return sign + result + " " + symbol_name();
|
||||
}
|
||||
@@ -34,12 +36,12 @@ string asset::to_string()const {
|
||||
asset asset::from_string(const string& from)
|
||||
{
|
||||
try {
|
||||
string s = fc::trim(from);
|
||||
string s = boost::algorithm::trim_copy(from);
|
||||
|
||||
// Find space in order to split amount and symbol
|
||||
auto space_pos = s.find(' ');
|
||||
EOS_ASSERT((space_pos != string::npos), asset_type_exception, "Asset's amount and symbol should be separated with space");
|
||||
auto symbol_str = fc::trim(s.substr(space_pos + 1));
|
||||
auto symbol_str = boost::algorithm::trim_copy(s.substr(space_pos + 1));
|
||||
auto amount_str = s.substr(0, space_pos);
|
||||
|
||||
// Ensure that if decimal point is used (.), decimal fraction is specified
|
||||
|
||||
@@ -462,8 +462,11 @@ namespace eosio { namespace chain {
|
||||
inline static uint32_t default_initial_version = block_log::max_supported_version;
|
||||
|
||||
std::mutex mtx;
|
||||
signed_block_ptr head;
|
||||
block_id_type head_id;
|
||||
struct signed_block_with_id {
|
||||
signed_block_ptr ptr;
|
||||
block_id_type id;
|
||||
};
|
||||
std::optional<signed_block_with_id> head;
|
||||
|
||||
virtual ~block_log_impl() = default;
|
||||
|
||||
@@ -482,34 +485,30 @@ namespace eosio { namespace chain {
|
||||
|
||||
virtual signed_block_ptr read_head() = 0;
|
||||
void update_head(const signed_block_ptr& b, const std::optional<block_id_type>& id = {}) {
|
||||
head = b;
|
||||
if (id) {
|
||||
head_id = *id;
|
||||
} else {
|
||||
if (head) {
|
||||
head_id = b->calculate_id();
|
||||
} else {
|
||||
head_id = {};
|
||||
}
|
||||
}
|
||||
if (b)
|
||||
head = { b, id ? *id : b->calculate_id() };
|
||||
else
|
||||
head = {};
|
||||
}
|
||||
}; // block_log_impl
|
||||
|
||||
/// Would remove pre-existing block log and index, never write blocks into disk.
|
||||
struct empty_block_log final : block_log_impl {
|
||||
uint32_t first_block_number = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
explicit empty_block_log(const std::filesystem::path& log_dir) {
|
||||
std::filesystem::remove(log_dir / "blocks.log");
|
||||
std::filesystem::remove(log_dir / "blocks.index");
|
||||
}
|
||||
|
||||
uint32_t first_block_num() final { return head ? head->block_num() : 1; }
|
||||
uint32_t first_block_num() final { return head ? head->ptr->block_num() : first_block_number; }
|
||||
void append(const signed_block_ptr& b, const block_id_type& id, const std::vector<char>& packed_block) final {
|
||||
update_head(b, id);
|
||||
}
|
||||
|
||||
uint64_t get_block_pos(uint32_t block_num) final { return block_log::npos; }
|
||||
void reset(const genesis_state& gs, const signed_block_ptr& first_block) final { update_head(first_block); }
|
||||
void reset(const chain_id_type& chain_id, uint32_t first_block_num) final {}
|
||||
void reset(const chain_id_type& chain_id, uint32_t first_block_num) final { first_block_number = first_block_num; }
|
||||
void flush() final {}
|
||||
|
||||
signed_block_ptr read_block_by_num(uint32_t block_num) final { return {}; };
|
||||
@@ -589,7 +588,7 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
uint64_t get_block_pos(uint32_t block_num) final {
|
||||
if (!(head && block_num <= block_header::num_from_id(head_id) &&
|
||||
if (!(head && block_num <= block_header::num_from_id(head->id) &&
|
||||
block_num >= working_block_file_first_block_num()))
|
||||
return block_log::npos;
|
||||
index_file.seek(sizeof(uint64_t) * (block_num - index_first_block_num()));
|
||||
@@ -705,7 +704,7 @@ namespace eosio { namespace chain {
|
||||
uint32_t num_blocks;
|
||||
this->block_file.seek_end(-sizeof(uint32_t));
|
||||
fc::raw::unpack(this->block_file, num_blocks);
|
||||
return this->head->block_num() - num_blocks + 1;
|
||||
return this->head->ptr->block_num() - num_blocks + 1;
|
||||
}
|
||||
|
||||
void reset(uint32_t first_bnum, std::variant<genesis_state, chain_id_type>&& chain_context, uint32_t version) {
|
||||
@@ -738,7 +737,6 @@ namespace eosio { namespace chain {
|
||||
|
||||
this->reset(first_block_num, chain_id, block_log::max_supported_version);
|
||||
this->head.reset();
|
||||
head_id = {};
|
||||
}
|
||||
|
||||
void flush() final {
|
||||
@@ -802,7 +800,7 @@ namespace eosio { namespace chain {
|
||||
size_t copy_from_pos = get_block_pos(first_block_num);
|
||||
block_file.seek_end(-sizeof(uint32_t));
|
||||
size_t copy_sz = block_file.tellp() - copy_from_pos;
|
||||
const uint32_t num_blocks_in_log = chain::block_header::num_from_id(head_id) - first_block_num + 1;
|
||||
const uint32_t num_blocks_in_log = chain::block_header::num_from_id(head->id) - first_block_num + 1;
|
||||
|
||||
const size_t offset_bytes = copy_from_pos - copy_to_pos;
|
||||
const size_t offset_blocks = first_block_num - index_first_block_num;
|
||||
@@ -990,7 +988,7 @@ namespace eosio { namespace chain {
|
||||
block_file.close();
|
||||
index_file.close();
|
||||
|
||||
catalog.add(preamble.first_block_num, this->head->block_num(), block_file.get_file_path().parent_path(),
|
||||
catalog.add(preamble.first_block_num, this->head->ptr->block_num(), block_file.get_file_path().parent_path(),
|
||||
"blocks");
|
||||
|
||||
using std::swap;
|
||||
@@ -1005,7 +1003,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
preamble.ver = block_log::max_supported_version;
|
||||
preamble.chain_context = preamble.chain_id();
|
||||
preamble.first_block_num = this->head->block_num() + 1;
|
||||
preamble.first_block_num = this->head->ptr->block_num() + 1;
|
||||
preamble.write_to(block_file);
|
||||
}
|
||||
|
||||
@@ -1016,7 +1014,7 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
void post_append(uint64_t pos) final {
|
||||
if (head->block_num() % stride == 0) {
|
||||
if (head->ptr->block_num() % stride == 0) {
|
||||
split_log();
|
||||
}
|
||||
}
|
||||
@@ -1119,7 +1117,7 @@ namespace eosio { namespace chain {
|
||||
if ((pos & prune_config.prune_threshold) != (end & prune_config.prune_threshold))
|
||||
num_blocks_in_log = prune(fc::log_level::debug);
|
||||
else
|
||||
num_blocks_in_log = chain::block_header::num_from_id(head_id) - first_block_number + 1;
|
||||
num_blocks_in_log = chain::block_header::num_from_id(head->id) - first_block_number + 1;
|
||||
fc::raw::pack(block_file, num_blocks_in_log);
|
||||
}
|
||||
|
||||
@@ -1140,7 +1138,7 @@ namespace eosio { namespace chain {
|
||||
uint32_t prune(const fc::log_level& loglevel) {
|
||||
if (!head)
|
||||
return 0;
|
||||
const uint32_t head_num = chain::block_header::num_from_id(head_id);
|
||||
const uint32_t head_num = chain::block_header::num_from_id(head->id);
|
||||
if (head_num - first_block_number < prune_config.prune_blocks)
|
||||
return head_num - first_block_number + 1;
|
||||
|
||||
@@ -1250,12 +1248,12 @@ namespace eosio { namespace chain {
|
||||
|
||||
signed_block_ptr block_log::head() const {
|
||||
std::lock_guard g(my->mtx);
|
||||
return my->head;
|
||||
return my->head ? my->head->ptr : signed_block_ptr{};
|
||||
}
|
||||
|
||||
block_id_type block_log::head_id() const {
|
||||
std::optional<block_id_type> block_log::head_id() const {
|
||||
std::lock_guard g(my->mtx);
|
||||
return my->head_id;
|
||||
return my->head ? my->head->id : std::optional<block_id_type>{};
|
||||
}
|
||||
|
||||
uint32_t block_log::first_block_num() const {
|
||||
@@ -1371,16 +1369,61 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<genesis_state> block_log::extract_genesis_state(const std::filesystem::path& block_dir) {
|
||||
std::filesystem::path p(block_dir / "blocks.log");
|
||||
for_each_file_in_dir_matches(block_dir, R"(blocks-1-\d+\.log)",
|
||||
[&p](std::filesystem::path log_path) { p = std::move(log_path); });
|
||||
return block_log_data(p).get_genesis_state();
|
||||
std::optional<block_log::chain_context> block_log::extract_chain_context(const std::filesystem::path& block_dir,
|
||||
const std::filesystem::path& retained_dir) {
|
||||
std::filesystem::path first_block_file;
|
||||
if (!retained_dir.empty() && std::filesystem::exists(retained_dir)) {
|
||||
for_each_file_in_dir_matches(retained_dir, R"(blocks-1-\d+\.log)",
|
||||
[&](std::filesystem::path log_path) {
|
||||
first_block_file = std::move(log_path);
|
||||
});
|
||||
}
|
||||
|
||||
if (first_block_file.empty() && std::filesystem::exists(block_dir / "blocks.log")) {
|
||||
first_block_file = block_dir / "blocks.log";
|
||||
}
|
||||
|
||||
if (!first_block_file.empty()) {
|
||||
return block_log_data(first_block_file).get_preamble().chain_context;
|
||||
}
|
||||
|
||||
if (!retained_dir.empty() && std::filesystem::exists(retained_dir)) {
|
||||
const std::regex my_filter(R"(blocks-\d+-\d+\.log)");
|
||||
std::smatch what;
|
||||
std::filesystem::directory_iterator end_itr; // Default ctor yields past-the-end
|
||||
for (std::filesystem::directory_iterator p(retained_dir); p != end_itr; ++p) {
|
||||
// Skip if not a file
|
||||
if (!std::filesystem::is_regular_file(p->status()))
|
||||
continue;
|
||||
// skip if it does not match the pattern
|
||||
std::string file = p->path().filename().string();
|
||||
if (!std::regex_match(file, what, my_filter))
|
||||
continue;
|
||||
return block_log_data(p->path()).chain_id();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// static
|
||||
chain_id_type block_log::extract_chain_id(const std::filesystem::path& data_dir) {
|
||||
return block_log_data(data_dir / "blocks.log").chain_id();
|
||||
std::optional<genesis_state> block_log::extract_genesis_state(const std::filesystem::path& block_dir,
|
||||
const std::filesystem::path& retained_dir) {
|
||||
auto context = extract_chain_context(block_dir, retained_dir);
|
||||
if (!context || std::holds_alternative<chain_id_type>(*context))
|
||||
return {};
|
||||
return std::get<genesis_state>(*context);
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<chain_id_type> block_log::extract_chain_id(const std::filesystem::path& block_dir,
|
||||
const std::filesystem::path& retained_dir) {
|
||||
auto context = extract_chain_context(block_dir, retained_dir);
|
||||
if (!context)
|
||||
return {};
|
||||
return std::visit(overloaded{
|
||||
[](const chain_id_type& id){ return id; },
|
||||
[](const genesis_state& gs){ return gs.compute_chain_id(); }
|
||||
} , *context);
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -1538,7 +1581,7 @@ namespace eosio { namespace chain {
|
||||
ilog("blocks.log and blocks.index agree on number of blocks");
|
||||
|
||||
if (interval == 0) {
|
||||
interval = std::max((log_bundle.log_index.num_blocks() + 7u) >> 3, 1u);
|
||||
interval = std::max((log_bundle.log_index.num_blocks() + 7) >> 3, 1U);
|
||||
}
|
||||
uint32_t expected_block_num = log_bundle.log_data.first_block_num();
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <eosio/chain/thread_utils.hpp>
|
||||
#include <eosio/chain/platform_timer.hpp>
|
||||
#include <eosio/chain/deep_mind.hpp>
|
||||
#include <eosio/chain/wasm_interface_collection.hpp>
|
||||
|
||||
#include <chainbase/chainbase.hpp>
|
||||
#include <eosio/vm/allocator.hpp>
|
||||
@@ -239,6 +240,7 @@ struct controller_impl {
|
||||
controller::config conf;
|
||||
const chain_id_type chain_id; // read by thread_pool threads, value will not be changed
|
||||
bool replaying = false;
|
||||
bool is_producer_node = false; // true if node is configured as a block producer
|
||||
db_read_mode read_mode = db_read_mode::HEAD;
|
||||
bool in_trx_requiring_checks = false; ///< if true, checks that are normally skipped on replay (e.g. auth checks) cannot be skipped
|
||||
std::optional<fc::microseconds> subjective_cpu_leeway;
|
||||
@@ -249,14 +251,11 @@ struct controller_impl {
|
||||
deep_mind_handler* deep_mind_logger = nullptr;
|
||||
bool okay_to_print_integrity_hash_on_stop = false;
|
||||
|
||||
std::thread::id main_thread_id;
|
||||
thread_local static platform_timer timer; // a copy for main thread and each read-only thread
|
||||
#if defined(EOSIO_EOS_VM_RUNTIME_ENABLED) || defined(EOSIO_EOS_VM_JIT_RUNTIME_ENABLED)
|
||||
thread_local static vm::wasm_allocator wasm_alloc; // a copy for main thread and each read-only thread
|
||||
#endif
|
||||
wasm_interface wasmif; // used by main thread and all threads for EOSVMOC
|
||||
std::mutex threaded_wasmifs_mtx;
|
||||
std::unordered_map<std::thread::id, std::unique_ptr<wasm_interface>> threaded_wasmifs; // one for each read-only thread, used by eos-vm and eos-vm-jit
|
||||
wasm_interface_collection wasm_if_collect;
|
||||
app_window_type app_window = app_window_type::write;
|
||||
|
||||
typedef pair<scope_name,action_name> handler_key;
|
||||
@@ -315,8 +314,7 @@ struct controller_impl {
|
||||
chain_id( chain_id ),
|
||||
read_mode( cfg.read_mode ),
|
||||
thread_pool(),
|
||||
main_thread_id( std::this_thread::get_id() ),
|
||||
wasmif( conf.wasm_runtime, conf.eosvmoc_tierup, db, conf.state_dir, conf.eosvmoc_config, !conf.profile_accounts.empty() )
|
||||
wasm_if_collect( conf.wasm_runtime, conf.eosvmoc_tierup, db, conf.state_dir, conf.eosvmoc_config, !conf.profile_accounts.empty() )
|
||||
{
|
||||
fork_db.open( [this]( block_timestamp_type timestamp,
|
||||
const flat_set<digest_type>& cur_features,
|
||||
@@ -342,12 +340,7 @@ struct controller_impl {
|
||||
set_activation_handler<builtin_protocol_feature_t::crypto_primitives>();
|
||||
|
||||
self.irreversible_block.connect([this](const block_state_ptr& bsp) {
|
||||
// producer_plugin has already asserted irreversible_block signal is
|
||||
// called in write window
|
||||
wasmif.current_lib(bsp->block_num);
|
||||
for (auto& w: threaded_wasmifs) {
|
||||
w.second->current_lib(bsp->block_num);
|
||||
}
|
||||
wasm_if_collect.current_lib(bsp->block_num);
|
||||
});
|
||||
|
||||
|
||||
@@ -402,11 +395,13 @@ struct controller_impl {
|
||||
}
|
||||
}
|
||||
|
||||
void dmlog_applied_transaction(const transaction_trace_ptr& t) {
|
||||
void dmlog_applied_transaction(const transaction_trace_ptr& t, const signed_transaction* trx = nullptr) {
|
||||
// dmlog_applied_transaction is called by push_scheduled_transaction
|
||||
// where transient transactions are not possible, and by push_transaction
|
||||
// only when the transaction is not transient
|
||||
if (auto dm_logger = get_deep_mind_logger(false)) {
|
||||
if (trx && is_onblock(*t))
|
||||
dm_logger->on_onblock(*trx);
|
||||
dm_logger->on_applied_transaction(self.head_block_num() + 1, t);
|
||||
}
|
||||
}
|
||||
@@ -414,10 +409,10 @@ struct controller_impl {
|
||||
void log_irreversible() {
|
||||
EOS_ASSERT( fork_db.root(), fork_database_exception, "fork database not properly initialized" );
|
||||
|
||||
const block_id_type log_head_id = blog.head_id();
|
||||
const bool valid_log_head = !log_head_id.empty();
|
||||
const std::optional<block_id_type> log_head_id = blog.head_id();
|
||||
const bool valid_log_head = !!log_head_id;
|
||||
|
||||
const auto lib_num = valid_log_head ? block_header::num_from_id(log_head_id) : (blog.first_block_num() - 1);
|
||||
const auto lib_num = valid_log_head ? block_header::num_from_id(*log_head_id) : (blog.first_block_num() - 1);
|
||||
|
||||
auto root_id = fork_db.root()->id;
|
||||
|
||||
@@ -425,7 +420,8 @@ struct controller_impl {
|
||||
EOS_ASSERT( root_id == log_head_id, fork_database_exception, "fork database root does not match block log head" );
|
||||
} else {
|
||||
EOS_ASSERT( fork_db.root()->block_num == lib_num, fork_database_exception,
|
||||
"empty block log expects the first appended block to build off a block that is not the fork database root. root block number: ${block_num}, lib: ${lib_num}", ("block_num", fork_db.root()->block_num) ("lib_num", lib_num) );
|
||||
"The first block ${lib_num} when starting with an empty block log should be the block after fork database root ${bn}.",
|
||||
("lib_num", lib_num)("bn", fork_db.root()->block_num) );
|
||||
}
|
||||
|
||||
const auto fork_head = fork_db_head();
|
||||
@@ -447,8 +443,6 @@ struct controller_impl {
|
||||
if( read_mode == db_read_mode::IRREVERSIBLE ) {
|
||||
controller::block_report br;
|
||||
apply_block( br, *bitr, controller::block_status::complete, trx_meta_cache_lookup{} );
|
||||
head = (*bitr);
|
||||
fork_db.mark_valid( head );
|
||||
}
|
||||
|
||||
emit( self.irreversible_block, *bitr );
|
||||
@@ -560,7 +554,10 @@ struct controller_impl {
|
||||
ilog( "no irreversible blocks need to be replayed" );
|
||||
}
|
||||
|
||||
if( !except_ptr && !check_shutdown() && fork_db.head() ) {
|
||||
if (snapshot_head_block != 0 && !blog_head) {
|
||||
// loading from snapshot without a block log so fork_db can't be considered valid
|
||||
fork_db.reset( *head );
|
||||
} else if( !except_ptr && !check_shutdown() && fork_db.head() ) {
|
||||
auto head_block_num = head->block_num;
|
||||
auto branch = fork_db.fetch_branch( fork_db.head()->id );
|
||||
int rev = 0;
|
||||
@@ -591,19 +588,21 @@ struct controller_impl {
|
||||
void startup(std::function<void()> shutdown, std::function<bool()> check_shutdown, const snapshot_reader_ptr& snapshot) {
|
||||
EOS_ASSERT( snapshot, snapshot_exception, "No snapshot reader provided" );
|
||||
this->shutdown = shutdown;
|
||||
ilog( "Starting initialization from snapshot, this may take a significant amount of time" );
|
||||
try {
|
||||
snapshot->validate();
|
||||
if( auto blog_head = blog.head() ) {
|
||||
ilog( "Starting initialization from snapshot and block log ${b}-${e}, this may take a significant amount of time",
|
||||
("b", blog.first_block_num())("e", blog_head->block_num()) );
|
||||
read_from_snapshot( snapshot, blog.first_block_num(), blog_head->block_num() );
|
||||
} else {
|
||||
ilog( "Starting initialization from snapshot and no block log, this may take a significant amount of time" );
|
||||
read_from_snapshot( snapshot, 0, std::numeric_limits<uint32_t>::max() );
|
||||
const uint32_t lib_num = head->block_num;
|
||||
EOS_ASSERT( lib_num > 0, snapshot_exception,
|
||||
EOS_ASSERT( head->block_num > 0, snapshot_exception,
|
||||
"Snapshot indicates controller head at block number 0, but that is not allowed. "
|
||||
"Snapshot is invalid." );
|
||||
blog.reset( chain_id, lib_num + 1 );
|
||||
blog.reset( chain_id, head->block_num + 1 );
|
||||
}
|
||||
ilog( "Snapshot loaded, lib: ${lib}", ("lib", head->block_num) );
|
||||
|
||||
init(check_shutdown);
|
||||
ilog( "Finished initialization from snapshot" );
|
||||
@@ -1176,7 +1175,7 @@ struct controller_impl {
|
||||
|
||||
transaction_checktime_timer trx_timer(timer);
|
||||
const packed_transaction trx( std::move( etrx ) );
|
||||
transaction_context trx_context( self, trx, std::move(trx_timer), start );
|
||||
transaction_context trx_context( self, trx, trx.id(), std::move(trx_timer), start );
|
||||
|
||||
if (auto dm_logger = get_deep_mind_logger(trx_context.is_transient())) {
|
||||
dm_logger->on_onerror(etrx);
|
||||
@@ -1342,7 +1341,7 @@ struct controller_impl {
|
||||
uint32_t cpu_time_to_bill_us = billed_cpu_time_us;
|
||||
|
||||
transaction_checktime_timer trx_timer( timer );
|
||||
transaction_context trx_context( self, *trx->packed_trx(), std::move(trx_timer) );
|
||||
transaction_context trx_context( self, *trx->packed_trx(), gtrx.trx_id, std::move(trx_timer) );
|
||||
trx_context.leeway = fc::microseconds(0); // avoid stealing cpu resource
|
||||
trx_context.block_deadline = block_deadline;
|
||||
trx_context.max_transaction_time_subjective = max_transaction_time;
|
||||
@@ -1556,7 +1555,7 @@ struct controller_impl {
|
||||
|
||||
const signed_transaction& trn = trx->packed_trx()->get_signed_transaction();
|
||||
transaction_checktime_timer trx_timer(timer);
|
||||
transaction_context trx_context(self, *trx->packed_trx(), std::move(trx_timer), start, trx->get_trx_type());
|
||||
transaction_context trx_context(self, *trx->packed_trx(), trx->id(), std::move(trx_timer), start, trx->get_trx_type());
|
||||
if ((bool)subjective_cpu_leeway && self.is_speculative_block()) {
|
||||
trx_context.leeway = *subjective_cpu_leeway;
|
||||
}
|
||||
@@ -1627,7 +1626,7 @@ struct controller_impl {
|
||||
emit(self.accepted_transaction, trx);
|
||||
}
|
||||
|
||||
dmlog_applied_transaction(trace);
|
||||
dmlog_applied_transaction(trace, &trn);
|
||||
emit(self.applied_transaction, std::tie(trace, trx->packed_trx()));
|
||||
}
|
||||
}
|
||||
@@ -1930,7 +1929,7 @@ struct controller_impl {
|
||||
/**
|
||||
* @post regardless of the success of commit block there is no active pending block
|
||||
*/
|
||||
void commit_block( bool add_to_fork_db ) {
|
||||
void commit_block( controller::block_status s ) {
|
||||
auto reset_pending_on_exit = fc::make_scoped_exit([this]{
|
||||
pending.reset();
|
||||
});
|
||||
@@ -1939,24 +1938,26 @@ struct controller_impl {
|
||||
EOS_ASSERT( std::holds_alternative<completed_block>(pending->_block_stage), block_validate_exception,
|
||||
"cannot call commit_block until pending block is completed" );
|
||||
|
||||
auto bsp = std::get<completed_block>(pending->_block_stage)._block_state;
|
||||
const auto& bsp = std::get<completed_block>(pending->_block_stage)._block_state;
|
||||
|
||||
if( add_to_fork_db ) {
|
||||
if( s == controller::block_status::incomplete ) {
|
||||
fork_db.add( bsp );
|
||||
fork_db.mark_valid( bsp );
|
||||
emit( self.accepted_block_header, bsp );
|
||||
head = fork_db.head();
|
||||
EOS_ASSERT( bsp == head, fork_database_exception, "committed block did not become the new head in fork database");
|
||||
EOS_ASSERT( bsp == fork_db.head(), fork_database_exception, "committed block did not become the new head in fork database");
|
||||
} else if (s != controller::block_status::irreversible) {
|
||||
fork_db.mark_valid( bsp );
|
||||
}
|
||||
head = bsp;
|
||||
|
||||
// at block level, no transaction specific logging is possible
|
||||
if (auto dm_logger = get_deep_mind_logger(false)) {
|
||||
if (auto* dm_logger = get_deep_mind_logger(false)) {
|
||||
dm_logger->on_accepted_block(bsp);
|
||||
}
|
||||
|
||||
emit( self.accepted_block, bsp );
|
||||
|
||||
if( add_to_fork_db ) {
|
||||
if( s == controller::block_status::incomplete ) {
|
||||
log_irreversible();
|
||||
}
|
||||
} catch (...) {
|
||||
@@ -2156,7 +2157,7 @@ struct controller_impl {
|
||||
pending->_block_stage = completed_block{ bsp };
|
||||
|
||||
br = pending->_block_report; // copy before commit block destroys pending
|
||||
commit_block(false);
|
||||
commit_block(s);
|
||||
br.total_time = fc::time_point::now() - start;
|
||||
return;
|
||||
} catch ( const std::bad_alloc& ) {
|
||||
@@ -2308,7 +2309,6 @@ struct controller_impl {
|
||||
controller::block_report br;
|
||||
if( s == controller::block_status::irreversible ) {
|
||||
apply_block( br, bsp, s, trx_meta_cache_lookup{} );
|
||||
head = bsp;
|
||||
|
||||
// On replay, log_irreversible is not called and so no irreversible_block signal is emitted.
|
||||
// So emit it explicitly here.
|
||||
@@ -2334,8 +2334,6 @@ struct controller_impl {
|
||||
if( new_head->header.previous == head->id ) {
|
||||
try {
|
||||
apply_block( br, new_head, s, trx_lookup );
|
||||
fork_db.mark_valid( new_head );
|
||||
head = new_head;
|
||||
} catch ( const std::exception& e ) {
|
||||
fork_db.remove( new_head->id );
|
||||
throw;
|
||||
@@ -2368,8 +2366,6 @@ struct controller_impl {
|
||||
br = controller::block_report{};
|
||||
apply_block( br, *ritr, (*ritr)->is_valid() ? controller::block_status::validated
|
||||
: controller::block_status::complete, trx_lookup );
|
||||
fork_db.mark_valid( *ritr );
|
||||
head = *ritr;
|
||||
} catch ( const std::bad_alloc& ) {
|
||||
throw;
|
||||
} catch ( const boost::interprocess::bad_alloc& ) {
|
||||
@@ -2400,7 +2396,6 @@ struct controller_impl {
|
||||
for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) {
|
||||
br = controller::block_report{};
|
||||
apply_block( br, *ritr, controller::block_status::validated /* we previously validated these blocks*/, trx_lookup );
|
||||
head = *ritr;
|
||||
}
|
||||
std::rethrow_exception(except);
|
||||
} // end if exception
|
||||
@@ -2665,11 +2660,6 @@ struct controller_impl {
|
||||
trx.set_reference_block( self.head_block_id() );
|
||||
}
|
||||
|
||||
// onblock transaction cannot be transient
|
||||
if (auto dm_logger = get_deep_mind_logger(false)) {
|
||||
dm_logger->on_onblock(trx);
|
||||
}
|
||||
|
||||
return trx;
|
||||
}
|
||||
|
||||
@@ -2682,31 +2672,6 @@ struct controller_impl {
|
||||
return (blog.first_block_num() != 0) ? blog.first_block_num() : fork_db.root()->block_num;
|
||||
}
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
bool is_eos_vm_oc_enabled() const {
|
||||
return ( conf.eosvmoc_tierup || conf.wasm_runtime == wasm_interface::vm_type::eos_vm_oc );
|
||||
}
|
||||
#endif
|
||||
|
||||
// only called from non-main threads (read-only trx execution threads)
|
||||
// when producer_plugin starts them
|
||||
void init_thread_local_data() {
|
||||
EOS_ASSERT( !is_on_main_thread(), misc_exception, "init_thread_local_data called on the main thread");
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if ( is_eos_vm_oc_enabled() )
|
||||
// EOSVMOC needs further initialization of its thread local data
|
||||
wasmif.init_thread_local_data();
|
||||
else
|
||||
#endif
|
||||
{
|
||||
std::lock_guard g(threaded_wasmifs_mtx);
|
||||
// Non-EOSVMOC needs a wasmif per thread
|
||||
threaded_wasmifs[std::this_thread::get_id()] = std::make_unique<wasm_interface>( conf.wasm_runtime, conf.eosvmoc_tierup, db, conf.state_dir, conf.eosvmoc_config, !conf.profile_accounts.empty());
|
||||
}
|
||||
}
|
||||
|
||||
bool is_on_main_thread() { return main_thread_id == std::this_thread::get_id(); };
|
||||
|
||||
void set_to_write_window() {
|
||||
app_window = app_window_type::write;
|
||||
}
|
||||
@@ -2717,25 +2682,22 @@ struct controller_impl {
|
||||
return app_window == app_window_type::write;
|
||||
}
|
||||
|
||||
wasm_interface& get_wasm_interface() {
|
||||
if ( is_on_main_thread()
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
|| is_eos_vm_oc_enabled()
|
||||
bool is_eos_vm_oc_enabled() const {
|
||||
return wasm_if_collect.is_eos_vm_oc_enabled();
|
||||
}
|
||||
#endif
|
||||
)
|
||||
return wasmif;
|
||||
else
|
||||
return *threaded_wasmifs[std::this_thread::get_id()];
|
||||
|
||||
void init_thread_local_data() {
|
||||
wasm_if_collect.init_thread_local_data(db, conf.state_dir, conf.eosvmoc_config, !conf.profile_accounts.empty());
|
||||
}
|
||||
|
||||
wasm_interface_collection& get_wasm_interface() {
|
||||
return wasm_if_collect;
|
||||
}
|
||||
|
||||
void code_block_num_last_used(const digest_type& code_hash, uint8_t vm_type, uint8_t vm_version, uint32_t block_num) {
|
||||
// The caller of this function apply_eosio_setcode has already asserted that
|
||||
// the transaction is not a read-only trx, which implies we are
|
||||
// in write window. Safe to call threaded_wasmifs's code_block_num_last_used
|
||||
wasmif.code_block_num_last_used(code_hash, vm_type, vm_version, block_num);
|
||||
for (auto& w: threaded_wasmifs) {
|
||||
w.second->code_block_num_last_used(code_hash, vm_type, vm_version, block_num);
|
||||
}
|
||||
wasm_if_collect.code_block_num_last_used(code_hash, vm_type, vm_version, block_num);
|
||||
}
|
||||
|
||||
block_state_ptr fork_db_head() const;
|
||||
@@ -2999,7 +2961,7 @@ block_state_ptr controller::finalize_block( block_report& br, const signer_callb
|
||||
|
||||
void controller::commit_block() {
|
||||
validate_db_available_size();
|
||||
my->commit_block(true);
|
||||
my->commit_block(block_status::incomplete);
|
||||
}
|
||||
|
||||
deque<transaction_metadata_ptr> controller::abort_block() {
|
||||
@@ -3432,7 +3394,7 @@ const apply_handler* controller::find_apply_handler( account_name receiver, acco
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
wasm_interface& controller::get_wasm_interface() {
|
||||
wasm_interface_collection& controller::get_wasm_interface() {
|
||||
return my->get_wasm_interface();
|
||||
}
|
||||
|
||||
@@ -3612,7 +3574,6 @@ vm::wasm_allocator& controller::get_wasm_allocator() {
|
||||
return my->wasm_alloc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
bool controller::is_eos_vm_oc_enabled() const {
|
||||
return my->is_eos_vm_oc_enabled();
|
||||
@@ -3720,6 +3681,14 @@ void controller::replace_account_keys( name account, name permission, const publ
|
||||
rlm.verify_account_ram_usage(account);
|
||||
}
|
||||
|
||||
void controller::set_producer_node(bool is_producer_node) {
|
||||
my->is_producer_node = is_producer_node;
|
||||
}
|
||||
|
||||
bool controller::is_producer_node()const {
|
||||
return my->is_producer_node;
|
||||
}
|
||||
|
||||
void controller::set_db_read_only_mode() {
|
||||
mutable_db().set_read_only_mode();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <utility>
|
||||
#include <fc/variant_object.hpp>
|
||||
#include <fc/scoped_exit.hpp>
|
||||
#include <fc/time.hpp>
|
||||
|
||||
namespace eosio::chain {
|
||||
|
||||
@@ -23,6 +24,7 @@ namespace impl {
|
||||
struct abi_traverse_context_with_path;
|
||||
struct binary_to_variant_context;
|
||||
struct variant_to_binary_context;
|
||||
struct action_data_to_variant_context;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +50,6 @@ struct abi_serializer {
|
||||
bool is_szarray(const std::string_view& type)const;
|
||||
bool is_optional(const std::string_view& type)const;
|
||||
bool is_type( const std::string_view& type, const yield_function_t& yield )const;
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
bool is_type(const std::string_view& type, const fc::microseconds& max_serialization_time)const;
|
||||
bool is_builtin_type(const std::string_view& type)const;
|
||||
bool is_integer(const std::string_view& type) const;
|
||||
@@ -67,33 +68,29 @@ struct abi_serializer {
|
||||
std::optional<string> get_error_message( uint64_t error_code )const;
|
||||
|
||||
fc::variant binary_to_variant( const std::string_view& type, const bytes& binary, const yield_function_t& yield, bool short_path = false )const;
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
fc::variant binary_to_variant( const std::string_view& type, const bytes& binary, const fc::microseconds& max_serialization_time, bool short_path = false )const;
|
||||
fc::variant binary_to_variant( const std::string_view& type, const bytes& binary, const fc::microseconds& max_action_data_serialization_time, bool short_path = false )const;
|
||||
fc::variant binary_to_variant( const std::string_view& type, fc::datastream<const char*>& binary, const yield_function_t& yield, bool short_path = false )const;
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
fc::variant binary_to_variant( const std::string_view& type, fc::datastream<const char*>& binary, const fc::microseconds& max_serialization_time, bool short_path = false )const;
|
||||
fc::variant binary_to_variant( const std::string_view& type, fc::datastream<const char*>& binary, const fc::microseconds& max_action_data_serialization_time, bool short_path = false )const;
|
||||
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
bytes variant_to_binary( const std::string_view& type, const fc::variant& var, const fc::microseconds& max_serialization_time, bool short_path = false )const;
|
||||
bytes variant_to_binary( const std::string_view& type, const fc::variant& var, const fc::microseconds& max_action_data_serialization_time, bool short_path = false )const;
|
||||
bytes variant_to_binary( const std::string_view& type, const fc::variant& var, const yield_function_t& yield, bool short_path = false )const;
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
void variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char*>& ds, const fc::microseconds& max_serialization_time, bool short_path = false )const;
|
||||
void variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char*>& ds, const fc::microseconds& max_action_data_serialization_time, bool short_path = false )const;
|
||||
void variant_to_binary( const std::string_view& type, const fc::variant& var, fc::datastream<char*>& ds, const yield_function_t& yield, bool short_path = false )const;
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
static void to_variant( const T& o, fc::variant& vo, const Resolver& resolver, const yield_function_t& yield );
|
||||
template<typename T, typename Resolver>
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
static void to_variant( const T& o, fc::variant& vo, const Resolver& resolver, const fc::microseconds& max_serialization_time );
|
||||
static void to_variant( const T& o, fc::variant& vo, const Resolver& resolver, const fc::microseconds& max_action_data_serialization_time );
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
static void to_log_variant( const T& o, fc::variant& vo, const Resolver& resolver, const yield_function_t& yield );
|
||||
template<typename T, typename Resolver>
|
||||
static void to_log_variant( const T& o, fc::variant& vo, const Resolver& resolver, const fc::microseconds& max_action_data_serialization_time );
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
static void from_variant( const fc::variant& v, T& o, const Resolver& resolver, const yield_function_t& yield );
|
||||
template<typename T, typename Resolver>
|
||||
[[deprecated("use the overload with yield_function_t[=create_yield_function(max_serialization_time)]")]]
|
||||
static void from_variant( const fc::variant& v, T& o, const Resolver& resolver, const fc::microseconds& max_serialization_time );
|
||||
static void from_variant( const fc::variant& v, T& o, const Resolver& resolver, const fc::microseconds& max_action_data_serialization_time );
|
||||
|
||||
template<typename Vec>
|
||||
static bool is_empty_abi(const Vec& abi_vec)
|
||||
@@ -120,14 +117,10 @@ struct abi_serializer {
|
||||
static constexpr size_t max_recursion_depth = 32; // arbitrary depth to prevent infinite recursion
|
||||
|
||||
// create standard yield function that checks for max_serialization_time and max_recursion_depth.
|
||||
// now() deadline caputered at time of this call
|
||||
// restricts serialization time from creation of yield function until serialization is complete.
|
||||
// now() deadline captured at time of this call
|
||||
static yield_function_t create_yield_function(const fc::microseconds& max_serialization_time) {
|
||||
fc::time_point deadline = fc::time_point::now();
|
||||
if( max_serialization_time > fc::microseconds::maximum() - deadline.time_since_epoch() ) {
|
||||
deadline = fc::time_point::maximum();
|
||||
} else {
|
||||
deadline += max_serialization_time;
|
||||
}
|
||||
fc::time_point deadline = fc::time_point::now().safe_add(max_serialization_time);
|
||||
return [max_serialization_time, deadline](size_t recursion_depth) {
|
||||
EOS_ASSERT( recursion_depth < max_recursion_depth, abi_recursion_depth_exception,
|
||||
"recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
|
||||
@@ -137,6 +130,13 @@ struct abi_serializer {
|
||||
};
|
||||
}
|
||||
|
||||
static yield_function_t create_depth_yield_function() {
|
||||
return [](size_t recursion_depth) {
|
||||
EOS_ASSERT( recursion_depth < max_recursion_depth, abi_recursion_depth_exception,
|
||||
"recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
map<type_name, type_name, std::less<>> typedefs;
|
||||
@@ -172,8 +172,9 @@ private:
|
||||
namespace impl {
|
||||
const static size_t hex_log_max_size = 64;
|
||||
struct abi_traverse_context {
|
||||
explicit abi_traverse_context( abi_serializer::yield_function_t yield )
|
||||
abi_traverse_context( abi_serializer::yield_function_t yield, fc::microseconds max_action_data_serialization )
|
||||
: yield(std::move( yield ))
|
||||
, max_action_serialization_time(max_action_data_serialization)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -187,7 +188,9 @@ namespace impl {
|
||||
|
||||
protected:
|
||||
abi_serializer::yield_function_t yield;
|
||||
size_t recursion_depth = 0;
|
||||
// if set then restricts each individual action data serialization
|
||||
fc::microseconds max_action_serialization_time;
|
||||
size_t recursion_depth = 1;
|
||||
bool log = false;
|
||||
};
|
||||
|
||||
@@ -226,8 +229,8 @@ namespace impl {
|
||||
using path_item = std::variant<empty_path_item, array_index_path_item, field_path_item, variant_path_item>;
|
||||
|
||||
struct abi_traverse_context_with_path : public abi_traverse_context {
|
||||
abi_traverse_context_with_path( const abi_serializer& abis, abi_serializer::yield_function_t yield, const std::string_view& type )
|
||||
: abi_traverse_context( std::move( yield ) ), abis(abis)
|
||||
abi_traverse_context_with_path( const abi_serializer& abis, abi_serializer::yield_function_t yield, fc::microseconds max_action_data_serialization_time, const std::string_view& type )
|
||||
: abi_traverse_context( std::move( yield ), max_action_data_serialization_time ), abis(abis)
|
||||
{
|
||||
set_path_root(type);
|
||||
}
|
||||
@@ -263,6 +266,22 @@ namespace impl {
|
||||
using abi_traverse_context_with_path::abi_traverse_context_with_path;
|
||||
};
|
||||
|
||||
struct action_data_to_variant_context : public binary_to_variant_context {
|
||||
action_data_to_variant_context( const abi_serializer& abis, const abi_traverse_context& ctx, const std::string_view& type )
|
||||
: binary_to_variant_context(abis, ctx, type)
|
||||
{
|
||||
short_path = true; // Just to be safe while avoiding the complexity of threading an override boolean all over the place
|
||||
if (max_action_serialization_time.count() > 0) {
|
||||
fc::time_point deadline = fc::time_point::now().safe_add(max_action_serialization_time);
|
||||
yield = [deadline, y=yield, max=max_action_serialization_time](size_t depth) {
|
||||
y(depth); // call provided yield that might include an overall time limit or not
|
||||
EOS_ASSERT( fc::time_point::now() < deadline, abi_serialization_deadline_exception,
|
||||
"serialization action data time limit ${t}us exceeded", ("t", max) );
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct variant_to_binary_context : public abi_traverse_context_with_path {
|
||||
using abi_traverse_context_with_path::abi_traverse_context_with_path;
|
||||
|
||||
@@ -491,11 +510,10 @@ namespace impl {
|
||||
auto type = abi.get_action_type(act.name);
|
||||
if (!type.empty()) {
|
||||
try {
|
||||
binary_to_variant_context _ctx(abi, ctx, type);
|
||||
_ctx.short_path = true; // Just to be safe while avoiding the complexity of threading an override boolean all over the place
|
||||
action_data_to_variant_context _ctx(abi, ctx, type);
|
||||
mvo( "data", abi._binary_to_variant( type, act.data, _ctx ));
|
||||
} catch(...) {
|
||||
// any failure to serialize data, then leave as not serailzed
|
||||
// any failure to serialize data, then leave as not serialized
|
||||
set_hex_data(mvo, "data", act.data);
|
||||
}
|
||||
} else {
|
||||
@@ -552,8 +570,7 @@ namespace impl {
|
||||
const abi_serializer& abi = *abi_optional;
|
||||
auto type = abi.get_action_result_type(act.name);
|
||||
if (!type.empty()) {
|
||||
binary_to_variant_context _ctx(abi, ctx, type);
|
||||
_ctx.short_path = true; // Just to be safe while avoiding the complexity of threading an override boolean all over the place
|
||||
action_data_to_variant_context _ctx(abi, ctx, type);
|
||||
mvo( "return_value_data", abi._binary_to_variant( type, act_trace.return_value, _ctx ));
|
||||
}
|
||||
}
|
||||
@@ -650,7 +667,7 @@ namespace impl {
|
||||
for (auto feature : new_protocol_features) {
|
||||
mutable_variant_object feature_mvo;
|
||||
add(feature_mvo, "feature_digest", feature, resolver, ctx);
|
||||
pf_array.push_back(feature_mvo);
|
||||
pf_array.push_back(std::move(feature_mvo));
|
||||
}
|
||||
mvo("new_protocol_features", pf_array);
|
||||
}
|
||||
@@ -719,7 +736,7 @@ namespace impl {
|
||||
* and can be degraded to the normal ::from_variant(...) processing
|
||||
*/
|
||||
template<typename M, typename Resolver, not_require_abi_t<M> = 1>
|
||||
static void extract( const fc::variant& v, M& o, Resolver, abi_traverse_context& ctx )
|
||||
static void extract( const fc::variant& v, M& o, const Resolver&, abi_traverse_context& ctx )
|
||||
{
|
||||
auto h = ctx.enter_scope();
|
||||
from_variant(v, o);
|
||||
@@ -808,13 +825,14 @@ namespace impl {
|
||||
from_variant(data, act.data);
|
||||
valid_empty_data = act.data.empty();
|
||||
} else if ( data.is_object() ) {
|
||||
auto abi = resolver(act.account);
|
||||
if (abi) {
|
||||
auto type = abi->get_action_type(act.name);
|
||||
auto abi_optional = resolver(act.account);
|
||||
if (abi_optional) {
|
||||
const abi_serializer& abi = *abi_optional;
|
||||
auto type = abi.get_action_type(act.name);
|
||||
if (!type.empty()) {
|
||||
variant_to_binary_context _ctx(*abi, ctx, type);
|
||||
variant_to_binary_context _ctx(abi, ctx, type);
|
||||
_ctx.short_path = true; // Just to be safe while avoiding the complexity of threading an override boolean all over the place
|
||||
act.data = std::move( abi->_variant_to_binary( type, data, _ctx ));
|
||||
act.data = abi._variant_to_binary( type, data, _ctx );
|
||||
valid_empty_data = act.data.empty();
|
||||
}
|
||||
}
|
||||
@@ -941,42 +959,55 @@ namespace impl {
|
||||
template<typename T, typename Resolver>
|
||||
void abi_serializer::to_variant( const T& o, fc::variant& vo, const Resolver& resolver, const yield_function_t& yield ) try {
|
||||
mutable_variant_object mvo;
|
||||
impl::abi_traverse_context ctx( yield );
|
||||
impl::abi_traverse_context ctx( yield, fc::microseconds{} );
|
||||
impl::abi_to_variant::add(mvo, "_", o, resolver, ctx);
|
||||
vo = std::move(mvo["_"]);
|
||||
} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize: ${type}", ("type", boost::core::demangle( typeid(o).name() ) ))
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
void abi_serializer::to_variant( const T& o, fc::variant& vo, const Resolver& resolver, const fc::microseconds& max_serialization_time ) {
|
||||
to_variant( o, vo, resolver, create_yield_function(max_serialization_time) );
|
||||
}
|
||||
void abi_serializer::to_variant( const T& o, fc::variant& vo, const Resolver& resolver, const fc::microseconds& max_action_data_serialization_time ) try {
|
||||
mutable_variant_object mvo;
|
||||
impl::abi_traverse_context ctx( create_depth_yield_function(), max_action_data_serialization_time );
|
||||
impl::abi_to_variant::add(mvo, "_", o, resolver, ctx);
|
||||
vo = std::move(mvo["_"]);
|
||||
} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize: ${type}", ("type", boost::core::demangle( typeid(o).name() ) ))
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
void abi_serializer::to_log_variant( const T& o, fc::variant& vo, const Resolver& resolver, const yield_function_t& yield ) try {
|
||||
mutable_variant_object mvo;
|
||||
impl::abi_traverse_context ctx( yield );
|
||||
impl::abi_traverse_context ctx( yield, fc::microseconds{} );
|
||||
ctx.logging();
|
||||
impl::abi_to_variant::add(mvo, "_", o, resolver, ctx);
|
||||
vo = std::move(mvo["_"]);
|
||||
} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize: ${type}", ("type", boost::core::demangle( typeid(o).name() ) ))
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
void abi_serializer::to_log_variant( const T& o, fc::variant& vo, const Resolver& resolver, const fc::microseconds& max_action_data_serialization_time ) try {
|
||||
mutable_variant_object mvo;
|
||||
impl::abi_traverse_context ctx( create_depth_yield_function(), max_action_data_serialization_time );
|
||||
ctx.logging();
|
||||
impl::abi_to_variant::add(mvo, "_", o, resolver, ctx);
|
||||
vo = std::move(mvo["_"]);
|
||||
} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize: ${type}", ("type", boost::core::demangle( typeid(o).name() ) ))
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
void abi_serializer::from_variant( const fc::variant& v, T& o, const Resolver& resolver, const yield_function_t& yield ) try {
|
||||
impl::abi_traverse_context ctx( yield );
|
||||
impl::abi_traverse_context ctx( yield, fc::microseconds{} );
|
||||
impl::abi_from_variant::extract(v, o, resolver, ctx);
|
||||
} FC_RETHROW_EXCEPTIONS(error, "Failed to deserialize variant", ("variant",v))
|
||||
|
||||
template<typename T, typename Resolver>
|
||||
void abi_serializer::from_variant( const fc::variant& v, T& o, const Resolver& resolver, const fc::microseconds& max_serialization_time ) {
|
||||
from_variant( v, o, resolver, create_yield_function(max_serialization_time) );
|
||||
}
|
||||
void abi_serializer::from_variant( const fc::variant& v, T& o, const Resolver& resolver, const fc::microseconds& max_action_data_serialization_time ) try {
|
||||
impl::abi_traverse_context ctx( create_depth_yield_function(), max_action_data_serialization_time );
|
||||
impl::abi_from_variant::extract(v, o, resolver, ctx);
|
||||
} FC_RETHROW_EXCEPTIONS(error, "Failed to deserialize variant", ("variant",v))
|
||||
|
||||
using abi_serializer_cache_t = std::unordered_map<account_name, std::optional<abi_serializer>>;
|
||||
using resolver_fn_t = std::function<std::optional<abi_serializer>(const account_name& name)>;
|
||||
|
||||
class abi_resolver {
|
||||
public:
|
||||
abi_resolver(abi_serializer_cache_t&& abi_serializers) :
|
||||
explicit abi_resolver(abi_serializer_cache_t&& abi_serializers) :
|
||||
abi_serializers(std::move(abi_serializers))
|
||||
{}
|
||||
|
||||
@@ -993,7 +1024,7 @@ private:
|
||||
|
||||
class abi_serializer_cache_builder {
|
||||
public:
|
||||
abi_serializer_cache_builder(std::function<std::optional<abi_serializer>(const account_name& name)> resolver) :
|
||||
explicit abi_serializer_cache_builder(resolver_fn_t resolver) :
|
||||
resolver_(std::move(resolver))
|
||||
{
|
||||
}
|
||||
@@ -1037,8 +1068,47 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
std::function<std::optional<abi_serializer>(const account_name& name)> resolver_;
|
||||
resolver_fn_t resolver_;
|
||||
abi_serializer_cache_t abi_serializers;
|
||||
};
|
||||
|
||||
/*
|
||||
* This is equivalent to a resolver, except that everytime the abi_serializer for an account
|
||||
* is retrieved, it is stored in an unordered_map, so we won't waste time retrieving it again.
|
||||
* This is handy when parsing packed_transactions received in a fc::variant.
|
||||
*/
|
||||
class caching_resolver {
|
||||
public:
|
||||
explicit caching_resolver(resolver_fn_t resolver) :
|
||||
resolver_(std::move(resolver))
|
||||
{
|
||||
}
|
||||
|
||||
// make it non-copiable (we should only move it for performance reasons)
|
||||
caching_resolver(const caching_resolver&) = delete;
|
||||
caching_resolver& operator=(const caching_resolver&) = delete;
|
||||
|
||||
std::optional<std::reference_wrapper<const abi_serializer>> operator()(const account_name& account) const {
|
||||
auto it = abi_serializers.find(account);
|
||||
if (it != abi_serializers.end()) {
|
||||
if (it->second)
|
||||
return *it->second;
|
||||
return {};
|
||||
}
|
||||
auto serializer = resolver_(account);
|
||||
auto& dest = abi_serializers[account]; // add entry regardless
|
||||
if (serializer) {
|
||||
// we got a serializer, so move it into the cache
|
||||
dest = abi_serializer_cache_t::mapped_type{std::move(*serializer)};
|
||||
return *dest; // and return a reference to it
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
private:
|
||||
const resolver_fn_t resolver_;
|
||||
mutable abi_serializer_cache_t abi_serializers;
|
||||
};
|
||||
|
||||
|
||||
} // eosio::chain
|
||||
|
||||
@@ -598,6 +598,9 @@ class apply_context {
|
||||
|
||||
action_name get_sender() const;
|
||||
|
||||
bool is_applying_block() const { return trx_context.explicit_billed_cpu_time; }
|
||||
bool should_use_eos_vm_oc()const;
|
||||
|
||||
/// Fields:
|
||||
public:
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
signed_block_ptr read_head()const; //use blocklog
|
||||
signed_block_ptr head()const;
|
||||
block_id_type head_id()const;
|
||||
std::optional<block_id_type> head_id()const;
|
||||
|
||||
uint32_t first_block_num() const;
|
||||
|
||||
@@ -82,9 +82,17 @@ namespace eosio { namespace chain {
|
||||
|
||||
static std::filesystem::path repair_log( const std::filesystem::path& data_dir, uint32_t truncate_at_block = 0, const char* reversible_block_dir_name="" );
|
||||
|
||||
static std::optional<genesis_state> extract_genesis_state( const std::filesystem::path& data_dir );
|
||||
using chain_context = std::variant<genesis_state, chain_id_type>;
|
||||
static std::optional<chain_context> extract_chain_context(const std::filesystem::path& data_dir,
|
||||
const std::filesystem::path& retained_dir);
|
||||
|
||||
static chain_id_type extract_chain_id( const std::filesystem::path& data_dir );
|
||||
static std::optional<genesis_state>
|
||||
extract_genesis_state(const std::filesystem::path& data_dir,
|
||||
const std::filesystem::path& retained_dir = std::filesystem::path{});
|
||||
|
||||
static std::optional<chain_id_type>
|
||||
extract_chain_id(const std::filesystem::path& data_dir,
|
||||
const std::filesystem::path& retained_dir = std::filesystem::path{});
|
||||
|
||||
static void construct_index(const std::filesystem::path& block_file_name, const std::filesystem::path& index_file_name);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace eosio { namespace chain {
|
||||
class account_object;
|
||||
class deep_mind_handler;
|
||||
class subjective_billing;
|
||||
class wasm_interface_collection;
|
||||
using resource_limits::resource_limits_manager;
|
||||
using apply_handler = std::function<void(apply_context&)>;
|
||||
using forked_branch_callback = std::function<void(const branch_type&)>;
|
||||
@@ -90,7 +91,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
wasm_interface::vm_type wasm_runtime = chain::config::default_wasm_runtime;
|
||||
eosvmoc::config eosvmoc_config;
|
||||
bool eosvmoc_tierup = false;
|
||||
wasm_interface::vm_oc_enable eosvmoc_tierup = wasm_interface::vm_oc_enable::oc_auto;
|
||||
|
||||
db_read_mode read_mode = db_read_mode::HEAD;
|
||||
validation_mode block_validation_mode = validation_mode::FULL;
|
||||
@@ -321,6 +322,8 @@ namespace eosio { namespace chain {
|
||||
|
||||
#if defined(EOSIO_EOS_VM_RUNTIME_ENABLED) || defined(EOSIO_EOS_VM_JIT_RUNTIME_ENABLED)
|
||||
vm::wasm_allocator& get_wasm_allocator();
|
||||
#endif
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
bool is_eos_vm_oc_enabled() const;
|
||||
#endif
|
||||
|
||||
@@ -346,27 +349,7 @@ namespace eosio { namespace chain {
|
||||
*/
|
||||
|
||||
const apply_handler* find_apply_handler( account_name contract, scope_name scope, action_name act )const;
|
||||
wasm_interface& get_wasm_interface();
|
||||
|
||||
|
||||
std::optional<abi_serializer> get_abi_serializer( account_name n, const abi_serializer::yield_function_t& yield )const {
|
||||
if( n.good() ) {
|
||||
try {
|
||||
const auto& a = get_account( n );
|
||||
if( abi_def abi; abi_serializer::to_abi( a.abi, abi ))
|
||||
return abi_serializer( std::move(abi), yield );
|
||||
} FC_CAPTURE_AND_LOG((n))
|
||||
}
|
||||
return std::optional<abi_serializer>();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
fc::variant to_variant_with_abi( const T& obj, const abi_serializer::yield_function_t& yield )const {
|
||||
fc::variant pretty_output;
|
||||
abi_serializer::to_variant( obj, pretty_output,
|
||||
[&]( account_name n ){ return get_abi_serializer( n, yield ); }, yield );
|
||||
return pretty_output;
|
||||
}
|
||||
wasm_interface_collection& get_wasm_interface();
|
||||
|
||||
static chain_id_type extract_chain_id(snapshot_reader& snapshot);
|
||||
|
||||
@@ -375,6 +358,9 @@ namespace eosio { namespace chain {
|
||||
void replace_producer_keys( const public_key_type& key );
|
||||
void replace_account_keys( name account, name permission, const public_key_type& key );
|
||||
|
||||
void set_producer_node(bool is_producer_node);
|
||||
bool is_producer_node()const;
|
||||
|
||||
void set_db_read_only_mode();
|
||||
void unset_db_read_only_mode();
|
||||
void init_thread_local_data();
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#pragma once
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/iostreams/device/mapped_file.hpp>
|
||||
#include <fc/io/cfile.hpp>
|
||||
#include <fc/io/datastream.hpp>
|
||||
#include <filesystem>
|
||||
#include <regex>
|
||||
#include <map>
|
||||
|
||||
namespace eosio {
|
||||
namespace chain {
|
||||
|
||||
|
||||
template <typename Lambda>
|
||||
void for_each_file_in_dir_matches(const std::filesystem::path& dir, std::string pattern, Lambda&& lambda) {
|
||||
const std::regex my_filter(pattern);
|
||||
void for_each_file_in_dir_matches(const std::filesystem::path& dir, std::string_view pattern, Lambda&& lambda) {
|
||||
const std::regex my_filter(pattern.begin(), pattern.size());
|
||||
std::smatch what;
|
||||
std::filesystem::directory_iterator end_itr; // Default ctor yields past-the-end
|
||||
for (std::filesystem::directory_iterator p(dir); p != end_itr; ++p) {
|
||||
@@ -36,10 +36,10 @@ struct log_catalog {
|
||||
using block_num_t = uint32_t;
|
||||
|
||||
struct mapped_type {
|
||||
block_num_t last_block_num;
|
||||
block_num_t last_block_num = 0;
|
||||
std::filesystem::path filename_base;
|
||||
};
|
||||
using collection_t = boost::container::flat_map<block_num_t, mapped_type>;
|
||||
using collection_t = std::map<block_num_t, mapped_type>;
|
||||
using size_type = typename collection_t::size_type;
|
||||
static constexpr size_type npos = std::numeric_limits<size_type>::max();
|
||||
|
||||
@@ -84,9 +84,10 @@ struct log_catalog {
|
||||
archive_dir = make_absolute_dir(log_dir, archive_path);
|
||||
}
|
||||
|
||||
for_each_file_in_dir_matches(retained_dir, std::string(name) + suffix_pattern, [this](std::filesystem::path path) {
|
||||
std::string pattern = std::string(name) + suffix_pattern;
|
||||
for_each_file_in_dir_matches(retained_dir, pattern, [this](std::filesystem::path path) {
|
||||
auto log_path = path;
|
||||
auto index_path = path.replace_extension("index");
|
||||
const auto& index_path = path.replace_extension("index");
|
||||
auto path_without_extension = log_path.parent_path() / log_path.stem().string();
|
||||
|
||||
LogData log(log_path);
|
||||
@@ -94,8 +95,10 @@ struct log_catalog {
|
||||
verifier.verify(log, log_path);
|
||||
|
||||
// check if index file matches the log file
|
||||
if (!index_matches_data(index_path, log))
|
||||
log.construct_index(index_path);
|
||||
if (!index_matches_data(index_path, log)) {
|
||||
ilog("Recreating index for: ${i}", ("i", index_path.string()));
|
||||
log.construct_index( index_path );
|
||||
}
|
||||
|
||||
auto existing_itr = collection.find(log.first_block_num());
|
||||
if (existing_itr != collection.end()) {
|
||||
@@ -112,7 +115,7 @@ struct log_catalog {
|
||||
}
|
||||
}
|
||||
|
||||
collection.insert_or_assign(log.first_block_num(), mapped_type{log.last_block_num(), path_without_extension});
|
||||
collection.insert_or_assign(log.first_block_num(), mapped_type{log.last_block_num(), std::move(path_without_extension)});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,24 +123,19 @@ struct log_catalog {
|
||||
if (!std::filesystem::exists(index_path))
|
||||
return false;
|
||||
|
||||
auto num_blocks_in_index = std::filesystem::file_size(index_path) / sizeof(uint64_t);
|
||||
if (num_blocks_in_index != log.num_blocks())
|
||||
LogIndex log_i;
|
||||
log_i.open(index_path);
|
||||
|
||||
if (log_i.num_blocks() != log.num_blocks())
|
||||
return false;
|
||||
|
||||
// make sure the last 8 bytes of index and log matches
|
||||
fc::cfile index_file;
|
||||
index_file.set_file_path(index_path);
|
||||
index_file.open("r");
|
||||
index_file.seek_end(-sizeof(uint64_t));
|
||||
uint64_t pos;
|
||||
index_file.read(reinterpret_cast<char*>(&pos), sizeof(pos));
|
||||
return pos == log.last_block_position();
|
||||
return log_i.back() == log.last_block_position();
|
||||
}
|
||||
|
||||
std::optional<uint64_t> get_block_position(uint32_t block_num) {
|
||||
try {
|
||||
if (active_index != npos) {
|
||||
auto active_item = collection.nth(active_index);
|
||||
auto active_item = std::next(collection.begin(), active_index);
|
||||
if (active_item->first <= block_num && block_num <= active_item->second.last_block_num) {
|
||||
return log_index.nth_block_position(block_num - log_data.first_block_num());
|
||||
}
|
||||
@@ -151,7 +149,7 @@ struct log_catalog {
|
||||
auto name = it->second.filename_base;
|
||||
log_data.open(name.replace_extension("log"));
|
||||
log_index.open(name.replace_extension("index"));
|
||||
active_index = collection.index_of(it);
|
||||
active_index = std::distance(collection.begin(), it);
|
||||
return log_index.nth_block_position(block_num - log_data.first_block_num());
|
||||
}
|
||||
return {};
|
||||
@@ -204,7 +202,7 @@ struct log_catalog {
|
||||
/// Add a new entry into the catalog.
|
||||
///
|
||||
/// Notice that \c start_block_num must be monotonically increasing between the invocations of this function
|
||||
/// so that the new entry would be inserted at the end of the flat_map; otherwise, \c active_index would be
|
||||
/// so that the new entry would be inserted at the 'end' of the map; otherwise, \c active_index would be
|
||||
/// invalidated and the mapping between the log data their block range would be wrong. This function is only used
|
||||
/// during the splitting of block log. Using this function for other purpose should make sure if the monotonically
|
||||
/// increasing block num guarantee can be met.
|
||||
@@ -216,23 +214,24 @@ struct log_catalog {
|
||||
std::filesystem::path new_path = retained_dir / buf;
|
||||
rename_bundle(dir / name, new_path);
|
||||
size_type items_to_erase = 0;
|
||||
collection.emplace(start_block_num, mapped_type{end_block_num, new_path});
|
||||
collection.emplace(start_block_num, mapped_type{end_block_num, std::move(new_path)});
|
||||
if (collection.size() >= max_retained_files) {
|
||||
items_to_erase =
|
||||
max_retained_files > 0 ? collection.size() - max_retained_files : collection.size();
|
||||
auto last = std::next( collection.begin(), items_to_erase);
|
||||
|
||||
for (auto it = collection.begin(); it < collection.begin() + items_to_erase; ++it) {
|
||||
for (auto it = collection.begin(); it != last; ++it) {
|
||||
auto orig_name = it->second.filename_base;
|
||||
if (archive_dir.empty()) {
|
||||
// delete the old files when no backup dir is specified
|
||||
std::filesystem::remove(orig_name.replace_extension("log"));
|
||||
std::filesystem::remove(orig_name.replace_extension("index"));
|
||||
} else {
|
||||
// move the the archive dir
|
||||
// move the archive dir
|
||||
rename_bundle(orig_name, archive_dir / orig_name.filename());
|
||||
}
|
||||
}
|
||||
collection.erase(collection.begin(), collection.begin() + items_to_erase);
|
||||
collection.erase(collection.begin(), last);
|
||||
active_index = active_index == npos || active_index < items_to_erase
|
||||
? npos
|
||||
: active_index - items_to_erase;
|
||||
@@ -258,7 +257,7 @@ struct log_catalog {
|
||||
active_index = npos;
|
||||
auto it = collection.upper_bound(block_num);
|
||||
|
||||
if (it == collection.begin() || block_num > (it - 1)->second.last_block_num) {
|
||||
if (it == collection.begin() || block_num > std::prev(it)->second.last_block_num) {
|
||||
std::for_each(it, collection.end(), remove_files);
|
||||
collection.erase(it, collection.end());
|
||||
return 0;
|
||||
@@ -267,7 +266,7 @@ struct log_catalog {
|
||||
auto name = truncate_it->second.filename_base;
|
||||
std::filesystem::rename(name.replace_extension("log"), new_name.replace_extension("log"));
|
||||
std::filesystem::rename(name.replace_extension("index"), new_name.replace_extension("index"));
|
||||
std::for_each(truncate_it + 1, collection.end(), remove_files);
|
||||
std::for_each(std::next(truncate_it), collection.end(), remove_files);
|
||||
auto result = truncate_it->first;
|
||||
collection.erase(truncate_it, collection.end());
|
||||
return result;
|
||||
|
||||
@@ -31,7 +31,7 @@ class log_index {
|
||||
bool is_open() const { return file_.is_open(); }
|
||||
|
||||
uint64_t back() { return nth_block_position(num_blocks()-1); }
|
||||
unsigned num_blocks() const { return num_blocks_; }
|
||||
uint32_t num_blocks() const { return num_blocks_; }
|
||||
uint64_t nth_block_position(uint32_t n) {
|
||||
file_.seek(n*sizeof(uint64_t));
|
||||
uint64_t r;
|
||||
|
||||
@@ -75,6 +75,41 @@ namespace eosio::chain {
|
||||
friend constexpr bool operator != ( const name& a, uint64_t b ) { return a.value != b; }
|
||||
|
||||
constexpr explicit operator bool()const { return value != 0; }
|
||||
|
||||
/**
|
||||
* Returns the prefix.
|
||||
* for exmaple:
|
||||
* "eosio.any" -> "eosio"
|
||||
* "eosio" -> "eosio"
|
||||
*/
|
||||
constexpr name prefix() const {
|
||||
uint64_t result = value;
|
||||
bool not_dot_character_seen = false;
|
||||
uint64_t mask = 0xFull;
|
||||
|
||||
// Get characters one-by-one in name in order from right to left
|
||||
for (int32_t offset = 0; offset <= 59;) {
|
||||
auto c = (value >> offset) & mask;
|
||||
|
||||
if (!c) { // if this character is a dot
|
||||
if (not_dot_character_seen) { // we found the rightmost dot character
|
||||
result = (value >> offset) << offset;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
not_dot_character_seen = true;
|
||||
}
|
||||
|
||||
if (offset == 0) {
|
||||
offset += 4;
|
||||
mask = 0x1Full;
|
||||
} else {
|
||||
offset += 5;
|
||||
}
|
||||
}
|
||||
|
||||
return name{ result };
|
||||
}
|
||||
};
|
||||
|
||||
// Each char of the string is encoded into 5-bit chunk and left-shifted
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace eosio::chain {
|
||||
|
||||
namespace bmi = boost::multi_index;
|
||||
@@ -38,10 +40,19 @@ public:
|
||||
struct snapshot_request_information {
|
||||
uint32_t block_spacing = 0;
|
||||
uint32_t start_block_num = 0;
|
||||
uint32_t end_block_num = 0;
|
||||
uint32_t end_block_num = std::numeric_limits<uint32_t>::max();
|
||||
std::string snapshot_description = "";
|
||||
};
|
||||
|
||||
// this struct used to hold request params in api call
|
||||
// it is differentiate between 0 and empty values
|
||||
struct snapshot_request_params {
|
||||
std::optional<uint32_t> block_spacing;
|
||||
std::optional<uint32_t> start_block_num;
|
||||
std::optional<uint32_t> end_block_num;
|
||||
std::optional<std::string> snapshot_description;
|
||||
};
|
||||
|
||||
struct snapshot_request_id_information {
|
||||
uint32_t snapshot_request_id = 0;
|
||||
};
|
||||
@@ -205,6 +216,7 @@ public:
|
||||
|
||||
FC_REFLECT(eosio::chain::snapshot_scheduler::snapshot_information, (head_block_id) (head_block_num) (head_block_time) (version) (snapshot_name))
|
||||
FC_REFLECT(eosio::chain::snapshot_scheduler::snapshot_request_information, (block_spacing) (start_block_num) (end_block_num) (snapshot_description))
|
||||
FC_REFLECT(eosio::chain::snapshot_scheduler::snapshot_request_params, (block_spacing) (start_block_num) (end_block_num) (snapshot_description))
|
||||
FC_REFLECT(eosio::chain::snapshot_scheduler::snapshot_request_id_information, (snapshot_request_id))
|
||||
FC_REFLECT(eosio::chain::snapshot_scheduler::get_snapshot_requests_result, (snapshot_requests))
|
||||
FC_REFLECT_DERIVED(eosio::chain::snapshot_scheduler::snapshot_schedule_information, (eosio::chain::snapshot_scheduler::snapshot_request_id_information)(eosio::chain::snapshot_scheduler::snapshot_request_information), (pending_snapshots))
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
#include <fc/exception/exception.hpp>
|
||||
#include <eosio/chain/exceptions.hpp>
|
||||
#include <eosio/chain/types.hpp>
|
||||
#include <eosio/chain/core_symbol.hpp>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace eosio {
|
||||
namespace chain {
|
||||
namespace eosio::chain {
|
||||
|
||||
/**
|
||||
class symbol represents a token and contains precision and name.
|
||||
@@ -65,20 +65,7 @@ namespace eosio {
|
||||
explicit symbol(uint64_t v = CORE_SYMBOL): m_value(v) {
|
||||
EOS_ASSERT(valid(), symbol_type_exception, "invalid symbol: ${name}", ("name",name()));
|
||||
}
|
||||
static symbol from_string(const string& from)
|
||||
{
|
||||
try {
|
||||
string s = fc::trim(from);
|
||||
EOS_ASSERT(!s.empty(), symbol_type_exception, "creating symbol from empty string");
|
||||
auto comma_pos = s.find(',');
|
||||
EOS_ASSERT(comma_pos != string::npos, symbol_type_exception, "missing comma in symbol");
|
||||
auto prec_part = s.substr(0, comma_pos);
|
||||
uint8_t p = fc::to_int64(prec_part);
|
||||
string name_part = s.substr(comma_pos + 1);
|
||||
EOS_ASSERT( p <= max_precision, symbol_type_exception, "precision ${p} should be <= 18", ("p", p));
|
||||
return symbol(string_to_symbol(p, name_part.c_str()));
|
||||
} FC_CAPTURE_LOG_AND_RETHROW((from))
|
||||
}
|
||||
static symbol from_string(const string& from);
|
||||
uint64_t value() const { return m_value; }
|
||||
bool valid() const
|
||||
{
|
||||
@@ -184,8 +171,7 @@ namespace eosio {
|
||||
{
|
||||
return std::tie(lhs.sym, lhs.contract) > std::tie(rhs.sym, rhs.contract);
|
||||
}
|
||||
} // namespace chain
|
||||
} // namespace eosio
|
||||
} // namespace eosio::chain
|
||||
|
||||
namespace fc {
|
||||
inline void to_variant(const eosio::chain::symbol& var, fc::variant& vo) { vo = var.to_string(); }
|
||||
|
||||
@@ -37,9 +37,13 @@ namespace eosio { namespace chain {
|
||||
|
||||
/// Spawn threads, can be re-started after stop().
|
||||
/// Assumes start()/stop() called from the same thread or externally protected.
|
||||
/// Blocks until all threads are created and completed their init function, or an exception is thrown
|
||||
/// during thread startup or an init function. Exceptions thrown during these stages are rethrown from start()
|
||||
/// but some threads might still have been started. Calling stop() after such a failure is safe.
|
||||
/// @param num_threads is number of threads spawned
|
||||
/// @param on_except is the function to call if io_context throws an exception, is called from thread pool thread.
|
||||
/// if an empty function then logs and rethrows exception on thread which will terminate.
|
||||
/// if an empty function then logs and rethrows exception on thread which will terminate. Not called
|
||||
/// for exceptions during the init function (such exceptions are rethrown from start())
|
||||
/// @param init is an optional function to call at startup to initialize any data.
|
||||
/// @throw assert_exception if already started and not stopped.
|
||||
void start( size_t num_threads, on_except_t on_except, init_t init = {} ) {
|
||||
@@ -47,9 +51,25 @@ namespace eosio { namespace chain {
|
||||
_ioc_work.emplace( boost::asio::make_work_guard( _ioc ) );
|
||||
_ioc.restart();
|
||||
_thread_pool.reserve( num_threads );
|
||||
for( size_t i = 0; i < num_threads; ++i ) {
|
||||
_thread_pool.emplace_back( std::thread( &named_thread_pool::run_thread, this, i, on_except, init ) );
|
||||
|
||||
std::promise<void> start_complete;
|
||||
std::atomic<uint32_t> threads_remaining = num_threads;
|
||||
std::exception_ptr pending_exception;
|
||||
std::mutex pending_exception_mutex;
|
||||
|
||||
try {
|
||||
for( size_t i = 0; i < num_threads; ++i ) {
|
||||
_thread_pool.emplace_back( std::thread( &named_thread_pool::run_thread, this, i, on_except, init, std::ref(start_complete),
|
||||
std::ref(threads_remaining), std::ref(pending_exception), std::ref(pending_exception_mutex) ) );
|
||||
}
|
||||
}
|
||||
catch( ... ) {
|
||||
/// only an exception from std::thread's ctor should end up here. shut down all threads to ensure no
|
||||
/// potential access to the promise, atomic, etc above performed after throwing out of start
|
||||
stop();
|
||||
throw;
|
||||
}
|
||||
start_complete.get_future().get();
|
||||
}
|
||||
|
||||
/// destroy work guard, stop io_context, join thread_pool
|
||||
@@ -63,16 +83,42 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
|
||||
private:
|
||||
void run_thread( size_t i, const on_except_t& on_except, const init_t& init ) {
|
||||
std::string tn = boost::core::demangle(typeid(this).name());
|
||||
auto offset = tn.rfind("::");
|
||||
if (offset != std::string::npos)
|
||||
tn.erase(0, offset+2);
|
||||
tn = tn.substr(0, tn.find('>')) + "-" + std::to_string( i );
|
||||
void run_thread( size_t i, const on_except_t& on_except, const init_t& init, std::promise<void>& start_complete,
|
||||
std::atomic<uint32_t>& threads_remaining, std::exception_ptr& pending_exception, std::mutex& pending_exception_mutex ) {
|
||||
|
||||
std::string tn;
|
||||
|
||||
auto decrement_remaining = [&]() {
|
||||
if( !--threads_remaining ) {
|
||||
if( pending_exception )
|
||||
start_complete.set_exception( pending_exception );
|
||||
else
|
||||
start_complete.set_value();
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
try {
|
||||
tn = boost::core::demangle(typeid(this).name());
|
||||
auto offset = tn.rfind("::");
|
||||
if (offset != std::string::npos)
|
||||
tn.erase(0, offset+2);
|
||||
tn = tn.substr(0, tn.find('>')) + "-" + std::to_string( i );
|
||||
fc::set_thread_name( tn );
|
||||
if ( init )
|
||||
init();
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
catch( ... ) {
|
||||
std::lock_guard<std::mutex> l( pending_exception_mutex );
|
||||
pending_exception = std::current_exception();
|
||||
decrement_remaining();
|
||||
return;
|
||||
}
|
||||
|
||||
decrement_remaining();
|
||||
|
||||
try {
|
||||
fc::set_os_thread_name( tn );
|
||||
if ( init )
|
||||
init();
|
||||
_ioc.run();
|
||||
} catch( const fc::exception& e ) {
|
||||
if( on_except ) {
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
transaction_context( controller& c,
|
||||
const packed_transaction& t,
|
||||
const transaction_id_type& trx_id, // trx_id diff than t.id() before replace_deferred
|
||||
transaction_checktime_timer&& timer,
|
||||
fc::time_point start = fc::time_point::now(),
|
||||
transaction_metadata::trx_type type = transaction_metadata::trx_type::input);
|
||||
@@ -127,6 +128,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
controller& control;
|
||||
const packed_transaction& packed_trx;
|
||||
const transaction_id_type& id;
|
||||
std::optional<chainbase::database::session> undo_session;
|
||||
transaction_trace_ptr trace;
|
||||
fc::time_point start;
|
||||
@@ -184,7 +186,6 @@ namespace eosio { namespace chain {
|
||||
speculative_executed_adjusted_max_transaction_time // prev_billed_cpu_time_us > 0
|
||||
};
|
||||
tx_cpu_usage_exceeded_reason tx_cpu_usage_reason = tx_cpu_usage_exceeded_reason::account_cpu_limit;
|
||||
fc::microseconds tx_cpu_usage_amount;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace eosio::chain {
|
||||
}
|
||||
|
||||
template<typename E, typename F>
|
||||
static inline auto has_field( F flags, E field )
|
||||
static constexpr auto has_field( F flags, E field )
|
||||
-> std::enable_if_t< std::is_integral<F>::value && std::is_unsigned<F>::value &&
|
||||
std::is_enum<E>::value && std::is_same< F, std::underlying_type_t<E> >::value, bool>
|
||||
{
|
||||
@@ -378,7 +378,7 @@ namespace eosio::chain {
|
||||
}
|
||||
|
||||
template<typename E, typename F>
|
||||
static inline auto set_field( F flags, E field, bool value = true )
|
||||
static constexpr auto set_field( F flags, E field, bool value = true )
|
||||
-> std::enable_if_t< std::is_integral<F>::value && std::is_unsigned<F>::value &&
|
||||
std::is_enum<E>::value && std::is_same< F, std::underlying_type_t<E> >::value, F >
|
||||
{
|
||||
|
||||
@@ -40,7 +40,13 @@ namespace eosio { namespace chain {
|
||||
}
|
||||
}
|
||||
|
||||
wasm_interface(vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile);
|
||||
enum class vm_oc_enable {
|
||||
oc_auto,
|
||||
oc_all,
|
||||
oc_none
|
||||
};
|
||||
|
||||
wasm_interface(vm_type vm, const chainbase::database& d, const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile);
|
||||
~wasm_interface();
|
||||
|
||||
// initialize exec per thread
|
||||
@@ -55,28 +61,32 @@ namespace eosio { namespace chain {
|
||||
//indicate that a particular code probably won't be used after given block_num
|
||||
void code_block_num_last_used(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version, const uint32_t& block_num);
|
||||
|
||||
//indicate the current LIB. evicts old cache entries
|
||||
void current_lib(const uint32_t lib);
|
||||
//indicate the current LIB. evicts old cache entries, each evicted entry is provided to callback
|
||||
void current_lib(const uint32_t lib, const std::function<void(const digest_type&, uint8_t)>& callback);
|
||||
|
||||
//Calls apply or error on a given code
|
||||
void apply(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version, apply_context& context);
|
||||
|
||||
//Returns true if the code is cached
|
||||
bool is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const;
|
||||
|
||||
// If substitute_apply is set, then apply calls it before doing anything else. If substitute_apply returns true,
|
||||
// then apply returns immediately.
|
||||
std::function<bool(
|
||||
const digest_type& code_hash, uint8_t vm_type, uint8_t vm_version, apply_context& context)> substitute_apply;
|
||||
private:
|
||||
unique_ptr<struct wasm_interface_impl> my;
|
||||
vm_type vm;
|
||||
};
|
||||
|
||||
} } // eosio::chain
|
||||
|
||||
namespace eosio{ namespace chain {
|
||||
std::istream& operator>>(std::istream& in, wasm_interface::vm_type& runtime);
|
||||
inline std::ostream& operator<<(std::ostream& os, wasm_interface::vm_oc_enable t) {
|
||||
if (t == wasm_interface::vm_oc_enable::oc_auto) {
|
||||
os << "auto";
|
||||
} else if (t == wasm_interface::vm_oc_enable::oc_all) {
|
||||
os << "all";
|
||||
} else if (t == wasm_interface::vm_oc_enable::oc_none) {
|
||||
os << "none";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
}}
|
||||
|
||||
FC_REFLECT_ENUM( eosio::chain::wasm_interface::vm_type, (eos_vm)(eos_vm_jit)(eos_vm_oc) )
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include <eosio/chain/wasm_interface.hpp>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace eosio::chain {
|
||||
|
||||
/**
|
||||
* @class wasm_interface_collection manages the active wasm_interface to use for execution.
|
||||
*/
|
||||
class wasm_interface_collection {
|
||||
public:
|
||||
inline static bool test_disable_tierup = false; // set by unittests to test tierup failing
|
||||
|
||||
wasm_interface_collection(wasm_interface::vm_type vm, wasm_interface::vm_oc_enable eosvmoc_tierup,
|
||||
const chainbase::database& d, const std::filesystem::path& data_dir,
|
||||
const eosvmoc::config& eosvmoc_config, bool profile);
|
||||
|
||||
~wasm_interface_collection();
|
||||
|
||||
void apply(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version, apply_context& context);
|
||||
|
||||
// used for tests, only valid on main thread
|
||||
bool is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) {
|
||||
EOS_ASSERT(is_on_main_thread(), wasm_execution_error, "is_code_cached called off the main thread");
|
||||
return wasmif.is_code_cached(code_hash, vm_type, vm_version);
|
||||
}
|
||||
|
||||
// update current lib of all wasm interfaces
|
||||
void current_lib(const uint32_t lib);
|
||||
|
||||
// only called from non-main threads (read-only trx execution threads) when producer_plugin starts them
|
||||
void init_thread_local_data(const chainbase::database& d, const std::filesystem::path& data_dir, const eosvmoc::config& eosvmoc_config, bool profile);
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
bool is_eos_vm_oc_enabled() const {
|
||||
return ((eosvmoc_tierup != wasm_interface::vm_oc_enable::oc_none) || wasm_runtime == wasm_interface::vm_type::eos_vm_oc);
|
||||
}
|
||||
#endif
|
||||
|
||||
void code_block_num_last_used(const digest_type& code_hash, uint8_t vm_type, uint8_t vm_version, uint32_t block_num);
|
||||
|
||||
// If substitute_apply is set, then apply calls it before doing anything else. If substitute_apply returns true,
|
||||
// then apply returns immediately. Provided function must be multi-thread safe.
|
||||
std::function<bool(const digest_type& code_hash, uint8_t vm_type, uint8_t vm_version, apply_context& context)> substitute_apply;
|
||||
|
||||
private:
|
||||
bool is_on_main_thread() { return main_thread_id == std::this_thread::get_id(); };
|
||||
|
||||
private:
|
||||
const std::thread::id main_thread_id;
|
||||
const wasm_interface::vm_type wasm_runtime;
|
||||
const wasm_interface::vm_oc_enable eosvmoc_tierup;
|
||||
|
||||
wasm_interface wasmif; // used by main thread
|
||||
std::mutex threaded_wasmifs_mtx;
|
||||
std::unordered_map<std::thread::id, std::unique_ptr<wasm_interface>> threaded_wasmifs; // one for each read-only thread, used by eos-vm and eos-vm-jit
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
std::unique_ptr<struct eosvmoc_tier> eosvmoc; // used by all threads
|
||||
#endif
|
||||
};
|
||||
|
||||
} // eosio::chain
|
||||
@@ -41,31 +41,13 @@ namespace eosio { namespace chain {
|
||||
uint8_t vm_version = 0;
|
||||
};
|
||||
struct by_hash;
|
||||
struct by_first_block_num;
|
||||
struct by_last_block_num;
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
struct eosvmoc_tier {
|
||||
eosvmoc_tier(const std::filesystem::path& d, const eosvmoc::config& c, const chainbase::database& db)
|
||||
: cc(d, c, db) {
|
||||
// construct exec for the main thread
|
||||
init_thread_local_data();
|
||||
}
|
||||
|
||||
// Support multi-threaded execution.
|
||||
void init_thread_local_data() {
|
||||
exec = std::make_unique<eosvmoc::executor>(cc);
|
||||
}
|
||||
|
||||
eosvmoc::code_cache_async cc;
|
||||
|
||||
// Each thread requires its own exec and mem. Defined in wasm_interface.cpp
|
||||
thread_local static std::unique_ptr<eosvmoc::executor> exec;
|
||||
thread_local static eosvmoc::memory mem;
|
||||
};
|
||||
#endif
|
||||
|
||||
wasm_interface_impl(wasm_interface::vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile) : db(d), wasm_runtime_time(vm) {
|
||||
wasm_interface_impl(wasm_interface::vm_type vm, const chainbase::database& d,
|
||||
const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile)
|
||||
: db(d)
|
||||
, wasm_runtime_time(vm)
|
||||
{
|
||||
#ifdef EOSIO_EOS_VM_RUNTIME_ENABLED
|
||||
if(vm == wasm_interface::vm_type::eos_vm)
|
||||
runtime_interface = std::make_unique<webassembly::eos_vm_runtime::eos_vm_runtime<eosio::vm::interpreter>>();
|
||||
@@ -84,22 +66,9 @@ namespace eosio { namespace chain {
|
||||
#endif
|
||||
if(!runtime_interface)
|
||||
EOS_THROW(wasm_exception, "${r} wasm runtime not supported on this platform and/or configuration", ("r", vm));
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if(eosvmoc_tierup) {
|
||||
EOS_ASSERT(vm != wasm_interface::vm_type::eos_vm_oc, wasm_exception, "You can't use EOS VM OC as the base runtime when tier up is activated");
|
||||
eosvmoc.emplace(data_dir, eosvmoc_config, d);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
~wasm_interface_impl() {
|
||||
if(is_shutting_down)
|
||||
for(wasm_cache_index::iterator it = wasm_instantiation_cache.begin(); it != wasm_instantiation_cache.end(); ++it)
|
||||
wasm_instantiation_cache.modify(it, [](wasm_cache_entry& e) {
|
||||
e.module.release()->fast_shutdown();
|
||||
});
|
||||
}
|
||||
~wasm_interface_impl() = default;
|
||||
|
||||
bool is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const {
|
||||
wasm_cache_index::iterator it = wasm_instantiation_cache.find( boost::make_tuple(code_hash, vm_type, vm_version) );
|
||||
@@ -114,14 +83,16 @@ namespace eosio { namespace chain {
|
||||
});
|
||||
}
|
||||
|
||||
void current_lib(uint32_t lib) {
|
||||
// reports each code_hash and vm_version that will be erased to callback
|
||||
void current_lib(uint32_t lib, const std::function<void(const digest_type&, uint8_t)>& callback) {
|
||||
//anything last used before or on the LIB can be evicted
|
||||
const auto first_it = wasm_instantiation_cache.get<by_last_block_num>().begin();
|
||||
const auto last_it = wasm_instantiation_cache.get<by_last_block_num>().upper_bound(lib);
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if(eosvmoc) for(auto it = first_it; it != last_it; it++)
|
||||
eosvmoc->cc.free_code(it->code_hash, it->vm_version);
|
||||
#endif
|
||||
if (callback) {
|
||||
for(auto it = first_it; it != last_it; it++) {
|
||||
callback(it->code_hash, it->vm_version);
|
||||
}
|
||||
}
|
||||
wasm_instantiation_cache.get<by_last_block_num>().erase(first_it, last_it);
|
||||
}
|
||||
|
||||
@@ -158,7 +129,6 @@ namespace eosio { namespace chain {
|
||||
return it->module;
|
||||
}
|
||||
|
||||
bool is_shutting_down = false;
|
||||
std::unique_ptr<wasm_runtime_interface> runtime_interface;
|
||||
|
||||
typedef boost::multi_index_container<
|
||||
@@ -178,10 +148,6 @@ namespace eosio { namespace chain {
|
||||
|
||||
const chainbase::database& db;
|
||||
const wasm_interface::vm_type wasm_runtime_time;
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
std::optional<eosvmoc_tier> eosvmoc;
|
||||
#endif
|
||||
};
|
||||
|
||||
} } // eosio::chain
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<eosio::chain::eosvmoc::code_tuple> {
|
||||
@@ -39,7 +38,7 @@ struct config;
|
||||
|
||||
class code_cache_base {
|
||||
public:
|
||||
code_cache_base(const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db);
|
||||
code_cache_base(const std::filesystem::path& data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db);
|
||||
~code_cache_base();
|
||||
|
||||
const int& fd() const { return _cache_fd; }
|
||||
@@ -78,9 +77,20 @@ class code_cache_base {
|
||||
local::datagram_protocol::socket _compile_monitor_write_socket{_ctx};
|
||||
local::datagram_protocol::socket _compile_monitor_read_socket{_ctx};
|
||||
|
||||
//these are really only useful to the async code cache, but keep them here so
|
||||
//free_code can be shared
|
||||
std::unordered_set<code_tuple> _queued_compiles;
|
||||
//these are really only useful to the async code cache, but keep them here so free_code can be shared
|
||||
using queued_compilies_t = boost::multi_index_container<
|
||||
code_tuple,
|
||||
indexed_by<
|
||||
sequenced<>,
|
||||
hashed_unique<tag<by_hash>,
|
||||
composite_key< code_tuple,
|
||||
member<code_tuple, digest_type, &code_tuple::code_id>,
|
||||
member<code_tuple, uint8_t, &code_tuple::vm_version>
|
||||
>
|
||||
>
|
||||
>
|
||||
>;
|
||||
queued_compilies_t _queued_compiles;
|
||||
std::unordered_map<code_tuple, bool> _outstanding_compiles_and_poison;
|
||||
|
||||
size_t _free_bytes_eviction_threshold;
|
||||
@@ -95,13 +105,13 @@ class code_cache_base {
|
||||
|
||||
class code_cache_async : public code_cache_base {
|
||||
public:
|
||||
code_cache_async(const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db);
|
||||
code_cache_async(const std::filesystem::path& data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db);
|
||||
~code_cache_async();
|
||||
|
||||
//If code is in cache: returns pointer & bumps to front of MRU list
|
||||
//If code is not in cache, and not blacklisted, and not currently compiling: return nullptr and kick off compile
|
||||
//otherwise: return nullptr
|
||||
const code_descriptor* const get_descriptor_for_code(const digest_type& code_id, const uint8_t& vm_version, bool is_write_window, get_cd_failure& failure);
|
||||
const code_descriptor* const get_descriptor_for_code(bool high_priority, const digest_type& code_id, const uint8_t& vm_version, bool is_write_window, get_cd_failure& failure);
|
||||
|
||||
private:
|
||||
std::thread _monitor_reply_thread;
|
||||
|
||||
@@ -13,7 +13,6 @@ class apply_context;
|
||||
class wasm_instantiated_module_interface {
|
||||
public:
|
||||
virtual void apply(apply_context& context) = 0;
|
||||
virtual void fast_shutdown() {}
|
||||
|
||||
virtual ~wasm_instantiated_module_interface();
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ platform_timer::platform_timer() {
|
||||
std::promise<void> p;
|
||||
auto f = p.get_future();
|
||||
checktime_thread = std::thread([&p]() {
|
||||
fc::set_os_thread_name("checktime");
|
||||
fc::set_thread_name("checktime");
|
||||
checktime_ios = std::make_unique<boost::asio::io_service>();
|
||||
boost::asio::io_service::work work(*checktime_ios);
|
||||
p.set_value();
|
||||
|
||||
@@ -51,7 +51,7 @@ platform_timer::platform_timer() {
|
||||
FC_ASSERT(kevent64(kqueue_fd, &quit_event, 1, NULL, 0, KEVENT_FLAG_IMMEDIATE, NULL) == 0, "failed to create quit event");
|
||||
|
||||
kevent_thread = std::thread([]() {
|
||||
fc::set_os_thread_name("checktime");
|
||||
fc::set_thread_name("checktime");
|
||||
while(true) {
|
||||
struct kevent64_s anEvent;
|
||||
int c = kevent64(kqueue_fd, NULL, 0, &anEvent, 1, 0, NULL);
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace eosio::chain {
|
||||
|
||||
// snapshot_scheduler_listener
|
||||
void snapshot_scheduler::on_start_block(uint32_t height, chain::controller& chain) {
|
||||
bool serialize_needed = false;
|
||||
bool snapshot_executed = false;
|
||||
|
||||
auto execute_snapshot_with_log = [this, height, &snapshot_executed, &chain](const auto& req) {
|
||||
@@ -25,28 +24,18 @@ void snapshot_scheduler::on_start_block(uint32_t height, chain::controller& chai
|
||||
std::vector<uint32_t> unschedule_snapshot_request_ids;
|
||||
for(const auto& req: _snapshot_requests.get<0>()) {
|
||||
// -1 since its called from start block
|
||||
bool recurring_snapshot = req.block_spacing && (height >= req.start_block_num + 1) && (!((height - req.start_block_num - 1) % req.block_spacing));
|
||||
bool onetime_snapshot = (!req.block_spacing) && (height == req.start_block_num + 1);
|
||||
bool recurring_snapshot = req.block_spacing && (height >= req.start_block_num + 1) && (!((height - req.start_block_num - 1) % req.block_spacing));
|
||||
bool onetime_snapshot = (!req.block_spacing) && (height == req.start_block_num + 1);
|
||||
|
||||
bool marked_for_deletion = ((!req.block_spacing) && (height >= req.start_block_num + 1)) || // if one time snapshot executed or scheduled for the past, it should be gone
|
||||
(height > 0 && ((height-1) >= req.end_block_num)); // any snapshot can expire by end block num (end_block_num can be max value)
|
||||
|
||||
// assume "asap" for snapshot with missed/zero start, it can have spacing
|
||||
if(!req.start_block_num) {
|
||||
// update start_block_num with current height only if this is recurring
|
||||
// if non recurring, will be executed and unscheduled
|
||||
if(req.block_spacing && height) {
|
||||
auto& snapshot_by_id = _snapshot_requests.get<by_snapshot_id>();
|
||||
auto it = snapshot_by_id.find(req.snapshot_request_id);
|
||||
_snapshot_requests.modify(it, [&height](auto& p) { p.start_block_num = height - 1; });
|
||||
serialize_needed = true;
|
||||
}
|
||||
execute_snapshot_with_log(req);
|
||||
} else if(recurring_snapshot || onetime_snapshot) {
|
||||
if(recurring_snapshot || onetime_snapshot) {
|
||||
execute_snapshot_with_log(req);
|
||||
}
|
||||
|
||||
// cleanup - remove expired (or invalid) request
|
||||
if((!req.start_block_num && !req.block_spacing) ||
|
||||
(!req.block_spacing && height >= (req.start_block_num + 1)) ||
|
||||
(req.end_block_num > 0 && height >= (req.end_block_num + 1))) {
|
||||
if(marked_for_deletion) {
|
||||
unschedule_snapshot_request_ids.push_back(req.snapshot_request_id);
|
||||
}
|
||||
}
|
||||
@@ -54,9 +43,6 @@ void snapshot_scheduler::on_start_block(uint32_t height, chain::controller& chai
|
||||
for(const auto& i: unschedule_snapshot_request_ids) {
|
||||
unschedule_snapshot(i);
|
||||
}
|
||||
|
||||
// store db to filesystem
|
||||
if(serialize_needed) x_serialize();
|
||||
}
|
||||
|
||||
void snapshot_scheduler::on_irreversible_block(const signed_block_ptr& lib, const chain::controller& chain) {
|
||||
@@ -80,15 +66,8 @@ snapshot_scheduler::snapshot_schedule_result snapshot_scheduler::schedule_snapsh
|
||||
auto& snapshot_by_value = _snapshot_requests.get<by_snapshot_value>();
|
||||
auto existing = snapshot_by_value.find(std::make_tuple(sri.block_spacing, sri.start_block_num, sri.end_block_num));
|
||||
EOS_ASSERT(existing == snapshot_by_value.end(), chain::duplicate_snapshot_request, "Duplicate snapshot request");
|
||||
|
||||
if(sri.end_block_num > 0) {
|
||||
// if "end" is specified, it should be greater then start
|
||||
EOS_ASSERT(sri.start_block_num <= sri.end_block_num, chain::invalid_snapshot_request, "End block number should be greater or equal to start block number");
|
||||
// if also block_spacing specified, check it
|
||||
if(sri.block_spacing > 0) {
|
||||
EOS_ASSERT(sri.start_block_num + sri.block_spacing <= sri.end_block_num, chain::invalid_snapshot_request, "Block spacing exceeds defined by start and end range");
|
||||
}
|
||||
}
|
||||
EOS_ASSERT(sri.start_block_num <= sri.end_block_num, chain::invalid_snapshot_request, "End block number should be greater or equal to start block number");
|
||||
EOS_ASSERT(sri.start_block_num + sri.block_spacing <= sri.end_block_num, chain::invalid_snapshot_request, "Block spacing exceeds defined by start and end range");
|
||||
|
||||
_snapshot_requests.emplace(snapshot_schedule_information{{_snapshot_id++}, {sri.block_spacing, sri.start_block_num, sri.end_block_num, sri.snapshot_description}, {}});
|
||||
x_serialize();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <eosio/chain/symbol.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace eosio::chain {
|
||||
|
||||
symbol symbol::from_string(const string& from)
|
||||
{
|
||||
try {
|
||||
string s = boost::algorithm::trim_copy(from);
|
||||
EOS_ASSERT(!s.empty(), symbol_type_exception, "creating symbol from empty string");
|
||||
auto comma_pos = s.find(',');
|
||||
EOS_ASSERT(comma_pos != string::npos, symbol_type_exception, "missing comma in symbol");
|
||||
auto prec_part = s.substr(0, comma_pos);
|
||||
uint8_t p = fc::to_int64(prec_part);
|
||||
string name_part = s.substr(comma_pos + 1);
|
||||
EOS_ASSERT( p <= max_precision, symbol_type_exception, "precision ${p} should be <= 18", ("p", p));
|
||||
return symbol(string_to_symbol(p, name_part.c_str()));
|
||||
} FC_CAPTURE_LOG_AND_RETHROW((from));
|
||||
}
|
||||
|
||||
} // namespace eosio::chain
|
||||
@@ -46,11 +46,13 @@ namespace eosio { namespace chain {
|
||||
|
||||
transaction_context::transaction_context( controller& c,
|
||||
const packed_transaction& t,
|
||||
const transaction_id_type& trx_id,
|
||||
transaction_checktime_timer&& tmr,
|
||||
fc::time_point s,
|
||||
transaction_metadata::trx_type type)
|
||||
:control(c)
|
||||
,packed_trx(t)
|
||||
,id(trx_id)
|
||||
,undo_session()
|
||||
,trace(std::make_shared<transaction_trace>())
|
||||
,start(s)
|
||||
@@ -62,7 +64,7 @@ namespace eosio { namespace chain {
|
||||
if (!c.skip_db_sessions() && !is_read_only()) {
|
||||
undo_session.emplace(c.mutable_db().start_undo_session(true));
|
||||
}
|
||||
trace->id = packed_trx.id();
|
||||
trace->id = id;
|
||||
trace->block_num = c.head_block_num() + 1;
|
||||
trace->block_time = c.pending_block_time();
|
||||
trace->producer_block_id = c.pending_producer_block_id();
|
||||
@@ -295,7 +297,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
init( initial_net_usage );
|
||||
if ( !is_read_only() ) {
|
||||
record_transaction( packed_trx.id(), trx.expiration );
|
||||
record_transaction( id, trx.expiration );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,7 +471,7 @@ namespace eosio { namespace chain {
|
||||
"not enough time left in block to complete executing transaction ${billing_timer}us",
|
||||
("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) );
|
||||
} else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) {
|
||||
std::string assert_msg = "transaction was executing for too long ${billing_timer}us";
|
||||
std::string assert_msg = "transaction ${id} was executing for too long ${billing_timer}us";
|
||||
if (subjective_cpu_bill_us > 0) {
|
||||
assert_msg += " with a subjective cpu of (${subjective} us)";
|
||||
}
|
||||
@@ -477,10 +479,10 @@ namespace eosio { namespace chain {
|
||||
assert_msg += get_tx_cpu_usage_exceeded_reason_msg(limit);
|
||||
if (cpu_limit_due_to_greylist) {
|
||||
assert_msg = "greylisted " + assert_msg;
|
||||
EOS_THROW( greylist_cpu_usage_exceeded, assert_msg,
|
||||
EOS_THROW( greylist_cpu_usage_exceeded, assert_msg, ("id", packed_trx.id())
|
||||
("billing_timer", now - pseudo_start)("subjective", subjective_cpu_bill_us)("limit", limit) );
|
||||
} else {
|
||||
EOS_THROW( tx_cpu_usage_exceeded, assert_msg,
|
||||
EOS_THROW( tx_cpu_usage_exceeded, assert_msg, ("id", packed_trx.id())
|
||||
("billing_timer", now - pseudo_start)("subjective", subjective_cpu_bill_us)("limit", limit) );
|
||||
}
|
||||
} else if( deadline_exception_code == leeway_deadline_exception::code_value ) {
|
||||
@@ -753,7 +755,7 @@ namespace eosio { namespace chain {
|
||||
|
||||
uint32_t trx_size = 0;
|
||||
const auto& cgto = control.mutable_db().create<generated_transaction_object>( [&]( auto& gto ) {
|
||||
gto.trx_id = packed_trx.id();
|
||||
gto.trx_id = id;
|
||||
gto.payer = first_auth;
|
||||
gto.sender = account_name(); /// delayed transactions have no sender
|
||||
gto.sender_id = transaction_id_to_sender_id( gto.trx_id );
|
||||
|
||||
@@ -32,16 +32,14 @@
|
||||
|
||||
namespace eosio { namespace chain {
|
||||
|
||||
wasm_interface::wasm_interface(vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile)
|
||||
: my( new wasm_interface_impl(vm, eosvmoc_tierup, d, data_dir, eosvmoc_config, profile) ), vm( vm ) {}
|
||||
wasm_interface::wasm_interface(vm_type vm, const chainbase::database& d, const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile)
|
||||
: my( new wasm_interface_impl(vm, d, data_dir, eosvmoc_config, profile) ) {}
|
||||
|
||||
wasm_interface::~wasm_interface() {}
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
void wasm_interface::init_thread_local_data() {
|
||||
if (my->eosvmoc)
|
||||
my->eosvmoc->init_thread_local_data();
|
||||
else if (vm == wasm_interface::vm_type::eos_vm_oc && my->runtime_interface)
|
||||
if (my->wasm_runtime_time == wasm_interface::vm_type::eos_vm_oc && my->runtime_interface)
|
||||
my->runtime_interface->init_thread_local_data();
|
||||
}
|
||||
#endif
|
||||
@@ -72,51 +70,17 @@ namespace eosio { namespace chain {
|
||||
//there are a couple opportunties for improvement here--
|
||||
//Easy: Cache the Module created here so it can be reused for instantiaion
|
||||
//Hard: Kick off instantiation in a separate thread at this location
|
||||
}
|
||||
|
||||
void wasm_interface::indicate_shutting_down() {
|
||||
my->is_shutting_down = true;
|
||||
}
|
||||
|
||||
void wasm_interface::code_block_num_last_used(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version, const uint32_t& block_num) {
|
||||
my->code_block_num_last_used(code_hash, vm_type, vm_version, block_num);
|
||||
}
|
||||
|
||||
void wasm_interface::current_lib(const uint32_t lib) {
|
||||
my->current_lib(lib);
|
||||
void wasm_interface::current_lib(const uint32_t lib, const std::function<void(const digest_type&, uint8_t)>& callback) {
|
||||
my->current_lib(lib, callback);
|
||||
}
|
||||
|
||||
void wasm_interface::apply( const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version, apply_context& context ) {
|
||||
if(substitute_apply && substitute_apply(code_hash, vm_type, vm_version, context))
|
||||
return;
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if(my->eosvmoc) {
|
||||
const chain::eosvmoc::code_descriptor* cd = nullptr;
|
||||
chain::eosvmoc::code_cache_base::get_cd_failure failure = chain::eosvmoc::code_cache_base::get_cd_failure::temporary;
|
||||
try {
|
||||
cd = my->eosvmoc->cc.get_descriptor_for_code(code_hash, vm_version, context.control.is_write_window(), failure);
|
||||
}
|
||||
catch(...) {
|
||||
//swallow errors here, if EOS VM OC has gone in to the weeds we shouldn't bail: continue to try and run baseline
|
||||
//In the future, consider moving bits of EOS VM that can fire exceptions and such out of this call path
|
||||
static bool once_is_enough;
|
||||
if(!once_is_enough)
|
||||
elog("EOS VM OC has encountered an unexpected failure");
|
||||
once_is_enough = true;
|
||||
}
|
||||
if(cd) {
|
||||
my->eosvmoc->exec->execute(*cd, my->eosvmoc->mem, context);
|
||||
return;
|
||||
}
|
||||
else if (context.trx_context.is_read_only()) {
|
||||
if (failure == chain::eosvmoc::code_cache_base::get_cd_failure::temporary) {
|
||||
EOS_ASSERT(false, ro_trx_vm_oc_compile_temporary_failure, "get_descriptor_for_code failed with temporary failure");
|
||||
} else {
|
||||
EOS_ASSERT(false, ro_trx_vm_oc_compile_permanent_failure, "get_descriptor_for_code failed with permanent failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
my->get_instantiated_module(code_hash, vm_type, vm_version, context.trx_context)->apply(context);
|
||||
}
|
||||
|
||||
@@ -124,13 +88,8 @@ namespace eosio { namespace chain {
|
||||
return my->is_code_cached(code_hash, vm_type, vm_version);
|
||||
}
|
||||
|
||||
wasm_instantiated_module_interface::~wasm_instantiated_module_interface() {}
|
||||
wasm_runtime_interface::~wasm_runtime_interface() {}
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
thread_local std::unique_ptr<eosvmoc::executor> wasm_interface_impl::eosvmoc_tier::exec {};
|
||||
thread_local eosvmoc::memory wasm_interface_impl::eosvmoc_tier::mem{ wasm_constraints::maximum_linear_memory/wasm_constraints::wasm_page_size };
|
||||
#endif
|
||||
wasm_instantiated_module_interface::~wasm_instantiated_module_interface() = default;
|
||||
wasm_runtime_interface::~wasm_runtime_interface() = default;
|
||||
|
||||
std::istream& operator>>(std::istream& in, wasm_interface::vm_type& runtime) {
|
||||
std::string s;
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include <eosio/chain/wasm_interface_collection.hpp>
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
#include <eosio/chain/webassembly/eos-vm-oc.hpp>
|
||||
#else
|
||||
#define _REGISTER_EOSVMOC_INTRINSIC(CLS, MOD, METHOD, WASM_SIG, NAME, SIG)
|
||||
#endif
|
||||
|
||||
namespace eosio::chain {
|
||||
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
struct eosvmoc_tier {
|
||||
eosvmoc_tier(const std::filesystem::path& d, const eosvmoc::config& c, const chainbase::database& db)
|
||||
: cc(d, c, db) {
|
||||
// construct exec for the main thread
|
||||
init_thread_local_data();
|
||||
}
|
||||
|
||||
// Support multi-threaded execution.
|
||||
void init_thread_local_data() {
|
||||
exec = std::make_unique<eosvmoc::executor>(cc);
|
||||
}
|
||||
|
||||
eosvmoc::code_cache_async cc;
|
||||
|
||||
// Each thread requires its own exec and mem.
|
||||
thread_local static std::unique_ptr<eosvmoc::executor> exec;
|
||||
thread_local static eosvmoc::memory mem;
|
||||
};
|
||||
|
||||
thread_local std::unique_ptr<eosvmoc::executor> eosvmoc_tier::exec{};
|
||||
thread_local eosvmoc::memory eosvmoc_tier::mem{wasm_constraints::maximum_linear_memory / wasm_constraints::wasm_page_size};
|
||||
#endif
|
||||
|
||||
wasm_interface_collection::wasm_interface_collection(wasm_interface::vm_type vm, wasm_interface::vm_oc_enable eosvmoc_tierup,
|
||||
const chainbase::database& d, const std::filesystem::path& data_dir,
|
||||
const eosvmoc::config& eosvmoc_config, bool profile)
|
||||
: main_thread_id(std::this_thread::get_id())
|
||||
, wasm_runtime(vm)
|
||||
, eosvmoc_tierup(eosvmoc_tierup)
|
||||
, wasmif(vm, d, data_dir, eosvmoc_config, profile) {
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if (eosvmoc_tierup != wasm_interface::vm_oc_enable::oc_none) {
|
||||
EOS_ASSERT(vm != wasm_interface::vm_type::eos_vm_oc, wasm_exception, "You can't use EOS VM OC as the base runtime when tier up is activated");
|
||||
eosvmoc = std::make_unique<eosvmoc_tier>(data_dir, eosvmoc_config, d);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
wasm_interface_collection::~wasm_interface_collection() = default;
|
||||
|
||||
void wasm_interface_collection::apply(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version, apply_context& context) {
|
||||
if (substitute_apply && substitute_apply(code_hash, vm_type, vm_version, context))
|
||||
return;
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if (eosvmoc && (eosvmoc_tierup == wasm_interface::vm_oc_enable::oc_all || context.should_use_eos_vm_oc())) {
|
||||
const chain::eosvmoc::code_descriptor* cd = nullptr;
|
||||
chain::eosvmoc::code_cache_base::get_cd_failure failure = chain::eosvmoc::code_cache_base::get_cd_failure::temporary;
|
||||
try {
|
||||
const bool high_priority = context.get_receiver().prefix() == chain::config::system_account_name;
|
||||
cd = eosvmoc->cc.get_descriptor_for_code(high_priority, code_hash, vm_version, context.control.is_write_window(), failure);
|
||||
if (test_disable_tierup)
|
||||
cd = nullptr;
|
||||
} catch (...) {
|
||||
// swallow errors here, if EOS VM OC has gone in to the weeds we shouldn't bail: continue to try and run baseline
|
||||
// In the future, consider moving bits of EOS VM that can fire exceptions and such out of this call path
|
||||
static bool once_is_enough;
|
||||
if (!once_is_enough)
|
||||
elog("EOS VM OC has encountered an unexpected failure");
|
||||
once_is_enough = true;
|
||||
}
|
||||
if (cd) {
|
||||
if (!context.is_applying_block()) // read_only_trx_test.py looks for this log statement
|
||||
tlog("${a} speculatively executing ${h} with eos vm oc", ("a", context.get_receiver())("h", code_hash));
|
||||
eosvmoc->exec->execute(*cd, eosvmoc->mem, context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (is_on_main_thread()) {
|
||||
wasmif.apply(code_hash, vm_type, vm_version, context);
|
||||
return;
|
||||
}
|
||||
threaded_wasmifs[std::this_thread::get_id()]->apply(code_hash, vm_type, vm_version, context);
|
||||
}
|
||||
|
||||
// update current lib of all wasm interfaces
|
||||
void wasm_interface_collection::current_lib(const uint32_t lib) {
|
||||
// producer_plugin has already asserted irreversible_block signal is called in write window
|
||||
std::function<void(const digest_type& code_hash, uint8_t vm_version)> cb{};
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if (eosvmoc) {
|
||||
cb = [&](const digest_type& code_hash, uint8_t vm_version) {
|
||||
eosvmoc->cc.free_code(code_hash, vm_version);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
wasmif.current_lib(lib, cb);
|
||||
for (auto& w : threaded_wasmifs) {
|
||||
w.second->current_lib(lib, cb);
|
||||
}
|
||||
}
|
||||
|
||||
// only called from non-main threads (read-only trx execution threads) when producer_plugin starts them
|
||||
void wasm_interface_collection::init_thread_local_data(const chainbase::database& d, const std::filesystem::path& data_dir,
|
||||
const eosvmoc::config& eosvmoc_config, bool profile) {
|
||||
EOS_ASSERT(!is_on_main_thread(), misc_exception, "init_thread_local_data called on the main thread");
|
||||
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED
|
||||
if (is_eos_vm_oc_enabled()) {
|
||||
// EOSVMOC needs further initialization of its thread local data
|
||||
if (eosvmoc)
|
||||
eosvmoc->init_thread_local_data();
|
||||
wasmif.init_thread_local_data();
|
||||
}
|
||||
#endif
|
||||
|
||||
std::lock_guard g(threaded_wasmifs_mtx);
|
||||
// Non-EOSVMOC needs a wasmif per thread
|
||||
threaded_wasmifs[std::this_thread::get_id()] = std::make_unique<wasm_interface>(wasm_runtime, d, data_dir, eosvmoc_config, profile);
|
||||
}
|
||||
|
||||
void wasm_interface_collection::code_block_num_last_used(const digest_type& code_hash, uint8_t vm_type, uint8_t vm_version, uint32_t block_num) {
|
||||
// The caller of this function apply_eosio_setcode has already asserted that
|
||||
// the transaction is not a read-only trx, which implies we are
|
||||
// in write window. Safe to call threaded_wasmifs's code_block_num_last_used
|
||||
wasmif.code_block_num_last_used(code_hash, vm_type, vm_version, block_num);
|
||||
for (auto& w : threaded_wasmifs) {
|
||||
w.second->code_block_num_last_used(code_hash, vm_type, vm_version, block_num);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eosio::chain
|
||||
@@ -38,7 +38,7 @@ static constexpr size_t descriptor_ptr_from_file_start = header_offset + offseto
|
||||
|
||||
static_assert(sizeof(code_cache_header) <= header_size, "code_cache_header too big");
|
||||
|
||||
code_cache_async::code_cache_async(const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db) :
|
||||
code_cache_async::code_cache_async(const std::filesystem::path& data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db) :
|
||||
code_cache_base(data_dir, eosvmoc_config, db),
|
||||
_result_queue(eosvmoc_config.threads * 2),
|
||||
_threads(eosvmoc_config.threads)
|
||||
@@ -48,7 +48,7 @@ code_cache_async::code_cache_async(const std::filesystem::path data_dir, const e
|
||||
wait_on_compile_monitor_message();
|
||||
|
||||
_monitor_reply_thread = std::thread([this]() {
|
||||
fc::set_os_thread_name("oc-monitor");
|
||||
fc::set_thread_name("oc-monitor");
|
||||
_ctx.run();
|
||||
});
|
||||
}
|
||||
@@ -106,7 +106,7 @@ std::tuple<size_t, size_t> code_cache_async::consume_compile_thread_queue() {
|
||||
}
|
||||
|
||||
|
||||
const code_descriptor* const code_cache_async::get_descriptor_for_code(const digest_type& code_id, const uint8_t& vm_version, bool is_write_window, get_cd_failure& failure) {
|
||||
const code_descriptor* const code_cache_async::get_descriptor_for_code(bool high_priority, const digest_type& code_id, const uint8_t& vm_version, bool is_write_window, get_cd_failure& failure) {
|
||||
//if there are any outstanding compiles, process the result queue now
|
||||
//When app is in write window, all tasks are running sequentially and read-only threads
|
||||
//are not running. Safe to update cache entries.
|
||||
@@ -156,13 +156,16 @@ const code_descriptor* const code_cache_async::get_descriptor_for_code(const dig
|
||||
it->second = false;
|
||||
return nullptr;
|
||||
}
|
||||
if(_queued_compiles.find(ct) != _queued_compiles.end()) {
|
||||
if(auto it = _queued_compiles.get<by_hash>().find(boost::make_tuple(std::ref(code_id), vm_version)); it != _queued_compiles.get<by_hash>().end()) {
|
||||
failure = get_cd_failure::temporary; // Compile might not be done yet
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(_outstanding_compiles_and_poison.size() >= _threads) {
|
||||
_queued_compiles.emplace(ct);
|
||||
if (high_priority)
|
||||
_queued_compiles.push_front(ct);
|
||||
else
|
||||
_queued_compiles.push_back(ct);
|
||||
failure = get_cd_failure::temporary; // Compile might not be done yet
|
||||
return nullptr;
|
||||
}
|
||||
@@ -221,7 +224,7 @@ const code_descriptor* const code_cache_sync::get_descriptor_for_code_sync(const
|
||||
return &*_cache_index.push_front(std::move(std::get<code_descriptor>(result.result))).first;
|
||||
}
|
||||
|
||||
code_cache_base::code_cache_base(const std::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db) :
|
||||
code_cache_base::code_cache_base(const std::filesystem::path& data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db) :
|
||||
_db(db),
|
||||
_cache_file_path(data_dir/"code_cache.bin")
|
||||
{
|
||||
@@ -377,7 +380,8 @@ void code_cache_base::free_code(const digest_type& code_id, const uint8_t& vm_ve
|
||||
}
|
||||
|
||||
//if it's in the queued list, erase it
|
||||
_queued_compiles.erase({code_id, vm_version});
|
||||
if(auto i = _queued_compiles.get<by_hash>().find(boost::make_tuple(std::ref(code_id), vm_version)); i != _queued_compiles.get<by_hash>().end())
|
||||
_queued_compiles.get<by_hash>().erase(i);
|
||||
|
||||
//however, if it's currently being compiled there is no way to cancel the compile,
|
||||
//so instead set a poison boolean that indicates not to insert the code in to the cache
|
||||
|
||||
@@ -201,10 +201,6 @@ class eos_vm_profiling_module : public wasm_instantiated_module_interface {
|
||||
}
|
||||
}
|
||||
|
||||
void fast_shutdown() override {
|
||||
_prof.clear();
|
||||
}
|
||||
|
||||
profile_data* start(apply_context& context) {
|
||||
name account = context.get_receiver();
|
||||
if(!context.control.is_profiling(account)) return nullptr;
|
||||
@@ -242,7 +238,7 @@ std::unique_ptr<wasm_instantiated_module_interface> eos_vm_runtime<Impl>::instan
|
||||
wasm_code_ptr code((uint8_t*)code_bytes, code_size);
|
||||
apply_options options = { .max_pages = 65536,
|
||||
.max_call_depth = 0 };
|
||||
std::unique_ptr<backend_t> bkend = std::make_unique<backend_t>(code, code_size, nullptr, options);
|
||||
std::unique_ptr<backend_t> bkend = std::make_unique<backend_t>(code, code_size, nullptr, options, false); // uses 2-passes parsing
|
||||
eos_vm_host_functions_t::resolve(bkend->get_module());
|
||||
return std::make_unique<eos_vm_instantiated_module<Impl>>(this, std::move(bkend));
|
||||
} catch(eosio::vm::exception& e) {
|
||||
@@ -264,7 +260,7 @@ std::unique_ptr<wasm_instantiated_module_interface> eos_vm_profile_runtime::inst
|
||||
wasm_code_ptr code((uint8_t*)code_bytes, code_size);
|
||||
apply_options options = { .max_pages = 65536,
|
||||
.max_call_depth = 0 };
|
||||
std::unique_ptr<backend_t> bkend = std::make_unique<backend_t>(code, code_size, nullptr, options);
|
||||
std::unique_ptr<backend_t> bkend = std::make_unique<backend_t>(code, code_size, nullptr, options, false); // uses 2-passes parsing
|
||||
eos_vm_host_functions_t::resolve(bkend->get_module());
|
||||
return std::make_unique<eos_vm_profiling_module>(std::move(bkend), code_bytes, code_size);
|
||||
} catch(eosio::vm::exception& e) {
|
||||
|
||||
+1
-1
Submodule libraries/chainbase updated: 0cc3c62aa6...bffb7ebde6
+1
-1
Submodule libraries/eos-vm updated: 329db27d88...1e9345f96a
@@ -65,19 +65,6 @@ file( GLOB_RECURSE fc_headers ${CMAKE_CURRENT_SOURCE_DIR} *.hpp *.h )
|
||||
|
||||
add_library(fc ${fc_sources} ${fc_headers})
|
||||
|
||||
function(detect_thread_name)
|
||||
include(CheckSymbolExists)
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES "-pthread")
|
||||
check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP)
|
||||
if(HAVE_PTHREAD_SETNAME_NP)
|
||||
set_source_files_properties(src/log/logger_config.cpp PROPERTIES COMPILE_DEFINITIONS FC_USE_PTHREAD_NAME_NP)
|
||||
endif()
|
||||
endfunction()
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
detect_thread_name()
|
||||
endif()
|
||||
|
||||
# Yuck: newer CMake files from boost iostreams will effectively target_link_libraries(Boost::iostreams z;bz2;lzma;zstd)
|
||||
# without first "finding" those libraries. This resolves to simple -lz -lbz2 etc: it'll look for those libraries in the linker's
|
||||
# library search path. This is most problematic on macOS where something like libzstd isn't in the standard search path. Historically
|
||||
@@ -91,12 +78,6 @@ if(APPLE)
|
||||
add_library(zstd INTERFACE)
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.66 REQUIRED COMPONENTS
|
||||
date_time
|
||||
chrono
|
||||
unit_test_framework
|
||||
iostreams)
|
||||
|
||||
find_path(GMP_INCLUDE_DIR NAMES gmp.h)
|
||||
find_library(GMP_LIBRARY gmp)
|
||||
if(NOT GMP_LIBRARY MATCHES ${CMAKE_SHARED_LIBRARY_SUFFIX})
|
||||
@@ -130,7 +111,8 @@ if(APPLE)
|
||||
find_library(security_framework Security)
|
||||
find_library(corefoundation_framework CoreFoundation)
|
||||
endif()
|
||||
target_link_libraries( fc PUBLIC Boost::date_time Boost::chrono Boost::iostreams Threads::Threads
|
||||
target_link_libraries( fc PUBLIC Boost::date_time Boost::chrono Boost::iostreams Boost::interprocess Boost::multi_index Boost::dll
|
||||
Boost::multiprecision Boost::beast Boost::asio Boost::thread Boost::unit_test_framework Threads::Threads
|
||||
OpenSSL::Crypto ZLIB::ZLIB ${PLATFORM_SPECIFIC_LIBS} ${CMAKE_DL_LIBS} secp256k1 ${security_framework} ${corefoundation_framework})
|
||||
|
||||
# Critically, this ensures that OpenSSL 1.1 & 3.0 both have a variant of BN_zero() with void return value. But it also allows access
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace fc {
|
||||
{
|
||||
static const char* name()
|
||||
{
|
||||
static std::string _name = std::string("fc::array<")+std::string(fc::get_typename<T>::name())+","+ fc::to_string(N) + ">";
|
||||
static std::string _name = std::string("fc::array<")+std::string(fc::get_typename<T>::name())+","+ std::to_string(N) + ">";
|
||||
return _name.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -606,7 +606,7 @@ namespace fc { namespace json_relaxed
|
||||
in.get();
|
||||
return obj;
|
||||
}
|
||||
FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) );
|
||||
FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", std::move(obj) ) );
|
||||
}
|
||||
catch( const fc::eof_exception& e )
|
||||
{
|
||||
|
||||
@@ -73,7 +73,6 @@ namespace fc {
|
||||
void configure_logging( const std::filesystem::path& log_config );
|
||||
bool configure_logging( const logging_config& l );
|
||||
|
||||
void set_os_thread_name( const std::string& name );
|
||||
void set_thread_name( const std::string& name );
|
||||
const std::string& get_thread_name();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
#pragma once
|
||||
|
||||
// Enable thread safety attributes only with clang.
|
||||
// The attributes can be safely erased when compiling with other compilers.
|
||||
#if defined(__clang__) && (!defined(SWIG))
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
|
||||
#else
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
|
||||
#endif
|
||||
|
||||
#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
|
||||
|
||||
#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
|
||||
|
||||
#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
|
||||
|
||||
#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
|
||||
|
||||
#define ACQUIRED_BEFORE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
|
||||
|
||||
#define ACQUIRED_AFTER(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
|
||||
|
||||
#define REQUIRES(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
|
||||
|
||||
#define REQUIRES_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define ACQUIRE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
|
||||
|
||||
#define ACQUIRE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define RELEASE(...) THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
|
||||
|
||||
#define RELEASE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define RELEASE_GENERIC(...) THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__))
|
||||
|
||||
#define TRY_ACQUIRE(...) THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
|
||||
|
||||
#define TRY_ACQUIRE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
|
||||
|
||||
#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
|
||||
|
||||
#define ASSERT_SHARED_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
|
||||
|
||||
#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
|
||||
|
||||
#define NO_THREAD_SAFETY_ANALYSIS THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
|
||||
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace fc {
|
||||
|
||||
// Defines an annotated interface for mutexes.
|
||||
// These methods can be implemented to use any internal mutex implementation.
|
||||
class CAPABILITY("mutex") mutex {
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
|
||||
public:
|
||||
// Acquire/lock this mutex exclusively. Only one thread can have exclusive
|
||||
// access at any one time. Write operations to guarded data require an
|
||||
// exclusive lock.
|
||||
void lock() ACQUIRE() { mutex_.lock(); }
|
||||
|
||||
// Release/unlock an exclusive mutex.
|
||||
void unlock() RELEASE() { mutex_.unlock(); }
|
||||
|
||||
// Try to acquire the mutex. Returns true on success, and false on failure.
|
||||
bool try_lock() TRY_ACQUIRE(true) { return mutex_.try_lock(); }
|
||||
};
|
||||
|
||||
// Defines an annotated interface for mutexes.
|
||||
// These methods can be implemented to use any internal mutex implementation.
|
||||
class CAPABILITY("shared_mutex") shared_mutex {
|
||||
private:
|
||||
std::shared_mutex mutex_;
|
||||
|
||||
public:
|
||||
// Acquire/lock this mutex exclusively. Only one thread can have exclusive
|
||||
// access at any one time. Write operations to guarded data require an
|
||||
// exclusive lock.
|
||||
void lock() ACQUIRE() { mutex_.lock(); }
|
||||
|
||||
// Acquire/lock this mutex for read operations, which require only a shared
|
||||
// lock. This assumes a multiple-reader, single writer semantics. Multiple
|
||||
// threads may acquire the mutex simultaneously as readers, but a writer
|
||||
// must wait for all of them to release the mutex before it can acquire it
|
||||
// exclusively.
|
||||
void lock_shared() ACQUIRE_SHARED() { mutex_.lock_shared(); }
|
||||
|
||||
// Release/unlock an exclusive mutex.
|
||||
void unlock() RELEASE() { mutex_.unlock(); }
|
||||
|
||||
// Release/unlock a shared mutex.
|
||||
void unlock_shared() RELEASE_SHARED() { mutex_.unlock_shared(); }
|
||||
|
||||
// Try to acquire the mutex. Returns true on success, and false on failure.
|
||||
bool try_lock() TRY_ACQUIRE(true) { return mutex_.try_lock(); }
|
||||
|
||||
// Try to acquire the mutex for read operations.
|
||||
bool try_lock_shared() TRY_ACQUIRE_SHARED(true) { return mutex_.try_lock_shared(); }
|
||||
|
||||
// Assert that this mutex is currently held by the calling thread.
|
||||
// void AssertHeld() ASSERT_CAPABILITY(this);
|
||||
|
||||
// Assert that is mutex is currently held for read operations.
|
||||
// void AssertReaderHeld() ASSERT_SHARED_CAPABILITY(this);
|
||||
|
||||
// For negative capabilities.
|
||||
// const Mutex& operator!() const { return *this; }
|
||||
};
|
||||
|
||||
// Tag types for selecting a constructor.
|
||||
struct adopt_lock_t {} inline constexpr adopt_lock = {};
|
||||
struct defer_lock_t {} inline constexpr defer_lock = {};
|
||||
struct shared_lock_t {} inline constexpr shared_lock = {};
|
||||
|
||||
// LockGuard is an RAII class that acquires a mutex in its constructor, and
|
||||
// releases it in its destructor.
|
||||
template <typename M>
|
||||
class SCOPED_CAPABILITY lock_guard {
|
||||
private:
|
||||
M& mut;
|
||||
|
||||
public:
|
||||
// Acquire mu, implicitly acquire *this and associate it with mu.
|
||||
lock_guard(M& mu) ACQUIRE(mu)
|
||||
: mut(mu) {
|
||||
mu.lock();
|
||||
}
|
||||
|
||||
// Assume mu is held, implicitly acquire *this and associate it with mu.
|
||||
lock_guard(M& mu, adopt_lock_t) REQUIRES(mu)
|
||||
: mut(mu) {}
|
||||
|
||||
~lock_guard() RELEASE() { mut.unlock(); }
|
||||
};
|
||||
|
||||
// unique_lock is an RAII class that acquires a mutex in its constructor, and
|
||||
// releases it in its destructor.
|
||||
template <typename M>
|
||||
class SCOPED_CAPABILITY unique_lock {
|
||||
private:
|
||||
using mutex_type = M;
|
||||
|
||||
M* mut;
|
||||
bool locked;
|
||||
|
||||
public:
|
||||
unique_lock() noexcept
|
||||
: mut(nullptr)
|
||||
, locked(false) {}
|
||||
|
||||
// Acquire mu, implicitly acquire *this and associate it with mu.
|
||||
explicit unique_lock(M& mu) ACQUIRE(mu)
|
||||
: mut(&mu)
|
||||
, locked(true) {
|
||||
mut->lock();
|
||||
}
|
||||
|
||||
// Assume mu is held, implicitly acquire *this and associate it with mu.
|
||||
unique_lock(M& mu, adopt_lock_t) REQUIRES(mu)
|
||||
: mut(&mu)
|
||||
, locked(true) {}
|
||||
|
||||
// Assume mu is not held, implicitly acquire *this and associate it with mu.
|
||||
unique_lock(M& mu, defer_lock_t) EXCLUDES(mu)
|
||||
: mut(mu)
|
||||
, locked(false) {}
|
||||
|
||||
// Release *this and all associated mutexes, if they are still held.
|
||||
// There is no warning if the scope was already unlocked before.
|
||||
~unique_lock() RELEASE() {
|
||||
if (locked)
|
||||
mut->unlock();
|
||||
}
|
||||
|
||||
// Acquire all associated mutexes exclusively.
|
||||
void lock() ACQUIRE() {
|
||||
mut->lock();
|
||||
locked = true;
|
||||
}
|
||||
|
||||
// Try to acquire all associated mutexes exclusively.
|
||||
bool try_lock() TRY_ACQUIRE(true) { return locked = mut->try_lock(); }
|
||||
|
||||
// Release all associated mutexes. Warn on double unlock.
|
||||
void unlock() RELEASE() {
|
||||
mut->unlock();
|
||||
locked = false;
|
||||
}
|
||||
|
||||
mutex_type* release() noexcept RETURN_CAPABILITY(this) {
|
||||
mutex_type* res = mut;
|
||||
mut = nullptr;
|
||||
locked = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
mutex_type* mutex() const noexcept { return mut; }
|
||||
|
||||
bool owns_lock() const noexcept { return locked; }
|
||||
|
||||
explicit operator bool() const noexcept { return locked; }
|
||||
};
|
||||
|
||||
} // namespace fc
|
||||
@@ -0,0 +1,265 @@
|
||||
#pragma once
|
||||
|
||||
#include <fc/log/logger.hpp>
|
||||
#include <fc/scoped_exit.hpp>
|
||||
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ip/v6_only.hpp>
|
||||
#include <boost/asio/local/stream_protocol.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fc {
|
||||
|
||||
inline std::string to_string(const boost::asio::ip::tcp::endpoint& endpoint) {
|
||||
const auto& ip_addr = endpoint.address();
|
||||
std::string ip_addr_string = ip_addr.to_string();
|
||||
if (ip_addr.is_v6()) {
|
||||
ip_addr_string = "[" + ip_addr_string + "]";
|
||||
}
|
||||
return ip_addr_string + ":" + std::to_string(endpoint.port());
|
||||
}
|
||||
|
||||
inline std::pair<std::string, std::string> split_host_port(std::string_view endpoint) {
|
||||
std::string::size_type colon_pos = endpoint.rfind(':');
|
||||
if (colon_pos != std::string::npos) {
|
||||
auto port = endpoint.substr(colon_pos + 1);
|
||||
auto hostname =
|
||||
(endpoint[0] == '[' && colon_pos >= 2) ? endpoint.substr(1, colon_pos - 2) : endpoint.substr(0, colon_pos);
|
||||
return { std::string(hostname), std::string(port) };
|
||||
} else {
|
||||
return { std::string(endpoint), {} };
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Protocol>
|
||||
struct listener_base;
|
||||
|
||||
template <>
|
||||
struct listener_base<boost::asio::ip::tcp> {
|
||||
listener_base(const std::string&) {}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct listener_base<boost::asio::local::stream_protocol> {
|
||||
std::filesystem::path path_;
|
||||
listener_base(const std::string& local_address) : path_(std::filesystem::absolute(local_address)) {}
|
||||
~listener_base() {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(path_, ec);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// @brief fc::listener is template class to simplify the code for accepting new socket connections.
|
||||
/// It can be used for both tcp or Unix socket connection.
|
||||
///
|
||||
/// @note Users should use fc::create_listener() instead, this class is the implementation
|
||||
/// detail for fc::create_listener().
|
||||
///
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template <typename Protocol, typename CreateSession>
|
||||
struct listener : listener_base<Protocol>, std::enable_shared_from_this<listener<Protocol, CreateSession>> {
|
||||
private:
|
||||
typename Protocol::acceptor acceptor_;
|
||||
boost::asio::deadline_timer accept_error_timer_;
|
||||
boost::posix_time::time_duration accept_timeout_;
|
||||
logger& logger_;
|
||||
std::string extra_listening_log_info_;
|
||||
CreateSession create_session_;
|
||||
|
||||
public:
|
||||
using endpoint_type = typename Protocol::endpoint;
|
||||
listener(boost::asio::io_context& executor, logger& logger, boost::posix_time::time_duration accept_timeout,
|
||||
const std::string& local_address, const endpoint_type& endpoint,
|
||||
const std::string& extra_listening_log_info, const CreateSession& create_session)
|
||||
: listener_base<Protocol>(local_address), acceptor_(executor, endpoint), accept_error_timer_(executor),
|
||||
accept_timeout_(accept_timeout), logger_(logger), extra_listening_log_info_(extra_listening_log_info),
|
||||
create_session_(create_session) {}
|
||||
|
||||
const auto& acceptor() const { return acceptor_; }
|
||||
|
||||
void do_accept() {
|
||||
acceptor_.async_accept([self = this->shared_from_this()](boost::system::error_code ec, auto&& peer_socket) {
|
||||
self->on_accept(ec, std::forward<decltype(peer_socket)>(peer_socket));
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
void on_accept(boost::system::error_code ec, Socket&& socket) {
|
||||
if (!ec) {
|
||||
create_session_(std::forward<Socket>(socket));
|
||||
do_accept();
|
||||
} else if (ec == boost::system::errc::too_many_files_open) {
|
||||
// retry accept() after timeout to avoid cpu loop on accept
|
||||
fc_elog(logger_, "open file limit reached: not accepting new connections for next ${timeout}ms",
|
||||
("timeout", accept_timeout_.total_milliseconds()));
|
||||
accept_error_timer_.expires_from_now(accept_timeout_);
|
||||
accept_error_timer_.async_wait([self = this->shared_from_this()](boost::system::error_code ec) {
|
||||
if (!ec)
|
||||
self->do_accept();
|
||||
});
|
||||
} else if (int code = ec.value(); code == ENETDOWN || code == EPROTO || code == ENOPROTOOPT ||
|
||||
code == EHOSTDOWN || code == EHOSTUNREACH || code == EOPNOTSUPP ||
|
||||
code == ENETUNREACH
|
||||
#ifdef ENONET
|
||||
|| code == ENONET
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
//guard against failure of asio's internal SO_NOSIGPIPE call after accept()
|
||||
|| code == EINVAL
|
||||
#endif
|
||||
) {
|
||||
// according to https://man7.org/linux/man-pages/man2/accept.2.html, reliable application should
|
||||
// retry when these error codes are returned
|
||||
fc_wlog(logger_, "closing connection, accept error: ${m}", ("m", ec.message()));
|
||||
do_accept();
|
||||
} else {
|
||||
fc_elog(logger_, "Unrecoverable accept error, stop listening: ${m}", ("m", ec.message()));
|
||||
}
|
||||
}
|
||||
|
||||
void log_listening(const endpoint_type& endpoint, const std::string& local_address) {
|
||||
std::string info;
|
||||
if constexpr (std::is_same_v<Protocol, boost::asio::ip::tcp>) {
|
||||
info = fc::to_string(endpoint) + " resolved from " + local_address;
|
||||
} else {
|
||||
info = "Unix socket " + local_address;
|
||||
}
|
||||
info += extra_listening_log_info_;
|
||||
fc_ilog(logger_, "start listening on ${info}", ("info", info));
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief create a stream-oriented socket listener which listens on the specified \c address and calls \c
|
||||
/// create_session whenever a socket is accepted.
|
||||
///
|
||||
/// @details
|
||||
/// This function is used for listening on TCP or Unix socket address and creating corresponding session when the
|
||||
/// socket is accepted.
|
||||
///
|
||||
/// For TCP socket, the address format can be <hostname>:port or <ipaddress>:port where the `:port` part is mandatory.
|
||||
/// If only the port is specified, all network interfaces are listened. The function can listen on multiple IP addresses
|
||||
/// if the specified hostname is resolved to multiple IP addresses; in other words, it can create more than one
|
||||
/// fc::listener objects. If port is not specified or none of the resolved address can be listened, an std::system_error
|
||||
/// with std::errc::bad_address error code will be thrown.
|
||||
///
|
||||
/// For Unix socket, this function will temporary change current working directory to the parent of the specified \c
|
||||
/// address (i.e. socket file path), listen on the filename component of the path, and then restore the working
|
||||
/// directory before return. This is the workaround for the socket file paths limitation which is around 100 characters.
|
||||
///
|
||||
/// The lifetime of the created listener objects is controlled by \c executor, the created objects will be destroyed
|
||||
/// when \c executor.stop() is called.
|
||||
///
|
||||
/// @note
|
||||
/// This function is not thread safe for Unix socket because it will temporarily change working directory without any
|
||||
/// lock. Any code which depends the current working directory (such as opening files with relative paths) in other
|
||||
/// threads should be protected.
|
||||
///
|
||||
/// @tparam Protocol either \c boost::asio::ip::tcp or \c boost::asio::local::stream_protocol
|
||||
/// @throws std::system_error or boost::system::system_error
|
||||
template <typename Protocol, typename CreateSession>
|
||||
void create_listener(boost::asio::io_context& executor, logger& logger, boost::posix_time::time_duration accept_timeout,
|
||||
const std::string& address, const std::string& extra_listening_log_info,
|
||||
const CreateSession& create_session) {
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
if constexpr (std::is_same_v<Protocol, tcp>) {
|
||||
auto [host, port] = split_host_port(address);
|
||||
if (port.empty()) {
|
||||
fc_elog(logger, "port is not specified for address ${addr}", ("addr", address));
|
||||
throw std::system_error(std::make_error_code(std::errc::bad_address));
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
tcp::resolver resolver(executor);
|
||||
auto endpoints = resolver.resolve(host, port, tcp::resolver::passive, ec);
|
||||
if (ec) {
|
||||
fc_elog(logger, "failed to resolve address: ${msg}", ("msg", ec.message()));
|
||||
throw std::system_error(ec);
|
||||
}
|
||||
|
||||
int listened = 0;
|
||||
std::optional<tcp::endpoint> unspecified_ipv4_addr;
|
||||
bool has_unspecified_ipv6_only = false;
|
||||
|
||||
auto create_listener = [&](const auto& endpoint) {
|
||||
const auto& ip_addr = endpoint.address();
|
||||
try {
|
||||
auto listener = std::make_shared<fc::listener<Protocol, CreateSession>>(
|
||||
executor, logger, accept_timeout, address, endpoint, extra_listening_log_info, create_session);
|
||||
listener->log_listening(endpoint, address);
|
||||
listener->do_accept();
|
||||
++listened;
|
||||
has_unspecified_ipv6_only = ip_addr.is_unspecified() && ip_addr.is_v6();
|
||||
if (has_unspecified_ipv6_only) {
|
||||
boost::asio::ip::v6_only option;
|
||||
listener->acceptor().get_option(option);
|
||||
has_unspecified_ipv6_only &= option.value();
|
||||
}
|
||||
|
||||
} catch (boost::system::system_error& ex) {
|
||||
fc_wlog(logger, "unable to listen on ${ip_addr}:${port} resolved from ${address}: ${msg}",
|
||||
("ip_addr", ip_addr.to_string())("port", endpoint.port())("address", address)("msg", ex.what()));
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& ep : endpoints) {
|
||||
const auto& endpoint = ep.endpoint();
|
||||
const auto& ip_addr = endpoint.address();
|
||||
if (ip_addr.is_unspecified() && ip_addr.is_v4() && endpoints.size() > 1) {
|
||||
// it is an error to bind a socket to the same port for both ipv6 and ipv4 INADDR_ANY address when
|
||||
// the system has ipv4-mapped ipv6 enabled by default, we just skip the ipv4 for now.
|
||||
unspecified_ipv4_addr = endpoint;
|
||||
continue;
|
||||
}
|
||||
create_listener(endpoint);
|
||||
}
|
||||
|
||||
if (unspecified_ipv4_addr.has_value() && has_unspecified_ipv6_only) {
|
||||
create_listener(*unspecified_ipv4_addr);
|
||||
}
|
||||
|
||||
if (listened == 0) {
|
||||
fc_elog(logger, "none of the addresses resolved from ${addr} can be listened to", ("addr", address));
|
||||
throw std::system_error(std::make_error_code(std::errc::bad_address));
|
||||
}
|
||||
} else {
|
||||
using stream_protocol = boost::asio::local::stream_protocol;
|
||||
static_assert(std::is_same_v<Protocol, stream_protocol>);
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
auto cwd = fs::current_path();
|
||||
fs::path sock_path = address;
|
||||
|
||||
fs::create_directories(sock_path.parent_path());
|
||||
// The maximum length of the socket path is defined by sockaddr_un::sun_path. On Linux,
|
||||
// according to unix(7), it is 108 bytes. On FreeBSD, according to unix(4), it is 104 bytes.
|
||||
// Therefore, we create the unix socket with the relative path to its parent path to avoid the
|
||||
// problem.
|
||||
fs::current_path(sock_path.parent_path());
|
||||
auto restore = fc::make_scoped_exit([cwd] { fs::current_path(cwd); });
|
||||
|
||||
stream_protocol::endpoint endpoint{ sock_path.filename().string() };
|
||||
|
||||
boost::system::error_code ec;
|
||||
stream_protocol::socket test_socket(executor);
|
||||
test_socket.connect(endpoint, ec);
|
||||
|
||||
// looks like a service is already running on that socket, don't touch it... fail out
|
||||
if (ec == boost::system::errc::success) {
|
||||
fc_elog(logger, "The unix socket path ${addr} is already in use", ("addr", address));
|
||||
throw std::system_error(std::make_error_code(std::errc::address_in_use));
|
||||
}
|
||||
else if (ec == boost::system::errc::connection_refused) {
|
||||
// socket exists but no one home, go ahead and remove it and continue on
|
||||
fs::remove(sock_path);
|
||||
}
|
||||
|
||||
auto listener = std::make_shared<fc::listener<stream_protocol, CreateSession>>(
|
||||
executor, logger, accept_timeout, address, endpoint, extra_listening_log_info, create_session);
|
||||
listener->log_listening(endpoint, address);
|
||||
listener->do_accept();
|
||||
}
|
||||
}
|
||||
} // namespace fc
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include <fc/reflect/typename.hpp>
|
||||
|
||||
@@ -200,7 +201,7 @@ template<> struct reflector<ENUM> { \
|
||||
switch( elem ) { \
|
||||
BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_ENUM_TO_STRING, ENUM, FIELDS ) \
|
||||
default: \
|
||||
fc::throw_bad_enum_cast( fc::to_string(int64_t(elem)).c_str(), BOOST_PP_STRINGIZE(ENUM) ); \
|
||||
fc::throw_bad_enum_cast( std::to_string(int64_t(elem)).c_str(), BOOST_PP_STRINGIZE(ENUM) ); \
|
||||
}\
|
||||
return nullptr; \
|
||||
} \
|
||||
@@ -211,7 +212,7 @@ template<> struct reflector<ENUM> { \
|
||||
switch( elem ) { \
|
||||
BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_ENUM_TO_FC_STRING, ENUM, FIELDS ) \
|
||||
} \
|
||||
return fc::to_string(int64_t(elem)); \
|
||||
return std::to_string(int64_t(elem)); \
|
||||
} \
|
||||
static std::string to_fc_string(int64_t i) { \
|
||||
return to_fc_string(ENUM(i)); \
|
||||
|
||||
@@ -8,19 +8,9 @@ namespace fc
|
||||
int64_t to_int64( const std::string& );
|
||||
uint64_t to_uint64( const std::string& );
|
||||
double to_double( const std::string& );
|
||||
std::string to_string( double );
|
||||
std::string to_string( uint64_t );
|
||||
std::string to_string( int64_t );
|
||||
std::string to_string( uint16_t );
|
||||
inline std::string to_string( int32_t v ) { return to_string( int64_t(v) ); }
|
||||
inline std::string to_string( uint32_t v ){ return to_string( uint64_t(v) ); }
|
||||
#ifdef __APPLE__
|
||||
inline std::string to_string( size_t s) { return to_string(uint64_t(s)); }
|
||||
#endif
|
||||
|
||||
class variant_object;
|
||||
std::string format_string( const std::string&, const variant_object&, bool minimize = false );
|
||||
std::string trim( const std::string& );
|
||||
|
||||
/**
|
||||
* Convert '\t', '\r', '\n', '\\' and '"' to "\t\r\n\\\"" if escape_ctrl == on
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (push)
|
||||
@@ -11,7 +12,8 @@ namespace fc {
|
||||
class microseconds {
|
||||
public:
|
||||
constexpr explicit microseconds( int64_t c = 0) :_count(c){}
|
||||
static constexpr microseconds maximum() { return microseconds(0x7fffffffffffffffll); }
|
||||
static constexpr microseconds maximum() { return microseconds(std::numeric_limits<int64_t>::max()); }
|
||||
static constexpr microseconds minimum() { return microseconds(std::numeric_limits<int64_t>::min()); }
|
||||
friend constexpr microseconds operator + (const microseconds& l, const microseconds& r ) { return microseconds(l._count+r._count); }
|
||||
friend constexpr microseconds operator - (const microseconds& l, const microseconds& r ) { return microseconds(l._count-r._count); }
|
||||
|
||||
@@ -49,6 +51,18 @@ namespace fc {
|
||||
std::string to_iso_string()const;
|
||||
static time_point from_iso_string( const std::string& s );
|
||||
|
||||
// protect against overflow/underflow
|
||||
constexpr time_point& safe_add( const microseconds& m ) {
|
||||
if (m.count() > 0 && elapsed > microseconds::maximum() - m) {
|
||||
elapsed = microseconds::maximum();
|
||||
} else if (m.count() < 0 && elapsed < microseconds::minimum() - m) {
|
||||
elapsed = microseconds::minimum();
|
||||
} else {
|
||||
elapsed += m;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr const microseconds& time_since_epoch()const { return elapsed; }
|
||||
constexpr uint32_t sec_since_epoch()const { return elapsed.count() / 1000000; }
|
||||
constexpr bool operator > ( const time_point& t )const { return elapsed._count > t.elapsed._count; }
|
||||
@@ -81,7 +95,7 @@ namespace fc {
|
||||
constexpr explicit time_point_sec( const time_point& t )
|
||||
:utc_seconds( t.time_since_epoch().count() / 1000000ll ){}
|
||||
|
||||
static constexpr time_point_sec maximum() { return time_point_sec(0xffffffff); }
|
||||
static constexpr time_point_sec maximum() { return time_point_sec(std::numeric_limits<uint32_t>::max()); }
|
||||
static constexpr time_point_sec min() { return time_point_sec(0); }
|
||||
|
||||
constexpr time_point to_time_point()const { return time_point( fc::seconds( utc_seconds) ); }
|
||||
|
||||
@@ -206,13 +206,20 @@ namespace fc
|
||||
///@}
|
||||
|
||||
|
||||
explicit mutable_variant_object( variant v )
|
||||
:_key_value( new std::vector<entry>() )
|
||||
{
|
||||
*this = v.get_object();
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename = std::enable_if_t<!std::is_base_of<mutable_variant_object,
|
||||
std::decay_t<T>>::value>>
|
||||
typename = std::enable_if_t<!std::is_base_of<mutable_variant_object, std::decay_t<T>>::value &&
|
||||
!std::is_base_of<variant, std::decay_t<T>>::value &&
|
||||
!std::is_base_of<variant_object, std::decay_t<T>>::value>>
|
||||
explicit mutable_variant_object( T&& v )
|
||||
:_key_value( new std::vector<entry>() )
|
||||
{
|
||||
*this = variant(fc::forward<T>(v)).get_object();
|
||||
*this = std::move(variant(fc::forward<T>(v)).get_object());
|
||||
}
|
||||
|
||||
mutable_variant_object();
|
||||
@@ -228,11 +235,22 @@ namespace fc
|
||||
|
||||
mutable_variant_object( mutable_variant_object&& );
|
||||
mutable_variant_object( const mutable_variant_object& );
|
||||
mutable_variant_object( const variant_object& );
|
||||
explicit mutable_variant_object( const variant_object& );
|
||||
/*
|
||||
* Use with care as the internal shared state of variant_object is moved.
|
||||
* asserts on exclusive ownership of variant_object shared state. Not thread safe.
|
||||
*/
|
||||
explicit mutable_variant_object( variant_object&& );
|
||||
|
||||
mutable_variant_object& operator=( mutable_variant_object&& );
|
||||
mutable_variant_object& operator=( const mutable_variant_object& );
|
||||
mutable_variant_object& operator=( const variant_object& );
|
||||
/**
|
||||
* Use with care as the internal shared state of variant_object is moved.
|
||||
* asserts on exclusive ownership of variant_object shared state. Not thread safe.
|
||||
*/
|
||||
mutable_variant_object& operator=( variant_object&& );
|
||||
|
||||
private:
|
||||
std::unique_ptr< std::vector< entry > > _key_value;
|
||||
friend class variant_object;
|
||||
|
||||
Submodule libraries/libfc/libraries/bn256 updated: 63c6c9919c...da781dbd15
@@ -133,7 +133,7 @@ namespace fc
|
||||
}
|
||||
void from_variant( const variant& v, exception& ll )
|
||||
{
|
||||
auto obj = v.get_object();
|
||||
const auto& obj = v.get_object();
|
||||
if( obj.contains( "stack" ) )
|
||||
ll.my->_elog = obj["stack"].as<log_messages>();
|
||||
if( obj.contains( "code" ) )
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace fc
|
||||
in.get();
|
||||
return obj;
|
||||
}
|
||||
FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) );
|
||||
FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", std::move(obj) ) );
|
||||
}
|
||||
catch( const fc::eof_exception& e )
|
||||
{
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace fc {
|
||||
const log_context context = m.get_context();
|
||||
std::string file_line = context.get_file().substr( 0, 22 );
|
||||
file_line += ':';
|
||||
file_line += fixed_size( 6, fc::to_string( context.get_line_number() ) );
|
||||
file_line += fixed_size( 6, std::to_string( context.get_line_number() ) );
|
||||
|
||||
std::string line;
|
||||
line.reserve( 256 );
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace fc
|
||||
|
||||
my->thread = std::thread([this] {
|
||||
try {
|
||||
fc::set_os_thread_name("gelf");
|
||||
fc::set_thread_name("gelf");
|
||||
my->io_context.run();
|
||||
} catch (std::exception& ex) {
|
||||
fprintf(stderr, "GELF logger caught exception at %s:%d : %s\n", __FILE__, __LINE__, ex.what());
|
||||
@@ -169,7 +169,7 @@ namespace fc
|
||||
gelf_message["_timestamp_ns"] = time_ns;
|
||||
|
||||
static uint64_t gelf_log_counter;
|
||||
gelf_message["_log_id"] = fc::to_string(++gelf_log_counter);
|
||||
gelf_message["_log_id"] = std::to_string(++gelf_log_counter);
|
||||
|
||||
switch (context.get_log_level())
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace fc
|
||||
log_context::log_context( const variant& v )
|
||||
:my( std::make_shared<detail::log_context_impl>() )
|
||||
{
|
||||
auto obj = v.get_object();
|
||||
const auto& obj = v.get_object();
|
||||
my->level = obj["level"].as<log_level>();
|
||||
my->file = obj["file"].as_string();
|
||||
my->line = obj["line"].as_uint64();
|
||||
@@ -73,7 +73,7 @@ namespace fc
|
||||
|
||||
std::string log_context::to_string()const
|
||||
{
|
||||
return my->thread_name + " " + my->file + ":" + fc::to_string(my->line) + " " + my->method;
|
||||
return my->thread_name + " " + my->file + ":" + std::to_string(my->line) + " " + my->method;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <fc/reflect/variant.hpp>
|
||||
#include <fc/exception/exception.hpp>
|
||||
|
||||
#define BOOST_DLL_USE_STD_FS
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
|
||||
namespace fc {
|
||||
|
||||
log_config& log_config::get() {
|
||||
@@ -133,26 +136,22 @@ namespace fc {
|
||||
}
|
||||
|
||||
static thread_local std::string thread_name;
|
||||
void set_os_thread_name( const std::string& name ) {
|
||||
#ifdef FC_USE_PTHREAD_NAME_NP
|
||||
pthread_setname_np( pthread_self(), name.c_str() );
|
||||
#endif
|
||||
}
|
||||
|
||||
void set_thread_name( const std::string& name ) {
|
||||
thread_name = name;
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
pthread_setname_np( pthread_self(), name.c_str() );
|
||||
#elif defined(__APPLE__)
|
||||
pthread_setname_np( name.c_str() );
|
||||
#endif
|
||||
}
|
||||
const std::string& get_thread_name() {
|
||||
if( thread_name.empty() ) {
|
||||
#ifdef FC_USE_PTHREAD_NAME_NP
|
||||
char thr_name[64];
|
||||
int rc = pthread_getname_np( pthread_self(), thr_name, 64 );
|
||||
if( rc == 0 ) {
|
||||
thread_name = thr_name;
|
||||
if(thread_name.empty()) {
|
||||
try {
|
||||
thread_name = boost::dll::program_location().filename().generic_string();
|
||||
} catch (...) {
|
||||
thread_name = "unknown";
|
||||
}
|
||||
#else
|
||||
static int thread_count = 0;
|
||||
thread_name = std::string( "thread-" ) + fc::to_string( thread_count++ );
|
||||
#endif
|
||||
}
|
||||
return thread_name;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <fc/io/json.hpp>
|
||||
#include <fc/exception/exception.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@@ -60,33 +59,6 @@ namespace fc {
|
||||
}
|
||||
FC_RETHROW_EXCEPTIONS( warn, "${i} => double", ("i",i) )
|
||||
}
|
||||
|
||||
std::string to_string(double d)
|
||||
{
|
||||
// +2 is required to ensure that the double is rounded correctly when read back in. http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
std::stringstream ss;
|
||||
ss << std::setprecision(std::numeric_limits<double>::digits10 + 2) << std::fixed << d;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string to_string( uint64_t d)
|
||||
{
|
||||
return boost::lexical_cast<std::string>(d);
|
||||
}
|
||||
|
||||
std::string to_string( int64_t d)
|
||||
{
|
||||
return boost::lexical_cast<std::string>(d);
|
||||
}
|
||||
std::string to_string( uint16_t d)
|
||||
{
|
||||
return boost::lexical_cast<std::string>(d);
|
||||
}
|
||||
std::string trim( const std::string& s )
|
||||
{
|
||||
return boost::algorithm::trim_copy(s);
|
||||
}
|
||||
|
||||
std::pair<std::string&, bool> escape_str( std::string& str, escape_control_chars escape_ctrl,
|
||||
std::size_t max_len, std::string_view add_truncate_str )
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace fc {
|
||||
if (count >= 0) {
|
||||
uint64_t secs = (uint64_t)count / 1000000ULL;
|
||||
uint64_t msec = ((uint64_t)count % 1000000ULL) / 1000ULL;
|
||||
std::string padded_ms = fc::to_string((uint64_t)(msec + 1000ULL)).substr(1);
|
||||
std::string padded_ms = std::to_string((uint64_t)(msec + 1000ULL)).substr(1);
|
||||
const auto ptime = boost::posix_time::from_time_t(time_t(secs));
|
||||
return boost::posix_time::to_iso_extended_string(ptime) + "." + padded_ms;
|
||||
} else {
|
||||
|
||||
@@ -466,6 +466,14 @@ bool variant::as_bool()const
|
||||
}
|
||||
}
|
||||
|
||||
static std::string s_fc_to_string(double d)
|
||||
{
|
||||
// +2 is required to ensure that the double is rounded correctly when read back in. http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
std::stringstream ss;
|
||||
ss << std::setprecision(std::numeric_limits<double>::digits10 + 2) << std::fixed << d;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string variant::as_string()const
|
||||
{
|
||||
switch( get_type() )
|
||||
@@ -473,11 +481,11 @@ std::string variant::as_string()const
|
||||
case string_type:
|
||||
return **reinterpret_cast<const const_string_ptr*>(this);
|
||||
case double_type:
|
||||
return to_string(*reinterpret_cast<const double*>(this));
|
||||
return s_fc_to_string(*reinterpret_cast<const double*>(this));
|
||||
case int64_type:
|
||||
return to_string(*reinterpret_cast<const int64_t*>(this));
|
||||
return std::to_string(*reinterpret_cast<const int64_t*>(this));
|
||||
case uint64_type:
|
||||
return to_string(*reinterpret_cast<const uint64_t*>(this));
|
||||
return std::to_string(*reinterpret_cast<const uint64_t*>(this));
|
||||
case bool_type:
|
||||
return *reinterpret_cast<const bool*>(this) ? "true" : "false";
|
||||
case blob_type:
|
||||
|
||||
@@ -286,6 +286,16 @@ namespace fc
|
||||
{
|
||||
}
|
||||
|
||||
mutable_variant_object::mutable_variant_object( variant_object&& obj )
|
||||
: _key_value( new std::vector<entry>() )
|
||||
{
|
||||
assert(obj._key_value.use_count() == 1); // should only be used if data not shared
|
||||
if (obj._key_value.use_count() == 1)
|
||||
*_key_value = std::move(*obj._key_value);
|
||||
else
|
||||
*_key_value = *obj._key_value;
|
||||
}
|
||||
|
||||
mutable_variant_object::mutable_variant_object( const mutable_variant_object& obj )
|
||||
: _key_value( new std::vector<entry>(*obj._key_value) )
|
||||
{
|
||||
@@ -302,6 +312,16 @@ namespace fc
|
||||
return *this;
|
||||
}
|
||||
|
||||
mutable_variant_object& mutable_variant_object::operator=( variant_object&& obj )
|
||||
{
|
||||
assert(obj._key_value.use_count() == 1); // should only be used if data not shared
|
||||
if (obj._key_value.use_count() == 1)
|
||||
*_key_value = std::move(*obj._key_value);
|
||||
else
|
||||
*_key_value = *obj._key_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
mutable_variant_object& mutable_variant_object::operator=( mutable_variant_object&& obj )
|
||||
{
|
||||
if (this != &obj)
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace state_history {
|
||||
std::filesystem::path retained_dir = "retained";
|
||||
std::filesystem::path archive_dir = "archive";
|
||||
uint32_t stride = 1000000;
|
||||
uint32_t max_retained_files = 10;
|
||||
uint32_t max_retained_files = UINT32_MAX;
|
||||
};
|
||||
} // namespace state_history
|
||||
|
||||
@@ -128,7 +128,7 @@ struct locked_decompress_stream {
|
||||
|
||||
namespace detail {
|
||||
|
||||
std::vector<char> zlib_decompress(fc::cfile& file, uint64_t compressed_size) {
|
||||
inline std::vector<char> zlib_decompress(fc::cfile& file, uint64_t compressed_size) {
|
||||
if (compressed_size) {
|
||||
std::vector<char> compressed(compressed_size);
|
||||
file.read(compressed.data(), compressed_size);
|
||||
@@ -137,7 +137,7 @@ std::vector<char> zlib_decompress(fc::cfile& file, uint64_t compressed_size) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<char> zlib_decompress(fc::datastream<const char*>& strm, uint64_t compressed_size) {
|
||||
inline std::vector<char> zlib_decompress(fc::datastream<const char*>& strm, uint64_t compressed_size) {
|
||||
if (compressed_size) {
|
||||
return state_history::zlib_decompress({strm.pos(), compressed_size});
|
||||
}
|
||||
@@ -282,7 +282,7 @@ private:
|
||||
class state_history_log {
|
||||
private:
|
||||
const char* const name = "";
|
||||
state_history_log_config config;
|
||||
state_history_log_config _config;
|
||||
|
||||
// provide exclusive access to all data of this object since accessed from the main thread and the ship thread
|
||||
mutable std::mutex _mx;
|
||||
@@ -304,7 +304,7 @@ class state_history_log {
|
||||
state_history_log(const char* name, const std::filesystem::path& log_dir,
|
||||
state_history_log_config conf = {})
|
||||
: name(name)
|
||||
, config(std::move(conf)) {
|
||||
, _config(std::move(conf)) {
|
||||
|
||||
log.set_file_path(log_dir/(std::string(name) + ".log"));
|
||||
index.set_file_path(log_dir/(std::string(name) + ".index"));
|
||||
@@ -326,7 +326,7 @@ class state_history_log {
|
||||
_begin_block = _end_block = catalog.last_block_num() +1;
|
||||
}
|
||||
}
|
||||
}, config);
|
||||
}, _config);
|
||||
|
||||
//check for conversions to/from pruned log, as long as log contains something
|
||||
if(_begin_block != _end_block) {
|
||||
@@ -334,7 +334,7 @@ class state_history_log {
|
||||
log.seek(0);
|
||||
read_header(first_header);
|
||||
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&config);
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&_config);
|
||||
|
||||
if((is_ship_log_pruned(first_header.magic) == false) && prune_config) {
|
||||
//need to convert non-pruned to pruned; first prune any ranges we can (might be none)
|
||||
@@ -361,7 +361,7 @@ class state_history_log {
|
||||
if(_begin_block == _end_block)
|
||||
return;
|
||||
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&config);
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&_config);
|
||||
if(!prune_config || !prune_config->vacuum_on_close)
|
||||
return;
|
||||
|
||||
@@ -371,6 +371,10 @@ class state_history_log {
|
||||
vacuum();
|
||||
}
|
||||
|
||||
const state_history_log_config& config() const {
|
||||
return _config;
|
||||
}
|
||||
|
||||
// begin end
|
||||
std::pair<uint32_t, uint32_t> block_range() const {
|
||||
std::lock_guard g(_mx);
|
||||
@@ -456,7 +460,7 @@ class state_history_log {
|
||||
return get_block_id_i(block_num);
|
||||
}
|
||||
|
||||
#ifdef BOOST_TEST_MODULE
|
||||
#ifdef BOOST_TEST
|
||||
fc::cfile& get_log_file() { return log;}
|
||||
#endif
|
||||
|
||||
@@ -499,7 +503,7 @@ class state_history_log {
|
||||
}
|
||||
}
|
||||
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&config);
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&_config);
|
||||
if (block_num < _end_block) {
|
||||
// This is typically because of a fork, and we need to truncate the log back to the beginning of the fork.
|
||||
static uint32_t start_block_num = block_num;
|
||||
@@ -552,7 +556,7 @@ class state_history_log {
|
||||
log.flush();
|
||||
index.flush();
|
||||
|
||||
auto partition_config = std::get_if<state_history::partition_config>(&config);
|
||||
auto partition_config = std::get_if<state_history::partition_config>(&_config);
|
||||
if (partition_config && block_num % partition_config->stride == 0) {
|
||||
split_log();
|
||||
}
|
||||
@@ -608,7 +612,7 @@ class state_history_log {
|
||||
}
|
||||
|
||||
void prune(const fc::log_level& loglevel) {
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&config);
|
||||
auto prune_config = std::get_if<state_history::prune_config>(&_config);
|
||||
|
||||
if(!prune_config)
|
||||
return;
|
||||
|
||||
@@ -404,6 +404,9 @@ namespace eosio { namespace testing {
|
||||
cfg.contracts_console = true;
|
||||
cfg.eosvmoc_config.cache_size = 1024*1024*8;
|
||||
|
||||
// don't use auto tier up for tests, since the point is to test diff vms
|
||||
cfg.eosvmoc_tierup = chain::wasm_interface::vm_oc_enable::oc_none;
|
||||
|
||||
for(int i = 0; i < boost::unit_test::framework::master_test_suite().argc; ++i) {
|
||||
if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--eos-vm"))
|
||||
cfg.wasm_runtime = chain::wasm_interface::vm_type::eos_vm;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <eosio/testing/tester.hpp>
|
||||
#include <eosio/chain/wasm_interface_collection.hpp>
|
||||
#include <eosio/chain/block_log.hpp>
|
||||
#include <eosio/chain/wast_to_wasm.hpp>
|
||||
#include <eosio/chain/eosio_contract.hpp>
|
||||
@@ -301,11 +302,14 @@ namespace eosio { namespace testing {
|
||||
if( !expected_chain_id ) {
|
||||
expected_chain_id = controller::extract_chain_id_from_db( cfg.state_dir );
|
||||
if( !expected_chain_id ) {
|
||||
if( std::filesystem::is_regular_file( cfg.blocks_dir / "blocks.log" ) ) {
|
||||
expected_chain_id = block_log::extract_chain_id( cfg.blocks_dir );
|
||||
} else {
|
||||
expected_chain_id = genesis_state().compute_chain_id();
|
||||
std::filesystem::path retained_dir;
|
||||
auto partitioned_config = std::get_if<partitioned_blocklog_config>(&cfg.blog);
|
||||
if (partitioned_config) {
|
||||
retained_dir = partitioned_config->retained_dir;
|
||||
if (retained_dir.is_relative())
|
||||
retained_dir = cfg.blocks_dir/retained_dir;
|
||||
}
|
||||
expected_chain_id = block_log::extract_chain_id( cfg.blocks_dir, retained_dir );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -46,13 +46,17 @@ set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/AntelopeIO/leap")
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_DEBIAN_BASE_PACKAGE_SECTION "utils")
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.22)
|
||||
set(CPACK_DEBIAN_COMPRESSION_TYPE "zstd")
|
||||
endif()
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "eosio, mandel")
|
||||
set(CPACK_RPM_PACKAGE_CONFLICTS "eosio, mandel")
|
||||
|
||||
#only consider "base" and "dev" components for per-component packages
|
||||
get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
|
||||
list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified")
|
||||
set(CPACK_COMPONENTS_ALL "base")
|
||||
if(ENABLE_LEAP_DEV_DEB)
|
||||
list(APPEND CPACK_COMPONENTS_ALL "dev")
|
||||
endif()
|
||||
|
||||
#enable per component packages for .deb; ensure main package is just "leap", not "leap-base", and make the dev package have "leap-dev" at the front not the back
|
||||
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
@@ -61,7 +65,7 @@ set(CPACK_DEBIAN_BASE_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}.deb")
|
||||
string(REGEX REPLACE "^(${CMAKE_PROJECT_NAME})" "\\1-dev" CPACK_DEBIAN_DEV_FILE_NAME "${CPACK_DEBIAN_BASE_FILE_NAME}")
|
||||
|
||||
#deb package tooling will be unable to detect deps for the dev package. llvm is tricky since we don't know what package could have been used; try to figure it out
|
||||
set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libboost-all-dev, libssl-dev, libgmp-dev, python3-numpy")
|
||||
set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libssl-dev, libgmp-dev, python3-distutils, python3-numpy, zlib1g-dev")
|
||||
find_program(DPKG_QUERY "dpkg-query")
|
||||
if(DPKG_QUERY AND OS_RELEASE MATCHES "\n?ID=\"?ubuntu" AND LLVM_CMAKE_DIR)
|
||||
execute_process(COMMAND "${DPKG_QUERY}" -S "${LLVM_CMAKE_DIR}" COMMAND cut -d: -f1 RESULT_VARIABLE LLVM_PKG_FIND_RESULT OUTPUT_VARIABLE LLVM_PKG_FIND_OUTPUT)
|
||||
|
||||
@@ -345,29 +345,18 @@ paths:
|
||||
schema:
|
||||
title: "GetProducersResponse"
|
||||
type: object
|
||||
additionalProperties: false
|
||||
minProperties: 3
|
||||
required:
|
||||
- active
|
||||
- pending
|
||||
- proposed
|
||||
properties:
|
||||
active:
|
||||
rows:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/ProducerSchedule.yaml"
|
||||
pending:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/ProducerSchedule.yaml"
|
||||
proposed:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/ProducerSchedule.yaml"
|
||||
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/Producer.yaml"
|
||||
total_producer_vote_weight:
|
||||
type: string
|
||||
description: The sum of all producer votes.
|
||||
more:
|
||||
type: string
|
||||
description: If not all producers were returned with the first request, more contains the lower bound to use for the next request.
|
||||
|
||||
/get_raw_code_and_abi:
|
||||
post:
|
||||
@@ -793,7 +782,7 @@ paths:
|
||||
|
||||
/compute_transaction:
|
||||
post:
|
||||
description: Executes specified transaction and creates a transaction trace, including resource usage, and then reverts all state changes but not contribute to the subjective billing for the account. If the transaction has signatures, they are processed, but any failures are ignored. Transactions which fail always include the transaction failure trace. Warning, users with exposed nodes who have enabled the compute_transaction endpoint should implement some sort of throttling to protect from Denial of Service attacks.
|
||||
description: Executes specified transaction and creates a transaction trace, including resource usage, and then reverts all state changes but not contribute to the subjective billing for the account. If the transaction has signatures, they are processed, but any failures are ignored. Transactions which fail always include the transaction failure trace. Warning, users with exposed nodes who have enabled the compute_transaction endpoint should implement some throttling to protect from Denial of Service attacks.
|
||||
operationId: compute_transaction
|
||||
requestBody:
|
||||
content:
|
||||
@@ -815,7 +804,6 @@ paths:
|
||||
packed_trx:
|
||||
type: string
|
||||
description: Transaction object, JSON to hex
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@@ -823,3 +811,120 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
description: Returns Nothing
|
||||
|
||||
/get_code_hash:
|
||||
post:
|
||||
description: Retrieves the code hash for a smart contract deployed on the blockchain. Once you have the code hash of a contract, you can compare it with a known or expected value to ensure that the contract code has not been modified or tampered with.
|
||||
operationId: get_code_hash
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
account_name:
|
||||
description: The name of the account for which you want to retrieve the code hash. It represents the account that owns the smart contract code.
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
account_name:
|
||||
description: The name of the account where the smart contract was deployed.
|
||||
type: string
|
||||
code_hash:
|
||||
type: string
|
||||
description: A string that represents the hash value of the specified account's smart contract code.
|
||||
|
||||
/get_transaction_id:
|
||||
post:
|
||||
description: Retrieves the transaction ID (also known as the transaction hash) of a specified transaction on the blockchain.
|
||||
operationId: get_transaction_id
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
description: The transaction in JSON format for which the ID should be retrieved.
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/Transaction.yaml"
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: The transaction ID.
|
||||
|
||||
/get_producer_schedule:
|
||||
post:
|
||||
description: Retrieves the current producer schedule from the blockchain, which includes the list of active producers and their respective rotation schedule.
|
||||
operationId: get_producer_schedule
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
description: A JSON object that encapsulates the list of active producers schedule and its version.
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/ProducerSchedule.yaml"
|
||||
pending:
|
||||
description: A JSON object that encapsulates the list of pending producers schedule and its version.
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/ProducerSchedule.yaml"
|
||||
proposed:
|
||||
description: A JSON object that encapsulates the list of proposed producers schedule and its version.
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/ProducerSchedule.yaml"
|
||||
|
||||
/send_read_only_transaction:
|
||||
post:
|
||||
description: Sends a read-only transaction in JSON format to the blockchain. This transaction is not intended for inclusion in the blockchain. When a user sends a transaction, which modifies the blockchain state, the connected node will fail the transaction.
|
||||
operationId: send_read_only_transaction
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
transaction:
|
||||
type: object
|
||||
properties:
|
||||
compression:
|
||||
type: boolean
|
||||
description: Compression used, usually false
|
||||
packed_context_free_data:
|
||||
type: string
|
||||
description: JSON to hex
|
||||
packed_trx:
|
||||
type: string
|
||||
description: Transaction object JSON to hex
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: Returns Nothing
|
||||
|
||||
/push_block:
|
||||
post:
|
||||
description: Sends a block to the blockchain.
|
||||
operationId: push_block
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "https://docs.eosnetwork.com/openapi/v2.0/Block.yaml"
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: Returns Nothing
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <eosio/chain_api_plugin/chain_api_plugin.hpp>
|
||||
#include <eosio/chain/exceptions.hpp>
|
||||
#include <eosio/http_plugin/macros.hpp>
|
||||
#include <fc/time.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
namespace eosio {
|
||||
@@ -11,15 +12,15 @@ using namespace eosio;
|
||||
|
||||
class chain_api_plugin_impl {
|
||||
public:
|
||||
chain_api_plugin_impl(controller& db)
|
||||
explicit chain_api_plugin_impl(controller& db)
|
||||
: db(db) {}
|
||||
|
||||
controller& db;
|
||||
};
|
||||
|
||||
|
||||
chain_api_plugin::chain_api_plugin(){}
|
||||
chain_api_plugin::~chain_api_plugin(){}
|
||||
chain_api_plugin::chain_api_plugin() = default;
|
||||
chain_api_plugin::~chain_api_plugin() = default;
|
||||
|
||||
void chain_api_plugin::set_program_options(options_description&, options_description&) {}
|
||||
void chain_api_plugin::plugin_initialize(const variables_map&) {}
|
||||
@@ -41,27 +42,76 @@ parse_params<chain_apis::read_only::get_transaction_status_params, http_params_t
|
||||
}
|
||||
}
|
||||
|
||||
#define CALL_WITH_400(api_name, api_handle, api_namespace, call_name, http_response_code, params_type) \
|
||||
// if actions.data & actions.hex_data provided, use the hex_data since only currently support unexploded data
|
||||
template<>
|
||||
chain_apis::read_only::get_transaction_id_params
|
||||
parse_params<chain_apis::read_only::get_transaction_id_params, http_params_types::params_required>(const std::string& body) {
|
||||
if (body.empty()) {
|
||||
EOS_THROW(chain::invalid_http_request, "A Request body is required");
|
||||
}
|
||||
|
||||
try {
|
||||
fc::variant trx_var = fc::json::from_string( body );
|
||||
if( trx_var.is_object() ) {
|
||||
fc::variant_object& vo = trx_var.get_object();
|
||||
if( vo.contains("actions") && vo["actions"].is_array() ) {
|
||||
fc::mutable_variant_object mvo{vo};
|
||||
fc::variants& action_variants = mvo["actions"].get_array();
|
||||
for( auto& action_v : action_variants ) {
|
||||
if( action_v.is_object() ) {
|
||||
fc::variant_object& action_vo = action_v.get_object();
|
||||
if( action_vo.contains( "data" ) && action_vo.contains( "hex_data" ) ) {
|
||||
fc::mutable_variant_object maction_vo{action_vo};
|
||||
maction_vo["data"] = maction_vo["hex_data"];
|
||||
action_vo = maction_vo;
|
||||
vo = mvo;
|
||||
} else if( action_vo.contains( "data" ) ) {
|
||||
if( !action_vo["data"].is_string() ) {
|
||||
EOS_THROW(chain::invalid_http_request, "Request supports only un-exploded 'data' (hex form)");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
EOS_THROW(chain::invalid_http_request, "Transaction contains invalid or empty action");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
EOS_THROW(chain::invalid_http_request, "Transaction actions are missing or invalid");
|
||||
}
|
||||
}
|
||||
else {
|
||||
EOS_THROW(chain::invalid_http_request, "Transaction object is missing or invalid");
|
||||
}
|
||||
auto trx = trx_var.as<chain_apis::read_only::get_transaction_id_params>();
|
||||
if( trx.id() == transaction().id() ) {
|
||||
EOS_THROW(chain::invalid_http_request, "Invalid transaction object");
|
||||
}
|
||||
return trx;
|
||||
} EOS_RETHROW_EXCEPTIONS(chain::invalid_http_request, "Invalid transaction");
|
||||
}
|
||||
|
||||
#define CALL_WITH_400(api_name, category, api_handle, api_namespace, call_name, http_response_code, params_type) \
|
||||
{std::string("/v1/" #api_name "/" #call_name), \
|
||||
api_category::category,\
|
||||
[api_handle](string&&, string&& body, url_response_callback&& cb) mutable { \
|
||||
auto deadline = api_handle.start(); \
|
||||
try { \
|
||||
auto params = parse_params<api_namespace::call_name ## _params, params_type>(body);\
|
||||
FC_CHECK_DEADLINE(deadline);\
|
||||
fc::variant result( api_handle.call_name( std::move(params), deadline ) ); \
|
||||
cb(http_response_code, deadline, std::move(result)); \
|
||||
cb(http_response_code, std::move(result)); \
|
||||
} catch (...) { \
|
||||
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define CHAIN_RO_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
|
||||
#define CHAIN_RW_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, rw_api, chain_apis::read_write, call_name, http_response_code, params_type)
|
||||
#define CHAIN_RO_CALL_POST(call_name, call_result, http_response_code, params_type) CALL_WITH_400_POST(chain, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
|
||||
#define CHAIN_RO_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
|
||||
#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, rw_api, chain_apis::read_write, call_name, call_result, http_response_code, params_type)
|
||||
#define CHAIN_RO_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, chain_ro, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
|
||||
#define CHAIN_RW_CALL(call_name, http_response_code, params_type) CALL_WITH_400(chain, chain_rw, rw_api, chain_apis::read_write, call_name, http_response_code, params_type)
|
||||
#define CHAIN_RO_CALL_POST(call_name, call_result, http_response_code, params_type) CALL_WITH_400_POST(chain, chain_ro, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
|
||||
#define CHAIN_RO_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, chain_ro, ro_api, chain_apis::read_only, call_name, call_result, http_response_code, params_type)
|
||||
#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code, params_type) CALL_ASYNC_WITH_400(chain, chain_rw, rw_api, chain_apis::read_write, call_name, call_result, http_response_code, params_type)
|
||||
|
||||
#define CHAIN_RO_CALL_WITH_400(call_name, http_response_code, params_type) CALL_WITH_400(chain, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
|
||||
#define CHAIN_RO_CALL_WITH_400(call_name, http_response_code, params_type) CALL_WITH_400(chain, chain_ro, ro_api, chain_apis::read_only, call_name, http_response_code, params_type)
|
||||
|
||||
void chain_api_plugin::plugin_startup() {
|
||||
ilog( "starting chain_api_plugin" );
|
||||
@@ -76,7 +126,8 @@ void chain_api_plugin::plugin_startup() {
|
||||
ro_api.set_shorten_abi_errors( !http_plugin::verbose_errors() );
|
||||
|
||||
_http_plugin.add_api( {
|
||||
CHAIN_RO_CALL(get_info, 200, http_params_types::no_params)}, appbase::exec_queue::read_only, appbase::priority::medium_high);
|
||||
CALL_WITH_400(chain, node, ro_api, chain_apis::read_only, get_info, 200, http_params_types::no_params)
|
||||
}, appbase::exec_queue::read_only, appbase::priority::medium_high);
|
||||
_http_plugin.add_api({
|
||||
CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params),
|
||||
CHAIN_RO_CALL_POST(get_block, fc::variant, 200, http_params_types::params_required), // _POST because get_block() returns a lambda to be executed on the http thread pool
|
||||
@@ -133,4 +184,4 @@ void chain_api_plugin::plugin_startup() {
|
||||
|
||||
void chain_api_plugin::plugin_shutdown() {}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ if(EOSIO_ENABLE_DEVELOPER_OPTIONS)
|
||||
target_compile_definitions(chain_plugin PUBLIC EOSIO_DEVELOPER)
|
||||
endif()
|
||||
|
||||
target_link_libraries( chain_plugin eosio_chain custom_appbase appbase resource_monitor_plugin )
|
||||
target_link_libraries( chain_plugin eosio_chain custom_appbase appbase resource_monitor_plugin Boost::bimap )
|
||||
target_include_directories( chain_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include" "${CMAKE_CURRENT_SOURCE_DIR}/../../libraries/appbase/include" "${CMAKE_CURRENT_SOURCE_DIR}/../resource_monitor_plugin/include")
|
||||
|
||||
add_subdirectory( test )
|
||||
add_subdirectory( test )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
#include <eosio/chain_plugin/trx_finality_status_processing.hpp>
|
||||
|
||||
#include <fc/static_variant.hpp>
|
||||
#include <fc/time.hpp>
|
||||
|
||||
namespace fc { class variant; }
|
||||
|
||||
@@ -44,9 +45,35 @@ namespace eosio {
|
||||
using chain::action_name;
|
||||
using chain::abi_def;
|
||||
using chain::abi_serializer;
|
||||
using chain::abi_serializer_cache_builder;
|
||||
using chain::abi_resolver;
|
||||
using chain::packed_transaction;
|
||||
|
||||
enum class throw_on_yield { no, yes };
|
||||
inline auto make_resolver(const controller& control, fc::microseconds abi_serializer_max_time, throw_on_yield yield_throw ) {
|
||||
return [&control, abi_serializer_max_time, yield_throw](const account_name& name) -> std::optional<abi_serializer> {
|
||||
if (name.good()) {
|
||||
const auto* accnt = control.db().template find<chain::account_object, chain::by_name>( name );
|
||||
if( accnt != nullptr ) {
|
||||
try {
|
||||
if( abi_def abi; abi_serializer::to_abi( accnt->abi, abi ) ) {
|
||||
return abi_serializer( std::move( abi ), abi_serializer::create_yield_function( abi_serializer_max_time ) );
|
||||
}
|
||||
} catch( ... ) {
|
||||
if( yield_throw == throw_on_yield::yes )
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline abi_resolver get_serializers_cache(const controller& db, const T& obj, const fc::microseconds& max_time) {
|
||||
return abi_resolver(abi_serializer_cache_builder(make_resolver(db, max_time, throw_on_yield::no)).add_serializers(obj).get());
|
||||
}
|
||||
|
||||
namespace chain_apis {
|
||||
struct empty{};
|
||||
|
||||
@@ -92,21 +119,10 @@ class read_write;
|
||||
|
||||
class api_base {
|
||||
public:
|
||||
static constexpr uint32_t max_return_items = 1000;
|
||||
static void handle_db_exhaustion();
|
||||
static void handle_bad_alloc();
|
||||
|
||||
static auto make_resolver(const controller& control, abi_serializer::yield_function_t yield) {
|
||||
return [&control, yield{std::move(yield)}](const account_name &name) -> std::optional<abi_serializer> {
|
||||
const auto* accnt = control.db().template find<chain::account_object, chain::by_name>(name);
|
||||
if (accnt != nullptr) {
|
||||
if (abi_def abi; abi_serializer::to_abi(accnt->abi, abi)) {
|
||||
return abi_serializer(std::move(abi), yield);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
protected:
|
||||
struct send_transaction_params_t {
|
||||
bool return_failure_trace = true;
|
||||
@@ -147,7 +163,7 @@ public:
|
||||
// return deadline for call
|
||||
fc::time_point start() const {
|
||||
validate();
|
||||
return fc::time_point::now() + http_max_response_time;
|
||||
return fc::time_point::now().safe_add(http_max_response_time);
|
||||
}
|
||||
|
||||
void set_shorten_abi_errors( bool f ) { shorten_abi_errors = f; }
|
||||
@@ -207,10 +223,10 @@ public:
|
||||
struct get_activated_protocol_features_params {
|
||||
std::optional<uint32_t> lower_bound;
|
||||
std::optional<uint32_t> upper_bound;
|
||||
uint32_t limit = 10;
|
||||
uint32_t limit = std::numeric_limits<uint32_t>::max(); // ignored
|
||||
bool search_by_block_num = false;
|
||||
bool reverse = false;
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to 10ms
|
||||
std::optional<uint32_t> time_limit_ms; // ignored
|
||||
};
|
||||
|
||||
struct get_activated_protocol_features_results {
|
||||
@@ -371,8 +387,7 @@ public:
|
||||
|
||||
// call from any thread
|
||||
fc::variant convert_block( const chain::signed_block_ptr& block,
|
||||
abi_resolver& resolver,
|
||||
const fc::microseconds& max_time ) const;
|
||||
abi_resolver& resolver ) const;
|
||||
|
||||
struct get_block_header_params {
|
||||
string block_num_or_id;
|
||||
@@ -413,7 +428,7 @@ public:
|
||||
string encode_type{"dec"}; //dec, hex , default=dec
|
||||
std::optional<bool> reverse;
|
||||
std::optional<bool> show_payer; // show RAM payer
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to 10ms
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to http-max-response-time-ms
|
||||
};
|
||||
|
||||
struct get_table_rows_result {
|
||||
@@ -433,7 +448,7 @@ public:
|
||||
string upper_bound; // upper bound of scope, optional
|
||||
uint32_t limit = 10;
|
||||
std::optional<bool> reverse;
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to 10ms
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to http-max-response-time-ms
|
||||
};
|
||||
struct get_table_by_scope_result_row {
|
||||
name code;
|
||||
@@ -475,7 +490,7 @@ public:
|
||||
bool json = false;
|
||||
string lower_bound;
|
||||
uint32_t limit = 50;
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to 10ms
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to http-max-response-time-ms
|
||||
};
|
||||
|
||||
struct get_producers_result {
|
||||
@@ -501,7 +516,7 @@ public:
|
||||
bool json = false;
|
||||
string lower_bound; /// timestamp OR transaction ID
|
||||
uint32_t limit = 50;
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to 10ms
|
||||
std::optional<uint32_t> time_limit_ms; // defaults to http-max-response-time-ms
|
||||
};
|
||||
|
||||
struct get_scheduled_transactions_result {
|
||||
@@ -563,8 +578,7 @@ public:
|
||||
const fc::time_point& deadline,
|
||||
ConvFn conv ) const {
|
||||
|
||||
fc::microseconds params_time_limit = p.time_limit_ms ? fc::milliseconds(*p.time_limit_ms) : fc::milliseconds(10);
|
||||
fc::time_point params_deadline = fc::time_point::now() + params_time_limit;
|
||||
fc::time_point params_deadline = p.time_limit_ms ? std::min(fc::time_point::now().safe_add(fc::milliseconds(*p.time_limit_ms)), deadline) : deadline;
|
||||
|
||||
struct http_params_t {
|
||||
name table;
|
||||
@@ -632,16 +646,17 @@ public:
|
||||
};
|
||||
|
||||
auto walk_table_row_range = [&]( auto itr, auto end_itr ) {
|
||||
auto cur_time = fc::time_point::now();
|
||||
vector<char> data;
|
||||
for( unsigned int count = 0;
|
||||
cur_time <= params_deadline && count < p.limit && itr != end_itr;
|
||||
++count, ++itr, cur_time = fc::time_point::now() ) {
|
||||
FC_CHECK_DEADLINE(deadline);
|
||||
uint32_t limit = p.limit;
|
||||
if (deadline != fc::time_point::maximum() && limit > max_return_items)
|
||||
limit = max_return_items;
|
||||
for( unsigned int count = 0; count < limit && itr != end_itr; ++count, ++itr ) {
|
||||
const auto* itr2 = d.find<chain::key_value_object, chain::by_scope_primary>( boost::make_tuple(t_id->id, itr->primary_key) );
|
||||
if( itr2 == nullptr ) continue;
|
||||
copy_inline_row(*itr2, data);
|
||||
http_params.rows.emplace_back(std::move(data), itr->payer);
|
||||
if (fc::time_point::now() >= params_deadline)
|
||||
break;
|
||||
}
|
||||
if( itr != end_itr ) {
|
||||
http_params.more = true;
|
||||
@@ -695,8 +710,7 @@ public:
|
||||
abi_def&& abi,
|
||||
const fc::time_point& deadline ) const {
|
||||
|
||||
fc::microseconds params_time_limit = p.time_limit_ms ? fc::milliseconds(*p.time_limit_ms) : fc::milliseconds(10);
|
||||
fc::time_point params_deadline = fc::time_point::now() + params_time_limit;
|
||||
fc::time_point params_deadline = p.time_limit_ms ? std::min(fc::time_point::now().safe_add(fc::milliseconds(*p.time_limit_ms)), deadline) : deadline;
|
||||
|
||||
struct http_params_t {
|
||||
name table;
|
||||
@@ -746,14 +760,15 @@ public:
|
||||
};
|
||||
|
||||
auto walk_table_row_range = [&]( auto itr, auto end_itr ) {
|
||||
auto cur_time = fc::time_point::now();
|
||||
vector<char> data;
|
||||
for( unsigned int count = 0;
|
||||
cur_time <= params_deadline && count < p.limit && itr != end_itr;
|
||||
++count, ++itr, cur_time = fc::time_point::now() ) {
|
||||
FC_CHECK_DEADLINE(deadline);
|
||||
uint32_t limit = p.limit;
|
||||
if (deadline != fc::time_point::maximum() && limit > max_return_items)
|
||||
limit = max_return_items;
|
||||
for( unsigned int count = 0; count < limit && itr != end_itr; ++count, ++itr ) {
|
||||
copy_inline_row(*itr, data);
|
||||
http_params.rows.emplace_back(std::move(data), itr->payer);
|
||||
if (fc::time_point::now() >= params_deadline)
|
||||
break;
|
||||
}
|
||||
if( itr != end_itr ) {
|
||||
http_params.more = true;
|
||||
@@ -832,7 +847,8 @@ public:
|
||||
// return deadline for call
|
||||
fc::time_point start() const {
|
||||
validate();
|
||||
return fc::time_point::now() + http_max_response_time;
|
||||
return http_max_response_time == fc::microseconds::maximum() ? fc::time_point::maximum()
|
||||
: fc::time_point::now() + http_max_response_time;
|
||||
}
|
||||
|
||||
using push_block_params = chain::signed_block;
|
||||
@@ -925,7 +941,7 @@ public:
|
||||
// the following will convert the input to array of 2 uint128_t in little endian, i.e. 50f0fa8360ec998f4bb65b00c86282f5 fb54b91bfed2fe7fe39a92d999d002c5
|
||||
// which is the format used by secondary index
|
||||
chain::key256_t k;
|
||||
uint8_t buffer[32];
|
||||
uint8_t buffer[32] = {};
|
||||
boost::multiprecision::export_bits(v, buffer, 8, false);
|
||||
memcpy(&k[0], buffer + 16, 16);
|
||||
memcpy(&k[1], buffer, 16);
|
||||
@@ -969,7 +985,6 @@ public:
|
||||
void enable_accept_transactions();
|
||||
|
||||
static void handle_guard_exception(const chain::guard_exception& e);
|
||||
void do_hard_replay(const variables_map& options);
|
||||
|
||||
bool account_queries_enabled() const;
|
||||
bool transaction_finality_status_enabled() const;
|
||||
@@ -979,8 +994,8 @@ public:
|
||||
// return variant of trx for logging, trace is modified to minimize log output
|
||||
fc::variant get_log_trx(const transaction& trx) const;
|
||||
|
||||
const controller::config& chain_config() const;
|
||||
private:
|
||||
static void log_guard_exception(const chain::guard_exception& e);
|
||||
|
||||
unique_ptr<class chain_plugin_impl> my;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,8 @@ add_executable( test_chain_plugin
|
||||
test_account_query_db.cpp
|
||||
test_trx_retry_db.cpp
|
||||
test_trx_finality_status_processing.cpp
|
||||
plugin_config_test.cpp
|
||||
main.cpp
|
||||
)
|
||||
target_link_libraries( test_chain_plugin chain_plugin eosio_testing)
|
||||
target_link_libraries( test_chain_plugin chain_plugin eosio_testing eosio_chain_wrap )
|
||||
add_test(NAME test_chain_plugin COMMAND plugins/chain_plugin/test/test_chain_plugin WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <array>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <eosio/chain/application.hpp>
|
||||
#include <eosio/chain_plugin/chain_plugin.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
BOOST_AUTO_TEST_CASE(chain_plugin_default_tests) {
|
||||
fc::temp_directory tmp;
|
||||
appbase::scoped_app app;
|
||||
|
||||
auto tmp_path = tmp.path().string();
|
||||
std::array args = {
|
||||
"test_chain_plugin", "--blocks-log-stride", "10", "--data-dir", tmp_path.c_str(),
|
||||
};
|
||||
|
||||
BOOST_CHECK(app->initialize<eosio::chain_plugin>(args.size(), const_cast<char**>(args.data())));
|
||||
auto& plugin = app->get_plugin<eosio::chain_plugin>();
|
||||
|
||||
auto* config = std::get_if<eosio::chain::partitioned_blocklog_config>(&plugin.chain_config().blog);
|
||||
BOOST_REQUIRE(config);
|
||||
BOOST_CHECK_EQUAL(config->max_retained_files, UINT32_MAX);
|
||||
}
|
||||
@@ -224,11 +224,15 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) {
|
||||
std::promise<chain_plugin*> plugin_promise;
|
||||
std::future<chain_plugin*> plugin_fut = plugin_promise.get_future();
|
||||
std::thread app_thread( [&]() {
|
||||
std::vector<const char*> argv = {"test"};
|
||||
app->initialize( argv.size(), (char**) &argv[0] );
|
||||
app->startup();
|
||||
plugin_promise.set_value(app->find_plugin<chain_plugin>());
|
||||
app->exec();
|
||||
try {
|
||||
std::vector<const char*> argv = {"test"};
|
||||
app->initialize(argv.size(), (char**)&argv[0]);
|
||||
app->startup();
|
||||
plugin_promise.set_value(app->find_plugin<chain_plugin>());
|
||||
app->exec();
|
||||
return;
|
||||
} FC_LOG_AND_DROP()
|
||||
BOOST_CHECK(!"app threw exception see logged error");
|
||||
} );
|
||||
(void)plugin_fut.get(); // wait for app to be started
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <eosio/chain_plugin/trx_retry_db.hpp>
|
||||
#include <eosio/chain_plugin/chain_plugin.hpp>
|
||||
|
||||
#include <eosio/chain/types.hpp>
|
||||
#include <eosio/chain/contract_types.hpp>
|
||||
@@ -145,7 +146,9 @@ struct trx_retry_db_impl {
|
||||
// Convert to variant with abi here and now because abi could change in very next transaction.
|
||||
// Alternatively, we could store off all the abis needed and do the conversion later, but as this is designed
|
||||
// to run on an API node, probably the best trade off to perform the abi serialization during block processing.
|
||||
tt.trx_trace_v = control.to_variant_with_abi( *trace, abi_serializer::create_yield_function( abi_max_time ) );
|
||||
auto resolver = get_serializers_cache(control, trace, abi_max_time);
|
||||
tt.trx_trace_v.clear();
|
||||
abi_serializer::to_variant(*trace, tt.trx_trace_v, resolver, abi_max_time);
|
||||
} catch( chain::abi_exception& ) {
|
||||
tt.trx_trace_v = *trace;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@ using namespace eosio;
|
||||
|
||||
#define CALL_WITH_400(api_name, api_handle, call_name, INVOKE, http_response_code) \
|
||||
{std::string("/v1/" #api_name "/" #call_name), \
|
||||
api_category::db_size, \
|
||||
[api_handle](string&&, string&& body, url_response_callback&& cb) mutable { \
|
||||
try { \
|
||||
body = parse_params<std::string, http_params_types::no_params>(body); \
|
||||
INVOKE \
|
||||
cb(http_response_code, fc::time_point::maximum(), fc::variant(result)); \
|
||||
cb(http_response_code, fc::variant(result)); \
|
||||
} catch (...) { \
|
||||
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
|
||||
} \
|
||||
|
||||
+326
-185
@@ -1,13 +1,15 @@
|
||||
|
||||
#include <eosio/http_plugin/http_plugin.hpp>
|
||||
#include <eosio/http_plugin/common.hpp>
|
||||
#include <eosio/http_plugin/beast_http_listener.hpp>
|
||||
#include <eosio/http_plugin/beast_http_session.hpp>
|
||||
#include <eosio/chain/exceptions.hpp>
|
||||
|
||||
#include <fc/log/logger_config.hpp>
|
||||
#include <fc/reflect/variant.hpp>
|
||||
#include <fc/network/listener.hpp>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
@@ -31,7 +33,6 @@ namespace eosio {
|
||||
|
||||
static http_plugin_defaults current_http_plugin_defaults;
|
||||
static bool verbose_http_errors = false;
|
||||
|
||||
void http_plugin::set_defaults(const http_plugin_defaults& config) {
|
||||
current_http_plugin_defaults = config;
|
||||
}
|
||||
@@ -42,6 +43,74 @@ namespace eosio {
|
||||
|
||||
using http_plugin_impl_ptr = std::shared_ptr<class http_plugin_impl>;
|
||||
|
||||
api_category to_category(std::string_view name) {
|
||||
if (name == "chain_ro") return api_category::chain_ro;
|
||||
if (name == "chain_rw") return api_category::chain_rw;
|
||||
if (name == "db_size") return api_category::db_size;
|
||||
if (name == "net_ro") return api_category::net_ro;
|
||||
if (name == "net_rw") return api_category::net_rw;
|
||||
if (name == "producer_ro") return api_category::producer_ro;
|
||||
if (name == "producer_rw") return api_category::producer_rw;
|
||||
if (name == "snapshot") return api_category::snapshot;
|
||||
if (name == "trace_api") return api_category::trace_api;
|
||||
if (name == "prometheus") return api_category::prometheus;
|
||||
if (name == "test_control") return api_category::test_control;
|
||||
return api_category::unknown;
|
||||
}
|
||||
|
||||
const char* from_category(api_category category) {
|
||||
if (category == api_category::chain_ro) return "chain_ro";
|
||||
if (category == api_category::chain_rw) return "chain_rw";
|
||||
if (category == api_category::db_size) return "db_size";
|
||||
if (category == api_category::net_ro) return "net_ro";
|
||||
if (category == api_category::net_rw) return "net_rw";
|
||||
if (category == api_category::producer_ro) return "producer_ro";
|
||||
if (category == api_category::producer_rw) return "producer_rw";
|
||||
if (category == api_category::snapshot) return "snapshot";
|
||||
if (category == api_category::trace_api) return "trace_api";
|
||||
if (category == api_category::prometheus) return "prometheus";
|
||||
if (category == api_category::test_control) return "test_control";
|
||||
if (category == api_category::node) return "node";
|
||||
// It's a programming error when the control flow reaches this point,
|
||||
// please make sure all the category names are returned from above statements.
|
||||
assert(false && "No correspding category name for the category value");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string category_plugin_name(api_category category) {
|
||||
if (category == api_category::db_size)
|
||||
return "eosio::db_size_api_plugin";
|
||||
if (category == api_category::trace_api)
|
||||
return "eosio::trace_api_plugin";
|
||||
if (category == api_category::prometheus)
|
||||
return "eosio::prometheus_plugin";
|
||||
if (category == api_category::test_control)
|
||||
return "eosio::test_control_plugin";
|
||||
if (api_category_set({api_category::chain_ro, api_category::chain_rw}).contains(category))
|
||||
return "eosio::chain_api_plugin";
|
||||
if (api_category_set({api_category::net_ro, api_category::net_rw}).contains(category))
|
||||
return "eosio::net_api_plugin";
|
||||
if (api_category_set({api_category::producer_ro, api_category::producer_rw, api_category::snapshot})
|
||||
.contains(category))
|
||||
return "eosio::producer_api_plugin";
|
||||
// It's a programming error when the control flow reaches this point,
|
||||
// please make sure all the plugin names are returned from above statements.
|
||||
assert(false && "No correspding plugin for the category value");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string category_names(api_category_set set) {
|
||||
if (set == api_category_set::all()) return "all";
|
||||
std::string result;
|
||||
for (uint32_t i = 1; i <= static_cast<uint32_t>(api_category::test_control); i<<=1) {
|
||||
if (set.contains(api_category(i))) {
|
||||
result += from_category(api_category(i));
|
||||
result += " ";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class http_plugin_impl : public std::enable_shared_from_this<http_plugin_impl> {
|
||||
public:
|
||||
http_plugin_impl() = default;
|
||||
@@ -52,14 +121,11 @@ namespace eosio {
|
||||
http_plugin_impl& operator=(const http_plugin_impl&) = delete;
|
||||
http_plugin_impl& operator=(http_plugin_impl&&) = delete;
|
||||
|
||||
std::optional<tcp::endpoint> listen_endpoint;
|
||||
std::map<std::string, api_category_set> categories_by_address;
|
||||
|
||||
std::filesystem::path unix_sock_path;
|
||||
http_plugin_state plugin_state{logger()};
|
||||
std::atomic<bool> listening;
|
||||
|
||||
shared_ptr<beast_http_listener<plain_session, tcp, tcp_socket_t > > beast_server;
|
||||
shared_ptr<beast_http_listener<unix_socket_session, stream_protocol, stream_protocol::socket > > beast_unix_server;
|
||||
|
||||
shared_ptr<http_plugin_state> plugin_state = std::make_shared<http_plugin_state>(logger());
|
||||
|
||||
/**
|
||||
* Make an internal_url_handler that will run the url_handler on the app() thread and then
|
||||
@@ -73,10 +139,11 @@ namespace eosio {
|
||||
* @param content_type - json or plain txt
|
||||
* @return the constructed internal_url_handler
|
||||
*/
|
||||
static detail::internal_url_handler make_app_thread_url_handler(const string& url, appbase::exec_queue to_queue, int priority, url_handler next, http_plugin_impl_ptr my, http_content_type content_type ) {
|
||||
static detail::internal_url_handler make_app_thread_url_handler(api_entry&& entry, appbase::exec_queue to_queue, int priority, http_plugin_impl_ptr my, http_content_type content_type ) {
|
||||
detail::internal_url_handler handler;
|
||||
handler.content_type = content_type;
|
||||
auto next_ptr = std::make_shared<url_handler>(std::move(next));
|
||||
handler.category = entry.category;
|
||||
auto next_ptr = std::make_shared<url_handler>(std::move(entry.handler));
|
||||
handler.fn = [my=std::move(my), priority, to_queue, next_ptr=std::move(next_ptr)]
|
||||
( detail::abstract_conn_ptr conn, string&& r, string&& b, url_response_callback&& then ) {
|
||||
if (auto error_str = conn->verify_max_bytes_in_flight(b.size()); !error_str.empty()) {
|
||||
@@ -84,8 +151,8 @@ namespace eosio {
|
||||
return;
|
||||
}
|
||||
|
||||
url_response_callback wrapped_then = [then=std::move(then)](int code, const fc::time_point& deadline, std::optional<fc::variant> resp) {
|
||||
then(code, deadline, std::move(resp));
|
||||
url_response_callback wrapped_then = [then=std::move(then)](int code, std::optional<fc::variant> resp) {
|
||||
then(code, std::move(resp));
|
||||
};
|
||||
|
||||
// post to the app thread taking shared ownership of next (via std::shared_ptr),
|
||||
@@ -111,10 +178,11 @@ namespace eosio {
|
||||
* @param next - the next handler for responses
|
||||
* @return the constructed internal_url_handler
|
||||
*/
|
||||
static detail::internal_url_handler make_http_thread_url_handler(const string& url, url_handler next, http_content_type content_type) {
|
||||
static detail::internal_url_handler make_http_thread_url_handler(api_entry&& entry, http_content_type content_type) {
|
||||
detail::internal_url_handler handler;
|
||||
handler.content_type = content_type;
|
||||
handler.fn = [next=std::move(next)]( const detail::abstract_conn_ptr& conn, string&& r, string&& b, url_response_callback&& then ) mutable {
|
||||
handler.category = entry.category;
|
||||
handler.fn = [next=std::move(entry.handler)]( const detail::abstract_conn_ptr& conn, string&& r, string&& b, url_response_callback&& then ) mutable {
|
||||
try {
|
||||
next(std::move(r), std::move(b), std::move(then));
|
||||
} catch( ... ) {
|
||||
@@ -123,23 +191,85 @@ namespace eosio {
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
||||
void add_aliases_for_endpoint( const tcp::endpoint& ep, const string& host, const string& port ) {
|
||||
auto resolved_port_str = std::to_string(ep.port());
|
||||
plugin_state->valid_hosts.emplace(host + ":" + port);
|
||||
plugin_state->valid_hosts.emplace(host + ":" + resolved_port_str);
|
||||
|
||||
bool is_unix_socket_address(const std::string& address) const {
|
||||
using boost::algorithm::starts_with;
|
||||
return starts_with(address, "/") || starts_with(address, "./") || starts_with(address, "../");
|
||||
}
|
||||
|
||||
void create_beast_server(bool isUnix) {
|
||||
if(isUnix) {
|
||||
beast_unix_server = std::make_shared<beast_http_listener<unix_socket_session, stream_protocol, stream_protocol::socket> >(plugin_state);
|
||||
fc_ilog( logger(), "created beast UNIX socket listener");
|
||||
bool on_loopback_only(const std::string& address) {
|
||||
if (is_unix_socket_address(address))
|
||||
return true;
|
||||
auto [host, port] = fc::split_host_port(address);
|
||||
boost::system::error_code ec;
|
||||
tcp::resolver resolver(plugin_state.thread_pool.get_executor());
|
||||
auto endpoints = resolver.resolve(host, port, boost::asio::ip::tcp::resolver::passive, ec);
|
||||
if (ec) {
|
||||
fc_wlog(logger(), "Cannot resolve address ${addr}: ${msg}", ("addr", address)("msg", ec.message()));
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
beast_server = std::make_shared<beast_http_listener<plain_session, tcp, tcp_socket_t> >(plugin_state);
|
||||
fc_ilog( logger(), "created beast HTTP listener");
|
||||
return std::all_of(endpoints.begin(), endpoints.end(), [](const auto& ep) {
|
||||
return ep.endpoint().address().is_loopback();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Protocol>
|
||||
void create_listener(const std::string& address, api_category_set categories) {
|
||||
const boost::posix_time::milliseconds accept_timeout(500);
|
||||
auto extra_listening_log_info = " for API categories: " + category_names(categories);
|
||||
using socket_type = typename Protocol::socket;
|
||||
auto create_session = [this, categories, address](socket_type&& socket) {
|
||||
std::string remote_endpoint;
|
||||
if constexpr (std::is_same_v<socket_type, tcp>) {
|
||||
boost::system::error_code re_ec;
|
||||
auto re = socket.remote_endpoint(re_ec);
|
||||
remote_endpoint = re_ec ? "unknown" : fc::to_string(re);
|
||||
} else {
|
||||
remote_endpoint = address;
|
||||
}
|
||||
std::make_shared<beast_http_session<socket_type>>(
|
||||
std::move(socket), plugin_state, std::move(remote_endpoint), categories, address)
|
||||
->run_session();
|
||||
};
|
||||
|
||||
fc::create_listener<Protocol>(plugin_state.thread_pool.get_executor(), logger(), accept_timeout, address,
|
||||
extra_listening_log_info, create_session);
|
||||
}
|
||||
|
||||
void create_beast_server(const std::string& address, api_category_set categories) {
|
||||
try {
|
||||
if (is_unix_socket_address(address)) {
|
||||
namespace fs = std::filesystem;
|
||||
fs::path sock_path = address;
|
||||
if (sock_path.is_relative())
|
||||
sock_path = fs::weakly_canonical(app().data_dir() / sock_path);
|
||||
create_listener<boost::asio::local::stream_protocol>(sock_path.string(), categories);
|
||||
} else {
|
||||
create_listener<tcp>(address, categories);
|
||||
}
|
||||
} catch (const fc::exception& e) {
|
||||
fc_elog(logger(), "http service failed to start for ${addr}: ${e}",
|
||||
("addr", address)("e", e.to_detail_string()));
|
||||
throw;
|
||||
} catch (const std::exception& e) {
|
||||
fc_elog(logger(), "http service failed to start for ${addr}: ${e}", ("addr", address)("e", e.what()));
|
||||
throw;
|
||||
} catch (...) {
|
||||
fc_elog(logger(), "error thrown from http io service");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::string addresses_for_category(api_category category) const {
|
||||
std::string result;
|
||||
for (const auto& [address, categories] : categories_by_address) {
|
||||
if (categories.contains(category)) {
|
||||
result += address;
|
||||
result += " ";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
http_plugin::http_plugin():my(new http_plugin_impl()){
|
||||
@@ -163,51 +293,82 @@ namespace eosio {
|
||||
else
|
||||
cfg.add_options()
|
||||
("http-server-address", bpo::value<string>(),
|
||||
"The local IP and port to listen for incoming http connections; leave blank to disable.");
|
||||
"The local IP and port to listen for incoming http connections; "
|
||||
"setting to http-category-address to enable http-category-address option. leave blank to disable.");
|
||||
|
||||
if (current_http_plugin_defaults.support_categories) {
|
||||
cfg.add_options()
|
||||
("http-category-address", bpo::value<std::vector<string>>(),
|
||||
"The local IP and port to listen for incoming http category connections."
|
||||
" Syntax: category,address\n"
|
||||
" Where the address can be <hostname>:port, <ipaddress>:port or unix socket path;\n"
|
||||
" in addition, unix socket path must starts with '/', './' or '../'. When relative path\n"
|
||||
" is used, it is relative to the data path.\n\n"
|
||||
" Valid categories include chain_ro, chain_rw, db_size, net_ro, net_rw, producer_ro\n"
|
||||
" producer_rw, snapshot, trace_api, prometheus, and test_control.\n\n"
|
||||
" A single `hostname:port` specification can be used by multiple categories\n"
|
||||
" However, two specifications having the same port with different hostname strings\n"
|
||||
" are always considered as configuration error regardless of whether they can be resolved\n"
|
||||
" into the same set of IP addresses.\n\n"
|
||||
" Examples:\n"
|
||||
" chain_ro,127.0.0.1:8080\n"
|
||||
" chain_ro,127.0.0.1:8081\n"
|
||||
" chain_rw,localhost:8081 # ERROR!, same port with different addresses\n"
|
||||
" chain_rw,[::1]:8082\n"
|
||||
" net_ro,localhost:8083\n"
|
||||
" net_rw,server.domain.net:8084\n"
|
||||
" producer_ro,/tmp/absolute_unix_path.sock\n"
|
||||
" producer_rw,./relative_unix_path.sock\n"
|
||||
" trace_api,:8086 # listen on all network interfaces\n\n"
|
||||
" Notice that the behavior for `[::1]` is platform dependent. For system with IPv4 mapped IPv6 networking\n"
|
||||
" is enabled, using `[::1]` will listen on both IPv4 and IPv6; other systems like FreeBSD, it will only\n"
|
||||
" listen on IPv6. On the other hand, the specfications without hostnames like `:8086` will always listen on\n"
|
||||
" both IPv4 and IPv6 on all platforms.");
|
||||
}
|
||||
|
||||
cfg.add_options()
|
||||
("access-control-allow-origin", bpo::value<string>()->notifier([this](const string& v) {
|
||||
my->plugin_state->access_control_allow_origin = v;
|
||||
my->plugin_state.access_control_allow_origin = v;
|
||||
fc_ilog( logger(), "configured http with Access-Control-Allow-Origin: ${o}",
|
||||
("o", my->plugin_state->access_control_allow_origin) );
|
||||
("o", my->plugin_state.access_control_allow_origin) );
|
||||
}),
|
||||
"Specify the Access-Control-Allow-Origin to be returned on each request")
|
||||
|
||||
("access-control-allow-headers", bpo::value<string>()->notifier([this](const string& v) {
|
||||
my->plugin_state->access_control_allow_headers = v;
|
||||
my->plugin_state.access_control_allow_headers = v;
|
||||
fc_ilog( logger(), "configured http with Access-Control-Allow-Headers : ${o}",
|
||||
("o", my->plugin_state->access_control_allow_headers) );
|
||||
("o", my->plugin_state.access_control_allow_headers) );
|
||||
}),
|
||||
"Specify the Access-Control-Allow-Headers to be returned on each request")
|
||||
|
||||
("access-control-max-age", bpo::value<string>()->notifier([this](const string& v) {
|
||||
my->plugin_state->access_control_max_age = v;
|
||||
my->plugin_state.access_control_max_age = v;
|
||||
fc_ilog( logger(), "configured http with Access-Control-Max-Age : ${o}",
|
||||
("o", my->plugin_state->access_control_max_age) );
|
||||
("o", my->plugin_state.access_control_max_age) );
|
||||
}),
|
||||
"Specify the Access-Control-Max-Age to be returned on each request.")
|
||||
|
||||
("access-control-allow-credentials",
|
||||
bpo::bool_switch()->notifier([this](bool v) {
|
||||
my->plugin_state->access_control_allow_credentials = v;
|
||||
my->plugin_state.access_control_allow_credentials = v;
|
||||
if( v ) fc_ilog( logger(), "configured http with Access-Control-Allow-Credentials: true" );
|
||||
})->default_value(false),
|
||||
"Specify if Access-Control-Allow-Credentials: true should be returned on each request.")
|
||||
("max-body-size", bpo::value<uint32_t>()->default_value(my->plugin_state->max_body_size),
|
||||
("max-body-size", bpo::value<uint32_t>()->default_value(my->plugin_state.max_body_size),
|
||||
"The maximum body size in bytes allowed for incoming RPC requests")
|
||||
("http-max-bytes-in-flight-mb", bpo::value<int64_t>()->default_value(500),
|
||||
"Maximum size in megabytes http_plugin should use for processing http requests. -1 for unlimited. 429 error response when exceeded." )
|
||||
("http-max-in-flight-requests", bpo::value<int32_t>()->default_value(-1),
|
||||
"Maximum number of requests http_plugin should use for processing http requests. 429 error response when exceeded." )
|
||||
("http-max-response-time-ms", bpo::value<int64_t>()->default_value(30),
|
||||
"Maximum time for processing a request, -1 for unlimited")
|
||||
("http-max-response-time-ms", bpo::value<int64_t>()->default_value(15),
|
||||
"Maximum time on main thread for processing a request, -1 for unlimited")
|
||||
("verbose-http-errors", bpo::bool_switch()->default_value(false),
|
||||
"Append the error log to HTTP responses")
|
||||
("http-validate-host", boost::program_options::value<bool>()->default_value(true),
|
||||
"If set to false, then any incoming \"Host\" header is considered valid")
|
||||
("http-alias", bpo::value<std::vector<string>>()->composing(),
|
||||
"Additionaly acceptable values for the \"Host\" header of incoming HTTP requests, can be specified multiple times. Includes http/s_server_address by default.")
|
||||
("http-threads", bpo::value<uint16_t>()->default_value( my->plugin_state->thread_pool_size ),
|
||||
"Additionally acceptable values for the \"Host\" header of incoming HTTP requests, can be specified multiple times. Includes http/s_server_address by default.")
|
||||
("http-threads", bpo::value<uint16_t>()->default_value( my->plugin_state.thread_pool_size ),
|
||||
"Number of worker threads in http thread pool")
|
||||
("http-keep-alive", bpo::value<bool>()->default_value(true),
|
||||
"If set to false, do not keep HTTP connections alive, even if client requests.")
|
||||
@@ -217,65 +378,96 @@ namespace eosio {
|
||||
void http_plugin::plugin_initialize(const variables_map& options) {
|
||||
try {
|
||||
handle_sighup(); // setup logging
|
||||
my->plugin_state->max_body_size = options.at( "max-body-size" ).as<uint32_t>();
|
||||
my->plugin_state.max_body_size = options.at( "max-body-size" ).as<uint32_t>();
|
||||
verbose_http_errors = options.at( "verbose-http-errors" ).as<bool>();
|
||||
|
||||
my->plugin_state->thread_pool_size = options.at( "http-threads" ).as<uint16_t>();
|
||||
EOS_ASSERT( my->plugin_state->thread_pool_size > 0, chain::plugin_config_exception,
|
||||
"http-threads ${num} must be greater than 0", ("num", my->plugin_state->thread_pool_size));
|
||||
my->plugin_state.thread_pool_size = options.at( "http-threads" ).as<uint16_t>();
|
||||
EOS_ASSERT( my->plugin_state.thread_pool_size > 0, chain::plugin_config_exception,
|
||||
"http-threads ${num} must be greater than 0", ("num", my->plugin_state.thread_pool_size));
|
||||
|
||||
auto max_bytes_mb = options.at( "http-max-bytes-in-flight-mb" ).as<int64_t>();
|
||||
EOS_ASSERT( (max_bytes_mb >= -1 && max_bytes_mb < std::numeric_limits<int64_t>::max() / (1024 * 1024)), chain::plugin_config_exception,
|
||||
"http-max-bytes-in-flight-mb (${max_bytes_mb}) must be equal to or greater than -1 and less than ${max}", ("max_bytes_mb", max_bytes_mb) ("max", std::numeric_limits<int64_t>::max() / (1024 * 1024)) );
|
||||
if ( max_bytes_mb == -1 ) {
|
||||
my->plugin_state->max_bytes_in_flight = std::numeric_limits<size_t>::max();
|
||||
my->plugin_state.max_bytes_in_flight = std::numeric_limits<size_t>::max();
|
||||
} else {
|
||||
my->plugin_state->max_bytes_in_flight = max_bytes_mb * 1024 * 1024;
|
||||
my->plugin_state.max_bytes_in_flight = max_bytes_mb * 1024 * 1024;
|
||||
}
|
||||
my->plugin_state->max_requests_in_flight = options.at( "http-max-in-flight-requests" ).as<int32_t>();
|
||||
my->plugin_state.max_requests_in_flight = options.at( "http-max-in-flight-requests" ).as<int32_t>();
|
||||
int64_t max_reponse_time_ms = options.at("http-max-response-time-ms").as<int64_t>();
|
||||
EOS_ASSERT( max_reponse_time_ms == -1 || max_reponse_time_ms >= 0, chain::plugin_config_exception,
|
||||
"http-max-response-time-ms must be -1, or non-negative: ${m}", ("m", max_reponse_time_ms) );
|
||||
// set to one year for -1, unlimited, since this is added to fc::time_point::now() for a deadline
|
||||
my->plugin_state->max_response_time = max_reponse_time_ms == -1 ?
|
||||
fc::days(365) : fc::microseconds( max_reponse_time_ms * 1000 );
|
||||
my->plugin_state.max_response_time = max_reponse_time_ms == -1 ?
|
||||
fc::microseconds::maximum() : fc::microseconds( max_reponse_time_ms * 1000 );
|
||||
|
||||
my->plugin_state->validate_host = options.at("http-validate-host").as<bool>();
|
||||
my->plugin_state.validate_host = options.at("http-validate-host").as<bool>();
|
||||
if( options.count( "http-alias" )) {
|
||||
const auto& aliases = options["http-alias"].as<vector<string>>();
|
||||
my->plugin_state->valid_hosts.insert(aliases.begin(), aliases.end());
|
||||
}
|
||||
|
||||
my->plugin_state->keep_alive = options.at("http-keep-alive").as<bool>();
|
||||
|
||||
tcp::resolver resolver( app().get_io_service());
|
||||
if( options.count( "http-server-address" ) && options.at( "http-server-address" ).as<string>().length()) {
|
||||
string lipstr = options.at( "http-server-address" ).as<string>();
|
||||
string host = lipstr.substr( 0, lipstr.find( ':' ));
|
||||
string port = lipstr.substr( host.size() + 1, lipstr.size());
|
||||
try {
|
||||
my->listen_endpoint = *resolver.resolve( tcp::v4(), host, port );
|
||||
fc_ilog(logger(), "configured http to listen on ${h}:${p}", ("h", host)( "p", port ));
|
||||
} catch ( const boost::system::system_error& ec ) {
|
||||
fc_elog(logger(), "failed to configure http to listen on ${h}:${p} (${m})",
|
||||
("h", host)( "p", port )( "m", ec.what()));
|
||||
}
|
||||
|
||||
// add in resolved hosts and ports as well
|
||||
if (my->listen_endpoint) {
|
||||
my->add_aliases_for_endpoint(*my->listen_endpoint, host, port);
|
||||
for (const auto& alias : aliases ) {
|
||||
auto [host, port] = fc::split_host_port(alias);
|
||||
my->plugin_state.valid_hosts.insert(host);
|
||||
}
|
||||
}
|
||||
|
||||
if( options.count( "unix-socket-path" ) && !options.at( "unix-socket-path" ).as<string>().empty()) {
|
||||
std::filesystem::path sock_path = options.at("unix-socket-path").as<string>();
|
||||
if (sock_path.is_relative())
|
||||
sock_path = app().data_dir() / sock_path;
|
||||
|
||||
my->unix_sock_path = sock_path;
|
||||
my->plugin_state.keep_alive = options.at("http-keep-alive").as<bool>();
|
||||
|
||||
std::string http_server_address;
|
||||
if (options.count("http-server-address")) {
|
||||
http_server_address = options.at("http-server-address").as<string>();
|
||||
if (http_server_address.size() && http_server_address != "http-category-address") {
|
||||
my->categories_by_address[http_server_address].insert(api_category::node);
|
||||
}
|
||||
}
|
||||
|
||||
my->plugin_state->server_header = current_http_plugin_defaults.server_header;
|
||||
if (options.count("unix-socket-path") && !options.at("unix-socket-path").as<string>().empty()) {
|
||||
std::string unix_sock_path = options.at("unix-socket-path").as<string>();
|
||||
if (unix_sock_path.size()) {
|
||||
if (unix_sock_path[0] != '/') unix_sock_path = "./" + unix_sock_path;
|
||||
my->categories_by_address[unix_sock_path].insert(api_category::node);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.count("http-category-address") != 0) {
|
||||
auto plugins = options["plugin"].as<std::vector<std::string>>();
|
||||
auto has_plugin = [&plugins](const std::string& s) {
|
||||
return std::find(plugins.begin(), plugins.end(), s) != plugins.end();
|
||||
};
|
||||
|
||||
EOS_ASSERT(http_server_address == "http-category-address" && options.count("unix-socket-path") == 0,
|
||||
chain::plugin_config_exception,
|
||||
"when http-category-address is specified, http-server-address must be set as "
|
||||
"`http-category-address` and `unix-socket-path` must be left unspecified");
|
||||
|
||||
std::map<std::string, std::string> hostnames;
|
||||
auto addresses = options["http-category-address"].as<vector<string>>();
|
||||
for (const auto& spec : addresses) {
|
||||
auto comma_pos = spec.find(',');
|
||||
EOS_ASSERT(comma_pos > 0 && comma_pos != std::string_view::npos, chain::plugin_config_exception,
|
||||
"http-category-address '${spec}' does not contain a required comma to separate the category and address",
|
||||
("spec", spec));
|
||||
auto category_name = spec.substr(0, comma_pos);
|
||||
auto category = to_category(category_name);
|
||||
|
||||
EOS_ASSERT(category != api_category::unknown, chain::plugin_config_exception,
|
||||
"invalid category name `${name}` for http_category_address", ("name", std::string(category_name)));
|
||||
|
||||
EOS_ASSERT(has_plugin(category_plugin_name(category)), chain::plugin_config_exception,
|
||||
"--plugin=${plugin_name} is required for --http-category-address=${spec}",
|
||||
("plugin_name", category_plugin_name(category))("spec", spec));
|
||||
|
||||
auto address = spec.substr(comma_pos+1);
|
||||
|
||||
auto [host, port] = fc::split_host_port(address);
|
||||
if (port.size()) {
|
||||
auto [itr, inserted] = hostnames.try_emplace(port, host);
|
||||
EOS_ASSERT(inserted || host == itr->second, chain::plugin_config_exception,
|
||||
"unable to listen to port ${port} for both ${host} and ${prev}",
|
||||
("port", port)("host", host)("prev", itr->second));
|
||||
}
|
||||
my->categories_by_address[address].insert(category);
|
||||
}
|
||||
}
|
||||
my->plugin_state.server_header = current_http_plugin_defaults.server_header;
|
||||
|
||||
|
||||
//watch out for the returns above when adding new code here
|
||||
@@ -285,71 +477,24 @@ namespace eosio {
|
||||
void http_plugin::plugin_startup() {
|
||||
app().executor().post(appbase::priority::high, [this] ()
|
||||
{
|
||||
// The reason we post here is because we want blockchain replay to happen before we start listening.
|
||||
try {
|
||||
my->plugin_state->thread_pool.start( my->plugin_state->thread_pool_size, [](const fc::exception& e) {
|
||||
my->plugin_state.thread_pool.start( my->plugin_state.thread_pool_size, [](const fc::exception& e) {
|
||||
fc_elog( logger(), "Exception in http thread pool, exiting: ${e}", ("e", e.to_detail_string()) );
|
||||
app().quit();
|
||||
} );
|
||||
|
||||
if(my->listen_endpoint) {
|
||||
try {
|
||||
my->create_beast_server(false);
|
||||
|
||||
fc_ilog( logger(), "start listening for http requests (boost::beast)" );
|
||||
|
||||
my->beast_server->listen(*my->listen_endpoint);
|
||||
my->beast_server->start_accept();
|
||||
} catch ( const fc::exception& e ){
|
||||
fc_elog( logger(), "http service failed to start: ${e}", ("e", e.to_detail_string()) );
|
||||
throw;
|
||||
} catch ( const std::exception& e ){
|
||||
fc_elog( logger(), "http service failed to start: ${e}", ("e", e.what()) );
|
||||
throw;
|
||||
} catch (...) {
|
||||
fc_elog( logger(), "error thrown from http io service" );
|
||||
throw;
|
||||
}
|
||||
for (const auto& [address, categories]: my->categories_by_address) {
|
||||
my->create_beast_server(address, categories);
|
||||
}
|
||||
|
||||
if(!my->unix_sock_path.empty()) {
|
||||
try {
|
||||
my->create_beast_server(true);
|
||||
|
||||
// The maximum length of the socket path is defined by sockaddr_un::sun_path. On Linux,
|
||||
// according to unix(7), it is 108 bytes. On FreeBSD, according to unix(4), it is 104 bytes.
|
||||
// Therefore, we create the unix socket with the relative path to its parent path to avoid the problem.
|
||||
|
||||
auto cwd = std::filesystem::current_path();
|
||||
std::filesystem::current_path(my->unix_sock_path.parent_path());
|
||||
asio::local::stream_protocol::endpoint endpoint(my->unix_sock_path.filename().string());
|
||||
my->beast_unix_server->listen(endpoint);
|
||||
std::filesystem::current_path(cwd);
|
||||
|
||||
my->beast_unix_server->start_accept();
|
||||
} catch ( const fc::exception& e ){
|
||||
fc_elog( logger(), "unix socket service (${path}) failed to start: ${e}", ("e", e.to_detail_string())("path",my->unix_sock_path) );
|
||||
throw;
|
||||
} catch ( const std::exception& e ){
|
||||
fc_elog( logger(), "unix socket service (${path}) failed to start: ${e}", ("e", e.what())("path",my->unix_sock_path) );
|
||||
throw;
|
||||
} catch (...) {
|
||||
fc_elog( logger(), "error thrown from unix socket (${path}) io service", ("path",my->unix_sock_path) );
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
add_api({{
|
||||
std::string("/v1/node/get_supported_apis"),
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
try {
|
||||
auto result = (*this).get_supported_apis();
|
||||
cb(200, fc::time_point::maximum(), fc::variant(result));
|
||||
} catch (...) {
|
||||
handle_exception("node", "get_supported_apis", body.empty() ? "{}" : body, cb);
|
||||
}
|
||||
}
|
||||
}}, appbase::exec_queue::read_only);
|
||||
|
||||
my->listening.store(true);
|
||||
} catch(fc::exception& e) {
|
||||
fc_elog(logger(), "http_plugin startup fails for ${e}", ("e", e.to_detail_string()));
|
||||
app().quit();
|
||||
} catch(std::exception& e) {
|
||||
fc_elog(logger(), "http_plugin startup fails for ${e}", ("e", e.what()));
|
||||
app().quit();
|
||||
} catch (...) {
|
||||
fc_elog(logger(), "http_plugin startup fails, shutting down");
|
||||
app().quit();
|
||||
@@ -362,37 +507,41 @@ namespace eosio {
|
||||
}
|
||||
|
||||
void http_plugin::plugin_shutdown() {
|
||||
if(my->beast_server)
|
||||
my->beast_server->stop_listening();
|
||||
if(my->beast_unix_server)
|
||||
my->beast_unix_server->stop_listening();
|
||||
|
||||
my->plugin_state->thread_pool.stop();
|
||||
|
||||
my->beast_server.reset();
|
||||
my->beast_unix_server.reset();
|
||||
my->plugin_state.thread_pool.stop();
|
||||
|
||||
// release http_plugin_impl_ptr shared_ptrs captured in url handlers
|
||||
my->plugin_state->url_handlers.clear();
|
||||
my->plugin_state.url_handlers.clear();
|
||||
|
||||
fc_ilog( logger(), "exit shutdown");
|
||||
}
|
||||
|
||||
void http_plugin::add_handler(const string& url, const url_handler& handler, appbase::exec_queue q, int priority, http_content_type content_type) {
|
||||
fc_ilog( logger(), "add api url: ${c}", ("c", url) );
|
||||
auto p = my->plugin_state->url_handlers.emplace(url, my->make_app_thread_url_handler(url, q, priority, handler, my, content_type));
|
||||
EOS_ASSERT( p.second, chain::plugin_config_exception, "http url ${u} is not unique", ("u", url) );
|
||||
void log_add_handler(http_plugin_impl* my, api_entry& entry) {
|
||||
auto addrs = my->addresses_for_category(entry.category);
|
||||
if (addrs.size())
|
||||
addrs = "on " + addrs;
|
||||
else
|
||||
addrs = "disabled for category address not configured";
|
||||
fc_ilog(logger(), "add ${category} api url: ${c} ${addrs}",
|
||||
("category", from_category(entry.category))("c", entry.path)("addrs", addrs));
|
||||
}
|
||||
|
||||
void http_plugin::add_async_handler(const string& url, const url_handler& handler, http_content_type content_type) {
|
||||
fc_ilog( logger(), "add api url: ${c}", ("c", url) );
|
||||
auto p = my->plugin_state->url_handlers.emplace(url, my->make_http_thread_url_handler(url, handler, content_type));
|
||||
EOS_ASSERT( p.second, chain::plugin_config_exception, "http url ${u} is not unique", ("u", url) );
|
||||
void http_plugin::add_handler(api_entry&& entry, appbase::exec_queue q, int priority, http_content_type content_type) {
|
||||
log_add_handler(my.get(), entry);
|
||||
std::string path = entry.path;
|
||||
auto p = my->plugin_state.url_handlers.emplace(path, my->make_app_thread_url_handler(std::move(entry), q, priority, my, content_type));
|
||||
EOS_ASSERT( p.second, chain::plugin_config_exception, "http url ${u} is not unique", ("u", path) );
|
||||
}
|
||||
|
||||
void http_plugin::add_async_handler(api_entry&& entry, http_content_type content_type) {
|
||||
log_add_handler(my.get(), entry);
|
||||
std::string path = entry.path;
|
||||
auto p = my->plugin_state.url_handlers.emplace(path, my->make_http_thread_url_handler(std::move(entry), content_type));
|
||||
EOS_ASSERT( p.second, chain::plugin_config_exception, "http url ${u} is not unique", ("u", path) );
|
||||
}
|
||||
|
||||
void http_plugin::post_http_thread_pool(std::function<void()> f) {
|
||||
if( f )
|
||||
boost::asio::post( my->plugin_state->thread_pool.get_executor(), f );
|
||||
boost::asio::post( my->plugin_state.thread_pool.get_executor(), f );
|
||||
}
|
||||
|
||||
void http_plugin::handle_exception( const char* api_name, const char* call_name, const string& body, const url_response_callback& cb) {
|
||||
@@ -401,48 +550,48 @@ namespace eosio {
|
||||
throw;
|
||||
} catch (chain::unknown_block_exception& e) {
|
||||
error_results results{400, "Unknown Block", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 400, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 400, fc::variant( results ));
|
||||
fc_dlog( logger(), "Unknown block while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
|
||||
} catch (chain::invalid_http_request& e) {
|
||||
error_results results{400, "Invalid Request", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 400, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 400, fc::variant( results ));
|
||||
fc_dlog( logger(), "Invalid http request while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
|
||||
} catch (chain::account_query_exception& e) {
|
||||
error_results results{400, "Account lookup", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 400, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 400, fc::variant( results ));
|
||||
fc_dlog( logger(), "Account query exception while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
|
||||
} catch (chain::unsatisfied_authorization& e) {
|
||||
error_results results{401, "UnAuthorized", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 401, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 401, fc::variant( results ));
|
||||
fc_dlog( logger(), "Auth error while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
|
||||
} catch (chain::tx_duplicate& e) {
|
||||
error_results results{409, "Conflict", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 409, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 409, fc::variant( results ));
|
||||
fc_dlog( logger(), "Duplicate trx while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)("call", call_name)("e", e.to_detail_string()) );
|
||||
} catch (fc::eof_exception& e) {
|
||||
error_results results{422, "Unprocessable Entity", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 422, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 422, fc::variant( results ));
|
||||
fc_elog( logger(), "Unable to parse arguments to ${api}.${call}", ("api", api_name)( "call", call_name ) );
|
||||
fc_dlog( logger(), "Bad arguments: ${args}", ("args", body) );
|
||||
} catch (fc::exception& e) {
|
||||
error_results results{500, "Internal Service Error", error_results::error_info(e, verbose_http_errors)};
|
||||
cb( 500, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 500, fc::variant( results ));
|
||||
fc_dlog( logger(), "Exception while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)( "call", call_name )("e", e.to_detail_string()) );
|
||||
} catch (std::exception& e) {
|
||||
error_results results{500, "Internal Service Error", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, e.what())), verbose_http_errors)};
|
||||
cb( 500, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 500, fc::variant( results ));
|
||||
fc_dlog( logger(), "STD Exception encountered while processing ${api}.${call}: ${e}",
|
||||
("api", api_name)("call", call_name)("e", e.what()) );
|
||||
} catch (...) {
|
||||
error_results results{500, "Internal Service Error",
|
||||
error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Exception" )), verbose_http_errors)};
|
||||
cb( 500, fc::time_point::maximum(), fc::variant( results ));
|
||||
cb( 500, fc::variant( results ));
|
||||
fc_elog( logger(), "Unknown Exception encountered while processing ${api}.${call}",
|
||||
("api", api_name)( "call", call_name ) );
|
||||
}
|
||||
@@ -451,39 +600,31 @@ namespace eosio {
|
||||
}
|
||||
}
|
||||
|
||||
bool http_plugin::is_on_loopback() const {
|
||||
return (!my->listen_endpoint || my->listen_endpoint->address().is_loopback());
|
||||
}
|
||||
|
||||
bool http_plugin::is_secure() const {
|
||||
return (!my->listen_endpoint || my->listen_endpoint->address().is_loopback());
|
||||
bool http_plugin::is_on_loopback(api_category category) const {
|
||||
return std::all_of(my->categories_by_address.begin(), my->categories_by_address.end(),
|
||||
[&category, this](const auto& entry) {
|
||||
const auto& [address, categories] = entry;
|
||||
return !categories.contains(category) || my->on_loopback_only(address);
|
||||
});
|
||||
}
|
||||
|
||||
bool http_plugin::verbose_errors() {
|
||||
return verbose_http_errors;
|
||||
}
|
||||
|
||||
http_plugin::get_supported_apis_result http_plugin::get_supported_apis()const {
|
||||
get_supported_apis_result result;
|
||||
|
||||
for (const auto& handler : my->plugin_state->url_handlers) {
|
||||
if (handler.first != "/v1/node/get_supported_apis")
|
||||
result.apis.emplace_back(handler.first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fc::microseconds http_plugin::get_max_response_time()const {
|
||||
return my->plugin_state->max_response_time;
|
||||
return my->plugin_state.max_response_time;
|
||||
}
|
||||
|
||||
size_t http_plugin::get_max_body_size()const {
|
||||
return my->plugin_state->max_body_size;
|
||||
return my->plugin_state.max_body_size;
|
||||
}
|
||||
|
||||
void http_plugin::register_update_metrics(std::function<void(metrics)>&& fun) {
|
||||
my->plugin_state->update_metrics = std::move(fun);
|
||||
my->plugin_state.update_metrics = std::move(fun);
|
||||
}
|
||||
|
||||
std::atomic<bool>& http_plugin::listening() {
|
||||
return my->listening;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <eosio/chain/types.hpp>
|
||||
#include <stdint.h>
|
||||
namespace eosio {
|
||||
|
||||
enum class api_category : uint32_t {
|
||||
unknown = 0,
|
||||
chain_ro = 1 << 0,
|
||||
chain_rw = 1 << 1,
|
||||
db_size = 1 << 2,
|
||||
net_ro = 1 << 3,
|
||||
net_rw = 1 << 4,
|
||||
producer_ro = 1 << 5,
|
||||
producer_rw = 1 << 6,
|
||||
snapshot = 1 << 7,
|
||||
trace_api = 1 << 8,
|
||||
prometheus = 1 << 9,
|
||||
test_control = 1 << 10,
|
||||
node = UINT32_MAX
|
||||
};
|
||||
|
||||
class api_category_set {
|
||||
uint32_t data = {};
|
||||
public:
|
||||
constexpr api_category_set() = default;
|
||||
constexpr explicit api_category_set(api_category c) : data(static_cast<uint32_t>(c)){}
|
||||
constexpr api_category_set(std::initializer_list<api_category> l) {
|
||||
for (auto c: l)
|
||||
insert(c);
|
||||
}
|
||||
constexpr bool contains(api_category category) const {
|
||||
return eosio::chain::has_field(data, category);
|
||||
}
|
||||
constexpr void insert(api_category category) {
|
||||
data = eosio::chain::set_field(data, category, true);
|
||||
}
|
||||
|
||||
constexpr static api_category_set all() {
|
||||
return api_category_set(api_category::node);
|
||||
}
|
||||
|
||||
constexpr bool operator == (const api_category_set& other) const {
|
||||
return data == other.data;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/http_plugin/beast_http_session.hpp>
|
||||
#include <eosio/http_plugin/common.hpp>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace eosio {
|
||||
// since beast_http_listener handles both TCP and UNIX endpoints we need a template here
|
||||
// to get the path if makes sense, so that we can call ::unlink() before opening socket
|
||||
// in beast_http_listener::listen() by tdefault return blank string
|
||||
template<typename T>
|
||||
std::string get_endpoint_path(const T& endpt) { return {}; }
|
||||
|
||||
std::string get_endpoint_path(const stream_protocol::endpoint& endpt) { return endpt.path(); }
|
||||
|
||||
// Accepts incoming connections and launches the sessions
|
||||
// session_type should be a subclass of beast_http_session
|
||||
// protocol type must have sub types acceptor and endpoint, e.g. boost::asio::ip::tcp;
|
||||
// socket type must be the socket e.g, boost::asio::ip::tcp::socket
|
||||
template<typename session_type, typename protocol_type, typename socket_type>
|
||||
class beast_http_listener : public std::enable_shared_from_this<beast_http_listener<session_type, protocol_type, socket_type>> {
|
||||
private:
|
||||
bool is_listening_ = false;
|
||||
|
||||
std::shared_ptr<http_plugin_state> plugin_state_;
|
||||
|
||||
typename protocol_type::acceptor acceptor_;
|
||||
socket_type socket_;
|
||||
|
||||
boost::asio::deadline_timer accept_error_timer_;
|
||||
|
||||
public:
|
||||
beast_http_listener() = default;
|
||||
beast_http_listener(const beast_http_listener&) = delete;
|
||||
beast_http_listener(beast_http_listener&&) = delete;
|
||||
|
||||
beast_http_listener& operator=(const beast_http_listener&) = delete;
|
||||
beast_http_listener& operator=(beast_http_listener&&) = delete;
|
||||
|
||||
beast_http_listener(std::shared_ptr<http_plugin_state> plugin_state) : is_listening_(false), plugin_state_(std::move(plugin_state)), acceptor_(plugin_state_->thread_pool.get_executor()), socket_(plugin_state_->thread_pool.get_executor()), accept_error_timer_(plugin_state_->thread_pool.get_executor()) {}
|
||||
|
||||
virtual ~beast_http_listener() {
|
||||
try {
|
||||
stop_listening();
|
||||
} catch(...) {}
|
||||
};
|
||||
|
||||
void listen(typename protocol_type::endpoint endpoint) {
|
||||
if(is_listening_) return;
|
||||
|
||||
// for unix sockets we should delete the old socket
|
||||
if(std::is_same<socket_type, stream_protocol::socket>::value) {
|
||||
::unlink(get_endpoint_path(endpoint).c_str());
|
||||
}
|
||||
|
||||
beast::error_code ec;
|
||||
// Open the acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if(ec) {
|
||||
fail(ec, "open", plugin_state_->logger, "closing port");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
acceptor_.set_option(asio::socket_base::reuse_address(true), ec);
|
||||
if(ec) {
|
||||
fail(ec, "set_option", plugin_state_->logger, "closing port");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to the server address
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if(ec) {
|
||||
fail(ec, "bind", plugin_state_->logger, "closing port");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening for connections
|
||||
auto max_connections = asio::socket_base::max_listen_connections;
|
||||
fc_ilog(plugin_state_->logger, "acceptor_.listen()");
|
||||
acceptor_.listen(max_connections, ec);
|
||||
if(ec) {
|
||||
fail(ec, "listen", plugin_state_->logger, "closing port");
|
||||
return;
|
||||
}
|
||||
is_listening_ = true;
|
||||
}
|
||||
|
||||
// Start accepting incoming connections
|
||||
void start_accept() {
|
||||
if(!is_listening_) return;
|
||||
do_accept();
|
||||
}
|
||||
|
||||
bool is_listening() {
|
||||
return is_listening_;
|
||||
}
|
||||
|
||||
void stop_listening() {
|
||||
if(is_listening_) {
|
||||
plugin_state_->thread_pool.stop();
|
||||
is_listening_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void do_accept() {
|
||||
auto self = this->shared_from_this();
|
||||
acceptor_.async_accept(socket_, [self](beast::error_code ec) {
|
||||
if(ec == boost::system::errc::too_many_files_open) {
|
||||
// retry accept() after timeout to avoid cpu loop on accept
|
||||
fail(ec, "accept", self->plugin_state_->logger, "too many files open - waiting 500ms");
|
||||
self->accept_error_timer_.expires_from_now(boost::posix_time::milliseconds(500));
|
||||
self->accept_error_timer_.async_wait([self = self->shared_from_this()](beast::error_code ec) {
|
||||
if (!ec)
|
||||
self->do_accept();
|
||||
});
|
||||
} else {
|
||||
if (ec) {
|
||||
fail(ec, "accept", self->plugin_state_->logger, "closing connection");
|
||||
} else {
|
||||
// Create the session object and run it
|
||||
std::string remote_endpoint = boost::lexical_cast<std::string>(self->socket_.remote_endpoint());
|
||||
std::make_shared<session_type>(
|
||||
std::move(self->socket_),
|
||||
self->plugin_state_,
|
||||
std::move(remote_endpoint))
|
||||
->run_session();
|
||||
}
|
||||
|
||||
// Accept another connection
|
||||
self->do_accept();
|
||||
}
|
||||
});
|
||||
}
|
||||
};// end class beast_http_Listener
|
||||
}// namespace eosio
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/http_plugin/common.hpp>
|
||||
#include <eosio/http_plugin/api_category.hpp>
|
||||
|
||||
#include <fc/io/json.hpp>
|
||||
#include <fc/time.hpp>
|
||||
|
||||
#include <boost/iostreams/device/array.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
@@ -14,21 +17,6 @@ namespace eosio {
|
||||
|
||||
using std::chrono::steady_clock;
|
||||
|
||||
typedef asio::basic_stream_socket<asio::ip::tcp, asio::io_context::executor_type> tcp_socket_t;
|
||||
|
||||
using boost::asio::local::stream_protocol;
|
||||
|
||||
#if BOOST_VERSION < 107300
|
||||
using local_stream = beast::basic_stream<
|
||||
stream_protocol,
|
||||
asio::executor,
|
||||
beast::unlimited_rate_policy>;
|
||||
#else
|
||||
using local_stream = beast::basic_stream<
|
||||
stream_protocol,
|
||||
asio::any_io_executor,
|
||||
beast::unlimited_rate_policy>;
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// fail()
|
||||
@@ -40,20 +28,14 @@ void fail(beast::error_code ec, char const* what, fc::logger& logger, char const
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
bool allow_host(const http::request<http::string_body>& req, T& session,
|
||||
const std::shared_ptr<http_plugin_state>& plugin_state) {
|
||||
auto is_conn_secure = session.is_secure();
|
||||
bool allow_host(const std::string& host_str, tcp::socket& socket,
|
||||
const http_plugin_state& plugin_state) {
|
||||
|
||||
auto& socket = session.socket();
|
||||
auto& lowest_layer = beast::get_lowest_layer(socket);
|
||||
auto local_endpoint = lowest_layer.local_endpoint();
|
||||
auto local_socket_host_port = local_endpoint.address().to_string() + ":" + std::to_string(local_endpoint.port());
|
||||
const std::string host_str(req["host"]);
|
||||
if(host_str.empty() || !host_is_valid(*plugin_state,
|
||||
if(host_str.empty() || !host_is_valid(plugin_state,
|
||||
host_str,
|
||||
local_socket_host_port,
|
||||
is_conn_secure)) {
|
||||
local_endpoint.address())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -84,9 +66,11 @@ std::string to_log_string(const T& req, size_t max_size = 1024) {
|
||||
|
||||
// use the Curiously Recurring Template Pattern so that
|
||||
// the same code works with both regular TCP sockets and UNIX sockets
|
||||
template<class Derived>
|
||||
class beast_http_session : public detail::abstract_conn {
|
||||
protected:
|
||||
template <class Socket>
|
||||
class beast_http_session : public detail::abstract_conn,
|
||||
public std::enable_shared_from_this<beast_http_session<Socket>> {
|
||||
Socket socket_;
|
||||
api_category_set categories_;
|
||||
beast::flat_buffer buffer_;
|
||||
|
||||
// time points for timeout measurement and perf metrics
|
||||
@@ -99,8 +83,9 @@ protected:
|
||||
// HTTP response object
|
||||
std::optional<http::response<http::string_body>> res_;
|
||||
|
||||
std::shared_ptr<http_plugin_state> plugin_state_;
|
||||
http_plugin_state& plugin_state_;
|
||||
std::string remote_endpoint_;
|
||||
std::string local_address_;
|
||||
|
||||
// whether response should be sent back to client when an exception occurs
|
||||
bool is_send_exception_response_ = true;
|
||||
@@ -127,11 +112,12 @@ protected:
|
||||
res_->version(req.version());
|
||||
res_->set(http::field::content_type, "application/json");
|
||||
res_->keep_alive(req.keep_alive());
|
||||
if(plugin_state_->server_header.size())
|
||||
res_->set(http::field::server, plugin_state_->server_header);
|
||||
if(plugin_state_.server_header.size())
|
||||
res_->set(http::field::server, plugin_state_.server_header);
|
||||
|
||||
// Request path must be absolute and not contain "..".
|
||||
if(req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) {
|
||||
fc_dlog( plugin_state_.get_logger(), "Return bad_reqest: ${target}", ("target", std::string(req.target())) );
|
||||
error_results results{static_cast<uint16_t>(http::status::bad_request), "Illegal request-target"};
|
||||
send_response( fc::json::to_string( results, fc::time_point::maximum() ),
|
||||
static_cast<unsigned int>(http::status::bad_request) );
|
||||
@@ -139,23 +125,24 @@ protected:
|
||||
}
|
||||
|
||||
try {
|
||||
if(!derived().allow_host(req)) {
|
||||
if(!allow_host(req)) {
|
||||
fc_dlog( plugin_state_.get_logger(), "bad host: ${HOST}", ("HOST", std::string(req["host"])));
|
||||
error_results results{static_cast<uint16_t>(http::status::bad_request), "Disallowed HTTP HOST header in the request"};
|
||||
send_response( fc::json::to_string( results, fc::time_point::maximum() ),
|
||||
static_cast<unsigned int>(http::status::bad_request) );
|
||||
return;
|
||||
}
|
||||
|
||||
if(!plugin_state_->access_control_allow_origin.empty()) {
|
||||
res_->set("Access-Control-Allow-Origin", plugin_state_->access_control_allow_origin);
|
||||
if(!plugin_state_.access_control_allow_origin.empty()) {
|
||||
res_->set("Access-Control-Allow-Origin", plugin_state_.access_control_allow_origin);
|
||||
}
|
||||
if(!plugin_state_->access_control_allow_headers.empty()) {
|
||||
res_->set("Access-Control-Allow-Headers", plugin_state_->access_control_allow_headers);
|
||||
if(!plugin_state_.access_control_allow_headers.empty()) {
|
||||
res_->set("Access-Control-Allow-Headers", plugin_state_.access_control_allow_headers);
|
||||
}
|
||||
if(!plugin_state_->access_control_max_age.empty()) {
|
||||
res_->set("Access-Control-Max-Age", plugin_state_->access_control_max_age);
|
||||
if(!plugin_state_.access_control_max_age.empty()) {
|
||||
res_->set("Access-Control-Max-Age", plugin_state_.access_control_max_age);
|
||||
}
|
||||
if(plugin_state_->access_control_allow_credentials) {
|
||||
if(plugin_state_.access_control_allow_credentials) {
|
||||
res_->set("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
@@ -165,28 +152,35 @@ protected:
|
||||
return;
|
||||
}
|
||||
|
||||
fc_dlog( plugin_state_->logger, "Request: ${ep} ${r}",
|
||||
fc_dlog( plugin_state_.get_logger(), "Request: ${ep} ${r}",
|
||||
("ep", remote_endpoint_)("r", to_log_string(req)) );
|
||||
|
||||
std::string resource = std::string(req.target());
|
||||
// look for the URL handler to handle this resource
|
||||
auto handler_itr = plugin_state_->url_handlers.find(resource);
|
||||
if(handler_itr != plugin_state_->url_handlers.end()) {
|
||||
if(plugin_state_->logger.is_enabled(fc::log_level::all))
|
||||
plugin_state_->logger.log(FC_LOG_MESSAGE(all, "resource: ${ep}", ("ep", resource)));
|
||||
auto handler_itr = plugin_state_.url_handlers.find(resource);
|
||||
if(handler_itr != plugin_state_.url_handlers.end() && categories_.contains(handler_itr->second.category)) {
|
||||
if(plugin_state_.get_logger().is_enabled(fc::log_level::all))
|
||||
plugin_state_.get_logger().log(FC_LOG_MESSAGE(all, "resource: ${ep}", ("ep", resource)));
|
||||
std::string body = req.body();
|
||||
auto content_type = handler_itr->second.content_type;
|
||||
set_content_type_header(content_type);
|
||||
|
||||
if (plugin_state_->update_metrics)
|
||||
plugin_state_->update_metrics({resource});
|
||||
if (plugin_state_.update_metrics)
|
||||
plugin_state_.update_metrics({resource});
|
||||
|
||||
handler_itr->second.fn(derived().shared_from_this(),
|
||||
handler_itr->second.fn(this->shared_from_this(),
|
||||
std::move(resource),
|
||||
std::move(body),
|
||||
make_http_response_handler(plugin_state_, derived().shared_from_this(), content_type));
|
||||
make_http_response_handler(plugin_state_, this->shared_from_this(), content_type));
|
||||
} else if (resource == "/v1/node/get_supported_apis") {
|
||||
http_plugin::get_supported_apis_result result;
|
||||
for (const auto& handler : plugin_state_.url_handlers) {
|
||||
if (categories_.contains(handler.second.category))
|
||||
result.apis.push_back(handler.first);
|
||||
}
|
||||
send_response(fc::json::to_string(fc::variant(result), fc::time_point::maximum()), 200);
|
||||
} else {
|
||||
fc_dlog( plugin_state_->logger, "404 - not found: ${ep}", ("ep", resource) );
|
||||
fc_dlog( plugin_state_.get_logger(), "404 - not found: ${ep}", ("ep", resource) );
|
||||
error_results results{static_cast<uint16_t>(http::status::not_found), "Not Found",
|
||||
error_results::error_info( fc::exception( FC_LOG_MESSAGE( error, "Unknown Endpoint" ) ),
|
||||
http_plugin::verbose_errors() )};
|
||||
@@ -210,12 +204,12 @@ private:
|
||||
res->result(http::status::unauthorized);
|
||||
continue_state_ = continue_state_t::reject;
|
||||
}
|
||||
res->set(http::field::server, plugin_state_->server_header);
|
||||
res->set(http::field::server, plugin_state_.server_header);
|
||||
|
||||
http::async_write(
|
||||
derived().stream(),
|
||||
socket_,
|
||||
*res,
|
||||
[self = derived().shared_from_this(), res](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
[self = this->shared_from_this(), res](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->on_write(ec, bytes_transferred, false);
|
||||
});
|
||||
}
|
||||
@@ -233,42 +227,37 @@ public:
|
||||
}
|
||||
|
||||
virtual std::string verify_max_bytes_in_flight(size_t extra_bytes) final {
|
||||
auto bytes_in_flight_size = plugin_state_->bytes_in_flight.load() + extra_bytes;
|
||||
if(bytes_in_flight_size > plugin_state_->max_bytes_in_flight) {
|
||||
fc_dlog(plugin_state_->logger, "429 - too many bytes in flight: ${bytes}", ("bytes", bytes_in_flight_size));
|
||||
auto bytes_in_flight_size = plugin_state_.bytes_in_flight.load() + extra_bytes;
|
||||
if(bytes_in_flight_size > plugin_state_.max_bytes_in_flight) {
|
||||
fc_dlog(plugin_state_.get_logger(), "429 - too many bytes in flight: ${bytes}", ("bytes", bytes_in_flight_size));
|
||||
return "Too many bytes in flight: " + std::to_string( bytes_in_flight_size );
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual std::string verify_max_requests_in_flight() final {
|
||||
if(plugin_state_->max_requests_in_flight < 0)
|
||||
if(plugin_state_.max_requests_in_flight < 0)
|
||||
return {};
|
||||
|
||||
auto requests_in_flight_num = plugin_state_->requests_in_flight.load();
|
||||
if(requests_in_flight_num > plugin_state_->max_requests_in_flight) {
|
||||
fc_dlog(plugin_state_->logger, "429 - too many requests in flight: ${requests}", ("requests", requests_in_flight_num));
|
||||
auto requests_in_flight_num = plugin_state_.requests_in_flight.load();
|
||||
if(requests_in_flight_num > plugin_state_.max_requests_in_flight) {
|
||||
fc_dlog(plugin_state_.get_logger(), "429 - too many requests in flight: ${requests}", ("requests", requests_in_flight_num));
|
||||
return "Too many requests in flight: " + std::to_string( requests_in_flight_num );
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Access the derived class, this is part of
|
||||
// the Curiously Recurring Template Pattern idiom.
|
||||
Derived& derived() {
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
|
||||
public:
|
||||
// shared_from_this() requires default constructor
|
||||
beast_http_session() = default;
|
||||
|
||||
beast_http_session(std::shared_ptr<http_plugin_state> plugin_state, std::string remote_endpoint)
|
||||
: plugin_state_(std::move(plugin_state)),
|
||||
remote_endpoint_(std::move(remote_endpoint)) {
|
||||
plugin_state_->requests_in_flight += 1;
|
||||
beast_http_session(Socket&& socket, http_plugin_state& plugin_state, std::string remote_endpoint,
|
||||
api_category_set categories, const std::string& local_address)
|
||||
: socket_(std::move(socket)), categories_(categories), plugin_state_(plugin_state),
|
||||
remote_endpoint_(std::move(remote_endpoint)), local_address_(local_address) {
|
||||
plugin_state_.requests_in_flight += 1;
|
||||
req_parser_.emplace();
|
||||
req_parser_->body_limit(plugin_state_->max_body_size);
|
||||
req_parser_->body_limit(plugin_state_.max_body_size);
|
||||
res_.emplace();
|
||||
|
||||
session_begin_ = steady_clock::now();
|
||||
@@ -280,14 +269,14 @@ public:
|
||||
|
||||
virtual ~beast_http_session() {
|
||||
is_send_exception_response_ = false;
|
||||
plugin_state_->requests_in_flight -= 1;
|
||||
if(plugin_state_->logger.is_enabled(fc::log_level::all)) {
|
||||
plugin_state_.requests_in_flight -= 1;
|
||||
if(plugin_state_.get_logger().is_enabled(fc::log_level::all)) {
|
||||
auto session_time = steady_clock::now() - session_begin_;
|
||||
auto session_time_us = std::chrono::duration_cast<std::chrono::microseconds>(session_time).count();
|
||||
plugin_state_->logger.log(FC_LOG_MESSAGE(all, "session time ${t}", ("t", session_time_us)));
|
||||
plugin_state_->logger.log(FC_LOG_MESSAGE(all, " read ${t}", ("t", read_time_us_)));
|
||||
plugin_state_->logger.log(FC_LOG_MESSAGE(all, " handle ${t}", ("t", handle_time_us_)));
|
||||
plugin_state_->logger.log(FC_LOG_MESSAGE(all, " write ${t}", ("t", write_time_us_)));
|
||||
plugin_state_.get_logger().log(FC_LOG_MESSAGE(all, "session time ${t}", ("t", session_time_us)));
|
||||
plugin_state_.get_logger().log(FC_LOG_MESSAGE(all, " read ${t}", ("t", read_time_us_)));
|
||||
plugin_state_.get_logger().log(FC_LOG_MESSAGE(all, " handle ${t}", ("t", handle_time_us_)));
|
||||
plugin_state_.get_logger().log(FC_LOG_MESSAGE(all, " write ${t}", ("t", write_time_us_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,10 +285,10 @@ public:
|
||||
|
||||
// Read a request
|
||||
http::async_read_header(
|
||||
derived().stream(),
|
||||
socket_,
|
||||
buffer_,
|
||||
*req_parser_,
|
||||
[self = derived().shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
[self = this->shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->on_read_header(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
@@ -307,9 +296,9 @@ public:
|
||||
void on_read_header(beast::error_code ec, std::size_t /* bytes_transferred */) {
|
||||
if(ec) {
|
||||
if(ec == http::error::end_of_stream) // other side closed the connection
|
||||
return derived().do_eof();
|
||||
return do_eof();
|
||||
|
||||
return fail(ec, "read_header", plugin_state_->logger, "closing connection");
|
||||
return fail(ec, "read_header", plugin_state_.get_logger(), "closing connection");
|
||||
}
|
||||
|
||||
// Check for the Expect field value
|
||||
@@ -317,7 +306,7 @@ public:
|
||||
bool do_continue = true;
|
||||
auto sv = req_parser_->get()[http::field::content_length];
|
||||
if (uint64_t sz; !sv.empty() && std::from_chars(sv.data(), sv.data() + sv.size(), sz).ec == std::errc() &&
|
||||
sz > plugin_state_->max_body_size) {
|
||||
sz > plugin_state_.max_body_size) {
|
||||
do_continue = false;
|
||||
}
|
||||
send_100_continue_response(do_continue);
|
||||
@@ -331,10 +320,10 @@ public:
|
||||
void do_read() {
|
||||
// Read a request
|
||||
http::async_read(
|
||||
derived().stream(),
|
||||
socket_,
|
||||
buffer_,
|
||||
*req_parser_,
|
||||
[self = derived().shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
[self = this->shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->on_read(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
@@ -347,9 +336,9 @@ public:
|
||||
// on another read. If the client disconnects, we may get
|
||||
// http::error::end_of_stream or asio::error::connection_reset.
|
||||
if(ec == http::error::end_of_stream || ec == asio::error::connection_reset)
|
||||
return derived().do_eof();
|
||||
return do_eof();
|
||||
|
||||
return fail(ec, "read", plugin_state_->logger, "closing connection");
|
||||
return fail(ec, "read", plugin_state_.get_logger(), "closing connection");
|
||||
}
|
||||
|
||||
auto req = req_parser_->release();
|
||||
@@ -368,7 +357,7 @@ public:
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
|
||||
if(ec) {
|
||||
return fail(ec, "write", plugin_state_->logger, "closing connection");
|
||||
return fail(ec, "write", plugin_state_.get_logger(), "closing connection");
|
||||
}
|
||||
|
||||
auto dt = steady_clock::now() - write_begin_;
|
||||
@@ -377,7 +366,7 @@ public:
|
||||
if(close) {
|
||||
// This means we should close the connection, usually because
|
||||
// the response indicated the "Connection: close" semantic.
|
||||
return derived().do_eof();
|
||||
return do_eof();
|
||||
}
|
||||
|
||||
// create a new response object
|
||||
@@ -393,7 +382,7 @@ public:
|
||||
case continue_state_t::reject:
|
||||
// request body too large. After issuing 401 response, close connection
|
||||
continue_state_ = continue_state_t::none;
|
||||
derived().do_eof();
|
||||
do_eof();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -401,7 +390,7 @@ public:
|
||||
|
||||
// create a new parser to clear state
|
||||
req_parser_.emplace();
|
||||
req_parser_->body_limit(plugin_state_->max_body_size);
|
||||
req_parser_->body_limit(plugin_state_.max_body_size);
|
||||
|
||||
// Read another request
|
||||
do_read_header();
|
||||
@@ -416,26 +405,26 @@ public:
|
||||
throw;
|
||||
} catch(const fc::exception& e) {
|
||||
err_str = e.to_detail_string();
|
||||
fc_elog(plugin_state_->logger, "fc::exception: ${w}", ("w", err_str));
|
||||
fc_elog(plugin_state_.get_logger(), "fc::exception: ${w}", ("w", err_str));
|
||||
if( is_send_exception_response_ ) {
|
||||
error_results results{static_cast<uint16_t>(http::status::internal_server_error),
|
||||
"Internal Service Error",
|
||||
error_results::error_info( e, http_plugin::verbose_errors() )};
|
||||
err_str = fc::json::to_string( results, fc::time_point::now() + plugin_state_->max_response_time );
|
||||
err_str = fc::json::to_string( results, fc::time_point::now().safe_add(plugin_state_.max_response_time) );
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
err_str = e.what();
|
||||
fc_elog(plugin_state_->logger, "std::exception: ${w}", ("w", err_str));
|
||||
fc_elog(plugin_state_.get_logger(), "std::exception: ${w}", ("w", err_str));
|
||||
if( is_send_exception_response_ ) {
|
||||
error_results results{static_cast<uint16_t>(http::status::internal_server_error),
|
||||
"Internal Service Error",
|
||||
error_results::error_info( fc::exception( FC_LOG_MESSAGE( error, err_str ) ),
|
||||
http_plugin::verbose_errors() )};
|
||||
err_str = fc::json::to_string( results, fc::time_point::now() + plugin_state_->max_response_time );
|
||||
err_str = fc::json::to_string( results, fc::time_point::now().safe_add(plugin_state_.max_response_time) );
|
||||
}
|
||||
} catch(...) {
|
||||
err_str = "Unknown exception";
|
||||
fc_elog(plugin_state_->logger, err_str);
|
||||
fc_elog(plugin_state_.get_logger(), err_str);
|
||||
if( is_send_exception_response_ ) {
|
||||
error_results results{static_cast<uint16_t>(http::status::internal_server_error),
|
||||
"Internal Service Error",
|
||||
@@ -446,10 +435,10 @@ public:
|
||||
}
|
||||
}
|
||||
} catch (fc::timeout_exception& e) {
|
||||
fc_elog( plugin_state_->logger, "Timeout exception ${te} attempting to handle exception: ${e}", ("te", e.to_detail_string())("e", err_str) );
|
||||
fc_elog( plugin_state_.get_logger(), "Timeout exception ${te} attempting to handle exception: ${e}", ("te", e.to_detail_string())("e", err_str) );
|
||||
err_str = R"xxx({"message": "Internal Server Error"})xxx";
|
||||
} catch (...) {
|
||||
fc_elog( plugin_state_->logger, "Exception attempting to handle exception: ${e}", ("e", err_str) );
|
||||
fc_elog( plugin_state_.get_logger(), "Exception attempting to handle exception: ${e}", ("e", err_str) );
|
||||
err_str = R"xxx({"message": "Internal Server Error"})xxx";
|
||||
}
|
||||
|
||||
@@ -460,16 +449,16 @@ public:
|
||||
res_->set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
|
||||
send_response(std::move(err_str), static_cast<unsigned int>(http::status::internal_server_error));
|
||||
derived().do_eof();
|
||||
do_eof();
|
||||
}
|
||||
}
|
||||
|
||||
void increment_bytes_in_flight(size_t sz) {
|
||||
plugin_state_->bytes_in_flight += sz;
|
||||
plugin_state_.bytes_in_flight += sz;
|
||||
}
|
||||
|
||||
void decrement_bytes_in_flight(size_t sz) {
|
||||
plugin_state_->bytes_in_flight -= sz;
|
||||
plugin_state_.bytes_in_flight -= sz;
|
||||
}
|
||||
|
||||
virtual void send_response(std::string&& json, unsigned int code) final {
|
||||
@@ -484,16 +473,16 @@ public:
|
||||
res_->prepare_payload();
|
||||
|
||||
// Determine if we should close the connection after
|
||||
bool close = !(plugin_state_->keep_alive) || res_->need_eof();
|
||||
bool close = !(plugin_state_.keep_alive) || res_->need_eof();
|
||||
|
||||
fc_dlog( plugin_state_->logger, "Response: ${ep} ${b}",
|
||||
fc_dlog( plugin_state_.get_logger(), "Response: ${ep} ${b}",
|
||||
("ep", remote_endpoint_)("b", to_log_string(*res_)) );
|
||||
|
||||
// Write the response
|
||||
http::async_write(
|
||||
derived().stream(),
|
||||
socket_,
|
||||
*res_,
|
||||
[self = derived().shared_from_this(), payload_size, close](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
[self = this->shared_from_this(), payload_size, close](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->decrement_bytes_in_flight(payload_size);
|
||||
self->on_write(ec, bytes_transferred, close);
|
||||
});
|
||||
@@ -502,103 +491,34 @@ public:
|
||||
void run_session() {
|
||||
if(auto error_str = verify_max_requests_in_flight(); !error_str.empty()) {
|
||||
send_busy_response(std::move(error_str));
|
||||
return derived().do_eof();
|
||||
return do_eof();
|
||||
}
|
||||
|
||||
derived().run();
|
||||
}
|
||||
};// end class beast_http_session
|
||||
|
||||
// Handles a plain HTTP connection
|
||||
class plain_session
|
||||
: public beast_http_session<plain_session>,
|
||||
public std::enable_shared_from_this<plain_session> {
|
||||
tcp_socket_t socket_;
|
||||
|
||||
public:
|
||||
// Create the session
|
||||
plain_session(
|
||||
tcp_socket_t socket,
|
||||
std::shared_ptr<http_plugin_state> plugin_state,
|
||||
std::string remote_endpoint)
|
||||
: beast_http_session<plain_session>(std::move(plugin_state), std::move(remote_endpoint)), socket_(std::move(socket)) {}
|
||||
|
||||
tcp_socket_t& stream() { return socket_; }
|
||||
tcp_socket_t& socket() { return socket_; }
|
||||
|
||||
// Start the asynchronous operation
|
||||
void run() {
|
||||
do_read_header();
|
||||
}
|
||||
|
||||
void do_eof() {
|
||||
is_send_exception_response_ = false;
|
||||
try {
|
||||
// Send a TCP shutdown
|
||||
beast::error_code ec;
|
||||
socket_.shutdown(tcp::socket::shutdown_send, ec);
|
||||
// At this point the connection is closed gracefully
|
||||
} catch(...) {
|
||||
handle_exception();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_secure() { return false; };
|
||||
|
||||
bool allow_host(const http::request<http::string_body>& req) {
|
||||
return eosio::allow_host(req, *this, plugin_state_);
|
||||
}
|
||||
|
||||
static constexpr auto name() {
|
||||
return "plain_session";
|
||||
}
|
||||
};// end class plain_session
|
||||
|
||||
// unix domain sockets
|
||||
class unix_socket_session
|
||||
: public std::enable_shared_from_this<unix_socket_session>,
|
||||
public beast_http_session<unix_socket_session> {
|
||||
|
||||
// The socket used to communicate with the client.
|
||||
stream_protocol::socket socket_;
|
||||
|
||||
public:
|
||||
unix_socket_session(stream_protocol::socket sock,
|
||||
std::shared_ptr<http_plugin_state> plugin_state,
|
||||
std::string remote_endpoint)
|
||||
: beast_http_session(std::move(plugin_state), std::move(remote_endpoint)), socket_(std::move(sock)) {}
|
||||
|
||||
virtual ~unix_socket_session() = default;
|
||||
|
||||
bool allow_host(const http::request<http::string_body>& req) {
|
||||
// always allow local hosts
|
||||
return true;
|
||||
}
|
||||
|
||||
void do_eof() {
|
||||
is_send_exception_response_ = false;
|
||||
try {
|
||||
// Send a shutdown signal
|
||||
boost::system::error_code ec;
|
||||
socket_.shutdown(stream_protocol::socket::shutdown_send, ec);
|
||||
beast::error_code ec;
|
||||
socket_.shutdown(Socket::shutdown_send, ec);
|
||||
// At this point the connection is closed gracefully
|
||||
} catch(...) {
|
||||
handle_exception();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_secure() { return false; };
|
||||
|
||||
void run() {
|
||||
do_read_header();
|
||||
bool allow_host(const http::request<http::string_body>& req) {
|
||||
if constexpr(std::is_same_v<Socket, tcp::socket>) {
|
||||
const std::string host_str(req["host"]);
|
||||
if (host_str != local_address_)
|
||||
return eosio::allow_host(host_str, socket_, plugin_state_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
stream_protocol::socket& stream() { return socket_; }
|
||||
stream_protocol::socket& socket() { return socket_; }
|
||||
|
||||
static constexpr auto name() {
|
||||
return "unix_socket_session";
|
||||
}
|
||||
};// end class unix_socket_session
|
||||
}; // end class beast_http_session
|
||||
|
||||
}// namespace eosio
|
||||
|
||||
@@ -2,18 +2,12 @@
|
||||
|
||||
#include <eosio/chain/thread_utils.hpp>// for thread pool
|
||||
#include <eosio/http_plugin/http_plugin.hpp>
|
||||
#include <fc/utility.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <fc/io/raw.hpp>
|
||||
#include <fc/log/logger_config.hpp>
|
||||
#include <fc/time.hpp>
|
||||
#include <fc/utility.hpp>
|
||||
#include <fc/network/listener.hpp>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
@@ -33,6 +27,14 @@
|
||||
#include <boost/asio/basic_stream_socket.hpp>
|
||||
#include <boost/asio/detail/config.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace eosio {
|
||||
static uint16_t const uri_default_port = 80;
|
||||
/// Default port for wss://
|
||||
@@ -70,6 +72,7 @@ using abstract_conn_ptr = std::shared_ptr<abstract_conn>;
|
||||
using internal_url_handler_fn = std::function<void(abstract_conn_ptr, string&&, string&&, url_response_callback&&)>;
|
||||
struct internal_url_handler {
|
||||
internal_url_handler_fn fn;
|
||||
api_category category;
|
||||
http_content_type content_type = http_content_type::json;
|
||||
};
|
||||
/**
|
||||
@@ -134,8 +137,11 @@ struct http_plugin_state {
|
||||
fc::logger& logger;
|
||||
std::function<void(http_plugin::metrics)> update_metrics;
|
||||
|
||||
fc::logger& get_logger() { return logger; }
|
||||
|
||||
explicit http_plugin_state(fc::logger& log)
|
||||
: logger(log) {}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -146,29 +152,24 @@ struct http_plugin_state {
|
||||
* @param session_ptr - beast_http_session object on which to invoke send_response
|
||||
* @return lambda suitable for url_response_callback
|
||||
*/
|
||||
auto make_http_response_handler(std::shared_ptr<http_plugin_state> plugin_state, detail::abstract_conn_ptr session_ptr, http_content_type content_type) {
|
||||
return [plugin_state{std::move(plugin_state)},
|
||||
session_ptr{std::move(session_ptr)}, content_type](int code, fc::time_point deadline, std::optional<fc::variant> response) {
|
||||
inline auto make_http_response_handler(http_plugin_state& plugin_state, detail::abstract_conn_ptr session_ptr, http_content_type content_type) {
|
||||
return [&plugin_state,
|
||||
session_ptr{std::move(session_ptr)}, content_type](int code, std::optional<fc::variant> response) {
|
||||
auto payload_size = detail::in_flight_sizeof(response);
|
||||
if(auto error_str = session_ptr->verify_max_bytes_in_flight(payload_size); !error_str.empty()) {
|
||||
session_ptr->send_busy_response(std::move(error_str));
|
||||
return;
|
||||
}
|
||||
|
||||
auto start = fc::time_point::now();
|
||||
if (deadline == fc::time_point::maximum()) { // no caller supplied deadline so use http configured deadline
|
||||
deadline = start + plugin_state->max_response_time;
|
||||
}
|
||||
|
||||
plugin_state->bytes_in_flight += payload_size;
|
||||
plugin_state.bytes_in_flight += payload_size;
|
||||
|
||||
// post back to an HTTP thread to allow the response handler to be called from any thread
|
||||
boost::asio::post(plugin_state->thread_pool.get_executor(),
|
||||
[plugin_state, session_ptr, code, deadline, start, payload_size, response = std::move(response), content_type]() {
|
||||
boost::asio::post(plugin_state.thread_pool.get_executor(),
|
||||
[&plugin_state, session_ptr, code, payload_size, response = std::move(response), content_type]() {
|
||||
try {
|
||||
plugin_state->bytes_in_flight -= payload_size;
|
||||
plugin_state.bytes_in_flight -= payload_size;
|
||||
if (response.has_value()) {
|
||||
std::string json = (content_type == http_content_type::plaintext) ? response->as_string() : fc::json::to_string(*response, deadline + (fc::time_point::now() - start));
|
||||
std::string json = (content_type == http_content_type::plaintext) ? response->as_string() : fc::json::to_string(*response, fc::time_point::maximum());
|
||||
if (auto error_str = session_ptr->verify_max_bytes_in_flight(json.size()); error_str.empty())
|
||||
session_ptr->send_response(std::move(json), code);
|
||||
else
|
||||
@@ -184,30 +185,21 @@ auto make_http_response_handler(std::shared_ptr<http_plugin_state> plugin_state,
|
||||
|
||||
}
|
||||
|
||||
bool host_port_is_valid(const http_plugin_state& plugin_state,
|
||||
const std::string& header_host_port,
|
||||
const string& endpoint_local_host_port) {
|
||||
return !plugin_state.validate_host || header_host_port == endpoint_local_host_port || plugin_state.valid_hosts.find(header_host_port) != plugin_state.valid_hosts.end();
|
||||
}
|
||||
|
||||
bool host_is_valid(const http_plugin_state& plugin_state,
|
||||
const std::string& host,
|
||||
const string& endpoint_local_host_port,
|
||||
bool secure) {
|
||||
inline bool host_is_valid(const http_plugin_state& plugin_state,
|
||||
const std::string& header_host_port,
|
||||
const asio::ip::address& addr) {
|
||||
if(!plugin_state.validate_host) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// normalise the incoming host so that it always has the explicit port
|
||||
static auto has_port_expr = std::regex("[^:]:[0-9]+$");/// ends in :<number> without a preceeding colon which implies ipv6
|
||||
if(std::regex_search(host, has_port_expr)) {
|
||||
return host_port_is_valid(plugin_state, host, endpoint_local_host_port);
|
||||
} else {
|
||||
// according to RFC 2732 ipv6 addresses should always be enclosed with brackets so we shouldn't need to special case here
|
||||
return host_port_is_valid(plugin_state,
|
||||
host + ":" + std::to_string(secure ? uri_default_secure_port : uri_default_port),
|
||||
endpoint_local_host_port);
|
||||
auto [hostname, port] = fc::split_host_port(header_host_port);
|
||||
boost::system::error_code ec;
|
||||
auto header_addr = boost::asio::ip::make_address(hostname, ec);
|
||||
if (ec)
|
||||
return plugin_state.valid_hosts.count(hostname);
|
||||
if (header_addr.is_v4() && addr.is_v6()) {
|
||||
header_addr = boost::asio::ip::address_v6::v4_mapped(header_addr.to_v4());
|
||||
}
|
||||
return header_addr == addr;
|
||||
}
|
||||
|
||||
}// end namespace eosio
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include <eosio/chain/application.hpp>
|
||||
#include <eosio/chain/exceptions.hpp>
|
||||
#include <eosio/http_plugin/api_category.hpp>
|
||||
#include <fc/exception/exception.hpp>
|
||||
#include <fc/reflect/reflect.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
namespace eosio {
|
||||
using namespace appbase;
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace eosio {
|
||||
* @brief A callback function provided to a URL handler to
|
||||
* allow it to specify the HTTP response code and body
|
||||
*
|
||||
* Arguments: response_code, deadline, response_body
|
||||
* Arguments: response_code, response_body
|
||||
*/
|
||||
using url_response_callback = std::function<void(int,fc::time_point,std::optional<fc::variant>)>;
|
||||
using url_response_callback = std::function<void(int,std::optional<fc::variant>)>;
|
||||
|
||||
/**
|
||||
* @brief Callback type for a URL handler
|
||||
@@ -36,7 +36,12 @@ namespace eosio {
|
||||
* a handler. The URL is the path on the web server that triggers the
|
||||
* call, and the handler is the function which implements the API call
|
||||
*/
|
||||
using api_entry = std::pair<string, url_handler>;
|
||||
struct api_entry {
|
||||
string path;
|
||||
api_category category;
|
||||
url_handler handler;
|
||||
};
|
||||
|
||||
using api_description = std::vector<api_entry>;
|
||||
|
||||
enum class http_content_type {
|
||||
@@ -54,6 +59,7 @@ namespace eosio {
|
||||
uint16_t default_http_port{0};
|
||||
//If set, a Server header will be added to the HTTP reply with this value
|
||||
string server_header;
|
||||
bool support_categories = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -88,16 +94,16 @@ namespace eosio {
|
||||
void plugin_shutdown();
|
||||
void handle_sighup() override;
|
||||
|
||||
void add_handler(const string& url, const url_handler&, appbase::exec_queue q, int priority = appbase::priority::medium_low, http_content_type content_type = http_content_type::json);
|
||||
void add_api(const api_description& api, appbase::exec_queue q, int priority = appbase::priority::medium_low, http_content_type content_type = http_content_type::json) {
|
||||
for (const auto& call : api)
|
||||
add_handler(call.first, call.second, q, priority, content_type);
|
||||
void add_handler(api_entry&& entry, appbase::exec_queue q, int priority = appbase::priority::medium_low, http_content_type content_type = http_content_type::json);
|
||||
void add_api(api_description&& api, appbase::exec_queue q, int priority = appbase::priority::medium_low, http_content_type content_type = http_content_type::json) {
|
||||
for (auto& call : api)
|
||||
add_handler(std::move(call), q, priority, content_type);
|
||||
}
|
||||
|
||||
void add_async_handler(const string& url, const url_handler& handler, http_content_type content_type = http_content_type::json);
|
||||
void add_async_api(const api_description& api, http_content_type content_type = http_content_type::json) {
|
||||
for (const auto& call : api)
|
||||
add_async_handler(call.first, call.second, content_type);
|
||||
void add_async_handler(api_entry&& entry, http_content_type content_type = http_content_type::json);
|
||||
void add_async_api(api_description&& api, http_content_type content_type = http_content_type::json) {
|
||||
for (auto& call : api)
|
||||
add_async_handler(std::move(call), content_type);
|
||||
}
|
||||
|
||||
// standard exception handling for api handlers
|
||||
@@ -105,8 +111,7 @@ namespace eosio {
|
||||
|
||||
void post_http_thread_pool(std::function<void()> f);
|
||||
|
||||
bool is_on_loopback() const;
|
||||
bool is_secure() const;
|
||||
bool is_on_loopback(api_category category) const;
|
||||
|
||||
static bool verbose_errors();
|
||||
|
||||
@@ -114,8 +119,6 @@ namespace eosio {
|
||||
vector<string> apis;
|
||||
};
|
||||
|
||||
get_supported_apis_result get_supported_apis()const;
|
||||
|
||||
/// @return the configured http-max-response-time-ms
|
||||
fc::microseconds get_max_response_time()const;
|
||||
|
||||
@@ -127,6 +130,7 @@ namespace eosio {
|
||||
|
||||
void register_update_metrics(std::function<void(metrics)>&& fun);
|
||||
|
||||
std::atomic<bool>& listening();
|
||||
private:
|
||||
std::shared_ptr<class http_plugin_impl> my;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
struct async_result_visitor : public fc::visitor<fc::variant> {
|
||||
template<typename T>
|
||||
fc::variant operator()(const T& v) const {
|
||||
return fc::variant(v);
|
||||
}
|
||||
};
|
||||
|
||||
#define CALL_ASYNC_WITH_400(api_name, api_handle, api_namespace, call_name, call_result, http_resp_code, params_type) \
|
||||
#define CALL_ASYNC_WITH_400(api_name, category, api_handle, api_namespace, call_name, call_result, http_resp_code, params_type) \
|
||||
{ std::string("/v1/" #api_name "/" #call_name), \
|
||||
api_category::category, \
|
||||
[api_handle, &_http_plugin](string&&, string&& body, url_response_callback&& cb) mutable { \
|
||||
auto deadline = api_handle.start(); \
|
||||
api_handle.start(); \
|
||||
try { \
|
||||
auto params = parse_params<api_namespace::call_name ## _params, params_type>(body); \
|
||||
FC_CHECK_DEADLINE(deadline); \
|
||||
using http_fwd_t = std::function<chain::t_or_exception<call_result>()>; \
|
||||
api_handle.call_name( std::move(params), \
|
||||
api_handle.call_name( std::move(params), /* called on main application thread */ \
|
||||
[&_http_plugin, cb=std::move(cb), body=std::move(body)] \
|
||||
(const chain::next_function_variant<call_result>& result) mutable { \
|
||||
if (std::holds_alternative<fc::exception_ptr>(result)) { \
|
||||
@@ -25,8 +18,7 @@ struct async_result_visitor : public fc::visitor<fc::variant> {
|
||||
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
|
||||
} \
|
||||
} else if (std::holds_alternative<call_result>(result)) { \
|
||||
cb(http_resp_code, fc::time_point::maximum(), \
|
||||
fc::variant(std::get<call_result>(std::move(result)))); \
|
||||
cb(http_resp_code, fc::variant(std::get<call_result>(std::move(result)))); \
|
||||
} else { \
|
||||
/* api returned a function to be processed on the http_plugin thread pool */ \
|
||||
assert(std::holds_alternative<http_fwd_t>(result)); \
|
||||
@@ -41,8 +33,7 @@ struct async_result_visitor : public fc::visitor<fc::variant> {
|
||||
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
|
||||
} \
|
||||
} else { \
|
||||
cb(resp_code, fc::time_point::maximum(), \
|
||||
fc::variant(std::get<call_result>(std::move(result)))) ; \
|
||||
cb(resp_code, fc::variant(std::get<call_result>(std::move(result)))); \
|
||||
} \
|
||||
}); \
|
||||
} \
|
||||
@@ -57,16 +48,16 @@ struct async_result_visitor : public fc::visitor<fc::variant> {
|
||||
// call an API which returns either fc::exception_ptr, or a function to be posted on the http thread pool
|
||||
// for execution (typically doing the final serialization)
|
||||
// ------------------------------------------------------------------------------------------------------
|
||||
#define CALL_WITH_400_POST(api_name, api_handle, api_namespace, call_name, call_result, http_resp_code, params_type) \
|
||||
#define CALL_WITH_400_POST(api_name, category, api_handle, api_namespace, call_name, call_result, http_resp_code, params_type) \
|
||||
{std::string("/v1/" #api_name "/" #call_name), \
|
||||
api_category::category, \
|
||||
[api_handle, &_http_plugin](string&&, string&& body, url_response_callback&& cb) { \
|
||||
auto deadline = api_handle.start(); \
|
||||
try { \
|
||||
auto params = parse_params<api_namespace::call_name ## _params, params_type>(body); \
|
||||
FC_CHECK_DEADLINE(deadline); \
|
||||
using http_fwd_t = std::function<chain::t_or_exception<call_result>()>; \
|
||||
/* called on main application thread */ \
|
||||
http_fwd_t http_fwd(api_handle.call_name(std::move(params), deadline)); \
|
||||
FC_CHECK_DEADLINE(deadline); \
|
||||
_http_plugin.post_http_thread_pool([resp_code=http_resp_code, cb=std::move(cb), \
|
||||
body=std::move(body), \
|
||||
http_fwd = std::move(http_fwd)]() { \
|
||||
@@ -78,8 +69,7 @@ struct async_result_visitor : public fc::visitor<fc::variant> {
|
||||
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
|
||||
} \
|
||||
} else { \
|
||||
cb(resp_code, fc::time_point::maximum(), \
|
||||
fc::variant(std::get<call_result>(std::move(result)))) ; \
|
||||
cb(resp_code, fc::variant(std::get<call_result>(std::move(result)))); \
|
||||
} \
|
||||
}); \
|
||||
} catch (...) { \
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <eosio/chain/application.hpp>
|
||||
#include <eosio/http_plugin/http_plugin.hpp>
|
||||
#include <eosio/http_plugin/common.hpp>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
@@ -7,6 +8,8 @@
|
||||
#include <boost/beast/version.hpp>
|
||||
|
||||
#include <boost/asio/basic_stream_socket.hpp>
|
||||
#include <boost/asio/local/stream_protocol.hpp>
|
||||
#include <fc/scoped_exit.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE http_plugin unit tests
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
@@ -33,29 +36,32 @@ using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
// -------------------------------------------------------------------------
|
||||
class Db
|
||||
{
|
||||
public:
|
||||
public:
|
||||
void add_api(http_plugin& p) {
|
||||
p.add_api({
|
||||
{ std::string("/hello"),
|
||||
api_category::node,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::time_point::maximum(), fc::variant("world!"));
|
||||
cb(200, fc::variant("world!"));
|
||||
}
|
||||
},
|
||||
{ std::string("/echo"),
|
||||
api_category::node,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::time_point::maximum(), fc::variant(body));
|
||||
cb(200, fc::variant(body));
|
||||
}
|
||||
},
|
||||
{ std::string("/check_ones"), // returns "yes" if body only has only '1' chars, "no" otherwise
|
||||
api_category::node,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
bool ok = std::all_of(body.begin(), body.end(), [](char c) { return c == '1'; });
|
||||
cb(200, fc::time_point::maximum(), fc::variant(ok ? string("yes") : string("no")));
|
||||
cb(200, fc::variant(ok ? string("yes") : string("no")));
|
||||
}
|
||||
},
|
||||
},
|
||||
}, appbase::exec_queue::read_write);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -128,7 +134,7 @@ struct Expect100ContinueProtocol : public ProtocolCommon<Results>
|
||||
req.set(http::field::expect, "100-continue");
|
||||
req.body() = body;
|
||||
req.prepare_payload();
|
||||
|
||||
|
||||
http::request_serializer<http::string_body> sr{req};
|
||||
beast::error_code ec;
|
||||
http::write_header(this->stream, sr, ec);
|
||||
@@ -136,7 +142,7 @@ struct Expect100ContinueProtocol : public ProtocolCommon<Results>
|
||||
BOOST_CHECK_MESSAGE(expect_fail, "write_header failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
http::response<http::string_body> res {};
|
||||
beast::flat_buffer buffer;
|
||||
@@ -159,7 +165,7 @@ struct Expect100ContinueProtocol : public ProtocolCommon<Results>
|
||||
// Server is OK with the request, send the body
|
||||
http::write(this->stream, sr, ec);
|
||||
return !ec;
|
||||
}
|
||||
}
|
||||
return http::write(this->stream, req) != 0;
|
||||
}
|
||||
catch(std::exception const& e)
|
||||
@@ -217,50 +223,98 @@ void run_test(Protocol& p, size_t max_body_size)
|
||||
test_str.resize(max_body_size + 1, '1');
|
||||
check_request(p, "/check_ones", test_str.c_str(), {}); // we don't expect a response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------
|
||||
BOOST_AUTO_TEST_CASE(http_plugin_unit_tests)
|
||||
{
|
||||
namespace eosio {
|
||||
class chain_api_plugin : public appbase::plugin<chain_api_plugin> {
|
||||
public:
|
||||
APPBASE_PLUGIN_REQUIRES();
|
||||
virtual void set_program_options(options_description& cli, options_description& cfg) override {}
|
||||
void plugin_initialize(const variables_map& options) {}
|
||||
void plugin_startup() {}
|
||||
void plugin_shutdown() {}
|
||||
};
|
||||
|
||||
class net_api_plugin : public appbase::plugin<net_api_plugin> {
|
||||
public:
|
||||
APPBASE_PLUGIN_REQUIRES();
|
||||
virtual void set_program_options(options_description& cli, options_description& cfg) override {}
|
||||
void plugin_initialize(const variables_map& options) {}
|
||||
void plugin_startup() {}
|
||||
void plugin_shutdown() {}
|
||||
};
|
||||
|
||||
class producer_api_plugin : public appbase::plugin<producer_api_plugin> {
|
||||
public:
|
||||
APPBASE_PLUGIN_REQUIRES();
|
||||
virtual void set_program_options(options_description& cli, options_description& cfg) override {}
|
||||
void plugin_initialize(const variables_map& options) {}
|
||||
void plugin_startup() {}
|
||||
void plugin_shutdown() {}
|
||||
};
|
||||
|
||||
static auto _chain_api_plugin = application::register_plugin<chain_api_plugin>();
|
||||
static auto _net_api_plugin = application::register_plugin<net_api_plugin>();
|
||||
static auto _producer_api_plugin = application::register_plugin<producer_api_plugin>();
|
||||
} // namespace eosio
|
||||
|
||||
struct http_plugin_test_fixture {
|
||||
appbase::scoped_app app;
|
||||
std::thread app_thread;
|
||||
|
||||
|
||||
const uint16_t default_port { 8888 };
|
||||
const char* port = "8888";
|
||||
const char* host = "127.0.0.1";
|
||||
|
||||
http_plugin::set_defaults({
|
||||
.default_unix_socket_path = "",
|
||||
.default_http_port = default_port,
|
||||
.server_header = "/"
|
||||
});
|
||||
http_plugin* init(std::initializer_list<const char*> args) {
|
||||
if (app->initialize<http_plugin>(args.size(), const_cast<char**>(args.begin()))) {
|
||||
auto plugin = app->find_plugin<http_plugin>();
|
||||
std::atomic<bool>& listening_or_failed = plugin->listening();
|
||||
app_thread = std::thread([&]() {
|
||||
try {
|
||||
app->startup();
|
||||
app->exec();
|
||||
} catch (...) {
|
||||
plugin = nullptr;
|
||||
listening_or_failed.store(true);
|
||||
}
|
||||
});
|
||||
|
||||
const char* argv[] = { bu::framework::current_test_case().p_name->c_str(),
|
||||
"--http-validate-host", "false",
|
||||
"--http-threads", "4",
|
||||
"--http-max-response-time-ms", "50" };
|
||||
|
||||
BOOST_CHECK(app->initialize<http_plugin>(sizeof(argv) / sizeof(char*), const_cast<char**>(argv)));
|
||||
while (!listening_or_failed.load()) {
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::promise<http_plugin&> plugin_promise;
|
||||
std::future<http_plugin&> plugin_fut = plugin_promise.get_future();
|
||||
std::thread app_thread( [&]() {
|
||||
app->startup();
|
||||
plugin_promise.set_value(app->get_plugin<http_plugin>());
|
||||
app->exec();
|
||||
} );
|
||||
~http_plugin_test_fixture() {
|
||||
if (app_thread.joinable()) {
|
||||
app->quit();
|
||||
app_thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto http_plugin = plugin_fut.get();
|
||||
BOOST_CHECK(http_plugin.get_state() == abstract_plugin::started);
|
||||
// -------------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------
|
||||
BOOST_FIXTURE_TEST_CASE(http_plugin_unit_tests, http_plugin_test_fixture) {
|
||||
|
||||
const uint16_t default_port{8888};
|
||||
const char* port = "8888";
|
||||
const char* host = "127.0.0.1";
|
||||
|
||||
http_plugin::set_defaults({.default_unix_socket_path = "", .default_http_port = default_port, .server_header = "/"});
|
||||
|
||||
auto http_plugin =
|
||||
init({bu::framework::current_test_case().p_name->c_str(), "--plugin", "eosio::http_plugin",
|
||||
"--http-validate-host", "false", "--http-threads", "4", "--http-max-response-time-ms", "50"});
|
||||
|
||||
BOOST_REQUIRE(http_plugin);
|
||||
BOOST_CHECK(http_plugin->get_state() == abstract_plugin::started);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
Db db;
|
||||
db.add_api(http_plugin);
|
||||
db.add_api(*http_plugin);
|
||||
|
||||
size_t max_body_size = http_plugin->get_max_body_size();
|
||||
|
||||
size_t max_body_size = http_plugin.get_max_body_size();
|
||||
|
||||
try
|
||||
{
|
||||
net::io_context ioc;
|
||||
@@ -291,9 +345,231 @@ BOOST_AUTO_TEST_CASE(http_plugin_unit_tests)
|
||||
}
|
||||
catch(std::exception const& e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
std::cerr << "Error: " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
class app_log {
|
||||
std::string result;
|
||||
int fork_app_and_redirect_stderr(const char* redirect_filename, std::initializer_list<const char*> args) {
|
||||
int pid = fork();
|
||||
if (pid == 0) {
|
||||
(void) freopen(redirect_filename, "w", stderr);
|
||||
bool ret = 0;
|
||||
try {
|
||||
appbase::scoped_app app;
|
||||
ret = app->initialize<http_plugin>(args.size(), const_cast<char**>(args.begin()));
|
||||
} catch (...) {
|
||||
}
|
||||
fclose(stderr);
|
||||
exit(ret ? 0 : 1);
|
||||
} else {
|
||||
int chld_state;
|
||||
waitpid(pid, &chld_state, 0);
|
||||
BOOST_CHECK(WIFEXITED(chld_state));
|
||||
return WEXITSTATUS(chld_state);
|
||||
}
|
||||
}
|
||||
|
||||
app->quit();
|
||||
app_thread.join();
|
||||
public:
|
||||
app_log(std::initializer_list<const char*> args) {
|
||||
fc::temp_directory dir;
|
||||
std::filesystem::path log = dir.path()/"test.stderr";
|
||||
BOOST_CHECK(fork_app_and_redirect_stderr(log.c_str(), args));
|
||||
std::ifstream file(log.c_str());
|
||||
result.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
std::filesystem::remove(log);
|
||||
}
|
||||
|
||||
boost::test_tools::predicate_result contains(const char* str) const {
|
||||
if (result.find(str) == std::string::npos) {
|
||||
boost::test_tools::predicate_result res(false);
|
||||
res.message() << "\nlog result: " << result << "\n";
|
||||
return res;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(invalid_category_addresses) {
|
||||
|
||||
const char* test_name = bu::framework::current_test_case().p_name->c_str();
|
||||
|
||||
BOOST_TEST(app_log({test_name, "--plugin=eosio::http_plugin", "--http-server-address",
|
||||
"http-category-address", "--http-category-address", "chain_ro,localhost:8889"})
|
||||
.contains("--plugin=eosio::chain_api_plugin is required"));
|
||||
|
||||
BOOST_TEST(app_log({test_name, "--plugin=eosio::chain_api_plugin", "--http-category-address",
|
||||
"chain_ro,localhost:8889"})
|
||||
.contains("http-server-address must be set as `http-category-address`"));
|
||||
|
||||
BOOST_TEST(app_log({test_name, "--plugin=eosio::chain_api_plugin", "--http-server-address",
|
||||
"http-category-address", "--unix-socket-path", "/tmp/tmp.sock",
|
||||
"--http-category-address", "chain_ro,localhost:8889"})
|
||||
.contains("`unix-socket-path` must be left unspecified"));
|
||||
|
||||
BOOST_TEST(app_log({test_name, "--plugin=eosio::chain_api_plugin", "--http-server-address",
|
||||
"http-category-address", "--http-category-address", "node,localhost:8889"})
|
||||
.contains("invalid category name"));
|
||||
|
||||
BOOST_TEST(app_log({test_name, "--plugin=eosio::chain_api_plugin", "--http-server-address",
|
||||
"http-category-address", "--http-category-address", "chain_ro,127.0.0.1:8889",
|
||||
"--http-category-address", "chain_rw,localhost:8889"})
|
||||
.contains("unable to listen to port 8889"));
|
||||
}
|
||||
|
||||
struct http_response_for {
|
||||
net::io_context ioc;
|
||||
http::response<http::dynamic_body> response;
|
||||
http_response_for(const char* addr, const char* path) {
|
||||
auto [host, port] = fc::split_host_port(addr);
|
||||
// These objects perform our I/O
|
||||
tcp::resolver resolver(ioc);
|
||||
beast::tcp_stream stream(ioc);
|
||||
|
||||
// Look up IP and connect to it
|
||||
auto const results = resolver.resolve(host, port);
|
||||
stream.connect(results);
|
||||
initiate(stream, addr, path);
|
||||
}
|
||||
|
||||
http_response_for(std::filesystem::path addr, const char* path) {
|
||||
using unix_stream = beast::basic_stream<boost::asio::local::stream_protocol, beast::tcp_stream::executor_type,
|
||||
beast::unlimited_rate_policy>;
|
||||
|
||||
unix_stream stream(ioc);
|
||||
stream.connect(addr.c_str());
|
||||
initiate(stream, "", path);
|
||||
}
|
||||
|
||||
template <typename Stream>
|
||||
void initiate(Stream&& stream, const char* addr, const char* path) {
|
||||
int http_version = 11;
|
||||
http::request<http::string_body> req{http::verb::post, path, http_version};
|
||||
if (addr)
|
||||
req.set(http::field::host, addr);
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
BOOST_CHECK(http::write(stream, req) != 0);
|
||||
|
||||
beast::flat_buffer buffer;
|
||||
http::read(stream, buffer, response);
|
||||
}
|
||||
|
||||
http::status status() const { return response.result(); }
|
||||
|
||||
std::string body() const { return beast::buffers_to_string(response.body().data()); }
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) {
|
||||
fc::temp_directory dir;
|
||||
auto data_dir = dir.path() / "data";
|
||||
|
||||
// clang-format off
|
||||
auto http_plugin = init({bu::framework::current_test_case().p_name->c_str(),
|
||||
"--data-dir", data_dir.c_str(),
|
||||
"--plugin=eosio::chain_api_plugin",
|
||||
"--plugin=eosio::net_api_plugin",
|
||||
"--plugin=eosio::producer_api_plugin",
|
||||
"--http-server-address", "http-category-address",
|
||||
"--http-category-address", "chain_ro,127.0.0.1:8890",
|
||||
"--http-category-address", "chain_rw,:8889",
|
||||
"--http-category-address", "net_ro,127.0.0.1:8890",
|
||||
"--http-category-address", "net_rw,:8889",
|
||||
"--http-category-address", "producer_ro,./producer_ro.sock",
|
||||
"--http-category-address", "producer_rw,../producer_rw.sock"
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
BOOST_REQUIRE(http_plugin);
|
||||
|
||||
http_plugin->add_api({{std::string("/v1/node/hello"), api_category::node,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}},
|
||||
{std::string("/v1/chain_ro/hello"), api_category::chain_ro,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}},
|
||||
{std::string("/v1/chain_rw/hello"), api_category::chain_rw,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}},
|
||||
{std::string("/v1/net_ro/hello"), api_category::net_ro,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}},
|
||||
{std::string("/v1/net_rw/hello"), api_category::net_rw,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}},
|
||||
{std::string("/v1/producer_ro/hello"), api_category::producer_ro,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}},
|
||||
{std::string("/v1/producer_rw/hello"), api_category::producer_rw,
|
||||
[&](string&&, string&& body, url_response_callback&& cb) {
|
||||
cb(200, fc::variant("world!"));
|
||||
}}},
|
||||
appbase::exec_queue::read_write);
|
||||
|
||||
BOOST_CHECK(http_plugin->is_on_loopback(api_category::chain_ro));
|
||||
BOOST_CHECK(http_plugin->is_on_loopback(api_category::net_ro));
|
||||
BOOST_CHECK(http_plugin->is_on_loopback(api_category::producer_ro));
|
||||
BOOST_CHECK(http_plugin->is_on_loopback(api_category::producer_rw));
|
||||
BOOST_CHECK(!http_plugin->is_on_loopback(api_category::chain_rw));
|
||||
BOOST_CHECK(!http_plugin->is_on_loopback(api_category::net_rw));
|
||||
|
||||
std::string world_string = "\"world!\"";
|
||||
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/node/hello").body(), world_string);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/node/hello").body(), world_string);
|
||||
|
||||
bool ip_v6_enabled = [] {
|
||||
try {
|
||||
net::io_context ioc;
|
||||
tcp::socket s(ioc, tcp::endpoint{net::ip::address::from_string("::1"), 9999});
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
if (ip_v6_enabled) {
|
||||
BOOST_CHECK_EQUAL(http_response_for("[::1]:8889", "/v1/node/hello").body(), world_string);
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/chain_ro/hello").body(), world_string);
|
||||
BOOST_CHECK_EQUAL(http_response_for("localhost:8890", "/v1/chain_ro/hello").status(), http::status::bad_request);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/net_ro/hello").body(), world_string);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/chain_rw/hello").status(), http::status::not_found);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/net_rw/hello").status(), http::status::not_found);
|
||||
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/chain_ro/hello").status(), http::status::not_found);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/net_ro/hello").status(), http::status::not_found);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/chain_rw/hello").body(), world_string);
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/net_rw/hello").body(), world_string);
|
||||
|
||||
BOOST_CHECK_EQUAL(http_response_for(data_dir / "./producer_ro.sock", "/v1/producer_ro/hello").body(), world_string);
|
||||
BOOST_CHECK_EQUAL(http_response_for(data_dir / "../producer_rw.sock", "/v1/producer_rw/hello").body(), world_string);
|
||||
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/node/get_supported_apis").body(),
|
||||
R"({"apis":["/v1/chain_ro/hello","/v1/net_ro/hello","/v1/node/hello"]})");
|
||||
|
||||
BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/node/get_supported_apis").body(),
|
||||
R"({"apis":["/v1/chain_rw/hello","/v1/net_rw/hello","/v1/node/hello"]})");
|
||||
}
|
||||
|
||||
|
||||
bool on_loopback(std::initializer_list<const char*> args){
|
||||
appbase::scoped_app app;
|
||||
BOOST_REQUIRE(app->initialize<http_plugin>(args.size(), const_cast<char**>(args.begin())));
|
||||
return app->get_plugin<http_plugin>().is_on_loopback(api_category::chain_rw);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_on_loopback) {
|
||||
BOOST_CHECK(on_loopback({"test", "--plugin=eosio::http_plugin", "--http-server-address", "", "--unix-socket-path=a"}));
|
||||
BOOST_CHECK(on_loopback({"test", "--plugin=eosio::http_plugin", "--http-server-address", "127.0.0.1:8888"}));
|
||||
BOOST_CHECK(on_loopback({"test", "--plugin=eosio::http_plugin", "--http-server-address", "localhost:8888"}));
|
||||
BOOST_CHECK(!on_loopback({"test", "--plugin=eosio::http_plugin", "--http-server-address", ":8888"}));
|
||||
BOOST_CHECK(!on_loopback({"test", "--plugin=eosio::http_plugin", "--http-server-address", "example.com:8888"}));
|
||||
}
|
||||
@@ -19,12 +19,13 @@ namespace eosio {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
#define CALL_WITH_400(api_name, api_handle, call_name, INVOKE, http_response_code) \
|
||||
#define CALL_WITH_400(api_name, category, api_handle, call_name, INVOKE, http_response_code) \
|
||||
{std::string("/v1/" #api_name "/" #call_name), \
|
||||
api_category::category, \
|
||||
[&api_handle](string&&, string&& body, url_response_callback&& cb) mutable { \
|
||||
try { \
|
||||
INVOKE \
|
||||
cb(http_response_code, fc::time_point::maximum(), fc::variant(result)); \
|
||||
cb(http_response_code, fc::variant(result)); \
|
||||
} catch (...) { \
|
||||
http_plugin::handle_exception(#api_name, #call_name, body, cb); \
|
||||
} \
|
||||
@@ -54,13 +55,13 @@ void net_api_plugin::plugin_startup() {
|
||||
// lifetime of plugin is lifetime of application
|
||||
auto& net_mgr = app().get_plugin<net_plugin>();
|
||||
app().get_plugin<http_plugin>().add_async_api({
|
||||
CALL_WITH_400(net, net_mgr, connect,
|
||||
CALL_WITH_400(net, net_rw, net_mgr, connect,
|
||||
INVOKE_R_R(net_mgr, connect, std::string), 201),
|
||||
CALL_WITH_400(net, net_mgr, disconnect,
|
||||
CALL_WITH_400(net, net_rw, net_mgr, disconnect,
|
||||
INVOKE_R_R(net_mgr, disconnect, std::string), 201),
|
||||
CALL_WITH_400(net, net_mgr, status,
|
||||
CALL_WITH_400(net, net_ro, net_mgr, status,
|
||||
INVOKE_R_R(net_mgr, status, std::string), 201),
|
||||
CALL_WITH_400(net, net_mgr, connections,
|
||||
CALL_WITH_400(net, net_ro, net_mgr, connections,
|
||||
INVOKE_R_V(net_mgr, connections), 201),
|
||||
} );
|
||||
}
|
||||
@@ -68,11 +69,11 @@ void net_api_plugin::plugin_startup() {
|
||||
void net_api_plugin::plugin_initialize(const variables_map& options) {
|
||||
try {
|
||||
const auto& _http_plugin = app().get_plugin<http_plugin>();
|
||||
if( !_http_plugin.is_on_loopback()) {
|
||||
if( !_http_plugin.is_on_loopback(api_category::net_rw)) {
|
||||
wlog( "\n"
|
||||
"**********SECURITY WARNING**********\n"
|
||||
"* *\n"
|
||||
"* -- Net API -- *\n"
|
||||
"* -- Net RW API -- *\n"
|
||||
"* - EXPOSED to the LOCAL NETWORK - *\n"
|
||||
"* - USE ONLY ON SECURE NETWORKS! - *\n"
|
||||
"* *\n"
|
||||
|
||||
@@ -3,6 +3,10 @@ add_library( net_plugin
|
||||
net_plugin.cpp
|
||||
${HEADERS} )
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14.0)
|
||||
target_compile_options(net_plugin PUBLIC -Wthread-safety)
|
||||
endif()
|
||||
|
||||
target_link_libraries( net_plugin chain_plugin producer_plugin appbase fc )
|
||||
target_include_directories( net_plugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include "${CMAKE_CURRENT_SOURCE_DIR}/../../libraries/appbase/include")
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user