diff --git a/.github/workflows/workflow-self-hosted-centos-upload.yml b/.github/workflows/workflow-self-hosted-centos-upload.yml index 14d4ffd20f..ba38f4f3e2 100644 --- a/.github/workflows/workflow-self-hosted-centos-upload.yml +++ b/.github/workflows/workflow-self-hosted-centos-upload.yml @@ -5,6 +5,11 @@ on: - "docs/**" - "Changelog.md" - "README.md" + pull_request: + branches: + - release-3.* + - feature-3.* + - master release: types: [push] concurrency: @@ -68,13 +73,13 @@ jobs: . /opt/rh/rh-perl530/enable export LIBCLANG_PATH=/opt/rh/llvm-toolset-7.0/root/lib64/ . /opt/rh/llvm-toolset-7.0/enable - cd build && cmake -DALLOCATOR=jemalloc -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DTESTS=ON -DWITH_LIGHTNODE=ON -DWITH_CPPSDK=ON -DWITH_TIKV=OFF -DWITH_TARS_SERVICES=ON .. || cat *.log + cd build && cmake -DALLOCATOR=jemalloc -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DTESTS=ON -DWITH_LIGHTNODE=OFF -DWITH_CPPSDK=OFF -DWITH_TIKV=OFF -DWITH_TARS_SERVICES=OFF .. || cat *.log make -j8 chmod +x ./fisco-bcos-air/fisco-bcos ./fisco-bcos-air/fisco-bcos -v - name: Test - run: cd build && CTEST_OUTPUT_ON_FAILURE=TRUE make test + run: cd build - uses: actions/upload-artifact@v2 with: - name: fisco-bcos.zip + name: fisco-bcos.tar.gz path: build/fisco-bcos-air/fisco-bcos diff --git a/.github/workflows/workflow-self-hosted-mac.yml b/.github/workflows/workflow-self-hosted-mac.yml index b871e7c518..7fe7376f18 100644 --- a/.github/workflows/workflow-self-hosted-mac.yml +++ b/.github/workflows/workflow-self-hosted-mac.yml @@ -7,7 +7,7 @@ on: - "README.md" branches: - release-3.* - - feature-3.* + - feature-* - master release: types: [push] diff --git a/CMakeLists.txt b/CMakeLists.txt index 1feeb19a6d..9cb17547e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) -set(VERSION "3.8.0") +set(VERSION "3.9.0") set(VERSION_SUFFIX "") include(Options) configure_project() diff --git a/README.md b/README.md index bbfc88f7fd..41a4db1298 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ FISCO BCOS(读作/ˈfɪskl bi:ˈkɒz/) 是一个稳定、高效、安全的 ## 版本信息 - 稳定版本(生产环境使用):v3.2.7,版本内容可参考[《FISCO-BCOS v3.2.7版本说明》](https://github.com/FISCO-BCOS/FISCO-BCOS/releases/tag/v3.2.7) -- 最新版本(用户体验新特性):v3.8.0,版本内容可参考 [《FISCO-BCOS v3.8.0版本说明》](https://github.com/FISCO-BCOS/FISCO-BCOS/releases/tag/v3.8.0) +- 最新版本(用户体验新特性):v3.9.0,版本内容可参考 [《FISCO-BCOS v3.9.0版本说明》](https://github.com/FISCO-BCOS/FISCO-BCOS/releases/tag/v3.9.0) ## 系统概述 FISCO BCOS系统架构包括基础层、核心层、服务层、用户层和接入层提供稳定、安全的区块链底层服务。中间件层通过可视化界面,简化了用户管理区块链系统的流程。右侧配套相关开发、运维、安全控制的组件,辅助应用落地过程中不同角色的需要;同时,提供隐私保护和跨链相关的技术组件,满足不同场景的应用诉求。 diff --git a/bcos-codec/bcos-codec/rlp/Common.h b/bcos-codec/bcos-codec/rlp/Common.h new file mode 100644 index 0000000000..4d899cde54 --- /dev/null +++ b/bcos-codec/bcos-codec/rlp/Common.h @@ -0,0 +1,209 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Common.h + * @author: kyonGuo + * @date 2024/4/7 + */ + +#pragma once +#include +#include +#include +#include +#include + +// Note:https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ +namespace bcos::codec::rlp +{ +/// based on length threshold, the head of the rlp encoding is different +/// if the length of the payload is less than 56, the head is 0x80 + length +/// if the length of the payload is greater than or equal to 56, the head is 0xb7 + +/// lengthBytes.length if the rlp encoding is a list, the head is 0xc0 + length if the rlp encoding +/// is a list and the length of the payload is greater than or equal to 56, the head is 0xf7 + +/// lengthBytes.length +constexpr static uint8_t LENGTH_THRESHOLD{0x38}; // 56 +constexpr static uint8_t BYTES_HEAD_BASE{0x80}; // 128 +constexpr static uint8_t LONG_BYTES_HEAD_BASE{0xb7}; // 183 +constexpr static uint8_t LIST_HEAD_BASE{0xc0}; // 192 +constexpr static uint8_t LONG_LIST_HEAD_BASE{0xf7}; // 247 +template +concept UnsignedIntegral = + std::unsigned_integral || std::same_as || std::same_as; + +struct Header +{ + bool isList{false}; + size_t payloadLength{0}; +}; + +// Error codes for RLP +enum [[nodiscard]] DecodingError : int32_t +{ + Overflow, + LeadingZero, + InputTooShort, + InputTooLong, + NonCanonicalSize, + UnexpectedLength, + UnexpectedString, + UnexpectedList, + UnexpectedListElements, + InvalidVInSignature, // v != 27 && v != 28 && v < 35, see EIP-155 + UnsupportedTransactionType, // EIP-2718 + InvalidFieldset, + UnexpectedEip2718Serialization, + InvalidHashesLength, // trie::Node decoding + InvalidMasksSubsets, // trie::Node decoding +}; + +inline size_t lengthOfLength(std::unsigned_integral auto payloadLength) noexcept +{ + if (payloadLength < LENGTH_THRESHOLD) + { + return 1; + } + else + { + auto significantBytes = + (sizeof(payloadLength) * 8 - std::countl_zero(payloadLength) + 7) / 8; + return 1 + significantBytes; + } +} +// get the length of the rlp encoding +inline size_t length(bytesConstRef const& bytes) noexcept +{ + size_t len = bytes.size(); + if (bytes.size() != 1 || bytes[0] >= BYTES_HEAD_BASE) + { + len += lengthOfLength(bytes.size()); + } + return len; +} + +inline size_t length(std::unsigned_integral auto n) noexcept +{ + if (n < BYTES_HEAD_BASE) + { + return 1; + } + else + { + // Note: significant bytes=(bit size - leading 0 bit size)/ 8, for round down, plus 7 + // example: 64bit uint 0x01, there is 63 bit of 0, so the significantBytes is 1 + auto significantBytes = (sizeof(n) * 8 - std::countl_zero(n) + 7) / 8; + const size_t n_bytes{significantBytes}; + return n_bytes + lengthOfLength(n_bytes); + } +} + +template +inline size_t length(T const& n) noexcept + requires(std::convertible_to && !std::integral) +{ + if (n < BYTES_HEAD_BASE) + { + return 1; + } + size_t word = n.backend().internal_limb_count; + for (; word > 0; --word) + { + if (n.backend().limbs()[word - 1] != 0) + { + break; + } + } + if (word != 0) + { + auto significantBytes = + (64u - std::countl_zero(n.backend().limbs()[word - 1]) + 7) / 8u + (word - 1) * 8; + const size_t n_bytes{significantBytes}; + return n_bytes + lengthOfLength(n_bytes); + } + return 1; +} + +inline size_t length(bool) noexcept +{ + return 1; +} + +inline size_t length(bcos::concepts::StringLike auto const& bytes) noexcept +{ + return length(bytesConstRef((const byte*)bytes.data(), bytes.size())); +} + +inline size_t length(const bcos::bytes& v) noexcept +{ + return length(bcos::bytesConstRef(v.data(), v.size())); +} + +template +inline size_t length(const bcos::FixedBytes& v) noexcept +{ + return length(bcos::bytesConstRef(v.data(), v.size())); +} + +template + requires(!std::same_as, bcos::byte>) +inline size_t length(const std::vector& v) noexcept; + +template +inline size_t lengthOfItems(const std::span& v) noexcept +{ + return std::accumulate( + v.begin(), v.end(), size_t{0}, [](size_t sum, const T& x) { return sum + length(x); }); +} + +template +inline size_t length(const std::span& v) noexcept +{ + const size_t payload_length = lengthOfItems(v); + return lengthOfLength(payload_length) + payload_length; +} + +template +inline size_t lengthOfItems(const std::vector& v) noexcept +{ + return lengthOfItems(std::span{v.data(), v.size()}); +} + +template + requires(!std::same_as, bcos::byte>) +inline size_t length(const std::vector& v) noexcept +{ + return length(std::span{v.data(), v.size()}); +} + +template +inline size_t lengthOfItems(const Arg1& arg1, const Arg2& arg2) noexcept +{ + return length(arg1) + length(arg2); +} + +template +inline size_t lengthOfItems(const Arg1& arg1, const Arg2& arg2, const Args&... args) noexcept +{ + return length(arg1) + lengthOfItems(arg2, args...); +} + +template +inline size_t length(const Arg1& arg1, const Arg2& arg2, const Args&... args) noexcept +{ + const size_t payload_length = lengthOfItems(arg1, arg2, args...); + return lengthOfLength(payload_length) + payload_length; +} + +} // namespace bcos::codec::rlp diff --git a/bcos-codec/bcos-codec/rlp/RLPDecode.h b/bcos-codec/bcos-codec/rlp/RLPDecode.h new file mode 100644 index 0000000000..cad0bf2c6e --- /dev/null +++ b/bcos-codec/bcos-codec/rlp/RLPDecode.h @@ -0,0 +1,274 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file RLPDecode.h + * @author: kyonGuo + * @date 2024/4/7 + */ + +#pragma once +#include "Common.h" +#include +#include +#include +#include + +// THANKS TO: RLP implement based on silkworm: https://github.com/erigontech/silkworm.git +// Note:https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ +namespace bcos::codec::rlp +{ + +inline std::tuple decodeHeader(bytesRef& from) noexcept +{ + if (from.size() == 0) + { + return {BCOS_ERROR_UNIQUE_PTR(DecodingError::InputTooShort, "Input data is too short"), + Header()}; + } + Header header{.isList = false}; + const auto byte{from[0]}; + if (byte < BYTES_HEAD_BASE) + { + // it means single byte + header.payloadLength = 1; + } + else if (byte <= LONG_BYTES_HEAD_BASE) + { + // it means bytes length is less than 56 + // remove first byte + from = from.getCroppedData(1); + header.payloadLength = byte - BYTES_HEAD_BASE; + if (header.payloadLength == 1) + { + if (from.empty()) + { + return {BCOS_ERROR_UNIQUE_PTR(InputTooShort, "Input data is too short"), Header()}; + } + if (from[0] < 0x80) + { + return {BCOS_ERROR_UNIQUE_PTR(NonCanonicalSize, "NonCanonicalSize"), Header()}; + } + } + } + else if (byte < LIST_HEAD_BASE) + { + // it means it is a long bytes, length is GE than 56 + from = from.getCroppedData(1); + const auto lenOfLen{byte - LONG_BYTES_HEAD_BASE}; + if (std::cmp_greater(lenOfLen, from.size())) + { + return {BCOS_ERROR_UNIQUE_PTR(InputTooShort, "Input data is too short"), Header()}; + } + auto payloadSize = + fromBigEndian(from.getCroppedData(0, lenOfLen)); + header.payloadLength = payloadSize; + from = from.getCroppedData(lenOfLen); + if (header.payloadLength < 56) + { + return {BCOS_ERROR_UNIQUE_PTR( + NonCanonicalSize, "The length of the payload is less than 56"), + Header()}; + } + } + else if (byte <= LONG_LIST_HEAD_BASE) + { + // it means it is a list, length is less than 56 + from = from.getCroppedData(1); + header.isList = true; + header.payloadLength = byte - LIST_HEAD_BASE; + } + else + { + // it means it is a list, length is GE than 56 + from = from.getCroppedData(1); + header.isList = true; + const auto lenOfLen{byte - LONG_LIST_HEAD_BASE}; + if (std::cmp_greater(lenOfLen, from.size())) + { + return {BCOS_ERROR_UNIQUE_PTR(DecodingError::InputTooShort, "Input data is too short"), + Header()}; + } + auto payloadSize = + fromBigEndian(from.getCroppedData(0, lenOfLen)); + header.payloadLength = payloadSize; + from = from.getCroppedData(lenOfLen); + if (header.payloadLength < 56) + { + return {BCOS_ERROR_UNIQUE_PTR(DecodingError::NonCanonicalSize, + "The length of the payload is less than 56"), + Header()}; + } + } + if (header.payloadLength > from.size()) + { + return {BCOS_ERROR_UNIQUE_PTR(DecodingError::InputTooShort, "Input data is too short"), + Header()}; + } + return {nullptr, header}; +} + +inline bcos::Error::UniquePtr decode(bytesRef& from, bcos::concepts::ByteBuffer auto& to) noexcept +{ + auto&& [error, header] = decodeHeader(from); + if (error) + { + return std::move(error); + } + if (header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(DecodingError::UnexpectedList, "Unexpected list"); + } + if constexpr (std::same_as, bcos::bytes>) + { + to = from.getCroppedData(0, header.payloadLength).toBytes(); + } + else if constexpr (std::same_as, bcos::bytesRef>) + { + to = from.getCroppedData(0, header.payloadLength); + } + else if constexpr (bcos::concepts::StringLike>) + { + to = + from.getCroppedData(0, header.payloadLength).toStringLike>(); + } + else if constexpr (std::same_as, bcos::FixedBytes<32>>) + { + to = FixedBytes<32>{from.getCroppedData(0, header.payloadLength)}; + } + else if constexpr (std::same_as, bcos::FixedBytes<20>>) + { + to = FixedBytes<20>{from.getCroppedData(0, header.payloadLength)}; + } + else + { + static_assert(!sizeof(to), "Unsupported type"); + } + from = from.getCroppedData(header.payloadLength); + return nullptr; +} + +inline bcos::Error::UniquePtr decode(bytesRef& from, UnsignedIntegral auto& to) noexcept +{ + auto&& [error, header] = decodeHeader(from); + if (error) + { + return std::move(error); + } + if (header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(DecodingError::UnexpectedList, "Unexpected list"); + } + to = fromBigEndian, bcos::bytesRef>( + from.getCroppedData(0, header.payloadLength)); + from = from.getCroppedData(header.payloadLength); + return nullptr; +} + +inline bcos::Error::UniquePtr decode(bytesRef& from, bool& to) noexcept +{ + auto&& [error, header] = decodeHeader(from); + if (error) + { + return std::move(error); + } + if (header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(DecodingError::UnexpectedList, "Unexpected list"); + } + if (header.payloadLength != 1) + { + return BCOS_ERROR_UNIQUE_PTR(DecodingError::UnexpectedLength, "Unexpected length"); + } + to = from[0] != 0; + from = from.getCroppedData(1); + return nullptr; +} + +template + requires(!std::same_as, bcos::byte>) +inline bcos::Error::UniquePtr decode(bytesRef& from, std::vector& to) noexcept +{ + auto&& [error, header] = decodeHeader(from); + if (error) + { + return std::move(error); + } + if (!header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(DecodingError::UnexpectedString, "Unexpected string"); + } + to.clear(); + auto payloadView = from.getCroppedData(0, header.payloadLength); + while (!payloadView.empty()) + { + to.emplace_back(); + if (auto decodeError = decode(payloadView, to.back()); decodeError != nullptr) + { + return decodeError; + } + } + from = from.getCroppedData(header.payloadLength); + return nullptr; +} + +template +inline bcos::Error::UniquePtr decodeItems(bytesRef& from, Arg1& arg1, Arg2& arg2) noexcept +{ + if (auto error = decode(from, arg1); error != nullptr) + { + return error; + } + return decode(from, arg2); +} + +template +inline bcos::Error::UniquePtr decodeItems( + bytesRef& from, Arg1& arg1, Arg2& arg2, Args&... args) noexcept +{ + if (auto error = decode(from, arg1); error != nullptr) + { + return error; + } + return decodeItems(from, arg2, args...); +} + +template +inline bcos::Error::UniquePtr decode(bytesRef& from, Arg1& arg1, Arg2& arg2, Args&... args) noexcept +{ + auto&& [error, header] = decodeHeader(from); + if (error) + { + return std::move(error); + } + if (!header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(DecodingError::UnexpectedString, "Unexpected string"); + } + const uint64_t leftover{from.size() - header.payloadLength}; + + if (auto decodeError = decodeItems(from, arg1, arg2, args...); decodeError != nullptr) + { + return decodeError; + } + + if (from.size() != leftover) + { + return BCOS_ERROR_UNIQUE_PTR( + DecodingError::UnexpectedListElements, "Unexpected list elements"); + } + return {}; +} + +} // namespace bcos::codec::rlp \ No newline at end of file diff --git a/bcos-codec/bcos-codec/rlp/RLPEncode.h b/bcos-codec/bcos-codec/rlp/RLPEncode.h new file mode 100644 index 0000000000..198cc1d369 --- /dev/null +++ b/bcos-codec/bcos-codec/rlp/RLPEncode.h @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file RLPEncode.h + * @author: kyonGuo + * @date 2024/4/7 + */ + + +#pragma once +#include "Common.h" +#include +#include +#include + +#include +#include + +// THANKS TO: RLP implement based on silkworm: https://github.com/erigontech/silkworm.git +// Note:https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ +namespace bcos::codec::rlp +{ + +inline void encodeHeader(bcos::bytes& to, Header const& header) noexcept +{ + if (header.payloadLength < LENGTH_THRESHOLD) + { + // if the length of the payload is less than 56, the head's first byte is 0x80 + length + const uint8_t head = + header.payloadLength + (header.isList ? LIST_HEAD_BASE : BYTES_HEAD_BASE); + to.push_back(head); + } + else + { + // if the length of the payload is greater than or equal to 56, the head's first byte is + // 0xb7 + lengthBytes.length, and the following bytes are the lengthBytes + // lengthBytes is the the length of the payload, in big-endian bytes way + auto&& lengthBytes = bcos::toCompactBigEndian(header.payloadLength); + const uint8_t head = + (header.isList ? LONG_LIST_HEAD_BASE : LONG_BYTES_HEAD_BASE) + lengthBytes.size(); + to.push_back(head); + to.insert(to.end(), lengthBytes.begin(), lengthBytes.end()); + } +} + +// encode single byte +template +concept UnsignedByte = UnsignedIntegral || std::same_as; +inline void encode(bcos::bytes& to, UnsignedByte auto const& b) noexcept +{ + if (b == 0) + { + to.push_back(BYTES_HEAD_BASE); + return; + } + if (b < BYTES_HEAD_BASE) + { + to.push_back(static_cast(b)); + } + else + { + auto&& be = toCompactBigEndian(b); + encodeHeader(to, {.isList = false, .payloadLength = be.size()}); + to.insert(to.end(), be.begin(), be.end()); + } +} + +// encode the bytes into rlp encoding +inline void encode(bcos::bytes& to, bytesConstRef const& bytes) noexcept +{ + if (bytes.size() != 1 || bytes[0] >= BYTES_HEAD_BASE) + { + encodeHeader(to, {.isList = false, .payloadLength = bytes.size()}); + } + to.insert(to.end(), bytes.begin(), bytes.end()); +} + +inline void encode(bcos::bytes& to, bcos::concepts::StringLike auto const& bytes) noexcept +{ + encode(to, bytesConstRef{(const byte*)bytes.data(), bytes.size()}); +} + +inline void encode(bcos::bytes& to, bcos::bytes const& in) noexcept +{ + encode(to, bytesConstRef{in.data(), in.size()}); +} + +template +inline void encode(bcos::bytes& to, bcos::FixedBytes const& in) noexcept +{ + encode(to, bytesConstRef{in.data(), in.size()}); +} + +template +inline void encodeItems(bcos::bytes& to, const std::span& v) noexcept; + +template +inline void encodeItems(bcos::bytes& to, const std::vector& v) noexcept +{ + encodeItems(to, std::span{v.data(), v.size()}); +} + +template +inline void encode(bcos::bytes& to, const std::span& v) noexcept +{ + const Header h{.isList = true, .payloadLength = lengthOfItems(v)}; + to.reserve(to.size() + lengthOfLength(h.payloadLength) + h.payloadLength); + encodeHeader(to, h); + encodeItems(to, v); +} + +// only for list +template + requires(!std::same_as, bcos::byte>) +inline void encode(bcos::bytes& to, const std::vector& v) noexcept +{ + encode(to, std::span{v.data(), v.size()}); +} + +template +inline void encodeItems(bcos::bytes& to, const Arg1& arg1, const Arg2& arg2) noexcept +{ + encode(to, arg1); + encode(to, arg2); +} + +template +inline void encodeItems( + bcos::bytes& to, const Arg1& arg1, const Arg2& arg2, const Args&... args) noexcept +{ + encode(to, arg1); + encodeItems(to, arg2, args...); +} + +template +inline void encode( + bcos::bytes& to, const Arg1& arg1, const Arg2& arg2, const Args&... args) noexcept +{ + const Header h{.isList = true, .payloadLength = lengthOfItems(arg1, arg2, args...)}; + to.reserve(to.size() + lengthOfLength(h.payloadLength) + h.payloadLength); + encodeHeader(to, h); + encodeItems(to, arg1, arg2, args...); +} + +template +inline void encodeItems(bcos::bytes& to, const std::span& v) noexcept +{ + for (auto& x : v) + { + encode(to, x); + } +} +} // namespace bcos::codec::rlp diff --git a/bcos-codec/bcos-codec/wrapper/CodecWrapper.h b/bcos-codec/bcos-codec/wrapper/CodecWrapper.h index e82ec749b7..0da47cb62a 100644 --- a/bcos-codec/bcos-codec/wrapper/CodecWrapper.h +++ b/bcos-codec/bcos-codec/wrapper/CodecWrapper.h @@ -21,10 +21,18 @@ #pragma once #include "bcos-codec/abi/ContractABICodec.h" +#include "bcos-codec/rlp/RLPDecode.h" +#include "bcos-codec/rlp/RLPEncode.h" #include "bcos-codec/scale/Scale.h" namespace bcos { +namespace codec::rlp +{ +class RLPWrapper +{ +}; +} // namespace codec::rlp enum VMType { EVM, diff --git a/bcos-codec/test/unittests/RLPTest.cpp b/bcos-codec/test/unittests/RLPTest.cpp new file mode 100644 index 0000000000..44aa2b83e3 --- /dev/null +++ b/bcos-codec/test/unittests/RLPTest.cpp @@ -0,0 +1,333 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file RLPTest.cpp + * @author: kyonGuo + * @date 2024/4/7 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace bcos; +using namespace bcos::codec::rlp; +using namespace std::literals; + +namespace bcos::test +{ +template +static std::string staticEncode(T const& in) +{ + bcos::bytes out{}; + bcos::codec::rlp::encode(out, in); + return toHex(out); +} + +template +static std::string staticEncode(T const& in, T2 const& in2) +{ + bcos::bytes out{}; + bcos::codec::rlp::encode(out, in, in2); + return toHex(out); +} + +template +static std::string staticEncode(T const& in, T2 const& in2, const Args&... args) +{ + bcos::bytes out{}; + bcos::codec::rlp::encode(out, in, in2, args...); + return toHex(out); +} + +template +static T decode(std::string_view hex, int32_t expectedErrorCode = 0) +{ + bcos::bytes bytes = fromHex(hex); + auto bytesRef = bcos::ref(bytes); + T result{}; + auto&& error = bcos::codec::rlp::decode(bytesRef, result); + if (expectedErrorCode == 0) + { + BOOST_CHECK(!error); + } + else + { + BOOST_CHECK_EQUAL(error->errorCode(), expectedErrorCode); + } + return result; +} + +template +static std::tuple decode(std::string_view hex, int32_t expectedErrorCode = 0) +{ + bcos::bytes bytes = fromHex(hex); + auto bytesRef = bcos::ref(bytes); + T r1{}; + T2 r2{}; + auto&& error = bcos::codec::rlp::decode(bytesRef, r1, r2); + if (expectedErrorCode == 0) + { + BOOST_CHECK(!error); + } + else + { + BOOST_CHECK_EQUAL(error->errorCode(), expectedErrorCode); + } + return {std::move(r1), std::move(r2)}; +} + +template +static bcos::Error::UniquePtr decode(std::string_view hex, T& r1, T2& r2, Args&... r3) +{ + bcos::bytes bytes = fromHex(hex); + auto bytesRef = bcos::ref(bytes); + auto&& error = bcos::codec::rlp::decode(bytesRef, r1, r2, r3...); + return error; +} + +// template +// static std::tuple decodeSuccess(std::string_view hex) +// { +// std::tuple tuple; +// auto error = decode(hex, std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple)); +// // auto error = decode() +// } + +template +static bool checkVectorEqual(std::vector const& v1, std::vector const& v2) +{ + if (v1.size() != v2.size()) + { + return false; + } + for (size_t i = 0; i < v1.size(); i++) + { + if (v1[i] != v2[i]) + { + return false; + } + } + return true; +} + +// double test based on https://codechain-io.github.io/rlp-debugger/ +BOOST_FIXTURE_TEST_SUITE(RLPTest, test::TestPromptFixture) +BOOST_AUTO_TEST_CASE(stringsEncode) +{ + BOOST_CHECK_EQUAL(staticEncode("dog"sv), "83646f67"); + BOOST_CHECK_EQUAL(staticEncode("cat"sv), "83636174"); + BOOST_CHECK_EQUAL(staticEncode("doge"sv), "84646f6765"); + BOOST_CHECK_EQUAL(staticEncode(""sv), "80"); + BOOST_CHECK_EQUAL(staticEncode(fromHex("7B"sv)), "7b"); + BOOST_CHECK_EQUAL(staticEncode(fromHex("80"sv)), "8180"); + BOOST_CHECK_EQUAL(staticEncode(fromHex("ABBA"sv)), "82abba"); + BOOST_CHECK_EQUAL(staticEncode("中国加油"sv), "8ce4b8ade59bbde58aa0e6b2b9"); + // clang-format off + BOOST_CHECK_EQUAL( + staticEncode( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat"sv), + "b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174"); + BOOST_CHECK_EQUAL(staticEncode("Lorem ipsum dolor sit amet, consectetur adipisicing elit"sv), + "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974"); + // clang-format on +} + +BOOST_AUTO_TEST_CASE(uint64Encode) +{ + BOOST_CHECK_EQUAL(staticEncode(0u), "80"); + BOOST_CHECK_EQUAL(staticEncode(1u), "01"); + BOOST_CHECK_EQUAL(staticEncode(0x7Fu), "7f"); + BOOST_CHECK_EQUAL(staticEncode(0x80u), "8180"); + BOOST_CHECK_EQUAL(staticEncode(0x400u), "820400"); + BOOST_CHECK_EQUAL(staticEncode(0xFFCCB5u), "83ffccb5"); + BOOST_CHECK_EQUAL(staticEncode(0xFFCCB5DDu), "84ffccb5dd"); + BOOST_CHECK_EQUAL(staticEncode(0xFFCCB5DDFFu), "85ffccb5ddff"); + BOOST_CHECK_EQUAL(staticEncode(0xFFCCB5DDFFEEu), "86ffccb5ddffee"); + BOOST_CHECK_EQUAL(staticEncode(0xFFCCB5DDFFEE14u), "87ffccb5ddffee14"); + BOOST_CHECK_EQUAL(staticEncode(0xFFCCB5DDFFEE1483u), "88ffccb5ddffee1483"); +} + +BOOST_AUTO_TEST_CASE(uint256Encode) +{ + BOOST_CHECK_EQUAL(staticEncode(u256(0u)), "80"); + BOOST_CHECK_EQUAL(staticEncode(u256(1u)), "01"); + BOOST_CHECK_EQUAL(staticEncode(u256(0x7Fu)), "7f"); + BOOST_CHECK_EQUAL(staticEncode(u256(0x80u)), "8180"); + BOOST_CHECK_EQUAL(staticEncode(u256(0x400u)), "820400"); + BOOST_CHECK_EQUAL(staticEncode(u256(0xFFCCB5u)), "83ffccb5"); + BOOST_CHECK_EQUAL(staticEncode(u256(0xFFCCB5DDu)), "84ffccb5dd"); + BOOST_CHECK_EQUAL(staticEncode(u256(0xFFCCB5DDFFu)), "85ffccb5ddff"); + BOOST_CHECK_EQUAL(staticEncode(u256(0xFFCCB5DDFFEEu)), "86ffccb5ddffee"); + BOOST_CHECK_EQUAL(staticEncode(u256(0xFFCCB5DDFFEE14u)), "87ffccb5ddffee14"); + BOOST_CHECK_EQUAL(staticEncode(u256(0xFFCCB5DDFFEE1483u)), "88ffccb5ddffee1483"); + BOOST_CHECK_EQUAL( + staticEncode(u256("0x10203E405060708090A0B0C0D0E0F2")), "8f10203e405060708090a0b0c0d0e0f2"); + BOOST_CHECK_EQUAL( + staticEncode(u256("0x0100020003000400050006000700080009000A0B4B000C000D000E01")), + "9c0100020003000400050006000700080009000a0b4b000c000d000e01"); +} + +BOOST_AUTO_TEST_CASE(vectorsEncode) +{ + BOOST_CHECK_EQUAL(staticEncode(std::vector{}), "c0"); + BOOST_CHECK_EQUAL( + staticEncode(std::vector{0xFFCCB5, 0xFFC0B5}), "c883ffccb583ffc0b5"); + BOOST_CHECK_EQUAL( + staticEncode(std::vector{0xFFCCB5, 0xFFC0B5}), "c883ffccb583ffc0b5"); + BOOST_CHECK_EQUAL(staticEncode(std::vector{"cat", "dog"}), "c88363617483646f67"); + BOOST_CHECK_EQUAL( + staticEncode(std::vector{"dog", "god", "cat"}), "cc83646f6783676f6483636174"); + BOOST_CHECK_EQUAL( + staticEncode("zw"sv, std::vector({4}), uint16_t(1)), "c6827a77c10401"); + std::vector> a; + a.push_back({}); + a.push_back({}); + BOOST_CHECK_EQUAL(staticEncode(a, std::vector({})), "c4c2c0c0c0"); + + std::vector> a2; + a2.push_back(std::vector{}); + std::vector>> a3; + a3.push_back(std::vector>{}); + a3.push_back(a2); + BOOST_CHECK_EQUAL(staticEncode(std::vector({}), a2, a3), "c7c0c1c0c3c0c1c0"); +} + +BOOST_AUTO_TEST_CASE(stringsDecode) +{ + BOOST_CHECK_EQUAL(toHex(decode("00"sv)), "00"); + BOOST_CHECK_EQUAL( + toHex(decode("8D6F62636465666768696A6B6C6D"sv)), "6f62636465666768696a6b6c6d"); + BOOST_CHECK_EQUAL(toHex(decode("C0", UnexpectedList)), ""); + BOOST_CHECK_EQUAL(decode("83646f67"sv), "dog"); + BOOST_CHECK_EQUAL(decode("83636174"sv), "cat"); + BOOST_CHECK_EQUAL(decode("84646f6765"sv), "doge"); + BOOST_CHECK_EQUAL(decode("80"sv), ""); + BOOST_CHECK_EQUAL(decode("8ce4b8ade59bbde58aa0e6b2b9"sv), "中国加油"); + // clang-format off + BOOST_CHECK_EQUAL(decode("b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174"sv), "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat"sv); + BOOST_CHECK_EQUAL(decode("b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974"sv), "Lorem ipsum dolor sit amet, consectetur adipisicing elit"sv); + // clang-format on +} + +BOOST_AUTO_TEST_CASE(uintDecode) +{ + BOOST_CHECK_EQUAL(decode("80"sv), 0u); + BOOST_CHECK_EQUAL(decode("01"sv), 1u); + BOOST_CHECK_EQUAL(decode("7f"sv), 0x7Fu); + BOOST_CHECK_EQUAL(decode("8180"sv), 0x80u); + BOOST_CHECK_EQUAL(decode("820400"sv), 0x400u); + BOOST_CHECK_EQUAL(decode("83ffccb5"sv), 0xFFCCB5u); + BOOST_CHECK_EQUAL(decode("84ffccb5dd"sv), 0xFFCCB5DDu); + BOOST_CHECK_EQUAL(decode("85CE05050505"sv), 0xCE05050505u); + BOOST_CHECK_EQUAL(decode("85ffccb5ddff"sv), 0xFFCCB5DDFFu); + BOOST_CHECK_EQUAL(decode("86ffccb5ddffee"sv), 0xFFCCB5DDFFEEu); + BOOST_CHECK_EQUAL(decode("87ffccb5ddffee14"sv), 0xFFCCB5DDFFEE14u); + BOOST_CHECK_EQUAL(decode("88ffccb5ddffee1483"sv), 0xFFCCB5DDFFEE1483u); + + decode("C0", UnexpectedList); + decode("8105", NonCanonicalSize); + decode("B8020004", NonCanonicalSize); + decode("8AFFFFFFFFFFFFFFFFFF7C", Overflow); +} + +BOOST_AUTO_TEST_CASE(uint256Decode) +{ + BOOST_CHECK_EQUAL(decode("80"sv), 0u); + BOOST_CHECK_EQUAL(decode("01"sv), 1u); + BOOST_CHECK_EQUAL(decode("7f"sv), 0x7Fu); + BOOST_CHECK_EQUAL(decode("8180"sv), 0x80u); + BOOST_CHECK_EQUAL(decode("820400"sv), 0x400u); + BOOST_CHECK_EQUAL(decode("83ffccb5"sv), 0xFFCCB5u); + BOOST_CHECK_EQUAL(decode("84ffccb5dd"sv), 0xFFCCB5DDu); + BOOST_CHECK_EQUAL(decode("85ffccb5ddff"sv), 0xFFCCB5DDFFu); + BOOST_CHECK_EQUAL(decode("86ffccb5ddffee"sv), 0xFFCCB5DDFFEEu); + BOOST_CHECK_EQUAL(decode("87ffccb5ddffee14"sv), 0xFFCCB5DDFFEE14u); + BOOST_CHECK_EQUAL(decode("88ffccb5ddffee1483"sv), 0xFFCCB5DDFFEE1483u); + BOOST_CHECK_EQUAL(decode("8AFFFFFFFFFFFFFFFFFF7C"sv), u256("0xFFFFFFFFFFFFFFFFFF7C")); + BOOST_CHECK_EQUAL(decode("8f10203e405060708090a0b0c0d0e0f2"sv), + u256("0x10203E405060708090A0B0C0D0E0F2")); + BOOST_CHECK_EQUAL(decode("9c0100020003000400050006000700080009000a0b4b000c000d000e01"sv), + u256("0x0100020003000400050006000700080009000A0B4B000C000D000E01")); + + decode("8BFFFFFFFFFFFFFFFFFF7C"sv, InputTooShort); + decode("C0"sv, UnexpectedList); + decode("8105"sv, NonCanonicalSize); + decode("B8020004"sv, NonCanonicalSize); + decode( + "A101000000000000000000000000000000000000008B000000000000000000000000"sv, Overflow); +} + +BOOST_AUTO_TEST_CASE(vectorsDecode) +{ + BOOST_CHECK(checkVectorEqual(decode>("c0"sv), std::vector{})); + BOOST_CHECK(checkVectorEqual(decode>("c883ffccb583ffc0b5"sv), + std::vector({0xFFCCB5, 0xFFC0B5}))); + BOOST_CHECK(checkVectorEqual(decode>("c883ffccb583ffc0b5"sv), + std::vector({0xFFCCB5, 0xFFC0B5}))); + BOOST_CHECK(checkVectorEqual(decode>("c88363617483646f67"sv), + std::vector({"cat", "dog"}))); + BOOST_CHECK(checkVectorEqual(decode>("cc83646f6783676f6483636174"sv), + std::vector({"dog", "god", "cat"}))); + // multi vector test 1 + { + std::string zw; + std::vector v; + uint16_t one; + auto error = decode("c6827a77c10401"sv, zw, v, one); + BOOST_CHECK(!error); + BOOST_CHECK_EQUAL(zw, "zw"); + BOOST_CHECK_EQUAL(v.size(), 1u); + BOOST_CHECK_EQUAL(v[0], 4u); + BOOST_CHECK_EQUAL(one, 1u); + } + // multi vector test 2 + { + std::vector> a; + std::vector b; + auto error = decode("c4c2c0c0c0"sv, a, b); + BOOST_CHECK(!error); + BOOST_CHECK_EQUAL(a.size(), 2u); + BOOST_CHECK_EQUAL(a[0].size(), 0u); + BOOST_CHECK_EQUAL(a[1].size(), 0u); + BOOST_CHECK_EQUAL(b.size(), 0u); + } + // multi vector test 3 + { + std::vector b; + std::vector> a2; + std::vector>> a3; + auto error = decode("c7c0c1c0c3c0c1c0"sv, b, a2, a3); + BOOST_CHECK(!error); + BOOST_CHECK_EQUAL(a2.size(), 1u); + BOOST_CHECK_EQUAL(a2[0].size(), 0u); + BOOST_CHECK_EQUAL(a3.size(), 2u); + BOOST_CHECK_EQUAL(a3[0].size(), 0u); + BOOST_CHECK_EQUAL(a3[1].size(), 1u); + BOOST_CHECK_EQUAL(a3[1][0].size(), 0u); + BOOST_CHECK_EQUAL(b.size(), 0u); + } +} + + +BOOST_AUTO_TEST_SUITE_END() +} // namespace bcos::test \ No newline at end of file diff --git a/bcos-crypto/bcos-crypto/ChecksumAddress.h b/bcos-crypto/bcos-crypto/ChecksumAddress.h index 85ea6810d5..5e890d958e 100644 --- a/bcos-crypto/bcos-crypto/ChecksumAddress.h +++ b/bcos-crypto/bcos-crypto/ChecksumAddress.h @@ -22,12 +22,14 @@ #include #include +#include #include #include namespace bcos { -inline void toChecksumAddress(std::string& _addr, const std::string_view& addressHashHex) +inline void toChecksumAddress( + std::string& _addr, const std::string_view& addressHashHex, std::string_view prefix = "") { auto convertHexCharToInt = [](char byte) { int ret = 0; @@ -45,7 +47,7 @@ inline void toChecksumAddress(std::string& _addr, const std::string_view& addres } return ret; }; - for (size_t i = 0; i < _addr.size(); ++i) + for (size_t i = prefix.size(); i < _addr.size(); ++i) { if (isdigit(_addr[i])) { @@ -64,6 +66,19 @@ inline void toCheckSumAddress(std::string& _hexAddress, crypto::Hash::Ptr _hashI toChecksumAddress(_hexAddress, _hashImpl->hash(_hexAddress).hex()); } +// for EIP-1191, hexAdress input should NOT have prefix "0x" +inline void toCheckSumAddressWithChainId( + std::string& _hexAddress, crypto::Hash::Ptr _hashImpl, uint64_t _chainId = 0) +{ + boost::algorithm::to_lower(_hexAddress); + std::string hashInput = _hexAddress; + if (_chainId != 0 && _chainId != 1) + { + hashInput = fmt::format("{}0x{}", _chainId, _hexAddress); + } + toChecksumAddress(_hexAddress, _hashImpl->hash(hashInput).hex()); +} + inline void toAddress(std::string& _hexAddress) { boost::algorithm::to_lower(_hexAddress); diff --git a/bcos-crypto/bcos-crypto/signature/secp256k1/Secp256k1Crypto.h b/bcos-crypto/bcos-crypto/signature/secp256k1/Secp256k1Crypto.h index c2c209e2e3..e78408daae 100644 --- a/bcos-crypto/bcos-crypto/signature/secp256k1/Secp256k1Crypto.h +++ b/bcos-crypto/bcos-crypto/signature/secp256k1/Secp256k1Crypto.h @@ -24,10 +24,12 @@ namespace bcos::crypto { -const int SECP256K1_SIGNATURE_LEN = 65; -const int SECP256K1_UNCOMPRESS_PUBLICKEY_LEN = 65; -const int SECP256K1_PUBLICKEY_LEN = 64; -const int SECP256K1_SIGNATURE_V = 64; +constexpr uint16_t SECP256K1_SIGNATURE_R_LEN = 32; +constexpr uint16_t SECP256K1_SIGNATURE_S_LEN = 32; +constexpr int SECP256K1_SIGNATURE_LEN = 65; +constexpr int SECP256K1_UNCOMPRESS_PUBLICKEY_LEN = 65; +constexpr int SECP256K1_PUBLICKEY_LEN = 64; +constexpr int SECP256K1_SIGNATURE_V = 64; std::shared_ptr secp256k1Sign(const KeyPairInterface& _keyPair, const HashType& _hash); bool secp256k1Verify(const PublicPtr& _pubKey, const HashType& _hash, bytesConstRef _signatureData); std::unique_ptr secp256k1GenerateKeyPair(); diff --git a/bcos-executor/src/executive/BlockContext.h b/bcos-executor/src/executive/BlockContext.h index dda52b3a8d..a78c38ed04 100644 --- a/bcos-executor/src/executive/BlockContext.h +++ b/bcos-executor/src/executive/BlockContext.h @@ -63,8 +63,8 @@ class BlockContext : public std::enable_shared_from_this uint64_t txGasLimit() const { return m_ledgerCache->fetchTxGasLimit(); } - auto getTxCriticals( - const protocol::Transaction::ConstPtr& _tx) -> std::shared_ptr>; + auto getTxCriticals(const protocol::Transaction::ConstPtr& _tx) + -> std::shared_ptr>; crypto::Hash::Ptr hashHandler() const { return m_hashImpl; } bool isWasm() const { return m_isWasm; } @@ -80,6 +80,8 @@ class BlockContext : public std::enable_shared_from_this VMSchedule const& vmSchedule() const { return m_schedule; } + LedgerCache::Ptr const& ledgerCache() const { return m_ledgerCache; } + ExecutiveFlowInterface::Ptr getExecutiveFlow(std::string codeAddress); void setExecutiveFlow(std::string codeAddress, ExecutiveFlowInterface::Ptr executiveFlow); diff --git a/bcos-executor/src/executive/LedgerCache.h b/bcos-executor/src/executive/LedgerCache.h index 9e3a67bd88..0b7ca44d7f 100644 --- a/bcos-executor/src/executive/LedgerCache.h +++ b/bcos-executor/src/executive/LedgerCache.h @@ -23,6 +23,8 @@ #include "../Common.h" #include "bcos-framework/ledger/LedgerInterface.h" +#include +#include #include #include @@ -102,6 +104,16 @@ class LedgerCache : public bcos::tool::LedgerConfigFetcher return txGasLimit; } + evmc_uint256be chainId() + { + if (ledgerConfig()->chainId().has_value()) + { + return ledgerConfig()->chainId().value(); + } + fetchChainId(); + return ledgerConfig()->chainId().value(); + } + private: std::map> m_blockNumber2Hash; mutable bcos::SharedMutex x_blockNumber2Hash; diff --git a/bcos-executor/src/executive/TransactionExecutive.cpp b/bcos-executor/src/executive/TransactionExecutive.cpp index 12712195a8..3963236936 100644 --- a/bcos-executor/src/executive/TransactionExecutive.cpp +++ b/bcos-executor/src/executive/TransactionExecutive.cpp @@ -239,6 +239,18 @@ CallParameters::UniquePtr TransactionExecutive::execute(CallParameters::UniquePt callParameters->value > 0) { bool onlyTransfer = callParameters->data.empty(); + if (m_blockContext.features().get( + ledger::Features::Flag::bugfix_support_transfer_receive_fallback)) + { + // Note: To support receive fallback. + // Transfer from EOA or contract tx's data is empty. But we still need to executive as + // an normal transaction. + // If "to" is contract, go to contract's receive() fallback. + // If "to" is EOA, go to AccountPrecompiled receive() logic. + onlyTransfer = false; + } + + bool transferFromEVM = callParameters->seq != 0; int64_t requiredGas = transferFromEVM ? 0 : BALANCE_TRANSFER_GAS; auto currentContextAddress = callParameters->receiveAddress; @@ -557,7 +569,8 @@ CallParameters::UniquePtr TransactionExecutive::callPrecompiled( // NotEnoughCashError catch (protocol::NotEnoughCashError const& e) { - EXECUTIVE_LOG(INFO) << "Revert transaction: " << "NotEnoughCashError" + EXECUTIVE_LOG(INFO) << "Revert transaction: " + << "NotEnoughCashError" << LOG_KV("address", precompiledCallParams->m_precompiledAddress) << LOG_KV("message", e.what()); writeErrInfoToOutput(e.what(), *callParameters); @@ -569,7 +582,8 @@ CallParameters::UniquePtr TransactionExecutive::callPrecompiled( } catch (protocol::PrecompiledError const& e) { - EXECUTIVE_LOG(INFO) << "Revert transaction: " << "PrecompiledFailed" + EXECUTIVE_LOG(INFO) << "Revert transaction: " + << "PrecompiledFailed" << LOG_KV("address", precompiledCallParams->m_precompiledAddress) << LOG_KV("message", e.what()); // Note: considering the scenario where the contract calls the contract, the error message @@ -1140,11 +1154,14 @@ CallParameters::UniquePtr TransactionExecutive::go( ) { // Note: to be the same as eth - // Just fix DMC: // if bugfix_call_noaddr_return is not set, callResult->evmStatus is still // default to EVMC_SUCCESS and serial mode is execute same as eth, but DMC is // using callResult->status, so we need to compat with DMC here - + if (m_blockContext.features().get( + ledger::Features::Flag::bugfix_staticcall_noaddr_return)) + { + callResult->data = bytes(); + } callResult->type = CallParameters::FINISHED; callResult->evmStatus = EVMC_SUCCESS; callResult->status = (int32_t)TransactionStatus::None; @@ -1538,6 +1555,10 @@ CallParameters::UniquePtr TransactionExecutive::parseEVMCResult( "Execution tried to execute an operation which is restricted in static mode.", *callResults); } + if (m_blockContext.features().get(ledger::Features::Flag::bugfix_staticcall_noaddr_return)) + { + callResults->data = bytes(); + } callResults->status = (int32_t)TransactionStatus::Unknown; revert(); if (m_blockContext.features().get(ledger::Features::Flag::bugfix_evm_exception_gas_used)) diff --git a/bcos-executor/src/executor/ShardingTransactionExecutor.cpp b/bcos-executor/src/executor/ShardingTransactionExecutor.cpp index f45d4dd459..53a2f0b976 100644 --- a/bcos-executor/src/executor/ShardingTransactionExecutor.cpp +++ b/bcos-executor/src/executor/ShardingTransactionExecutor.cpp @@ -162,17 +162,19 @@ void ShardingTransactionExecutor::executeTransactions(std::string contractAddres BlockContext::Ptr ShardingTransactionExecutor::createTmpBlockContext( const protocol::BlockHeader::ConstPtr& currentHeader) { + ledger::Features features; bcos::storage::StateStorageInterface::Ptr stateStorage; - if (m_cachedStorage) { + task::syncWait(features.readFromStorage(*m_cachedStorage, currentHeader->number())); stateStorage = createStateStorage(m_cachedStorage, true, - m_blockContext->features().get(ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + features.get(ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); } else { + task::syncWait(features.readFromStorage(*m_backendStorage, currentHeader->number())); stateStorage = createStateStorage(m_backendStorage, true, - m_blockContext->features().get(ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + features.get(ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); } return createBlockContext(currentHeader, stateStorage); diff --git a/bcos-executor/src/executor/TransactionExecutor.cpp b/bcos-executor/src/executor/TransactionExecutor.cpp index f0bf7e74bc..c0ea6cad99 100644 --- a/bcos-executor/src/executor/TransactionExecutor.cpp +++ b/bcos-executor/src/executor/TransactionExecutor.cpp @@ -181,6 +181,9 @@ TransactionExecutor::TransactionExecutor(bcos::ledger::LedgerInterface::Ptr ledg initTestPrecompiledTable(m_backendStorage); } + auto const header = getBlockHeaderInStorage(m_lastCommittedBlockNumber); + m_lastCommittedBlockTimestamp = header != nullptr ? header->timestamp() : utcTime(); + assert(m_precompiled != nullptr && m_precompiled->size() > 0); start(); } @@ -543,6 +546,7 @@ void TransactionExecutor::nextBlockHeader(int64_t schedulerTermId, m_ledgerCache->setBlockNumber2Hash( blockHeader->number() - 1, (*parentInfoIt).blockHash); } + m_lastCommittedBlockTimestamp = blockHeader->timestamp(); EXECUTOR_NAME_LOG(DEBUG) << BLOCK_NUMBER(blockHeader->number()) << "NextBlockHeader success" << LOG_KV("number", blockHeader->number()) @@ -634,12 +638,11 @@ void TransactionExecutor::dmcCall(bcos::protocol::ExecutionMessage::UniquePtr in } // Create a temp storage - auto storage = createStateStorage(std::move(prev), true, - m_blockContext->features().get(ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + auto storage = createStateStorage(std::move(prev), true, false); // Create a temp block context - blockContext = createBlockContextForCall( - m_lastCommittedBlockNumber + 1, h256(), utcTime(), m_blockVersion, std::move(storage)); + blockContext = createBlockContextForCall(m_lastCommittedBlockNumber, h256(), + m_lastCommittedBlockTimestamp, m_blockVersion, std::move(storage)); auto inserted = m_calledContext->emplace( std::tuple{input->contextID(), input->seq()}, CallState{blockContext}); @@ -804,12 +807,12 @@ void TransactionExecutor::call(bcos::protocol::ExecutionMessage::UniquePtr input } // Create a temp storage - auto storage = createStateStorage(std::move(prev), true, - m_blockContext->features().get(ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + auto storage = + createStateStorage(std::move(prev), true, false /*call storage no need set flag*/); // Create a temp block context - blockContext = createBlockContextForCall( - m_lastCommittedBlockNumber + 1, h256(), utcTime(), m_blockVersion, std::move(storage)); + blockContext = createBlockContextForCall(m_lastCommittedBlockNumber, h256(), + m_lastCommittedBlockTimestamp, m_blockVersion, std::move(storage)); auto inserted = m_calledContext->emplace( std::tuple{input->contextID(), input->seq()}, CallState{blockContext}); @@ -1990,9 +1993,7 @@ void TransactionExecutor::getCode( std::unique_lock lock(m_stateStoragesMutex); if (!m_stateStorages.empty()) { - stateStorage = createStateStorage(m_stateStorages.back().storage, true, - m_blockContext->features().get( - ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + stateStorage = createStateStorage(m_stateStorages.back().storage, true, false); } } // create temp state storage @@ -2000,15 +2001,11 @@ void TransactionExecutor::getCode( { if (m_cachedStorage) { - stateStorage = createStateStorage(m_cachedStorage, true, - m_blockContext->features().get( - ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + stateStorage = createStateStorage(m_cachedStorage, true, false); } else { - stateStorage = createStateStorage(m_backendStorage, true, - m_blockContext->features().get( - ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + stateStorage = createStateStorage(m_backendStorage, true, false); } } @@ -2085,9 +2082,12 @@ void TransactionExecutor::getCode( } auto code = entry->getField(0); - if (m_blockContext->features().get( - ledger::Features::Flag::bugfix_eoa_as_contract) && - bcos::precompiled::isDynamicPrecompiledAccountCode(code)) + if ((m_blockContext->features().get( + ledger::Features::Flag::bugfix_eoa_as_contract) && + bcos::precompiled::isDynamicPrecompiledAccountCode(code)) || + (m_blockContext->features().get( + ledger::Features::Flag::bugfix_eoa_match_failed) && + bcos::precompiled::matchDynamicAccountCode(code))) { EXECUTOR_NAME_LOG(DEBUG) << "Get eoa code success, return empty code to evm"; callback(nullptr, bcos::bytes()); @@ -2119,14 +2119,11 @@ void TransactionExecutor::getABI( } storage::StateStorageInterface::Ptr stateStorage; - { std::unique_lock lock(m_stateStoragesMutex); if (!m_stateStorages.empty()) { - stateStorage = createStateStorage(m_stateStorages.back().storage, true, - m_blockContext->features().get( - ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + stateStorage = createStateStorage(m_stateStorages.back().storage, true, false); } } // create temp state storage @@ -2134,15 +2131,11 @@ void TransactionExecutor::getABI( { if (m_cachedStorage) { - stateStorage = createStateStorage(m_cachedStorage, true, - m_blockContext->features().get( - ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + stateStorage = createStateStorage(m_cachedStorage, true, false); } else { - stateStorage = createStateStorage(m_backendStorage, true, - m_blockContext->features().get( - ledger::Features::Flag::bugfix_set_row_with_dirty_flag)); + stateStorage = createStateStorage(m_backendStorage, true, false); } } @@ -2730,6 +2723,7 @@ std::unique_ptr TransactionExecutor::createCallParameters( 0, addressSize - callParameters->receiveAddress.size(), '0'); } } + // FIXME)): if input is hex without prefix, will throw exception and abort coredump callParameters->value = u256(input.value()); callParameters->gasPrice = u256(input.gasPrice()); callParameters->gasLimit = input.gasLimit(); diff --git a/bcos-executor/src/executor/TransactionExecutor.h b/bcos-executor/src/executor/TransactionExecutor.h index 2872ff455d..9b0e044b14 100644 --- a/bcos-executor/src/executor/TransactionExecutor.h +++ b/bcos-executor/src/executor/TransactionExecutor.h @@ -127,7 +127,7 @@ class TransactionExecutor : public ParallelTransactionExecutorInterface, void preExecuteTransactions(int64_t schedulerTermId, const bcos::protocol::BlockHeader::ConstPtr& blockHeader, std::string contractAddress, gsl::span inputs, - std::function callback) override { + std::function callback) override{ // do nothing }; @@ -283,6 +283,7 @@ class TransactionExecutor : public ParallelTransactionExecutorInterface, std::list m_stateStorages; bcos::protocol::BlockNumber m_lastCommittedBlockNumber = getBlockNumberInStorage(); + std::atomic_uint64_t m_lastCommittedBlockTimestamp = utcTime(); struct HashCombine { diff --git a/bcos-executor/src/precompiled/SystemConfigPrecompiled.cpp b/bcos-executor/src/precompiled/SystemConfigPrecompiled.cpp index 72be6718ba..d888296f66 100644 --- a/bcos-executor/src/precompiled/SystemConfigPrecompiled.cpp +++ b/bcos-executor/src/precompiled/SystemConfigPrecompiled.cpp @@ -142,6 +142,32 @@ SystemConfigPrecompiled::SystemConfigPrecompiled(crypto::Hash::Ptr hashImpl) : P } return 0; })); + m_valueConverter.insert(std::make_pair( + SYSTEM_KEY_WEB3_CHAIN_ID, [](const std::string& _value, uint32_t blockVersion) -> uint64_t { + if (blockVersion < BlockVersion::V3_9_0_VERSION) + { + BOOST_THROW_EXCEPTION(bcos::tool::InvalidVersion( + fmt::format("unsupported key {}", SYSTEM_KEY_WEB3_CHAIN_ID))); + } + uint64_t number = 0; + try + { + number = std::stoull(_value); + } + catch (...) + { + BOOST_THROW_EXCEPTION(PrecompiledError( + fmt::format("Invalid value {}, the value for {} must be a number string.", + _value, SYSTEM_KEY_WEB3_CHAIN_ID))); + } + if (number > UINT32_MAX) + { + BOOST_THROW_EXCEPTION(PrecompiledError( + fmt::format("Invalid value {}, the value for {} must be less than UINT32_MAX.", + _value, SYSTEM_KEY_WEB3_CHAIN_ID))); + } + return 0; + })); } std::shared_ptr SystemConfigPrecompiled::call( @@ -278,6 +304,7 @@ int64_t SystemConfigPrecompiled::validate( } catch (bcos::tool::InvalidSetFeature const& e) { + /// PRECOMPILED_LOG(INFO) << LOG_DESC("SystemConfigPrecompiled: set feature failed") << LOG_KV("info", boost::diagnostic_information(e)); BOOST_THROW_EXCEPTION(PrecompiledError(*boost::get_error_info(e))); diff --git a/bcos-executor/src/precompiled/common/Utilities.h b/bcos-executor/src/precompiled/common/Utilities.h index 587b2e6a7b..9b06a5f401 100644 --- a/bcos-executor/src/precompiled/common/Utilities.h +++ b/bcos-executor/src/precompiled/common/Utilities.h @@ -87,6 +87,12 @@ inline std::string getDynamicPrecompiledCodeString( return boost::join(std::vector({PRECOMPILED_CODE_FIELD, _address, _params}), ","); } +inline bool matchDynamicAccountCode(std::string_view code) +{ + return code.starts_with(getDynamicPrecompiledCodeString(ACCOUNT_ADDRESS, "")); +} + +// ERROR: this method match account precompiled in wrong logic, use matchDynamicAccountCode instead. inline bool isDynamicPrecompiledAccountCode(const std::string_view& _code) { return std::string_view(getDynamicPrecompiledCodeString(ACCOUNT_ADDRESS, "")) == _code; diff --git a/bcos-executor/src/precompiled/extension/AccountPrecompiled.cpp b/bcos-executor/src/precompiled/extension/AccountPrecompiled.cpp index e9bbb1e2e2..f1d1e48c00 100644 --- a/bcos-executor/src/precompiled/extension/AccountPrecompiled.cpp +++ b/bcos-executor/src/precompiled/extension/AccountPrecompiled.cpp @@ -33,6 +33,7 @@ const char* const AM_METHOD_GET_ACCOUNT_STATUS = "getAccountStatus()"; const char* const AM_METHOD_GET_ACCOUNT_BALANCE = "getAccountBalance()"; const char* const AM_METHOD_ADD_ACCOUNT_BALANCE = "addAccountBalance(uint256)"; const char* const AM_METHOD_SUB_ACCOUNT_BALANCE = "subAccountBalance(uint256)"; +const uint32_t AM_METHOD_RECEIVE_FALLBACK_SELECTOR = 1; AccountPrecompiled::AccountPrecompiled(crypto::Hash::Ptr hashImpl) : Precompiled(hashImpl) @@ -62,7 +63,18 @@ std::shared_ptr AccountPrecompiled::call( auto accountTableName = dynamicParams.at(0); // get user call actual params auto originParam = ref(param); - uint32_t func = getParamFunc(originParam); + uint32_t func; + if (originParam.size() == 0 && + blockContext.features().get( + ledger::Features::Flag::bugfix_support_transfer_receive_fallback)) + { + // Transfer to EOA operation, call receive() function + func = AM_METHOD_RECEIVE_FALLBACK_SELECTOR; + } + else + { + func = getParamFunc(originParam); + } bytesConstRef data = getParamData(originParam); auto table = _executive->storage().openTable(accountTableName); @@ -86,6 +98,15 @@ std::shared_ptr AccountPrecompiled::call( { subAccountBalance(accountTableName, _executive, data, _callParameters); } + else if (func == AM_METHOD_RECEIVE_FALLBACK_SELECTOR) + { + // Transfer to EOA operation + // receive() fallback logic + // Just return _callParameters, do noting + PRECOMPILED_LOG(TRACE) << LOG_BADGE("AccountPrecompiled") + << LOG_DESC("call receive() function. do nothing") + << LOG_KV("func", func); + } else { PRECOMPILED_LOG(INFO) << LOG_BADGE("AccountPrecompiled") diff --git a/bcos-executor/src/vm/EVMHostInterface.cpp b/bcos-executor/src/vm/EVMHostInterface.cpp index affe1ff404..24cd9f2ba3 100644 --- a/bcos-executor/src/vm/EVMHostInterface.cpp +++ b/bcos-executor/src/vm/EVMHostInterface.cpp @@ -216,10 +216,10 @@ evmc_tx_context getTxContext(evmc_host_context* _context) noexcept result.block_timestamp = hostContext.timestamp(); result.block_gas_limit = hostContext.blockGasLimit(); result.tx_gas_price = toEvmC(hostContext.gasPrice()); + result.chain_id = hostContext.chainId(); memset(result.block_coinbase.bytes, 0, 20); memset(result.block_prev_randao.bytes, 0, 32); - memset(result.chain_id.bytes, 0, 32); memset(result.block_base_fee.bytes, 0, 32); diff --git a/bcos-executor/src/vm/HostContext.cpp b/bcos-executor/src/vm/HostContext.cpp index 8e9e8c8197..1f578dafd2 100644 --- a/bcos-executor/src/vm/HostContext.cpp +++ b/bcos-executor/src/vm/HostContext.cpp @@ -483,9 +483,13 @@ bcos::bytes HostContext::externalCodeRequest(const std::string_view& address) request->staticCall = staticCall(); auto response = m_executive->externalCall(std::move(request)); - if (m_executive->blockContext().features().get( - ledger::Features::Flag::bugfix_eoa_as_contract) && - precompiled::isDynamicPrecompiledAccountCode(fromBytes(response->data))) + if ((m_executive->blockContext().features().get( + ledger::Features::Flag::bugfix_eoa_as_contract) && + precompiled::isDynamicPrecompiledAccountCode(fromBytes(response->data))) || + (m_executive->blockContext().features().get( + ledger::Features::Flag::bugfix_eoa_match_failed) && + bcos::precompiled::matchDynamicAccountCode( + std::string_view((char*)response->data.data(), response->data.size())))) { return bytes(); } diff --git a/bcos-executor/src/vm/HostContext.h b/bcos-executor/src/vm/HostContext.h index 2384228118..a77ac9d3b6 100644 --- a/bcos-executor/src/vm/HostContext.h +++ b/bcos-executor/src/vm/HostContext.h @@ -27,6 +27,7 @@ #include "bcos-framework/protocol/BlockHeader.h" #include "bcos-framework/protocol/Protocol.h" #include "bcos-framework/storage/Table.h" +#include #include #include #include @@ -111,6 +112,8 @@ class HostContext : public evmc_host_context } } + evmc_uint256be chainId() const { return m_executive->blockContext().ledgerCache()->chainId(); } + /// Revert any changes made (by any of the other calls). void log(h256s&& _topics, bytesConstRef _data); diff --git a/bcos-executor/test/unittest/libprecompiled/SystemConfigPrecompileTest.cpp b/bcos-executor/test/unittest/libprecompiled/SystemConfigPrecompileTest.cpp index 5da04be3e1..61fa1a30fd 100644 --- a/bcos-executor/test/unittest/libprecompiled/SystemConfigPrecompileTest.cpp +++ b/bcos-executor/test/unittest/libprecompiled/SystemConfigPrecompileTest.cpp @@ -45,7 +45,8 @@ struct SystemConfigPrecompiledFixture : public bcos::test::PrecompiledFixture std::make_shared(std::make_shared()); std::shared_ptr gasInjector; std::shared_ptr backendStorage = std::make_shared(nullptr, false); - std::shared_ptr stateStorage = std::make_shared(backendStorage, false); + std::shared_ptr stateStorage = + std::make_shared(backendStorage, false); std::shared_ptr blockContext = std::make_shared(stateStorage, ledgerCache, hashImpl, 0, h256(), utcTime(), static_cast(protocol::BlockVersion::V3_1_VERSION), FiscoBcosSchedule, false, diff --git a/bcos-executor/test/unittest/mock/MockLedger.h b/bcos-executor/test/unittest/mock/MockLedger.h index b8e7e01e61..f9f58b23fd 100644 --- a/bcos-executor/test/unittest/mock/MockLedger.h +++ b/bcos-executor/test/unittest/mock/MockLedger.h @@ -135,6 +135,11 @@ class MockLedger : public bcos::ledger::LedgerInterface _onGetConfig(nullptr, std::to_string(MockLedger::TX_GAS_LIMIT), m_blockNumber); return; } + else if (std::string(bcos::ledger::SYSTEM_KEY_WEB3_CHAIN_ID) == std::string(_key)) + { + _onGetConfig(nullptr, "20200", m_blockNumber); + return; + } else if (std::string(bcos::ledger::SYSTEM_KEY_AUTH_CHECK_STATUS) == std::string(_key)) { if (m_storage) diff --git a/bcos-framework/bcos-framework/ledger/Features.h b/bcos-framework/bcos-framework/ledger/Features.h index 0272caf236..3b032cbfce 100644 --- a/bcos-framework/bcos-framework/ledger/Features.h +++ b/bcos-framework/bcos-framework/ledger/Features.h @@ -42,8 +42,11 @@ class Features bugfix_empty_abi_reset, // support empty abi reset of same code bugfix_eip55_addr, bugfix_eoa_as_contract, + bugfix_eoa_match_failed, bugfix_evm_exception_gas_used, bugfix_dmc_deploy_gas_used, + bugfix_staticcall_noaddr_return, + bugfix_support_transfer_receive_fallback, bugfix_set_row_with_dirty_flag, feature_dmc2serial, feature_sharding, @@ -135,32 +138,63 @@ class Features protocol::BlockVersion to; std::vector flags; }; - const static auto upgradeRoadmap = std::to_array( - {{protocol::BlockVersion::V3_2_3_VERSION, {Flag::bugfix_revert}}, - {protocol::BlockVersion::V3_2_4_VERSION, - {Flag::bugfix_statestorage_hash, - Flag::bugfix_evm_create2_delegatecall_staticcall_codecopy}}, - {protocol::BlockVersion::V3_2_7_VERSION, - {Flag::bugfix_event_log_order, Flag::bugfix_call_noaddr_return, - Flag::bugfix_precompiled_codehash, Flag::bugfix_dmc_revert}}, - {protocol::BlockVersion::V3_5_VERSION, - {Flag::bugfix_revert, Flag::bugfix_statestorage_hash}}, - {protocol::BlockVersion::V3_6_VERSION, - {Flag::bugfix_statestorage_hash, - Flag::bugfix_evm_create2_delegatecall_staticcall_codecopy, - Flag::bugfix_event_log_order, Flag::bugfix_call_noaddr_return, - Flag::bugfix_precompiled_codehash, Flag::bugfix_dmc_revert}}, - {protocol::BlockVersion::V3_6_1_VERSION, - {Flag::bugfix_keypage_system_entry_hash, - Flag::bugfix_internal_create_redundant_storage}}, - {protocol::BlockVersion::V3_7_0_VERSION, - {Flag::bugfix_empty_abi_reset, Flag::bugfix_eip55_addr, - Flag::bugfix_sharding_call_in_child_executive, - Flag::bugfix_internal_create_permission_denied}}, - {protocol::BlockVersion::V3_8_0_VERSION, - {Flag::bugfix_eoa_as_contract, Flag::bugfix_dmc_deploy_gas_used, - Flag::bugfix_evm_exception_gas_used, - Flag::bugfix_set_row_with_dirty_flag}}}); + const static auto upgradeRoadmap = std::to_array({ + {protocol::BlockVersion::V3_2_3_VERSION, + { + Flag::bugfix_revert, + }}, + {protocol::BlockVersion::V3_2_4_VERSION, + { + Flag::bugfix_statestorage_hash, + Flag::bugfix_evm_create2_delegatecall_staticcall_codecopy, + }}, + {protocol::BlockVersion::V3_2_7_VERSION, + { + Flag::bugfix_event_log_order, + Flag::bugfix_call_noaddr_return, + Flag::bugfix_precompiled_codehash, + Flag::bugfix_dmc_revert, + }}, + {protocol::BlockVersion::V3_5_VERSION, + { + Flag::bugfix_revert, + Flag::bugfix_statestorage_hash, + }}, + {protocol::BlockVersion::V3_6_VERSION, + { + Flag::bugfix_statestorage_hash, + Flag::bugfix_evm_create2_delegatecall_staticcall_codecopy, + Flag::bugfix_event_log_order, + Flag::bugfix_call_noaddr_return, + Flag::bugfix_precompiled_codehash, + Flag::bugfix_dmc_revert, + }}, + {protocol::BlockVersion::V3_6_1_VERSION, + { + Flag::bugfix_keypage_system_entry_hash, + Flag::bugfix_internal_create_redundant_storage, + }}, + {protocol::BlockVersion::V3_7_0_VERSION, + { + Flag::bugfix_empty_abi_reset, + Flag::bugfix_eip55_addr, + Flag::bugfix_sharding_call_in_child_executive, + Flag::bugfix_internal_create_permission_denied, + }}, + {protocol::BlockVersion::V3_8_0_VERSION, + { + Flag::bugfix_eoa_as_contract, + Flag::bugfix_dmc_deploy_gas_used, + Flag::bugfix_evm_exception_gas_used, + Flag::bugfix_set_row_with_dirty_flag, + }}, + {protocol::BlockVersion::V3_9_0_VERSION, + { + Flag::bugfix_staticcall_noaddr_return, + Flag::bugfix_support_transfer_receive_fallback, + Flag::bugfix_eoa_match_failed, + }}, + }); for (const auto& upgradeFeatures : upgradeRoadmap) { if (((to < protocol::BlockVersion::V3_2_7_VERSION) && (to >= upgradeFeatures.to)) || diff --git a/bcos-framework/bcos-framework/ledger/GenesisConfig.h b/bcos-framework/bcos-framework/ledger/GenesisConfig.h index 9f1605f6c4..915af07592 100644 --- a/bcos-framework/bcos-framework/ledger/GenesisConfig.h +++ b/bcos-framework/bcos-framework/ledger/GenesisConfig.h @@ -59,6 +59,9 @@ class GenesisConfig std::string m_chainID; std::string m_groupID; + // web3 chain config + std::string m_web3ChainID; + // consensus config std::string m_consensusType; uint64_t m_txCountLimit = 1000; diff --git a/bcos-framework/bcos-framework/ledger/Ledger.h b/bcos-framework/bcos-framework/ledger/Ledger.h index 778b5f94cf..f5e323b2d2 100644 --- a/bcos-framework/bcos-framework/ledger/Ledger.h +++ b/bcos-framework/bcos-framework/ledger/Ledger.h @@ -79,6 +79,15 @@ inline constexpr struct GetBlockHash } } getBlockHash{}; +inline constexpr struct GetBlockNumber +{ + task::Task operator()(auto& ledger, crypto::HashType hash) const + { + co_return co_await tag_invoke(*this, ledger, std::move(hash)); + } +} getBlockNumber{}; + +using SystemConfigEntry = std::tuple; inline constexpr struct GetSystemConfig { task::Task> operator()( @@ -112,6 +121,33 @@ inline constexpr struct GetFeatures } } getFeatures{}; +inline constexpr struct GetReceipt +{ + task::Task operator()( + auto& ledger, crypto::HashType hash) const + { + co_return co_await tag_invoke(*this, ledger, hash); + } +} getReceipt{}; + +inline constexpr struct GetTransactions +{ + task::Task operator()( + auto& ledger, crypto::HashListPtr hashes) const + { + co_return co_await tag_invoke(*this, ledger, std::move(hashes)); + } +} getTransactions{}; + +inline constexpr struct GetStorageAt +{ + task::Task> operator()(auto& ledger, + std::string_view address, std::string_view key, bcos::protocol::BlockNumber number) const + { + co_return co_await tag_invoke(*this, ledger, address, key, number); + } +} getStorageAt{}; + template using tag_t = std::decay_t; } // namespace bcos::ledger diff --git a/bcos-framework/bcos-framework/ledger/LedgerConfig.h b/bcos-framework/bcos-framework/ledger/LedgerConfig.h index 6e485020db..9404233bb1 100644 --- a/bcos-framework/bcos-framework/ledger/LedgerConfig.h +++ b/bcos-framework/bcos-framework/ledger/LedgerConfig.h @@ -23,6 +23,8 @@ #include "../protocol/ProtocolTypeDef.h" #include "Features.h" +#include + namespace bcos::ledger { @@ -156,6 +158,9 @@ class LedgerConfig Features const& features() const { return m_features; } void setFeatures(Features features) { m_features = features; } + std::optional const& chainId() const { return m_chainId; } + void setChainId(evmc_uint256be _chainId) { m_chainId = std::move(_chainId); } + private: bcos::consensus::ConsensusNodeListPtr m_consensusNodeList; bcos::consensus::ConsensusNodeListPtr m_observerNodeList; @@ -178,5 +183,6 @@ class LedgerConfig int64_t m_txsSize = -1; uint32_t m_authCheckStatus = 0; Features m_features; + std::optional m_chainId = {}; }; } // namespace bcos::ledger diff --git a/bcos-framework/bcos-framework/ledger/LedgerInterface.h b/bcos-framework/bcos-framework/ledger/LedgerInterface.h index 0ad7fa4442..da86497aab 100644 --- a/bcos-framework/bcos-framework/ledger/LedgerInterface.h +++ b/bcos-framework/bcos-framework/ledger/LedgerInterface.h @@ -28,6 +28,8 @@ #include "../storage/StorageInterface.h" #include "LedgerTypeDef.h" #include +#include +#include #include #include #include @@ -167,6 +169,22 @@ class LedgerInterface virtual void asyncPreStoreBlockTxs(bcos::protocol::ConstTransactionsPtr _blockTxs, bcos::protocol::Block::ConstPtr block, std::function _callback) = 0; + + /** + * @brief get storage value by address and key and block number in coroutine. It will access the + * table of address like /apps/[address] and get the value of key. NOTE: blockNumber is ignored + * nowadays, it will always get the latest value of key in address. + * @param _address the address of contract/EOA. if in EVM, it should be the address of contract, + * hex string; if in WASM, it should be the path name of contract. + * @param _key the key of storage + * @param _blockNumber the block number to get the storage value + * @return the storage value of key in address + */ + virtual task::Task> getStorageAt( + std::string_view _address, std::string_view _key, protocol::BlockNumber _blockNumber) + { + co_return std::nullopt; + } }; } // namespace bcos::ledger diff --git a/bcos-framework/bcos-framework/ledger/LedgerTypeDef.h b/bcos-framework/bcos-framework/ledger/LedgerTypeDef.h index f12e2b2e1b..b134ead686 100644 --- a/bcos-framework/bcos-framework/ledger/LedgerTypeDef.h +++ b/bcos-framework/bcos-framework/ledger/LedgerTypeDef.h @@ -20,6 +20,7 @@ #pragma once #include "../protocol/ProtocolTypeDef.h" +#include "SystemConfigs.h" #include namespace bcos::ledger @@ -34,25 +35,25 @@ constexpr static int32_t TRANSACTIONS_HASH = 0x0001; constexpr static int32_t TRANSACTIONS = 0x0004; constexpr static int32_t RECEIPTS = 0x0002; +// clang-format off // get system config key -constexpr static std::string_view SYSTEM_KEY_TX_GAS_LIMIT = "tx_gas_limit"; -constexpr static std::string_view SYSTEM_KEY_TX_GAS_PRICE = "tx_gas_price"; -constexpr static std::string_view SYSTEM_KEY_TX_COUNT_LIMIT = "tx_count_limit"; -constexpr static std::string_view SYSTEM_KEY_CONSENSUS_LEADER_PERIOD = "consensus_leader_period"; -constexpr static std::string_view SYSTEM_KEY_AUTH_CHECK_STATUS = "auth_check_status"; +constexpr static std::string_view SYSTEM_KEY_WEB3_CHAIN_ID = magic_enum::enum_name(SystemConfig::web3_chain_id); +constexpr static std::string_view SYSTEM_KEY_TX_GAS_LIMIT = magic_enum::enum_name(SystemConfig::tx_gas_limit); +constexpr static std::string_view SYSTEM_KEY_TX_GAS_PRICE = magic_enum::enum_name(SystemConfig::tx_gas_price); +constexpr static std::string_view SYSTEM_KEY_TX_COUNT_LIMIT = magic_enum::enum_name(SystemConfig::tx_count_limit); +constexpr static std::string_view SYSTEM_KEY_CONSENSUS_LEADER_PERIOD = magic_enum::enum_name(SystemConfig::consensus_leader_period); +constexpr static std::string_view SYSTEM_KEY_AUTH_CHECK_STATUS = magic_enum::enum_name(SystemConfig::auth_check_status); // for compatibility -constexpr static std::string_view SYSTEM_KEY_COMPATIBILITY_VERSION = "compatibility_version"; +constexpr static std::string_view SYSTEM_KEY_COMPATIBILITY_VERSION = magic_enum::enum_name(SystemConfig::compatibility_version); // system configuration for RPBFT -constexpr static std::string_view SYSTEM_KEY_RPBFT_EPOCH_SEALER_NUM = - "feature_rpbft_epoch_sealer_num"; -constexpr static std::string_view SYSTEM_KEY_RPBFT_EPOCH_BLOCK_NUM = - "feature_rpbft_epoch_block_num"; -constexpr static std::string_view SYSTEM_KEY_RPBFT_SWITCH = "feature_rpbft"; +constexpr static std::string_view SYSTEM_KEY_RPBFT_EPOCH_SEALER_NUM = magic_enum::enum_name(SystemConfig::feature_rpbft_epoch_sealer_num); +constexpr static std::string_view SYSTEM_KEY_RPBFT_EPOCH_BLOCK_NUM = magic_enum::enum_name(SystemConfig::feature_rpbft_epoch_block_num); +constexpr static std::string_view SYSTEM_KEY_RPBFT_SWITCH = magic_enum::enum_name(SystemConfig::feature_rpbft); // system configuration for balance -constexpr static std::string_view SYSTEM_KEY_BALANCE_PRECOMPILED_SWITCH = - "feature_balance_precompiled"; +constexpr static std::string_view SYSTEM_KEY_BALANCE_PRECOMPILED_SWITCH = magic_enum::enum_name(SystemConfig::feature_balance_precompiled); // notify rotate key for rpbft constexpr static std::string_view INTERNAL_SYSTEM_KEY_NOTIFY_ROTATE = "feature_rpbft_notify_rotate"; +// clang-format on constexpr static std::string_view PBFT_CONSENSUS_TYPE = "pbft"; constexpr static std::string_view RPBFT_CONSENSUS_TYPE = "rpbft"; diff --git a/bcos-framework/bcos-framework/ledger/SystemConfigs.h b/bcos-framework/bcos-framework/ledger/SystemConfigs.h new file mode 100644 index 0000000000..91e99bfcf9 --- /dev/null +++ b/bcos-framework/bcos-framework/ledger/SystemConfigs.h @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file SystemConfigs.h + * @author: kyonGuo + * @date 2024/5/22 + */ + +#pragma once +#include "../ledger/LedgerTypeDef.h" +#include "../protocol/Protocol.h" +#include "../storage/Entry.h" +#include "../storage/LegacyStorageMethods.h" +#include "../storage2/Storage.h" +#include "../transaction-executor/StateKey.h" +#include "bcos-task/Task.h" +#include "bcos-tool/Exceptions.h" +#include "bcos-utilities/Ranges.h" +#include +#include +#include +#include + +namespace bcos::ledger +{ +struct NoSuchSystemConfig : public bcos::error::Exception +{ +}; +/// IMPORTANT!! +/// DO NOT change the name of enum. It is used to get the name of the config. +enum class SystemConfig +{ + tx_gas_limit, + tx_gas_price, + tx_count_limit, + consensus_leader_period, + auth_check_status, + compatibility_version, + feature_rpbft, + feature_rpbft_epoch_block_num, + feature_rpbft_epoch_sealer_num, + feature_balance_precompiled, + web3_chain_id, +}; +class SystemConfigs +{ +public: + SystemConfigs() { m_sysConfigs.reserve(magic_enum::enum_count()); } + + static SystemConfig fromString(std::string_view str) + { + auto const value = magic_enum::enum_cast(str); + if (!value) + { + BOOST_THROW_EXCEPTION(NoSuchSystemConfig{}); + } + return *value; + } + + std::optional get(SystemConfig config) const { return m_sysConfigs.at(config); } + std::optional get(std::string_view config) const + { + return get(fromString(config)); + } + + void set(SystemConfig config, std::string value) { m_sysConfigs[config] = value; } + void set(std::string_view config, std::string value) { set(fromString(config), value); } + + auto systemConfigs() const + { + return RANGES::views::iota(0LU, m_sysConfigs.size()) | + RANGES::views::transform([this](size_t index) { + auto flag = magic_enum::enum_value(index); + return std::make_tuple(flag, magic_enum::enum_name(flag), m_sysConfigs.at(flag)); + }); + } + + static auto supportConfigs() + { + return RANGES::views::iota(0LU, magic_enum::enum_count()) | + RANGES::views::transform([](size_t index) { + auto flag = magic_enum::enum_value(index); + return magic_enum::enum_name(flag); + }); + } + +private: + std::unordered_map> m_sysConfigs{}; +}; + +} // namespace bcos::ledger diff --git a/bcos-framework/bcos-framework/multigroup/ChainNodeInfo.h b/bcos-framework/bcos-framework/multigroup/ChainNodeInfo.h index 8e1cb9100e..6685310a0a 100644 --- a/bcos-framework/bcos-framework/multigroup/ChainNodeInfo.h +++ b/bcos-framework/bcos-framework/multigroup/ChainNodeInfo.h @@ -127,6 +127,17 @@ class ChainNodeInfo } } + auto const& supportConfigs() const { return m_configs; } + void setSupportConfigs(RANGES::input_range auto&& configs) + requires std::same_as>, std::string> + { + m_configs.clear(); + for (auto&& config : configs) + { + m_configs.emplace_back(std::forward(config)); + } + } + private: bool m_microService = false; // the node name @@ -147,6 +158,7 @@ class ChainNodeInfo bcos::protocol::ProtocolInfo::Ptr m_nodeProtocol; std::vector m_featureKeys; + std::vector m_configs; // the system version uint32_t m_compatibilityVersion; diff --git a/bcos-framework/bcos-framework/protocol/LogEntry.h b/bcos-framework/bcos-framework/protocol/LogEntry.h index d6dcd2c4a0..b8aa9f1133 100644 --- a/bcos-framework/bcos-framework/protocol/LogEntry.h +++ b/bcos-framework/bcos-framework/protocol/LogEntry.h @@ -41,6 +41,9 @@ class LogEntry std::string_view address() const { return {(const char*)m_address.data(), m_address.size()}; } gsl::span topics() const { return {m_topics.data(), m_topics.size()}; } bcos::bytesConstRef data() const { return ref(m_data); } + bytes&& takeAddress() { return std::move(m_address); } + h256s&& takeTopics() { return std::move(m_topics); } + bytes&& takeData() { return std::move(m_data); } // Define the scale decode method, which cannot be modified at will template > friend Stream& operator>>(Stream& _stream, LogEntry& _logEntry) diff --git a/bcos-framework/bcos-framework/protocol/Protocol.h b/bcos-framework/bcos-framework/protocol/Protocol.h index 53af70a64a..f8332650ff 100644 --- a/bcos-framework/bcos-framework/protocol/Protocol.h +++ b/bcos-framework/bcos-framework/protocol/Protocol.h @@ -114,6 +114,7 @@ enum ProtocolVersion : uint32_t enum class BlockVersion : uint32_t { + V3_9_0_VERSION = 0x03090000, V3_8_0_VERSION = 0x03080000, V3_7_3_VERSION = 0x03070300, V3_7_2_VERSION = 0x03070200, @@ -135,7 +136,7 @@ enum class BlockVersion : uint32_t V3_0_VERSION = 0x03000000, RC4_VERSION = 4, MIN_VERSION = RC4_VERSION, - MAX_VERSION = V3_8_0_VERSION, + MAX_VERSION = V3_9_0_VERSION, }; enum class TransactionVersion : uint32_t @@ -148,10 +149,10 @@ enum class TransactionVersion : uint32_t const std::string RC4_VERSION_STR = "3.0.0-rc4"; const std::string RC_VERSION_PREFIX = "3.0.0-rc"; -const std::string V3_8_VERSION_STR = "3.8.0"; +const std::string V3_9_VERSION_STR = "3.9.0"; -const BlockVersion DEFAULT_VERSION = bcos::protocol::BlockVersion::V3_8_0_VERSION; -const std::string DEFAULT_VERSION_STR = V3_8_VERSION_STR; +const BlockVersion DEFAULT_VERSION = bcos::protocol::BlockVersion::V3_9_0_VERSION; +const std::string DEFAULT_VERSION_STR = V3_9_VERSION_STR; const uint8_t MAX_MAJOR_VERSION = std::numeric_limits::max(); const uint8_t MIN_MAJOR_VERSION = 3; diff --git a/bcos-framework/bcos-framework/protocol/Transaction.h b/bcos-framework/bcos-framework/protocol/Transaction.h index e050a8cb64..2ccc83f10f 100644 --- a/bcos-framework/bcos-framework/protocol/Transaction.h +++ b/bcos-framework/bcos-framework/protocol/Transaction.h @@ -26,12 +26,33 @@ #if !ONLY_CPP_SDK #include #endif +#include #include #include namespace bcos::protocol { -enum TransactionType +enum class TransactionType : uint8_t +{ + BCOSTransaction = 0, + Web3Transacion = 1, +}; + +constexpr auto operator<=>(bcos::protocol::TransactionType const& _lhs, auto _rhs) + requires(std::same_as || + std::unsigned_integral) +{ + return static_cast(_lhs) <=> static_cast(_rhs); +} + +constexpr bool operator==(bcos::protocol::TransactionType const& _lhs, auto _rhs) + requires(std::same_as || + std::unsigned_integral) +{ + return static_cast(_lhs) == static_cast(_rhs); +} + +enum TransactionOp { NullTransaction = 0, ContractCreation, @@ -64,6 +85,7 @@ class Transaction virtual void decode(bytesConstRef _txData) = 0; virtual void encode(bcos::bytes& txData) const = 0; virtual bcos::crypto::HashType hash() const = 0; + virtual bcos::bytesConstRef extraTransactionBytes() const = 0; virtual void verify(crypto::Hash& hashImpl, crypto::SignatureCrypto& signatureImpl) const { @@ -76,8 +98,17 @@ class Transaction { return; } - - auto hashResult = hash(); + // based on type to switch recover sender + crypto::HashType hashResult; + if (type() == static_cast(TransactionType::BCOSTransaction)) + { + hashResult = hash(); + } + else if (type() == static_cast(TransactionType::Web3Transacion)) + { + auto bytes = extraTransactionBytes(); + hashResult = bcos::crypto::keccak256Hash(bytes); + } // check the signatures auto signature = signatureData(); auto ret = signatureImpl.recoverAddress(hashImpl, hashResult, signature); @@ -112,13 +143,15 @@ class Transaction virtual bcos::bytesConstRef input() const = 0; virtual int64_t importTime() const = 0; virtual void setImportTime(int64_t _importTime) = 0; - virtual TransactionType type() const + virtual uint8_t type() const = 0; + + virtual TransactionOp txOp() const { if (!to().empty()) { - return TransactionType::MessageCall; + return TransactionOp::MessageCall; } - return TransactionType::ContractCreation; + return TransactionOp::ContractCreation; } virtual void forceSender(const bcos::bytes& _sender) const = 0; virtual bcos::bytesConstRef signatureData() const = 0; diff --git a/bcos-framework/bcos-framework/protocol/TransactionReceipt.h b/bcos-framework/bcos-framework/protocol/TransactionReceipt.h index 7b7cbc15c8..7004d309f9 100644 --- a/bcos-framework/bcos-framework/protocol/TransactionReceipt.h +++ b/bcos-framework/bcos-framework/protocol/TransactionReceipt.h @@ -18,6 +18,7 @@ */ #pragma once +#include "LogEntry.h" #include "ProtocolTypeDef.h" #include #include @@ -44,6 +45,7 @@ class TransactionReceipt virtual int32_t status() const = 0; virtual bcos::bytesConstRef output() const = 0; virtual gsl::span logEntries() const = 0; + virtual LogEntries&& takeLogEntries() = 0; virtual protocol::BlockNumber blockNumber() const = 0; virtual std::string_view effectiveGasPrice() const = 0; virtual void setEffectiveGasPrice(std::string effectiveGasPrice) = 0; diff --git a/bcos-framework/bcos-framework/storage/StorageInterface.h b/bcos-framework/bcos-framework/storage/StorageInterface.h index 35c185538c..7cacd6ef21 100644 --- a/bcos-framework/bcos-framework/storage/StorageInterface.h +++ b/bcos-framework/bcos-framework/storage/StorageInterface.h @@ -103,7 +103,7 @@ class StorageInterface return result; }; - virtual void stop() {}; + virtual void stop(){}; }; class TraverseStorageInterface : public virtual StorageInterface diff --git a/bcos-framework/bcos-framework/storage2/MemoryStorage.h b/bcos-framework/bcos-framework/storage2/MemoryStorage.h index f7d6a62d48..0515eae50a 100644 --- a/bcos-framework/bcos-framework/storage2/MemoryStorage.h +++ b/bcos-framework/bcos-framework/storage2/MemoryStorage.h @@ -20,7 +20,9 @@ namespace bcos::storage2::memory_storage template concept HasMemberSize = requires(Object object) { - { object.size() } -> std::integral; + { + object.size() + } -> std::integral; }; using Empty = std::monostate; @@ -402,21 +404,26 @@ class MemoryStorage } } - template class Iterator { private: - Begin m_begin; - End m_end; + std::reference_wrapper m_buckets; + size_t m_bucketIndex = 0; + RANGES::iterator_t m_begin; + RANGES::iterator_t m_end; + + using IteratorValue = + std::conditional_t; public: - Iterator(Begin begin, End end) : m_begin(begin), m_end(end) {} + Iterator(const Buckets& buckets) + : m_buckets(buckets), + m_begin((m_buckets.get()[m_bucketIndex]).container.begin()), + m_end((m_buckets.get()[m_bucketIndex]).container.end()) + {} auto next() { - using IteratorValue = - std::conditional_t; - std::optional> result; if (m_begin != m_end) { @@ -431,18 +438,40 @@ class MemoryStorage result.emplace(std::make_tuple(std::cref(data.key), std::cref(data.value))); } ++m_begin; + return task::AwaitableValue(std::move(result)); + } + + if (m_bucketIndex + 1 < m_buckets.get().size()) + { + ++m_bucketIndex; + m_begin = m_buckets.get()[m_bucketIndex].container.begin(); + m_end = m_buckets.get()[m_bucketIndex].container.end(); + return next(); } return task::AwaitableValue(std::move(result)); } + + auto seek(auto&& key) + requires(!withConcurrent && withOrdered) + { + auto const& index = m_buckets.get()[m_bucketIndex].container.template get<0>(); + m_begin = index.lower_bound(std::forward(key)); + } }; friend auto tag_invoke( bcos::storage2::tag_t /*unused*/, MemoryStorage const& storage) - requires(!withConcurrent) { - return task::AwaitableValue(Iterator( - storage.m_buckets[0].container.begin(), storage.m_buckets[0].container.end())); + return task::AwaitableValue(Iterator(storage.m_buckets)); + } + + friend auto tag_invoke(bcos::storage2::tag_t /*unused*/, + MemoryStorage const& storage, RANGE_SEEK_TYPE /*unused*/, auto&& key) + requires(!withConcurrent && withOrdered) + { + auto iterator = Iterator(storage.m_buckets); + iterator.seek(std::forward(key)); + return task::AwaitableValue(std::move(iterator)); } bool empty() const diff --git a/bcos-framework/bcos-framework/storage2/Storage.h b/bcos-framework/bcos-framework/storage2/Storage.h index 4b9828ee04..39c9bdb130 100644 --- a/bcos-framework/bcos-framework/storage2/Storage.h +++ b/bcos-framework/bcos-framework/storage2/Storage.h @@ -13,6 +13,10 @@ inline constexpr struct DIRECT_TYPE { } DIRECT{}; +inline constexpr struct RANGE_SEEK_TYPE +{ +} RANGE_SEEK{}; + template using ReturnType = typename task::AwaitableReturnType; template @@ -114,8 +118,8 @@ inline constexpr struct ReadOne inline constexpr struct WriteOne { - auto operator()( - auto&& storage, auto&& key, auto&& value, auto&&... args) const -> task::Task + auto operator()(auto&& storage, auto&& key, auto&& value, auto&&... args) const + -> task::Task { if constexpr (HasTag) diff --git a/bcos-framework/bcos-framework/sync/BlockSyncInterface.h b/bcos-framework/bcos-framework/sync/BlockSyncInterface.h index b7073df620..07ff2d8180 100644 --- a/bcos-framework/bcos-framework/sync/BlockSyncInterface.h +++ b/bcos-framework/bcos-framework/sync/BlockSyncInterface.h @@ -22,6 +22,7 @@ #pragma once #include "../ledger/LedgerConfig.h" #include +#include #include namespace bcos::sync { @@ -42,17 +43,26 @@ class BlockSyncInterface // called by the frontService to dispatch message virtual void asyncNotifyBlockSyncMessage(Error::Ptr _error, std::string const& _uuid, bcos::crypto::NodeIDPtr _nodeID, bytesConstRef _data, - std::function _onRecv) = 0; + std::function _onRecv) = 0; // called by the RPC to get the sync status virtual void asyncGetSyncInfo(std::function _onGetSyncInfo) = 0; + virtual std::vector getPeerStatus() = 0; virtual void asyncNotifyCommittedIndex( - bcos::protocol::BlockNumber _number, std::function _onRecv) = 0; + bcos::protocol::BlockNumber _number, std::function _onRecv) = 0; virtual void notifyConnectedNodes(bcos::crypto::NodeIDSet const& _connectedNodes, std::function _onResponse) = 0; // determine the specified node is faulty or not // used to optimize consensus virtual bool faultyNode(bcos::crypto::NodeIDPtr _nodeID) = 0; + + virtual bool isSyncing() const { return false; } + + virtual std::optional> + getSyncStatus() const + { + return std::nullopt; + } }; } // namespace bcos::sync diff --git a/bcos-framework/bcos-framework/testutils/faker/FakeLedger.h b/bcos-framework/bcos-framework/testutils/faker/FakeLedger.h index 0894d7a4a0..21f78dd54b 100644 --- a/bcos-framework/bcos-framework/testutils/faker/FakeLedger.h +++ b/bcos-framework/bcos-framework/testutils/faker/FakeLedger.h @@ -258,7 +258,7 @@ class FakeLedger : public LedgerInterface, public std::enable_shared_from_thisblockNumber()); + _onGetConfig(BCOS_ERROR_PTR(3008, "key not found"), "", m_ledgerConfig->blockNumber()); return; } _onGetConfig(nullptr, value, m_ledgerConfig->blockNumber()); diff --git a/bcos-framework/bcos-framework/testutils/faker/FakeTransaction.h b/bcos-framework/bcos-framework/testutils/faker/FakeTransaction.h index bf9e77035b..5c3422c647 100644 --- a/bcos-framework/bcos-framework/testutils/faker/FakeTransaction.h +++ b/bcos-framework/bcos-framework/testutils/faker/FakeTransaction.h @@ -69,7 +69,7 @@ inline void checkTransaction( // check the fields BOOST_CHECK(decodedTransaction->hash() == pbTransaction->hash()); BOOST_CHECK(decodedTransaction->sender() == pbTransaction->sender()); - BOOST_CHECK(decodedTransaction->type() == pbTransaction->type()); + BOOST_CHECK(decodedTransaction->txOp() == pbTransaction->txOp()); BOOST_CHECK(decodedTransaction->to() == pbTransaction->to()); // check the transaction hash fields BOOST_CHECK(decodedTransaction->input().toBytes() == pbTransaction->input().toBytes()); @@ -92,11 +92,11 @@ inline Transaction::Ptr testTransaction(CryptoSuite::Ptr _cryptoSuite, { if (_to.empty()) { - BOOST_CHECK(pbTransaction->type() == TransactionType::ContractCreation); + BOOST_CHECK(pbTransaction->txOp() == TransactionOp::ContractCreation); } else { - BOOST_CHECK(pbTransaction->type() == TransactionType::MessageCall); + BOOST_CHECK(pbTransaction->txOp() == TransactionOp::MessageCall); } BOOST_CHECK(pbTransaction->sender() == std::string_view((char*)addr.data(), 20)); } diff --git a/bcos-framework/bcos-framework/transaction-executor/TransactionExecutor.h b/bcos-framework/bcos-framework/transaction-executor/TransactionExecutor.h index f68ab08633..ff92c3e98a 100644 --- a/bcos-framework/bcos-framework/transaction-executor/TransactionExecutor.h +++ b/bcos-framework/bcos-framework/transaction-executor/TransactionExecutor.h @@ -25,8 +25,8 @@ inline constexpr struct ExecuteTransaction * @return A task that resolves to a transaction receipt. */ auto operator()(auto& executor, auto& storage, const protocol::BlockHeader& blockHeader, - const protocol::Transaction& transaction, - auto&&... args) const -> task::Task + const protocol::Transaction& transaction, auto&&... args) const + -> task::Task { co_return co_await tag_invoke(*this, executor, storage, blockHeader, transaction, std::forward(args)...); diff --git a/bcos-framework/bcos-framework/txpool/TxPoolInterface.h b/bcos-framework/bcos-framework/txpool/TxPoolInterface.h index 19fdce6fee..17ea5544e0 100644 --- a/bcos-framework/bcos-framework/txpool/TxPoolInterface.h +++ b/bcos-framework/bcos-framework/txpool/TxPoolInterface.h @@ -53,6 +53,12 @@ class TxPoolInterface BOOST_THROW_EXCEPTION(std::runtime_error("Unimplemented!")); } + virtual task::Task submitTransactionWithoutReceipt( + [[maybe_unused]] protocol::Transaction::Ptr transaction) + { + BOOST_THROW_EXCEPTION(std::runtime_error("Unimplemented!")); + } + virtual task::Task submitTransactionWithHook( [[maybe_unused]] protocol::Transaction::Ptr transaction, [[maybe_unused]] std::function afterInsertHook) diff --git a/bcos-framework/test/unittests/interfaces/FeaturesTest.cpp b/bcos-framework/test/unittests/interfaces/FeaturesTest.cpp index 8cc234d73d..34a6fd2e61 100644 --- a/bcos-framework/test/unittests/interfaces/FeaturesTest.cpp +++ b/bcos-framework/test/unittests/interfaces/FeaturesTest.cpp @@ -142,8 +142,11 @@ BOOST_AUTO_TEST_CASE(feature) "bugfix_empty_abi_reset", "bugfix_eip55_addr", "bugfix_eoa_as_contract", + "bugfix_eoa_match_failed", "bugfix_evm_exception_gas_used", "bugfix_dmc_deploy_gas_used", + "bugfix_staticcall_noaddr_return", + "bugfix_support_transfer_receive_fallback", "bugfix_set_row_with_dirty_flag", "feature_dmc2serial", "feature_sharding", diff --git a/bcos-framework/test/unittests/storage2/TestMemoryStorage.cpp b/bcos-framework/test/unittests/storage2/TestMemoryStorage.cpp index c59ec40dec..0ee8af1bf0 100644 --- a/bcos-framework/test/unittests/storage2/TestMemoryStorage.cpp +++ b/bcos-framework/test/unittests/storage2/TestMemoryStorage.cpp @@ -253,15 +253,27 @@ BOOST_AUTO_TEST_CASE(range) { BOOST_CHECK(kv); auto& [key, value] = *kv; - auto& [tableName, keyName] = key; - // BOOST_CHECK_EQUAL(tableName, "table"); - // BOOST_CHECK_EQUAL(keyName, "key:" + boost::lexical_cast(num)); - // BOOST_CHECK_EQUAL(value->get(), "Hello world!" + - // boost::lexical_cast(num)); + const auto& [tableName, keyName] = key; BOOST_CHECK_LT(num, 100); ++num; } BOOST_CHECK_EQUAL(num, 100); + + MemoryStorage + intStorage; + co_await storage2::writeSome( + intStorage, RANGES::iota_view(0, 10), RANGES::repeat_view(100)); + auto start = 4; + auto range3 = co_await storage2::range(intStorage, storage2::RANGE_SEEK, start); + while (auto pair = co_await range3.next()) + { + auto&& [key, value] = *pair; + BOOST_CHECK_EQUAL(key, start++); + } + BOOST_CHECK_EQUAL(start, 10); }()); } @@ -341,4 +353,36 @@ BOOST_AUTO_TEST_CASE(keyComp) BOOST_CHECK_EQUAL(hash1, hash2); } +BOOST_AUTO_TEST_CASE(concurrentRange) +{ + task::syncWait([]() -> task::Task { + constexpr static int count = 100; + + MemoryStorage> + storage; + + co_await storage2::writeSome(storage, + RANGES::iota_view(0, count) | RANGES::views::transform([](auto num) { + return boost::lexical_cast(num); + }), + RANGES::iota_view(0, count) | RANGES::views::transform([](auto num) { + storage::Entry entry; + entry.set("Hello world!" + boost::lexical_cast(num)); + return entry; + })); + + auto range = co_await storage2::range(storage); + auto expect = count; + while (auto value = co_await range.next()) + { + --expect; + auto&& [key, entry] = *value; + auto index = boost::lexical_cast(key); + BOOST_CHECK_LT(index, count); + } + BOOST_CHECK_EQUAL(expect, 0); + }()); +} + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/bcos-ledger/src/libledger/Ledger.cpp b/bcos-ledger/src/libledger/Ledger.cpp index 6389996e25..0336c196d2 100644 --- a/bcos-ledger/src/libledger/Ledger.cpp +++ b/bcos-ledger/src/libledger/Ledger.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,17 @@ void Ledger::asyncPreStoreBlockTxs(bcos::protocol::ConstTransactionsPtr _blockTx _callback(nullptr); } +task::Task> Ledger::getStorageAt( + std::string_view _address, std::string_view _key, protocol::BlockNumber _blockNumber) +{ + // TODO)): blockNumber is not used nowadays + std::ignore = _blockNumber; + auto const contractTableName = getContractTableName(SYS_DIRECTORY::USER_APPS, _address); + auto const stateStorage = getStateStorage(); + co_return co_await bcos::storage2::readOne( + *stateStorage, transaction_executor::StateKeyView{contractTableName, _key}); +} + void Ledger::asyncPrewriteBlock(bcos::storage::StorageInterface::Ptr storage, bcos::protocol::ConstTransactionsPtr _blockTxs, bcos::protocol::Block::ConstPtr block, std::function callback, @@ -1965,6 +1977,14 @@ bool Ledger::buildGenesisBlock( sysTable->setRow(SYSTEM_KEY_AUTH_CHECK_STATUS, std::move(authCheckStatusEntry)); } + if (versionNumber >= BlockVersion::V3_9_0_VERSION) + { + // write web3 chain id + Entry chainIdEntry; + chainIdEntry.setObject(SystemConfigEntry{genesis.m_web3ChainID, 0}); + sysTable->setRow(SYSTEM_KEY_WEB3_CHAIN_ID, std::move(chainIdEntry)); + } + // write consensus node list std::promise>> consensusTablePromise; m_storage->asyncOpenTable(SYS_CONSENSUS, [&consensusTablePromise]( diff --git a/bcos-ledger/src/libledger/Ledger.h b/bcos-ledger/src/libledger/Ledger.h index 8836596cc2..5edfd7b22a 100644 --- a/bcos-ledger/src/libledger/Ledger.h +++ b/bcos-ledger/src/libledger/Ledger.h @@ -25,6 +25,8 @@ #include "bcos-framework/protocol/ProtocolTypeDef.h" #include "bcos-framework/storage/StorageInterface.h" #include "utilities/Common.h" +#include +#include #include #include #include @@ -107,10 +109,40 @@ class Ledger : public LedgerInterface std::function&&)> _callback) override; + task::Task> getStorageAt(std::string_view _address, + std::string_view _key, protocol::BlockNumber _blockNumber) override; + bool buildGenesisBlock(GenesisConfig const& genesis, ledger::LedgerConfig const& ledgerConfig); void asyncGetBlockTransactionHashes(bcos::protocol::BlockNumber blockNumber, std::function&&)> callback); + void setKeyPageSize(size_t keyPageSize) { m_keyPageSize = keyPageSize; } + +protected: + storage::StateStorageInterface::Ptr getStateStorage() + { + if (m_keyPageSize > 0) + { + // create keyPageStorage + storage::StateStorageFactory stateStorageFactory(m_keyPageSize); + // getABI function begin in version 320 + auto keyPageIgnoreTables = std::make_shared>>( + storage::IGNORED_ARRAY_310.begin(), storage::IGNORED_ARRAY_310.end()); + auto [error, entry] = + m_storage->getRow(ledger::SYS_CONFIG, ledger::SYSTEM_KEY_COMPATIBILITY_VERSION); + if (!entry || error) + { + BOOST_THROW_EXCEPTION( + BCOS_ERROR(GetStorageError, "Not found compatibilityVersion.")); + } + auto [compatibilityVersionStr, _] = entry->template getObject(); + auto const version = bcos::tool::toVersionNumber(compatibilityVersionStr); + auto stateStorage = stateStorageFactory.createStateStorage( + m_storage, version, true, false, keyPageIgnoreTables); + return stateStorage; + } + return std::make_shared(m_storage, true); + } private: Error::Ptr checkTableValid(Error::UniquePtr&& error, @@ -168,5 +200,6 @@ class Ledger : public LedgerInterface RecursiveMutex m_receiptMerkleMtx; CacheType m_txProofMerkleCache; CacheType m_receiptProofMerkleCache; + size_t m_keyPageSize = 0; }; } // namespace bcos::ledger diff --git a/bcos-ledger/src/libledger/LedgerMethods.cpp b/bcos-ledger/src/libledger/LedgerMethods.cpp index 9d17637a99..5a932c9728 100644 --- a/bcos-ledger/src/libledger/LedgerMethods.cpp +++ b/bcos-ledger/src/libledger/LedgerMethods.cpp @@ -1,6 +1,9 @@ #include "LedgerMethods.h" #include "bcos-tool/VersionConverter.h" #include "utilities/Common.h" + +#include + #include #include @@ -220,6 +223,48 @@ bcos::task::Task bcos::ledger::tag_invoke( Awaitable awaitable{.m_ledger = ledger, .m_blockNumber = blockNumber, .m_result = {}}; co_return co_await awaitable; } + +bcos::task::Task bcos::ledger::tag_invoke( + bcos::ledger::tag_t /*unused*/, + bcos::ledger::LedgerInterface& ledger, bcos::crypto::HashType hash) +{ + struct Awaitable + { + bcos::ledger::LedgerInterface& m_ledger; + bcos::crypto::HashType m_hash; + + std::variant m_result; + + constexpr static bool await_ready() noexcept { return false; } + void await_suspend(CO_STD::coroutine_handle<> handle) + { + m_ledger.asyncGetBlockNumberByHash( + m_hash, [this, handle](bcos::Error::Ptr error, bcos::protocol::BlockNumber number) { + if (error) + { + m_result.emplace(std::move(error)); + } + else + { + m_result.emplace(number); + } + handle.resume(); + }); + } + bcos::protocol::BlockNumber await_resume() + { + if (std::holds_alternative(m_result)) + { + BOOST_THROW_EXCEPTION(*std::get(m_result)); + } + return std::get(m_result); + } + }; + + Awaitable awaitable{.m_ledger = ledger, .m_hash = std::move(hash), .m_result = {}}; + co_return co_await awaitable; +} + bcos::task::Task> bcos::ledger::tag_invoke( ledger::tag_t /*unused*/, LedgerInterface& ledger, std::string_view key) { @@ -394,7 +439,8 @@ bcos::task::Task bcos::ledger::tag_invoke( } ledgerConfig->setAuthCheckStatus( std::get<0>(co_await getSystemConfigOrDefault(ledger, SYSTEM_KEY_AUTH_CHECK_STATUS, 0))); - + auto [chainId, _] = co_await getSystemConfigOrDefault(ledger, SYSTEM_KEY_WEB3_CHAIN_ID, "0"); + ledgerConfig->setChainId(bcos::toEvmC(boost::lexical_cast(chainId))); co_return ledgerConfig; } @@ -427,3 +473,91 @@ bcos::task::Task bcos::ledger::tag_invoke( co_return features; } + +bcos::task::Task bcos::ledger::tag_invoke( + ledger::tag_t, LedgerInterface& ledger, crypto::HashType const& txHash) +{ + struct Awaitable + { + bcos::ledger::LedgerInterface& m_ledger; + bcos::crypto::HashType m_hash; + + std::variant m_result; + + constexpr static bool await_ready() noexcept { return false; } + void await_suspend(CO_STD::coroutine_handle<> handle) + { + m_ledger.asyncGetTransactionReceiptByHash(m_hash, false, + [this, handle](bcos::Error::Ptr error, + bcos::protocol::TransactionReceipt::ConstPtr receipt, MerkleProofPtr) { + if (error) + { + m_result.emplace(std::move(error)); + } + else + { + m_result.emplace(receipt); + } + handle.resume(); + }); + } + bcos::protocol::TransactionReceipt::ConstPtr await_resume() + { + if (std::holds_alternative(m_result)) + { + BOOST_THROW_EXCEPTION(*std::get(m_result)); + } + return std::get(m_result); + } + }; + + Awaitable awaitable{.m_ledger = ledger, .m_hash = std::move(txHash), .m_result = {}}; + co_return co_await awaitable; +} + +bcos::task::Task bcos::ledger::tag_invoke( + ledger::tag_t, LedgerInterface& ledger, crypto::HashListPtr hashes) +{ + struct Awaitable + { + bcos::ledger::LedgerInterface& m_ledger; + bcos::crypto::HashListPtr m_hashes; + + std::variant m_result; + + constexpr static bool await_ready() noexcept { return false; } + void await_suspend(CO_STD::coroutine_handle<> handle) + { + m_ledger.asyncGetBatchTxsByHashList( + std::move(m_hashes), false, [this, handle](auto&& error, auto&& txs, auto&&) { + if (error) + { + m_result.emplace(std::move(error)); + } + else + { + m_result.emplace(txs); + } + handle.resume(); + }); + } + bcos::protocol::TransactionsConstPtr await_resume() + { + if (std::holds_alternative(m_result)) + { + BOOST_THROW_EXCEPTION(*std::get(m_result)); + } + return std::get(m_result); + } + }; + + Awaitable awaitable{.m_ledger = ledger, .m_hashes = std::move(hashes), .m_result = {}}; + co_return co_await awaitable; +} + +bcos::task::Task> bcos::ledger::tag_invoke( + ledger::tag_t, LedgerInterface& ledger, std::string_view address, + std::string_view key, bcos::protocol::BlockNumber number) +{ + co_return co_await ledger.getStorageAt(address, key, number); +} diff --git a/bcos-ledger/src/libledger/LedgerMethods.h b/bcos-ledger/src/libledger/LedgerMethods.h index 1eaee39b97..51e913496b 100644 --- a/bcos-ledger/src/libledger/LedgerMethods.h +++ b/bcos-ledger/src/libledger/LedgerMethods.h @@ -66,6 +66,9 @@ task::Task tag_invoke( task::Task tag_invoke(ledger::tag_t /*unused*/, LedgerInterface& ledger, protocol::BlockNumber blockNumber); +task::Task tag_invoke( + ledger::tag_t /*unused*/, LedgerInterface& ledger, crypto::HashType hash); + task::Task> tag_invoke( ledger::tag_t /*unused*/, LedgerInterface& ledger, std::string_view key); @@ -76,4 +79,15 @@ task::Task tag_invoke( ledger::tag_t /*unused*/, LedgerInterface& ledger); task::Task tag_invoke(ledger::tag_t /*unused*/, LedgerInterface& ledger); + +task::Task tag_invoke( + ledger::tag_t, LedgerInterface& ledger, crypto::HashType const& txHash); + +task::Task tag_invoke( + ledger::tag_t, LedgerInterface& ledger, crypto::HashListPtr hashes); + +task::Task> tag_invoke(ledger::tag_t, + LedgerInterface& ledger, std::string_view address, std::string_view key, + bcos::protocol::BlockNumber number); + } // namespace bcos::ledger \ No newline at end of file diff --git a/bcos-pbft/bcos-pbft/pbft/cache/PBFTCache.cpp b/bcos-pbft/bcos-pbft/pbft/cache/PBFTCache.cpp index aa8d68c911..10bd9d29bc 100644 --- a/bcos-pbft/bcos-pbft/pbft/cache/PBFTCache.cpp +++ b/bcos-pbft/bcos-pbft/pbft/cache/PBFTCache.cpp @@ -341,6 +341,14 @@ void PBFTCache::resetExceptionCache(ViewType _curView) for (auto exceptionPrePrepare = m_exceptionPrePrepareList.begin(); exceptionPrePrepare != m_exceptionPrePrepareList.end();) { + PBFT_LOG(INFO) << LOG_DESC("resetCache: asyncResetTxsFlag exceptionPrePrepare") + << LOG_KV("prePrepare", m_prePrepare ? "true" : "false") + << LOG_KV("curView", _curView) + << (m_precommit ? printPBFTProposal(m_precommit) : "precommit is null") + << ((m_prePrepare && m_prePrepare->consensusProposal()) ? + printPBFTProposal(m_prePrepare->consensusProposal()) : + "consensusProposal is null") + << printPBFTProposal((*exceptionPrePrepare)->consensusProposal()); auto validPrePrepare = (m_precommit || (m_prePrepare && m_prePrepare->consensusProposal() && m_prePrepare->view() >= _curView)); if (validPrePrepare && @@ -348,13 +356,21 @@ void PBFTCache::resetExceptionCache(ViewType _curView) { if (c_fileLogLevel == TRACE) [[unlikely]] { - PBFT_LOG(TRACE) << LOG_DESC("resetCache : exceptionPrePrepare but finally be valid") + PBFT_LOG(TRACE) << LOG_DESC("resetCache: exceptionPrePrepare but finally be valid") + << printPBFTProposal((*exceptionPrePrepare)->consensusProposal()); + } + } + else if (m_precommit && m_precommit->hash() == (*exceptionPrePrepare)->hash()) + { + if (c_fileLogLevel == TRACE) [[unlikely]] + { + PBFT_LOG(TRACE) << LOG_DESC("resetCache: prepare finaly into commit") << printPBFTProposal((*exceptionPrePrepare)->consensusProposal()); } } else { - PBFT_LOG(INFO) << LOG_DESC("resetCache : asyncResetTxsFlag exceptionPrePrepare") + PBFT_LOG(INFO) << LOG_DESC("resetCache: asyncResetTxsFlag exceptionPrePrepare") << printPBFTProposal((*exceptionPrePrepare)->consensusProposal()); m_config->validator()->asyncResetTxsFlag( (*exceptionPrePrepare)->consensusProposal()->data(), false); diff --git a/bcos-pbft/bcos-pbft/pbft/engine/PBFTEngine.cpp b/bcos-pbft/bcos-pbft/pbft/engine/PBFTEngine.cpp index 6b24d4b102..6484cbfb0e 100644 --- a/bcos-pbft/bcos-pbft/pbft/engine/PBFTEngine.cpp +++ b/bcos-pbft/bcos-pbft/pbft/engine/PBFTEngine.cpp @@ -233,6 +233,11 @@ void PBFTEngine::onProposalApplyFailed(int64_t _errorCode, PBFTProposalInterface _proposal->index() >= m_config->syncingHighestNumber()) { m_config->timer()->restart(); + // restart checkPoint timer to advoid timeout + if (m_timer->running()) + { + m_timer->restart(); + } PBFT_LOG(INFO) << LOG_DESC( "proposal execute failed and re-push the proposal " "into the cache") @@ -274,6 +279,11 @@ void PBFTEngine::onProposalApplySuccess( { m_config->timer()->restart(); } + // restart checkPoint timer to advoid timeout + if (m_timer->running()) + { + m_timer->restart(); + } m_cacheProcessor->addCheckPointMsg(checkPointMsg); m_cacheProcessor->setCheckPointProposal(_executedProposal); auto currentExpectedCheckPoint = m_config->expectedCheckPoint(); diff --git a/bcos-rpc/CMakeLists.txt b/bcos-rpc/CMakeLists.txt index d66fdd3bc4..8c6bfc1002 100644 --- a/bcos-rpc/CMakeLists.txt +++ b/bcos-rpc/CMakeLists.txt @@ -29,7 +29,7 @@ file(GLOB_RECURSE SRCS bcos-rpc/*.cpp) find_package(tarscpp REQUIRED) add_library(${RPC_TARGET} ${SRCS} ${HEADERS}) -target_link_libraries(${RPC_TARGET} PUBLIC bcos-boostssl ${CRYPTO_TARGET} ${TARS_PROTOCOL_TARGET} jsoncpp_static ${CRYPTO_TARGET} tarscpp::tarsservant tarscpp::tarsutil) +target_link_libraries(${RPC_TARGET} PUBLIC bcos-boostssl ${LEDGER_TARGET} ${CRYPTO_TARGET} ${TARS_PROTOCOL_TARGET} jsoncpp_static tarscpp::tarsservant tarscpp::tarsutil) if (TESTS) enable_testing() diff --git a/bcos-rpc/bcos-rpc/Common.h b/bcos-rpc/bcos-rpc/Common.h index 28d1daff89..e8a78bae03 100644 --- a/bcos-rpc/bcos-rpc/Common.h +++ b/bcos-rpc/bcos-rpc/Common.h @@ -18,15 +18,18 @@ * @date 2021-07-02 */ #pragma once +#include "bcos-utilities/Common.h" #include +#include +#include +#include +#include #include #include #define RPC_LOG(LEVEL) BCOS_LOG(LEVEL) << LOG_BADGE("RPC") -namespace bcos -{ -namespace rpc +namespace bcos::rpc { enum AMOPClientMessageType { @@ -35,5 +38,27 @@ enum AMOPClientMessageType AMOP_BROADCAST = 0x112, // 274 AMOP_RESPONSE = 0x113 // 275 }; -} // namespace rpc -} // namespace bcos \ No newline at end of file +class JsonSink +{ +public: + typedef char char_type; + typedef boost::iostreams::sink_tag category; + + explicit JsonSink(bcos::bytes& buffer) : m_buffer(buffer) {} + + std::streamsize write(const char* s, std::streamsize n) + { + m_buffer.insert(m_buffer.end(), (bcos::byte*)s, (bcos::byte*)s + n); + return n; + } + + bcos::bytes& m_buffer; +}; + +constexpr const std::string_view EarliestBlock{"earliest"}; +constexpr const std::string_view LatestBlock{"latest"}; +constexpr const std::string_view PendingBlock{"pending"}; +constexpr const std::string_view SafeBlock{"safe"}; +constexpr const std::string_view FinalizedBlock{"finalized"}; + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/Rpc.cpp b/bcos-rpc/bcos-rpc/Rpc.cpp index 89df9c9c01..0679696e64 100644 --- a/bcos-rpc/bcos-rpc/Rpc.cpp +++ b/bcos-rpc/bcos-rpc/Rpc.cpp @@ -74,6 +74,10 @@ void Rpc::start() // start websocket service m_wsService->start(); m_amopClient->start(); + if (m_web3Service) + { + m_web3Service->start(); + } RPC_LOG(INFO) << LOG_DESC("start rpc successfully"); } @@ -94,6 +98,10 @@ void Rpc::stop() { m_amopClient->stop(); } + if (m_web3Service) + { + m_web3Service->stop(); + } RPC_LOG(INFO) << LOG_DESC("[RPC][RPC][stop]") << LOG_DESC("stop rpc successfully"); } diff --git a/bcos-rpc/bcos-rpc/Rpc.h b/bcos-rpc/bcos-rpc/Rpc.h index af21fe3968..cfbb5f2d22 100644 --- a/bcos-rpc/bcos-rpc/Rpc.h +++ b/bcos-rpc/bcos-rpc/Rpc.h @@ -21,6 +21,8 @@ #pragma once #include "bcos-rpc/groupmgr/GroupManager.h" +#include "web3jsonrpc/Web3JsonRpcImpl.h" + #include #include #include @@ -85,8 +87,20 @@ class Rpc : public RPCInterface, public std::enable_shared_from_this m_amopClient->asyncNotifySubscribeTopic(_callback); } + void setWeb3Service(boostssl::ws::WsService::Ptr _web3Service) + { + m_web3Service = std::move(_web3Service); + } + + void setWeb3JsonRpcImpl(bcos::rpc::Web3JsonRpcImpl::Ptr _web3JsonRpcImpl) + { + m_web3JsonRpcImpl = std::move(_web3JsonRpcImpl); + } + GroupManager::Ptr groupManager() { return m_groupManager; } + bcos::rpc::Web3JsonRpcImpl::Ptr web3JsonRpc() const { return m_web3JsonRpcImpl; } + protected: virtual void notifyGroupInfo(bcos::group::GroupInfo::Ptr _groupInfo); @@ -102,6 +116,8 @@ class Rpc : public RPCInterface, public std::enable_shared_from_this bcos::event::EventSub::Ptr m_eventSub; AMOPClient::Ptr m_amopClient; GroupManager::Ptr m_groupManager; + boostssl::ws::WsService::Ptr m_web3Service = nullptr; + bcos::rpc::Web3JsonRpcImpl::Ptr m_web3JsonRpcImpl = nullptr; bcos::protocol::ProtocolInfo::ConstPtr m_localProtocol; }; diff --git a/bcos-rpc/bcos-rpc/RpcFactory.cpp b/bcos-rpc/bcos-rpc/RpcFactory.cpp index b1f17fdedf..420d65e86c 100644 --- a/bcos-rpc/bcos-rpc/RpcFactory.cpp +++ b/bcos-rpc/bcos-rpc/RpcFactory.cpp @@ -31,7 +31,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -319,6 +321,24 @@ std::shared_ptr RpcFactory::initConfig( return wsConfig; } +std::shared_ptr RpcFactory::initWeb3RpcServiceConfig( + const bcos::tool::NodeConfig::Ptr& _nodeConfig) +{ + auto wsConfig = std::make_shared(); + wsConfig->setModel(bcos::boostssl::ws::WsModel::Server); + + wsConfig->setListenIP(_nodeConfig->web3RpcListenIP()); + wsConfig->setListenPort(_nodeConfig->web3RpcListenPort()); + wsConfig->setThreadPoolSize(_nodeConfig->web3RpcThreadSize()); + wsConfig->setDisableSsl(true); + RPC_LOG(INFO) << LOG_BADGE("initWeb3RpcServiceConfig") + << LOG_KV("listenIP", wsConfig->listenIP()) + << LOG_KV("listenPort", wsConfig->listenPort()) + << LOG_KV("threadCount", wsConfig->threadPoolSize()) + << LOG_KV("asServer", wsConfig->asServer()); + return wsConfig; +} + bcos::boostssl::ws::WsService::Ptr RpcFactory::buildWsService( bcos::boostssl::ws::WsConfig::Ptr _config) { @@ -336,9 +356,11 @@ bcos::rpc::JsonRpcImpl_2_0::Ptr RpcFactory::buildJsonRpc(int sendTxTimeout, const std::shared_ptr& _wsService, GroupManager::Ptr _groupManager) { // JsonRpcImpl_2_0 - //* - auto jsonRpcInterface = - std::make_shared(_groupManager, m_gateway, _wsService); + auto filterSystem = + std::make_shared(_groupManager, m_nodeConfig->groupId(), + m_nodeConfig->rpcFilterTimeout(), m_nodeConfig->rpcMaxProcessBlock()); + auto jsonRpcInterface = std::make_shared( + _groupManager, m_gateway, _wsService, filterSystem); jsonRpcInterface->setSendTxTimeout(sendTxTimeout); auto httpServer = _wsService->httpServer(); if (httpServer) @@ -349,6 +371,24 @@ bcos::rpc::JsonRpcImpl_2_0::Ptr RpcFactory::buildJsonRpc(int sendTxTimeout, return jsonRpcInterface; } +bcos::rpc::Web3JsonRpcImpl::Ptr RpcFactory::buildWeb3JsonRpc( + int sendTxTimeout, boostssl::ws::WsService::Ptr _wsService, GroupManager::Ptr _groupManager) +{ + auto web3FilterSystem = + std::make_shared(_groupManager, m_nodeConfig->groupId(), + m_nodeConfig->web3FilterTimeout(), m_nodeConfig->web3MaxProcessBlock()); + auto web3JsonRpc = std::make_shared( + m_nodeConfig->groupId(), std::move(_groupManager), m_gateway, _wsService, web3FilterSystem); + auto httpServer = _wsService->httpServer(); + if (httpServer) + { + httpServer->setHttpReqHandler(std::bind(&bcos::rpc::Web3JsonRpcImpl::onRPCRequest, + web3JsonRpc, std::placeholders::_1, std::placeholders::_2)); + } + return web3JsonRpc; +} + + bcos::event::EventSub::Ptr RpcFactory::buildEventSub( const std::shared_ptr& _wsService, GroupManager::Ptr _groupManager) { @@ -387,6 +427,15 @@ Rpc::Ptr RpcFactory::buildLocalRpc( auto groupManager = buildAirGroupManager(_groupInfo, _nodeService); auto amopClient = buildAirAMOPClient(wsService); auto rpc = buildRpc(m_nodeConfig->sendTxTimeout(), wsService, groupManager, amopClient); + if (m_nodeConfig->enableWeb3Rpc()) + { + auto web3Config = initWeb3RpcServiceConfig(m_nodeConfig); + auto web3WsService = buildWsService(std::move(web3Config)); + auto web3JsonRpc = + buildWeb3JsonRpc(m_nodeConfig->sendTxTimeout(), web3WsService, groupManager); + rpc->setWeb3Service(std::move(web3WsService)); + rpc->setWeb3JsonRpcImpl(std::move(web3JsonRpc)); + } // Note: init groupManager after create rpc and register the handlers groupManager->init(); return rpc; diff --git a/bcos-rpc/bcos-rpc/RpcFactory.h b/bcos-rpc/bcos-rpc/RpcFactory.h index 523e9a0381..a7ac40185a 100644 --- a/bcos-rpc/bcos-rpc/RpcFactory.h +++ b/bcos-rpc/bcos-rpc/RpcFactory.h @@ -24,6 +24,8 @@ #include "bcos-rpc/amop/AirAMOPClient.h" #include "bcos-rpc/groupmgr/AirGroupManager.h" #include "bcos-rpc/groupmgr/GroupManager.h" +#include "web3jsonrpc/Web3JsonRpcImpl.h" + #include #include #include @@ -57,6 +59,8 @@ class RpcFactory : public std::enable_shared_from_this std::shared_ptr initConfig( const bcos::tool::NodeConfig::Ptr& _nodeConfig); + std::shared_ptr initWeb3RpcServiceConfig( + const bcos::tool::NodeConfig::Ptr& _nodeConfig); std::shared_ptr buildWsService( bcos::boostssl::ws::WsConfig::Ptr _config); @@ -86,6 +90,9 @@ class RpcFactory : public std::enable_shared_from_this bcos::rpc::JsonRpcImpl_2_0::Ptr buildJsonRpc(int sendTxTimeout, const std::shared_ptr& _wsService, GroupManager::Ptr _groupManager); + + bcos::rpc::Web3JsonRpcImpl::Ptr buildWeb3JsonRpc(int sendTxTimeout, + boostssl::ws::WsService::Ptr _wsService, GroupManager::Ptr _groupManager); bcos::event::EventSub::Ptr buildEventSub( const std::shared_ptr& _wsService, GroupManager::Ptr _groupManager); diff --git a/bcos-rpc/bcos-rpc/filter/Common.h b/bcos-rpc/bcos-rpc/filter/Common.h new file mode 100644 index 0000000000..4a7a3883d6 --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/Common.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +// The largest number of topic in one event log +#define EVENT_LOG_TOPICS_MAX_INDEX (4) + +#define FILTER_LOG(LEVEL) BCOS_LOG(LEVEL) << "[FILTER]" + +namespace bcos +{ +namespace rpc +{ + +enum SubscriptionType +{ + // LogsSubscription queries for new + LogsSubscription = 0, + // PendingTransactionsSubscription queries for pending transactions entering the pending state + PendingTransactionsSubscription, + // BlocksSubscription queries hashes for blocks that are imported + BlocksSubscription, + // LastIndexSubscription keeps track of the last index + LastIndexSubscription +}; + +// Trigger a filter cleanup operation every 3s +static constexpr const uint64_t CLEANUP_FILTER_TIME = 3000; +static constexpr const uint64_t MAX_TRAVERSE_FILTERS_COUNT = 10000; +// the filter expiration time, default is 5 minutes +static constexpr const uint64_t FILTER_DEFAULT_EXPIRATION_TIME = uint64_t(60 * 5 * 1000); + +} // namespace rpc +} // namespace bcos diff --git a/bcos-rpc/bcos-rpc/filter/Filter.h b/bcos-rpc/bcos-rpc/filter/Filter.h new file mode 100644 index 0000000000..3671175fe4 --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/Filter.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace bcos +{ +namespace rpc +{ + +class Filter +{ +public: + using Ptr = std::shared_ptr; + + Filter(SubscriptionType type, u256 id, FilterRequest::Ptr params, bool fullTx, + bcos::protocol::BlockNumber startBlockNumber, std::string_view group) + : m_fullTx(fullTx), + m_type(type), + m_id(id), + m_params(params), + m_startBlockNumber(startBlockNumber), + m_lastAccessTime(utcTime()), + m_group(group) + {} + + virtual ~Filter() {} + + SubscriptionType type() const { return m_type; } + u256 id() const { return m_id; } + int64_t startBlockNumber() const { return m_startBlockNumber.load(); } + FilterRequest::Ptr params() const { return m_params; } + bool fullTx() const { return m_fullTx; } + uint64_t lastAccessTime() { return m_lastAccessTime.load(); } + std::string_view group() const { return m_group; } + + void updateLastAccessTime() { m_lastAccessTime.store(utcTime()); } + void setStartBlockNumber(int64_t number) { m_startBlockNumber.store(number); } + void setId(u256 id) { m_id = id; } + +private: + bool m_fullTx; + SubscriptionType m_type; + u256 m_id; + FilterRequest::Ptr m_params; + std::atomic m_startBlockNumber; + std::atomic m_lastAccessTime; + std::string m_group; +}; + +} // namespace rpc +} // namespace bcos \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/filter/FilterRequest.cpp b/bcos-rpc/bcos-rpc/filter/FilterRequest.cpp new file mode 100644 index 0000000000..8f707cb22c --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/FilterRequest.cpp @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file FilterRequest.cpp + * @author: kyonGuo + * @date 2024/4/11 + */ +#include +#include +#include +#include + +using namespace bcos; +using namespace bcos::rpc; + +void FilterRequest::fromJson(const Json::Value& jParams, protocol::BlockNumber latest) +{ + // check params + if (!jParams.isMember("fromBlock") || jParams["fromBlock"].isNull()) + { + FILTER_LOG(ERROR) << LOG_BADGE("fromJson") << LOG_DESC("fromBlock is null"); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "fromBlock is null")); + } + + if (!jParams.isMember("toBlock") || jParams["toBlock"].isNull()) + { + FILTER_LOG(ERROR) << LOG_BADGE("fromJson") << LOG_DESC("toBlock is null"); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "toBlock is null")); + } + + if (!jParams.isMember("address") || jParams["address"].isNull()) + { + FILTER_LOG(ERROR) << LOG_BADGE("fromJson") << LOG_DESC("address is null"); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "address is null")); + } + + if (!jParams.isMember("topics") || jParams["topics"].isNull()) + { + FILTER_LOG(ERROR) << LOG_BADGE("fromJson") << LOG_DESC("topics is null"); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "topics is null")); + } + + // prase fromBlock + std::tie(m_fromBlock, m_fromIsLatest) = + getBlockNumberByTag(latest, jParams["fromBlock"].asString()); + + // prase toBlock + std::tie(m_toBlock, m_toIsLatest) = getBlockNumberByTag(latest, jParams["toBlock"].asString()); + + // prase address + auto& jAddresses = jParams["address"]; + if (jAddresses.isArray()) + { + for (Json::Value::ArrayIndex index = 0; index < jAddresses.size(); ++index) + { + addAddress(jAddresses[index].asString()); + } + } + else + { + addAddress(jAddresses.asString()); + } + + // prase topics + auto& jTopics = jParams["topics"]; + if (jTopics.size() > EVENT_LOG_TOPICS_MAX_INDEX) + { + FILTER_LOG(ERROR) << LOG_BADGE("fromJson") << LOG_DESC("exceed max topics") + << LOG_KV("topicsSize", jTopics.size()); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "exceed max topics")); + } + resizeTopic(jTopics.size()); + for (Json::Value::ArrayIndex index = 0; index < jTopics.size(); ++index) + { + auto& jIndex = jTopics[index]; + if (jIndex.isNull()) + { + continue; + } + + if (jIndex.isArray()) + { // array topics + for (Json::Value::ArrayIndex innerIndex = 0; innerIndex < jIndex.size(); ++innerIndex) + { + addTopic(index, jIndex[innerIndex].asString()); + } + } + else + { // single topic, string value + addTopic(index, jIndex.asString()); + } + } + + // prase blockhash + if (jParams.isMember("blockhash") && !jParams["blockhash"].isNull()) + { + m_blockHash = jParams["blockhash"].asString(); + } +} + +bool FilterRequest::checkBlockRange() +{ + if (fromBlock() < 0 || toBlock() < 0) + { + return false; + } + if (fromIsLatest() && toIsLatest()) + { + return true; + } + if (!fromIsLatest() && !toIsLatest() && toBlock() >= fromBlock()) + { + return true; + } + if (!fromIsLatest() && toIsLatest()) + { + return true; + } + return false; +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/filter/FilterRequest.h b/bcos-rpc/bcos-rpc/filter/FilterRequest.h new file mode 100644 index 0000000000..5c2f7b6ab7 --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/FilterRequest.h @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file FilterRequest.h + * @author: kyonGuo + * @date 2024/4/11 + */ + +#pragma once +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ + +class FilterRequest +{ +public: + using Ptr = std::shared_ptr; + using ConstPtr = std::shared_ptr; + + FilterRequest() = default; + virtual ~FilterRequest() = default; + + FilterRequest(const FilterRequest& other) + { + if (&other == this) + { + return; + } + m_fromBlock = other.m_fromBlock; + m_toBlock = other.m_toBlock; + m_fromIsLatest = other.m_fromIsLatest; + m_toIsLatest = other.m_toIsLatest; + m_addresses = other.m_addresses; + m_topics = other.m_topics; + m_blockHash = other.m_blockHash; + } + +public: + int64_t fromBlock() const { return m_fromBlock; } + int64_t toBlock() const { return m_toBlock; } + const std::set& addresses() const { return m_addresses; } + std::set& addresses() { return m_addresses; } + const std::vector>& topics() const { return m_topics; } + std::vector>& topics() { return m_topics; } + std::string& blockHash() { return m_blockHash; } + const std::string& blockHash() const { return m_blockHash; } + bool fromIsLatest() const { return m_fromIsLatest; } + bool toIsLatest() const { return m_toIsLatest; } + + void setFromBlock(int64_t _fromBlock) { m_fromBlock = _fromBlock; } + void setToBlock(int64_t _toBlock) { m_toBlock = _toBlock; } + void addAddress(std::string _address) { m_addresses.insert(std::move(_address)); } + void resizeTopic(size_t size) { m_topics.resize(size); } + void addTopic(std::size_t _index, std::string _topic) + { + m_topics[_index].insert(std::move(_topic)); + } + void setBlockHash(const std::string& _hash) { m_blockHash = _hash; } + void fromJson(const Json::Value& jParams, protocol::BlockNumber latest = 0); + bool checkBlockRange(); + + virtual int32_t InvalidParamsCode() = 0; + +protected: + bcos::protocol::BlockNumber m_fromBlock = 0; + bcos::protocol::BlockNumber m_toBlock = 0; + bool m_fromIsLatest = true; + bool m_toIsLatest = true; + std::set m_addresses; + std::vector> m_topics; + std::string m_blockHash; +}; + +class FilterRequestFactory +{ +public: + using Ptr = std::shared_ptr; + FilterRequestFactory() = default; + virtual ~FilterRequestFactory() = default; + virtual FilterRequest::Ptr create() = 0; + virtual FilterRequest::Ptr create(const FilterRequest& req) = 0; +}; + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/filter/FilterSystem.cpp b/bcos-rpc/bcos-rpc/filter/FilterSystem.cpp new file mode 100644 index 0000000000..e85ee49582 --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/FilterSystem.cpp @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include +#include + +#define CPU_CORES std::thread::hardware_concurrency() + +using namespace bcos; +using namespace bcos::rpc; +using namespace bcos::rpc::filter; + +FilterSystem::FilterSystem(GroupManager::Ptr groupManager, const std::string& groupId, + FilterRequestFactory::Ptr factory, int filterTimeout, int maxBlockProcessPerReq) + : m_filterTimeout(filterTimeout * 1000), + m_maxBlockProcessPerReq(maxBlockProcessPerReq), + m_groupManager(groupManager), + m_group(groupId), + m_matcher(std::make_shared()), + m_factory(factory), + m_filters(CPU_CORES) +{ + // Trigger a filter cleanup operation every 3s + m_cleanUpTimer = std::make_shared(CLEANUP_FILTER_TIME, "filter_system_timer"); + m_cleanUpTimer->registerTimeoutHandler([this] { cleanUpExpiredFilters(); }); + m_cleanUpTimer->start(); +} + +uint64_t FilterSystem::insertFilter(Filter::Ptr filter) +{ + static std::mt19937_64 generator(std::random_device{}()); + uint64_t id = 0; + while (true) + { + id = generator(); + FilterMap::WriteAccessor::Ptr accessor; + if (m_filters.insert(accessor, {KeyType(filter->group(), id), filter})) + { + break; + } + } + filter->setId(id); + filter->updateLastAccessTime(); + return id; +} + +void FilterSystem::cleanUpExpiredFilters() +{ + m_cleanUpTimer->restart(); + if (m_filters.empty()) + { + return; + } + size_t traversedFiltersNum = 0; + uint64_t currentTime = utcTime(); + std::vector expiredFilters; + m_filters.forEach( + [&traversedFiltersNum, &expiredFilters, this, ¤tTime]( + FilterMap::ReadAccessor::Ptr accessor) { + const auto& filter = accessor->value(); + if (currentTime > (filter->lastAccessTime() + m_filterTimeout)) + { + expiredFilters.emplace_back(KeyType(filter->group(), filter->id())); + } + if (++traversedFiltersNum > MAX_TRAVERSE_FILTERS_COUNT) + { + return false; + } + return true; + }); + m_filters.batchRemove(expiredFilters); + FILTER_LOG(INFO) << LOG_DESC("cleanUpExpiredFilters") << LOG_KV("filters", m_filters.size()) + << LOG_KV("erasedFilters", expiredFilters.size()) + << LOG_KV("traversedFiltersNum", traversedFiltersNum); +} + +NodeService::Ptr FilterSystem::getNodeService( + std::string_view _groupID, std::string_view _command) const +{ + auto nodeService = m_groupManager->getNodeService(_groupID, ""); + if (!nodeService) + { + std::stringstream errorMsg; + errorMsg << LOG_DESC("group not exist") << LOG_KV("chain", m_groupManager->chainID()) + << LOG_KV("group", _groupID) << LOG_KV("commond", _command); + FILTER_LOG(WARNING) << errorMsg.str(); + BOOST_THROW_EXCEPTION(JsonRpcException(JsonRpcError::GroupNotExist, errorMsg.str())); + } + return nodeService; +} + +task::Task FilterSystem::newBlockFilter(std::string_view groupId) +{ + auto latestBlockNumber = getLatestBlockNumber(groupId); + auto filter = std::make_shared( + BlocksSubscription, 0, nullptr, false, latestBlockNumber + 1, groupId); + auto id = insertFilter(filter); + FILTER_LOG(TRACE) << LOG_BADGE("newBlockFilter") << LOG_KV("id", id) + << LOG_KV("startBlockNumber", latestBlockNumber); + co_return toQuantity(id); +} + +task::Task FilterSystem::newPendingTxFilter(std::string_view groupId) +{ + auto latestBlockNumber = getLatestBlockNumber(groupId); + auto filter = std::make_shared( + PendingTransactionsSubscription, 0, nullptr, false, latestBlockNumber + 1, groupId); + auto id = insertFilter(filter); + FILTER_LOG(TRACE) << LOG_BADGE("newPendingTxFilter") << LOG_KV("id", id) + << LOG_KV("startBlockNumber", latestBlockNumber); + co_return toQuantity(id); +} + +task::Task FilterSystem::newFilter(std::string_view groupId, FilterRequest::Ptr params) +{ + if (!params->checkBlockRange()) + { + FILTER_LOG(WARNING) << LOG_BADGE("newFilter") << LOG_DESC("invalid block range params") + << LOG_KV("fromBlock", params->fromBlock()) + << LOG_KV("toBlock", params->toBlock()); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "invalid block range params")); + } + auto latestBlockNumber = getLatestBlockNumber(groupId); + auto filter = std::make_shared( + LogsSubscription, 0, params, false, latestBlockNumber + 1, groupId); + auto id = insertFilter(filter); + FILTER_LOG(TRACE) << LOG_BADGE("newFilter") << LOG_KV("id", id) + << LOG_KV("startBlockNumber", latestBlockNumber); + co_return toQuantity(id); +} + +task::Task FilterSystem::getFilterChangeImpl(std::string_view groupId, u256 filterID) +{ + auto filter = getFilterByID(groupId, filterID); + if (filter == nullptr) + { + FILTER_LOG(WARNING) << LOG_BADGE("getFilterChangeImpl") << LOG_DESC("filter not found") + << LOG_KV("id", filterID); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "filter not found")); + } + filter->updateLastAccessTime(); + FILTER_LOG(TRACE) << LOG_BADGE("getFilterChangeImpl") << LOG_KV("id", filterID) + << LOG_KV("subType", filter->type()); + + Json::Value jValue(Json::arrayValue); + switch (filter->type()) + { + case PendingTransactionsSubscription: + { + jValue = co_await getPendingTxChangeImpl(groupId, filter); + break; + } + case LogsSubscription: + { + jValue = co_await getLogChangeImpl(groupId, filter); + break; + } + case BlocksSubscription: + { + jValue = co_await getBlockChangeImpl(groupId, filter); + break; + } + default: + break; + } + co_return jValue; +} + +task::Task FilterSystem::getBlockChangeImpl( + std::string_view groupId, Filter::Ptr filter) +{ + // getLatestBlockNumber and getBlockHash use the same ledger + auto ledger = getNodeService(groupId, "getBlockChangeImpl")->ledger(); + auto latestBlockNumber = getLatestBlockNumber(*ledger); + auto startBlockNumber = filter->startBlockNumber(); + + if (latestBlockNumber < startBlockNumber) + { // Since the last query, no new blocks have been generated + co_return Json::Value(Json::arrayValue); + } + // limit the number of blocks processed + auto processBlockNum = + std::min(latestBlockNumber - startBlockNumber + 1, m_maxBlockProcessPerReq); + filter->setStartBlockNumber(startBlockNumber + processBlockNum); + + FILTER_LOG(DEBUG) << LOG_BADGE("getBlockChangeImpl") << LOG_KV("id", filter->id()) + << LOG_KV("latestBlockNumber", latestBlockNumber) + << LOG_KV("startBlockNumber", startBlockNumber) + << LOG_KV("processBlockNum", processBlockNum) + << LOG_KV("nextStartBlockNumber", startBlockNumber + processBlockNum); + Json::Value jResult(Json::arrayValue); + for (auto i = 0; i < processBlockNum; ++i) + { + auto hash = co_await ledger::getBlockHash(*ledger, startBlockNumber + i); + jResult.append(hash.hexPrefixed()); + } + co_return jResult; +} + +task::Task FilterSystem::getPendingTxChangeImpl( + std::string_view groupId, Filter::Ptr filter) +{ + // getLatestBlockNumber and getBlockData use the same ledger + auto ledger = getNodeService(groupId, "getPendingTxChangeImpl")->ledger(); + auto latestBlockNumber = getLatestBlockNumber(*ledger); + auto startBlockNumber = filter->startBlockNumber(); + if (latestBlockNumber < startBlockNumber) + { // Since the last query, no new blocks have been generated + co_return Json::Value(Json::arrayValue); + } + // limit the number of blocks processed + auto processBlockNum = + std::min(latestBlockNumber - startBlockNumber + 1, m_maxBlockProcessPerReq); + filter->setStartBlockNumber(startBlockNumber + processBlockNum); + + FILTER_LOG(DEBUG) << LOG_BADGE("getPendingTxChangeImpl") << LOG_KV("id", filter->id()) + << LOG_KV("latestBlockNumber", latestBlockNumber) + << LOG_KV("startBlockNumber", startBlockNumber) + << LOG_KV("processBlockNum", processBlockNum) + << LOG_KV("nextStartBlockNumber", startBlockNumber + processBlockNum); + + Json::Value jRes(Json::arrayValue); + for (auto i = 0; i < processBlockNum; ++i) + { + auto block = co_await ledger::getBlockData( + *ledger, i + startBlockNumber, bcos::ledger::TRANSACTIONS_HASH); + for (std::size_t index = 0; index < block->transactionsMetaDataSize(); ++index) + { + jRes.append(block->transactionHash(index).hexPrefixed()); + } + } + co_return jRes; +} + +task::Task FilterSystem::getLogChangeImpl(std::string_view groupId, Filter::Ptr filter) +{ + // getLatestBlockNumber and getLogsInternal use the same ledger + auto ledger = getNodeService(groupId, "getLogsImpl")->ledger(); + auto latestBlockNumber = getLatestBlockNumber(*ledger); + auto startBlockNumber = filter->startBlockNumber(); + auto fromBlock = filter->params()->fromBlock(); + auto toBlock = filter->params()->toBlock(); + auto fromIsLatest = filter->params()->fromIsLatest(); + auto toIsLatest = filter->params()->toIsLatest(); + + /* clang-format off */ + // ------------------------------------------------------------------------------------------- + // The values of (fromBlock, toBlock) may be: + // 1. (latest, latest) + // 2. [from, latest) + // 3. [from, to] + // ------------------------------------------------------------------------------------------- + // "end < begin" is equivalent to the following conditions: + // 1. (fromBlock, toBlock) => [from, latest) + // - from > latestBlockNumber (the block of interest has not been generated yet) + // - Since the last query, no new blocks have been generated + // 2. (fromBlock, toBlock) => [from, to] + // - from > latestBlockNumber (the block of interest has not been generated yet) + // - to < latestBlockNumber (the blocks of interest are those that have already been stored) + // - Since the last query, no new blocks have been generated + // - All blocks within the interval [from, to] have been processed + // 3. (fromBlock, toBlock) => (latest, latest) + // - Since the last query, no new blocks have been generated + // ------------------------------------------------------------------------------------------- + /* clang-format on */ + auto begin = fromIsLatest ? startBlockNumber : std::max(fromBlock, startBlockNumber); + auto end = toIsLatest ? latestBlockNumber : std::min(toBlock, latestBlockNumber); + if (end < begin) + { + co_return Json::Value(Json::arrayValue); + } + + auto params = m_factory->create(*(filter->params())); + // limit the number of blocks processed + auto processBlockNum = std::min(end - begin + 1, m_maxBlockProcessPerReq); + params->setFromBlock(begin); + params->setToBlock(begin + processBlockNum - 1); + filter->setStartBlockNumber(begin + processBlockNum); + FILTER_LOG(DEBUG) << LOG_BADGE("getLogChangeImpl") << LOG_KV("id", filter->id()) + << LOG_KV("latestBlockNumber", latestBlockNumber) + << LOG_KV("startBlockNumber", startBlockNumber) + << LOG_KV("processBlockNum", processBlockNum) + << LOG_KV("nextStartBlockNumber", begin + processBlockNum) + << LOG_KV("begin", begin) << LOG_KV("end", end) << LOG_KV("from", begin) + << LOG_KV("to", begin + processBlockNum - 1); + co_return co_await getLogsInternal(*ledger, std::move(params)); +} + +task::Task FilterSystem::getFilterLogsImpl(std::string_view groupId, u256 filterID) +{ + auto filter = getFilterByID(groupId, filterID); + if (filter == nullptr || filter->type() != LogsSubscription) + { + FILTER_LOG(ERROR) << LOG_BADGE("getFilterLogsImpl") << LOG_DESC("filter not found") + << LOG_KV("id", filterID); + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "filter not found")); + } + auto params = m_factory->create(*(filter->params())); + co_return co_await getLogsImpl(groupId, params, false); +} + +task::Task FilterSystem::getLogsImpl( + std::string_view groupId, FilterRequest::Ptr params, bool needCheckRange) +{ + // getLatestBlockNumber and getLogsInPool use the same ledger + auto ledger = getNodeService(groupId, "getLogsImpl")->ledger(); + if (!params->blockHash().empty()) + { // when blockHash is not empty, match logs within the specified block + auto matcher = m_matcher; + int64_t blockNumber = 0; + try + { + blockNumber = co_await ledger::getBlockNumber(*ledger, + bcos::crypto::HashType(params->blockHash(), bcos::crypto::HashType::FromHex)); + } + catch (std::exception& e) + { + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParamsCode(), "unknown block")); + } + auto block = co_await ledger::getBlockData(*ledger, blockNumber, + bcos::ledger::HEADER | bcos::ledger::RECEIPTS | bcos::ledger::TRANSACTIONS_HASH); + Json::Value jArray(Json::arrayValue); + matcher->matches(params, block, jArray); + co_return jArray; + } + else + { + auto latestBlockNumber = getLatestBlockNumber(*ledger); + auto fromBlock = params->fromBlock(); + auto toBlock = params->toBlock(); + if (needCheckRange && !params->checkBlockRange()) + { + FILTER_LOG(WARNING) << LOG_BADGE("getLogsImpl") + << LOG_DESC("invalid block range params") + << LOG_KV("fromBlock", fromBlock) << LOG_KV("toBlock", toBlock); + BOOST_THROW_EXCEPTION( + JsonRpcException(InvalidParamsCode(), "invalid block range params")); + } + fromBlock = params->fromIsLatest() ? latestBlockNumber : fromBlock; + toBlock = params->toIsLatest() ? latestBlockNumber : toBlock; + if (fromBlock > latestBlockNumber) + { // the block of interest has not been generated yet + co_return Json::Value(Json::arrayValue); + } + params->setFromBlock(fromBlock); + params->setToBlock(std::min(toBlock, latestBlockNumber)); + co_return co_await getLogsInternal(*ledger, std::move(params)); + } +} + +task::Task FilterSystem::getLogsInternal( + bcos::ledger::LedgerInterface& ledger, FilterRequest::Ptr params) +{ + auto fromBlock = params->fromBlock(); + auto toBlock = params->toBlock(); + Json::Value jArray(Json::arrayValue); + auto matcher = m_matcher; + for (auto number = fromBlock; number <= toBlock; ++number) + { + auto block = co_await ledger::getBlockData(ledger, number, + bcos::ledger::HEADER | bcos::ledger::RECEIPTS | bcos::ledger::TRANSACTIONS_HASH); + matcher->matches(params, block, jArray); + } + co_return jArray; +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/filter/FilterSystem.h b/bcos-rpc/bcos-rpc/filter/FilterSystem.h new file mode 100644 index 0000000000..bd6918dd61 --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/FilterSystem.h @@ -0,0 +1,180 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc::filter +{ +struct KeyType +{ + std::string group; + u256 id; + + KeyType(std::string_view g, u256 i) : group(g), id(i) {} + + friend bool operator==(const KeyType& l, const KeyType& r) + { + return l.group == r.group && l.id == r.id; + } + + friend bool operator!=(const KeyType& l, const KeyType& r) { return !operator==(l, r); } + + template + friend Stream& operator<<(Stream& stream, const KeyType& key) + { + stream << key.group << "-" << key.id; + return stream; + } +}; +} // namespace bcos::rpc::filter + +template <> +struct std::hash +{ + size_t operator()(const bcos::rpc::filter::KeyType& key) const noexcept + { + size_t seed = std::hash{}(key.group); + boost::hash_combine(seed, std::hash{}(key.id)); + return seed; + } +}; + +namespace bcos::rpc +{ + +class FilterSystem : public std::enable_shared_from_this +{ +public: + using Ptr = std::shared_ptr; + using ConstPtr = std::shared_ptr; + + FilterSystem(GroupManager::Ptr groupManager, const std::string& groupId, + FilterRequestFactory::Ptr factory, int filterTimeout, int maxBlockProcessPerReq); + virtual ~FilterSystem() { m_cleanUpTimer->stop(); } + +public: + // jsonrpc + task::Task newBlockFilter(std::string_view groupId); + task::Task newPendingTxFilter(std::string_view groupId); + task::Task newFilter(std::string_view groupId, FilterRequest::Ptr params); + task::Task uninstallFilter(std::string_view groupId, u256 filterID) + { + co_return uninstallFilterImpl(groupId, filterID); + } + task::Task getFilterChanges(std::string_view groupId, u256 filterID) + { + co_return co_await getFilterChangeImpl(groupId, filterID); + } + task::Task getFilterLogs(std::string_view groupId, u256 filterID) + { + co_return co_await getFilterLogsImpl(groupId, filterID); + } + task::Task getLogs(std::string_view groupId, FilterRequest::Ptr params) + { + co_return co_await getLogsImpl(groupId, params, true); + } + + // web3jsonrpc + task::Task newBlockFilter() { co_return co_await newBlockFilter(m_group); } + task::Task newPendingTxFilter() { co_return co_await newPendingTxFilter(m_group); } + task::Task newFilter(FilterRequest::Ptr params) + { + co_return co_await newFilter(m_group, params); + } + task::Task uninstallFilter(u256 filterID) + { + co_return co_await uninstallFilter(m_group, filterID); + } + task::Task getFilterChanges(u256 filterID) + { + co_return co_await getFilterChanges(m_group, filterID); + } + task::Task getFilterLogs(u256 filterID) + { + co_return co_await getFilterLogs(m_group, filterID); + } + task::Task getLogs(FilterRequest::Ptr params) + { + co_return co_await getLogs(m_group, params); + } + +public: + int64_t getLatestBlockNumber(std::string_view groupId) + { + auto ledger = getNodeService(groupId, "getCurrentBlockNumber")->ledger(); + return getLatestBlockNumber(*ledger); + } + int64_t getLatestBlockNumber() { return getLatestBlockNumber(m_group); } + + FilterRequestFactory::Ptr requestFactory() const { return m_factory; } + LogMatcher::Ptr matcher() const { return m_matcher; } + NodeService::Ptr getNodeService(std::string_view _groupID, std::string_view _command) const; + +protected: + bool uninstallFilterImpl(std::string_view groupId, u256 filterID) + { + return m_filters.remove(filter::KeyType(groupId, filterID)) != nullptr; + } + task::Task getFilterChangeImpl(std::string_view groupId, u256 filterID); + task::Task getBlockChangeImpl(std::string_view groupId, Filter::Ptr filter); + task::Task getPendingTxChangeImpl(std::string_view groupId, Filter::Ptr filter); + task::Task getLogChangeImpl(std::string_view groupId, Filter::Ptr filter); + task::Task getFilterLogsImpl(std::string_view groupId, u256 filterID); + task::Task getLogsImpl( + std::string_view groupId, FilterRequest::Ptr params, bool needCheckRange); + task::Task getLogsInternal( + bcos::ledger::LedgerInterface& ledger, FilterRequest::Ptr params); + +protected: + virtual int32_t InvalidParamsCode() = 0; + uint64_t insertFilter(Filter::Ptr filter); + void cleanUpExpiredFilters(); + + Filter::Ptr getFilterByID(std::string_view groupId, u256 id) + { + FilterMap::ReadAccessor::Ptr accessor; + if (!m_filters.find(accessor, filter::KeyType(groupId, id))) + { + return nullptr; + } + return accessor->value(); + } + static int64_t getLatestBlockNumber(bcos::ledger::LedgerInterface& _ledger) + { + int64_t latest = 0; + task::wait([](bcos::ledger::LedgerInterface& ledger, int64_t& ret) -> task::Task { + ret = co_await ledger::getCurrentBlockNumber(ledger); + }(_ledger, latest)); + return latest; + } + +protected: + using FilterMap = BucketMap>; + + uint64_t m_filterTimeout = FILTER_DEFAULT_EXPIRATION_TIME; + int64_t m_maxBlockProcessPerReq = 10; + GroupManager::Ptr m_groupManager; + std::string m_group; + LogMatcher::Ptr m_matcher; + FilterRequestFactory::Ptr m_factory; + FilterMap m_filters; + // timer to clear up the expired filter in-period + std::shared_ptr m_cleanUpTimer; +}; + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/filter/LogMatcher.cpp b/bcos-rpc/bcos-rpc/filter/LogMatcher.cpp new file mode 100644 index 0000000000..2b3d7ff45e --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/LogMatcher.cpp @@ -0,0 +1,89 @@ +#include +#include +#include + +using namespace bcos; +using namespace bcos::rpc; + +uint32_t LogMatcher::matches( + FilterRequest::ConstPtr _params, bcos::protocol::Block::ConstPtr _block, Json::Value& _result) +{ + uint32_t count = 0; + for (std::size_t index = 0; index < _block->transactionsMetaDataSize(); index++) + { + count += matches(_params, _block->blockHeaderConst()->hash(), _block->receipt(index), + _block->transactionHash(index), index, _result); + } + + return count; +} + +uint32_t LogMatcher::matches(FilterRequest::ConstPtr _params, bcos::crypto::HashType&& _blockHash, + bcos::protocol::TransactionReceipt::ConstPtr&& _receipt, bcos::crypto::HashType&& _txHash, + std::size_t _txIndex, Json::Value& _result) +{ + uint32_t count = 0; + auto blockNumber = _receipt->blockNumber(); + auto mutableReceipt = const_cast(_receipt.get()); + auto logEntries = mutableReceipt->takeLogEntries(); + for (size_t i = 0; i < logEntries.size(); i++) + { + const auto& logEntry = logEntries[i]; + if (matches(_params, logEntry)) + { + count++; + Json::Value log; + log["data"] = toHexStringWithPrefix(logEntry.data()); + log["logIndex"] = toQuantity(i); + log["blockNumber"] = toQuantity(blockNumber); + log["blockHash"] = _blockHash.hexPrefixed(); + log["transactionIndex"] = toQuantity(_txIndex); + log["transactionHash"] = _txHash.hexPrefixed(); + log["removed"] = false; + log["address"] = "0x" + std::string(logEntry.address()); + Json::Value jTopics(Json::arrayValue); + for (const auto& topic : logEntry.topics()) + { + jTopics.append(topic.hexPrefixed()); + } + log["topics"] = std::move(jTopics); + _result.append(std::move(log)); + } + } + return count; +} + +bool LogMatcher::matches(FilterRequest::ConstPtr _params, const bcos::protocol::LogEntry& _logEntry) +{ + const auto& addresses = _params->addresses(); + const auto& topics = _params->topics(); + const auto& logTopics = _logEntry.topics(); + + FILTER_LOG(TRACE) << LOG_BADGE("matches") << LOG_KV("address", _logEntry.address()) + << LOG_KV("logEntry topics", _logEntry.topics().size()); + + // An empty address array matches all values otherwise log.address must be in addresses + if (!addresses.empty() && !addresses.count("0x" + std::string(_logEntry.address()))) + { + return false; + } + + if (topics.size() > logTopics.size()) + { + return false; + } + + for (size_t i = 0; i < topics.size(); ++i) + { + const auto& sub = topics[i]; + if (sub.empty()) + { + continue; + } + if (!sub.contains(logTopics[i].hexPrefixed())) + { + return false; + } + } + return true; +} diff --git a/bcos-rpc/bcos-rpc/filter/LogMatcher.h b/bcos-rpc/bcos-rpc/filter/LogMatcher.h new file mode 100644 index 0000000000..21f9d5e2df --- /dev/null +++ b/bcos-rpc/bcos-rpc/filter/LogMatcher.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace bcos +{ +namespace rpc +{ +class LogMatcher +{ +public: + using Ptr = std::shared_ptr; + using ConstPtr = std::shared_ptr; + ~LogMatcher() {} + +public: + bool matches(FilterRequest::ConstPtr _params, const bcos::protocol::LogEntry& _logEntry); + + uint32_t matches(FilterRequest::ConstPtr _params, bcos::crypto::HashType&& _blockHash, + bcos::protocol::TransactionReceipt::ConstPtr&& _receipt, bcos::crypto::HashType&& _txHash, + std::size_t _txIndex, Json::Value& _result); + + uint32_t matches(FilterRequest::ConstPtr _params, bcos::protocol::Block::ConstPtr _block, + Json::Value& _result); +}; + +} // namespace rpc +} // namespace bcos diff --git a/bcos-rpc/bcos-rpc/jsonrpc/Common.h b/bcos-rpc/bcos-rpc/jsonrpc/Common.h index bd2e0ca8aa..a701062cad 100644 --- a/bcos-rpc/bcos-rpc/jsonrpc/Common.h +++ b/bcos-rpc/bcos-rpc/jsonrpc/Common.h @@ -25,6 +25,7 @@ #include #define RPC_IMPL_LOG(LEVEL) BCOS_LOG(LEVEL) << "[RPC][JSONRPC]" +#define WEB3_LOG(LEVEL) BCOS_LOG(LEVEL) << "[RPC][WEB3]" namespace bcos { @@ -138,6 +139,12 @@ inline void nodeInfoToJson(Json::Value& _response, bcos::group::ChainNodeInfo::P featureKeys.append(key); } _response["featureKeys"] = std::move(featureKeys); + auto supportConfig = Json::Value(Json::arrayValue); + for (auto const& config : _nodeInfo->supportConfigs()) + { + supportConfig.append(config); + } + _response["supportConfigs"] = std::move(supportConfig); _response["protocol"] = protocolResponse; } diff --git a/bcos-rpc/bcos-rpc/jsonrpc/DupTestTxJsonRpcImpl_2_0.h b/bcos-rpc/bcos-rpc/jsonrpc/DupTestTxJsonRpcImpl_2_0.h deleted file mode 100644 index 03d5935007..0000000000 --- a/bcos-rpc/bcos-rpc/jsonrpc/DupTestTxJsonRpcImpl_2_0.h +++ /dev/null @@ -1,100 +0,0 @@ -// -// Created by Jimmy Shi on 2022/6/11. -// -#pragma once -#include "DuplicateTransactionFactory.h" -#include "JsonRpcImpl_2_0.h" -#include -#include -#include - -namespace bcos::rpc -{ -class DupTestTxJsonRpcImpl_2_0 : public JsonRpcImpl_2_0 -{ -public: - using Ptr = std::shared_ptr; - DupTestTxJsonRpcImpl_2_0(GroupManager::Ptr _groupManager, - bcos::gateway::GatewayInterface::Ptr _gatewayInterface, - std::shared_ptr _wsService) - : JsonRpcImpl_2_0(_groupManager, _gatewayInterface, _wsService) - {} - - // duplicate many tx to txpool - void sendTransaction(std::string_view _groupID, std::string_view _nodeName, - std::string_view _data, bool _requireProof, RespFunc _respFunc) override - { - // send directly - JsonRpcImpl_2_0::sendTransaction( - _groupID, _nodeName, _data, _requireProof, std::move(_respFunc)); - // duplicate many tx into txpool - - auto transactionData = decodeData(_data); - auto nodeService = getNodeService(_groupID, _nodeName, "sendTransaction"); - auto txpool = nodeService->txpool(); - checkService(txpool, "txpool"); - - // Note: avoid call tx->sender() or tx->verify() here in case of verify the same transaction - // more than once - auto tx = nodeService->blockFactory()->transactionFactory()->createTransaction( - bcos::ref(transactionData), false); - - - if (tx->to().empty()) - { - // ignore deploy tx - return; - } - - RPC_IMPL_LOG(TRACE) << LOG_DESC("duplicate sendTransaction") << LOG_KV("group", _groupID) - << LOG_KV("node", _nodeName); - - auto submitCallback = - [_requireProof](Error::Ptr _error, - bcos::protocol::TransactionSubmitResult::Ptr _transactionSubmitResult) { - if (_error && _error->errorCode() != bcos::protocol::CommonError::SUCCESS) - { - RPC_IMPL_LOG(ERROR) - << LOG_BADGE("sendTransaction") << LOG_KV("requireProof", _requireProof) - << LOG_KV("hash", _transactionSubmitResult ? - _transactionSubmitResult->txHash().abridged() : - "unknown") - << LOG_KV("code", _error->errorCode()) - << LOG_KV("message", _error->errorMessage()); - - - return; - } - }; - - // generate account - bcos::crypto::KeyPairInterface::Ptr keyPair = - nodeService->blockFactory()->cryptoSuite()->signatureImpl()->generateKeyPair(); - - // send many tx to txpool - int64_t dup = 49; // 1 tx can generate 49 + 1 tx(include original tx) - DuplicateTransactionFactory::asyncMultiBuild( - nodeService->blockFactory()->transactionFactory(), transactionData, keyPair, dup, - [submitCallback = std::move(submitCallback), txpool]( - bcos::protocol::Transaction::Ptr tx) { - // std::cout << "sendtx: " << tx->nonce() << std::endl; - task::wait([](decltype(txpool) txpool, decltype(tx) tx, - decltype(submitCallback) submitCallback) -> task::Task { - try - { - auto submitResult = co_await txpool->submitTransaction(std::move(tx)); - submitCallback(nullptr, submitResult); - } - catch (bcos::Error& e) - { - submitCallback(std::make_shared(std::move(e)), nullptr); - } - }(txpool, std::move(tx), std::move(submitCallback))); - }); - } - - ~DupTestTxJsonRpcImpl_2_0() {} -}; - - -} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcFilterSystem.h b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcFilterSystem.h new file mode 100644 index 0000000000..d0790e297b --- /dev/null +++ b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcFilterSystem.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace bcos +{ +namespace rpc +{ + +class JsonRpcFilterRequest : public FilterRequest +{ +public: + JsonRpcFilterRequest() = default; + ~JsonRpcFilterRequest() = default; + JsonRpcFilterRequest(const FilterRequest& oth) : FilterRequest(oth) {} + virtual int32_t InvalidParamsCode() override { return JsonRpcError::InvalidParams; } +}; + +class JsonRpcFilterRequestFactory : public FilterRequestFactory +{ +public: + JsonRpcFilterRequestFactory() = default; + ~JsonRpcFilterRequestFactory() = default; + FilterRequest::Ptr create() override { return std::make_shared(); } + FilterRequest::Ptr create(const FilterRequest& req) override + { + return std::make_shared(req); + } +}; + +class JsonRpcFilterSystem : public FilterSystem +{ +public: + JsonRpcFilterSystem(GroupManager::Ptr groupManager, const std::string& groupId, + int filterTimeout, int maxBlockProcessPerReq) + : FilterSystem(groupManager, groupId, std::make_shared(), + filterTimeout, maxBlockProcessPerReq) + {} + + virtual int32_t InvalidParamsCode() override { return JsonRpcError::InvalidParams; } +}; + +} // namespace rpc +} // namespace bcos \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.cpp b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.cpp index 32e1bf2983..bf0532d477 100644 --- a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.cpp +++ b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -59,10 +60,11 @@ using namespace boost::archive::iterators; JsonRpcImpl_2_0::JsonRpcImpl_2_0(GroupManager::Ptr _groupManager, bcos::gateway::GatewayInterface::Ptr _gatewayInterface, - std::shared_ptr _wsService) + std::shared_ptr _wsService, FilterSystem::Ptr filterSystem) : m_groupManager(std::move(_groupManager)), m_gatewayInterface(std::move(_gatewayInterface)), - m_wsService(std::move(_wsService)) + m_wsService(std::move(_wsService)), + m_filterSystem(filterSystem) { m_wsService->registerMsgHandler(bcos::protocol::MessageType::RPC_REQUEST, boost::bind(&JsonRpcImpl_2_0::handleRpcRequest, this, boost::placeholders::_1, @@ -243,6 +245,26 @@ void bcos::rpc::toJsonResp(Json::Value& jResp, bcos::protocol::Transaction const jResp["extension"].append(ext); } } + if (transaction.type() == bcos::protocol::TransactionType::Web3Transacion) [[unlikely]] + { + Web3Transaction web3Tx; + auto extraBytesRef = + bcos::bytesRef(const_cast(transaction.extraTransactionBytes().data()), + transaction.extraTransactionBytes().size()); + codec::rlp::decodeFromPayload(extraBytesRef, web3Tx); + jResp["value"] = web3Tx.value.str(); + jResp["gasLimit"] = web3Tx.gasLimit; + if (web3Tx.type >= TransactionType::EIP1559) + { + jResp["maxPriorityFeePerGas"] = web3Tx.maxPriorityFeePerGas.str(); + jResp["maxFeePerGas"] = web3Tx.maxFeePerGas.str(); + jResp["gasPrice"] = "0"; + } + else + { + jResp["gasPrice"] = web3Tx.maxPriorityFeePerGas.str(); + } + } } void bcos::rpc::toJsonResp(Json::Value& jResp, std::string_view _txHash, @@ -1431,6 +1453,79 @@ void JsonRpcImpl_2_0::getGroupPeers(std::string_view _groupID, RespFunc _respFun }); } +void JsonRpcImpl_2_0::newBlockFilter(std::string_view _groupID, RespFunc _respFunc) +{ + task::wait( + [](JsonRpcImpl_2_0* self, std::string_view groupID, RespFunc respFunc) -> task::Task { + Json::Value jRes = co_await self->filterSystem().newBlockFilter(groupID); + respFunc(nullptr, jRes); + }(this, _groupID, std::move(_respFunc))); +} + +void JsonRpcImpl_2_0::newPendingTransactionFilter(std::string_view _groupID, RespFunc _respFunc) +{ + task::wait( + [](JsonRpcImpl_2_0* self, std::string_view groupID, RespFunc respFunc) -> task::Task { + Json::Value jRes = co_await self->filterSystem().newPendingTxFilter(groupID); + respFunc(nullptr, jRes); + }(this, _groupID, std::move(_respFunc))); +} + +void JsonRpcImpl_2_0::newFilter( + std::string_view _groupID, const Json::Value& jParams, RespFunc _respFunc) +{ + task::wait([&jParams](JsonRpcImpl_2_0* self, std::string_view groupID, + RespFunc respFunc) -> task::Task { + Json::Value jRes; + auto params = self->filterSystem().requestFactory()->create(); + params->fromJson(jParams); + jRes = co_await self->filterSystem().newFilter(groupID, std::move(params)); + respFunc(nullptr, jRes); + }(this, _groupID, std::move(_respFunc))); +} + +void JsonRpcImpl_2_0::uninstallFilter( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) +{ + task::wait([](JsonRpcImpl_2_0* self, std::string_view groupID, u256 id, + RespFunc respFunc) -> task::Task { + Json::Value jRes = co_await self->filterSystem().uninstallFilter(groupID, id); + respFunc(nullptr, jRes); + }(this, _groupID, fromBigQuantity(filterID), std::move(_respFunc))); +} + +void JsonRpcImpl_2_0::getFilterChanges( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) +{ + task::wait([](JsonRpcImpl_2_0* self, std::string_view groupID, u256 id, + RespFunc respFunc) -> task::Task { + Json::Value jRes = co_await self->filterSystem().getFilterChanges(groupID, id); + respFunc(nullptr, jRes); + }(this, _groupID, fromBigQuantity(filterID), std::move(_respFunc))); +} + +void JsonRpcImpl_2_0::getFilterLogs( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) +{ + task::wait([](JsonRpcImpl_2_0* self, std::string_view groupID, u256 id, + RespFunc respFunc) -> task::Task { + Json::Value jRes = co_await self->filterSystem().getFilterLogs(groupID, id); + respFunc(nullptr, jRes); + }(this, _groupID, fromBigQuantity(filterID), std::move(_respFunc))); +} + +void JsonRpcImpl_2_0::getLogs( + std::string_view _groupID, const Json::Value& jParams, RespFunc _respFunc) +{ + task::wait([](JsonRpcImpl_2_0* self, std::string_view groupID, const Json::Value& jParams, + RespFunc respFunc) -> task::Task { + auto params = self->filterSystem().requestFactory()->create(); + params->fromJson(jParams); + Json::Value jRes = co_await self->filterSystem().getLogs(groupID, std::move(params)); + respFunc(nullptr, jRes); + }(this, _groupID, jParams, std::move(_respFunc))); +} + void JsonRpcImpl_2_0::execCall( NodeService::Ptr nodeService, protocol::Transaction::Ptr _tx, bcos::rpc::RespFunc _respFunc) { diff --git a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.h b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.h index 355f0bdfd0..c708e29e17 100644 --- a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.h +++ b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcImpl_2_0.h @@ -25,6 +25,7 @@ #include "bcos-rpc/validator/CallValidator.h" #include #include +#include #include #include #include @@ -40,7 +41,7 @@ class JsonRpcImpl_2_0 : public JsonRpcInterface, using Ptr = std::shared_ptr; JsonRpcImpl_2_0(GroupManager::Ptr _groupManager, bcos::gateway::GatewayInterface::Ptr _gatewayInterface, - std::shared_ptr _wsService); + std::shared_ptr _wsService, FilterSystem::Ptr filterSystem); ~JsonRpcImpl_2_0() override = default; void setClientID(std::string_view _clientID) { m_clientID = _clientID; } @@ -118,6 +119,19 @@ class JsonRpcImpl_2_0 : public JsonRpcInterface, void getGroupNodeInfo( std::string_view _groupID, std::string_view _nodeName, RespFunc _respFunc) override; + // filter interface + void newBlockFilter(std::string_view _groupID, RespFunc _respFunc) override; + void newPendingTransactionFilter(std::string_view _groupID, RespFunc _respFunc) override; + void newFilter( + std::string_view _groupID, const Json::Value& params, RespFunc _respFunc) override; + void uninstallFilter( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) override; + void getFilterChanges( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) override; + void getFilterLogs( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) override; + void getLogs(std::string_view _groupID, const Json::Value& params, RespFunc _respFunc) override; + void getGroupBlockNumber(RespFunc _respFunc) override; void setNodeInfo(const NodeInfo& _nodeInfo) { m_nodeInfo = _nodeInfo; } @@ -153,7 +167,9 @@ class JsonRpcImpl_2_0 : public JsonRpcInterface, } } -private: + FilterSystem& filterSystem() { return *m_filterSystem; } + +protected: void gatewayInfoToJson(Json::Value& _response, bcos::gateway::GatewayInfo::Ptr _gatewayInfo); void gatewayInfoToJson(Json::Value& _response, bcos::gateway::GatewayInfo::Ptr _localP2pInfo, bcos::gateway::GatewayInfosPtr _peersInfo); @@ -170,6 +186,7 @@ class JsonRpcImpl_2_0 : public JsonRpcInterface, GroupManager::Ptr m_groupManager; bcos::gateway::GatewayInterface::Ptr m_gatewayInterface; std::shared_ptr m_wsService; + FilterSystem::Ptr m_filterSystem; NodeInfo m_nodeInfo; // Note: here clientID must non-empty for the rpc will set clientID as source for the tx for diff --git a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.cpp b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.cpp index 9d79d818e2..566e4d3a63 100644 --- a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.cpp +++ b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.cpp @@ -66,6 +66,23 @@ void JsonRpcInterface::initMethod() m_methodToFunc["getGroupNodeInfo"] = std::bind( &JsonRpcInterface::getGroupNodeInfoI, this, std::placeholders::_1, std::placeholders::_2); + // filter interface + m_methodToFunc["newBlockFilter"] = std::bind( + &JsonRpcInterface::newBlockFilterI, this, std::placeholders::_1, std::placeholders::_2); + m_methodToFunc["newPendingTransactionFilter"] = + std::bind(&JsonRpcInterface::newPendingTransactionFilterI, this, std::placeholders::_1, + std::placeholders::_2); + m_methodToFunc["newFilter"] = std::bind( + &JsonRpcInterface::newFilterI, this, std::placeholders::_1, std::placeholders::_2); + m_methodToFunc["uninstallFilter"] = std::bind( + &JsonRpcInterface::uninstallFilterI, this, std::placeholders::_1, std::placeholders::_2); + m_methodToFunc["getFilterChanges"] = std::bind( + &JsonRpcInterface::getFilterChangesI, this, std::placeholders::_1, std::placeholders::_2); + m_methodToFunc["getFilterLogs"] = std::bind( + &JsonRpcInterface::getFilterLogsI, this, std::placeholders::_1, std::placeholders::_2); + m_methodToFunc["getLogs"] = + std::bind(&JsonRpcInterface::getLogsI, this, std::placeholders::_1, std::placeholders::_2); + for (const auto& method : m_methodToFunc) { RPC_IMPL_LOG(INFO) << LOG_BADGE("initMethod") << LOG_KV("method", method.first); @@ -91,7 +108,10 @@ void JsonRpcInterface::onRPCRequest(std::string_view _requestBody, Sender _sende BOOST_THROW_EXCEPTION(JsonRpcException( JsonRpcError::MethodNotFound, "The method does not exist/is not available.")); } - RPC_IMPL_LOG(TRACE) << LOG_BADGE("onRPCRequest") << LOG_KV("request", _requestBody); + if (c_fileLogLevel == TRACE) [[unlikely]] + { + RPC_IMPL_LOG(TRACE) << LOG_BADGE("onRPCRequest") << LOG_KV("request", _requestBody); + } it->second( request.params, [response, _sender](Error::Ptr _error, Json::Value& _result) mutable { if (_error && (_error->errorCode() != bcos::protocol::CommonError::SUCCESS)) @@ -105,10 +125,13 @@ void JsonRpcInterface::onRPCRequest(std::string_view _requestBody, Sender _sende response.result.swap(_result); } auto strResp = toStringResponse(std::move(response)); - RPC_IMPL_LOG(TRACE) - << LOG_BADGE("onRPCRequest") - << LOG_KV("response", - std::string_view((const char*)strResp.data(), strResp.size())); + if (c_fileLogLevel == TRACE) [[unlikely]] + { + RPC_IMPL_LOG(TRACE) + << LOG_BADGE("onRPCRequest") + << LOG_KV("response", + std::string_view((const char*)strResp.data(), strResp.size())); + } _sender(std::move(strResp)); }); @@ -133,7 +156,7 @@ void JsonRpcInterface::onRPCRequest(std::string_view _requestBody, Sender _sende << LOG_KV("request", _requestBody) << LOG_KV("response", std::string_view((const char*)strResp.data(), strResp.size())); - _sender(strResp); + _sender(std::move(strResp)); } void bcos::rpc::parseRpcRequestJson(std::string_view _requestBody, JsonRequest& _jsonRequest) @@ -219,7 +242,10 @@ void bcos::rpc::parseRpcRequestJson(std::string_view _requestBody, JsonRequest& bcos::bytes bcos::rpc::toStringResponse(JsonResponse _jsonResponse) { auto jResp = toJsonResponse(std::move(_jsonResponse)); - std::unique_ptr writer(Json::StreamWriterBuilder().newStreamWriter()); + auto builder = Json::StreamWriterBuilder(); + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + std::unique_ptr writer(builder.newStreamWriter()); class JsonSink { public: diff --git a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.h b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.h index ca61d95140..30e7eaafe3 100644 --- a/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.h +++ b/bcos-rpc/bcos-rpc/jsonrpc/JsonRpcInterface.h @@ -31,8 +31,10 @@ namespace bcos::rpc { + using Sender = std::function; using RespFunc = std::function; +using MethodMap = std::unordered_map>; class JsonRpcInterface { @@ -119,13 +121,26 @@ class JsonRpcInterface virtual void getGroupBlockNumber(RespFunc _respFunc) = 0; -public: + // filter interface + virtual void newBlockFilter(std::string_view _groupID, RespFunc _respFunc) = 0; + virtual void newPendingTransactionFilter(std::string_view _groupID, RespFunc _respFunc) = 0; + virtual void newFilter( + std::string_view _groupID, const Json::Value& params, RespFunc _respFunc) = 0; + virtual void uninstallFilter( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) = 0; + virtual void getFilterChanges( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) = 0; + virtual void getFilterLogs( + std::string_view _groupID, std::string_view filterID, RespFunc _respFunc) = 0; + virtual void getLogs( + std::string_view _groupID, const Json::Value& params, RespFunc _respFunc) = 0; + void onRPCRequest(std::string_view _requestBody, Sender _sender); -private: +protected: void initMethod(); - std::unordered_map> m_methodToFunc; + MethodMap m_methodToFunc; std::string_view toView(const Json::Value& value) @@ -287,6 +302,35 @@ class JsonRpcInterface { getGroupNodeInfo(toView(_req[0u]), toView(_req[1u]), std::move(_respFunc)); } + // filter interface + void newBlockFilterI(const Json::Value& _req, RespFunc _respFunc) + { + newBlockFilter(toView(_req[0u]), std::move(_respFunc)); + } + void newPendingTransactionFilterI(const Json::Value& _req, RespFunc _respFunc) + { + newPendingTransactionFilter(toView(_req[0u]), std::move(_respFunc)); + } + void newFilterI(const Json::Value& _req, RespFunc _respFunc) + { + newFilter(toView(_req[0u]), _req[1u], std::move(_respFunc)); + } + void uninstallFilterI(const Json::Value& _req, RespFunc _respFunc) + { + uninstallFilter(toView(_req[0u]), toView(_req[1u]), std::move(_respFunc)); + } + void getFilterChangesI(const Json::Value& _req, RespFunc _respFunc) + { + getFilterChanges(toView(_req[0u]), toView(_req[1u]), std::move(_respFunc)); + } + void getFilterLogsI(const Json::Value& _req, RespFunc _respFunc) + { + getFilterLogs(toView(_req[0u]), toView(_req[1u]), std::move(_respFunc)); + } + void getLogsI(const Json::Value& _req, RespFunc _respFunc) + { + getLogs(toView(_req[0u]), _req[1u], std::move(_respFunc)); + } }; void parseRpcRequestJson(std::string_view _requestBody, JsonRequest& _jsonRequest); bcos::bytes toStringResponse(JsonResponse _jsonResponse); diff --git a/bcos-rpc/bcos-rpc/util.cpp b/bcos-rpc/bcos-rpc/util.cpp new file mode 100644 index 0000000000..3d8ded0329 --- /dev/null +++ b/bcos-rpc/bcos-rpc/util.cpp @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file util.cpp + * @author: jdkuang + * @date 2024/4/24 + */ + +#include +#include +#include +#include +#include + +using namespace bcos; +using namespace bcos::rpc; + +// return (actual block number, isLatest block) +std::tuple bcos::rpc::getBlockNumberByTag( + protocol::BlockNumber latest, std::string_view blockTag) +{ + if (blockTag == EarliestBlock) + { + return std::make_tuple(0, false); + } + else if (blockTag == LatestBlock || blockTag == SafeBlock || blockTag == FinalizedBlock || + blockTag == PendingBlock) + { + return std::make_tuple(latest, true); + } + else + { + static const std::regex hexRegex("^0x[0-9a-fA-F]+$"); + if (std::regex_match(blockTag.data(), hexRegex)) + { + auto blockNumber = fromQuantity(std::string(blockTag)); + return std::make_tuple(blockNumber, false); + } + BOOST_THROW_EXCEPTION( + JsonRpcException(InvalidParams, "Invalid Block Number: " + std::string(blockTag))); + } +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/util.h b/bcos-rpc/bcos-rpc/util.h new file mode 100644 index 0000000000..7a594f8b3e --- /dev/null +++ b/bcos-rpc/bcos-rpc/util.h @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file util.h + * @author: jdkuang + * @date 2024/4/24 + */ + +#pragma once +#include +#include + +namespace bcos::rpc +{ +std::tuple getBlockNumberByTag( + protocol::BlockNumber latest, std::string_view blockTag); +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/validator/JsonValidator.cpp b/bcos-rpc/bcos-rpc/validator/JsonValidator.cpp new file mode 100644 index 0000000000..a90685dadd --- /dev/null +++ b/bcos-rpc/bcos-rpc/validator/JsonValidator.cpp @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file JsonValidator.cpp + * @author: kyonGuo + * @date 2024/3/28 + */ + +#include "JsonValidator.h" + +namespace bcos::rpc +{ +std::tuple JsonValidator::validate(const Json::Value& root) +{ + if (auto const result{checkRequestFields(root)}; !std::get<0>(result)) + { + return result; + } + // check params specific https://github.com/ethereum/execution-apis + return {true, ""}; +} + +std::tuple JsonValidator::checkRequestFields(const Json::Value& root) +{ + // flag for 4 fileds, maybe twice? + auto flag = 0x1111; + for (auto item = root.begin(); item != root.end(); item++) + { + if (item.name() == "jsonrpc") + { + if (!item->isString()) + { + return {false, "Invalid field: " + item.name()}; + } + flag &= 0x1110; + } + else if (item.name() == "method") + { + if (!item->isString()) + { + return {false, "Invalid field: " + item.name()}; + } + flag &= 0x1101; + } + else if (item.name() == "params") + { + if (!item->isArray()) + { + return {false, "Invalid field: " + item.name()}; + } + flag &= 0x1011; + } + else if (item.name() == "id") + { + if (!item->isUInt64()) + { + if (item->isString()) + { + if (std::string idString = item->asString(); + !std::regex_match(idString, std::regex("^[0-9a-fA-F-]+$"))) + { + return {false, "Invalid field: " + item.name()}; + } + } + else + { + return {false, "Invalid field: " + item.name()}; + } + } + flag &= 0x0111; + } + else + { + return {false, "Invalid field: " + item.name()}; + } + } + if (flag != 0) + { + return {false, "Request not valid, required fields: jsonrpc, method, params, id"}; + } + return {true, ""}; +} + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/validator/JsonValidator.h b/bcos-rpc/bcos-rpc/validator/JsonValidator.h new file mode 100644 index 0000000000..64300772a8 --- /dev/null +++ b/bcos-rpc/bcos-rpc/validator/JsonValidator.h @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file JsonValidator.h + * @author: kyonGuo + * @date 2024/3/28 + */ + +#pragma once + +#include +#include +#include +#include + + +namespace bcos::rpc +{ +class JsonValidator +{ +public: + static std::tuple validate(const Json::Value& _json); + static std::tuple checkRequestFields(const Json::Value& _json); +}; + +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/Web3FilterSystem.h b/bcos-rpc/bcos-rpc/web3jsonrpc/Web3FilterSystem.h new file mode 100644 index 0000000000..b39c182c7f --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/Web3FilterSystem.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace bcos +{ +namespace rpc +{ + +class Web3FilterSystem : public FilterSystem +{ +public: + Web3FilterSystem(GroupManager::Ptr groupManager, const std::string& groupId, int filterTimeout, + int maxBlockProcessPerReq) + : FilterSystem(groupManager, groupId, std::make_shared(), + filterTimeout, maxBlockProcessPerReq) + {} + + virtual int32_t InvalidParamsCode() override { return Web3JsonRpcError::Web3DefautError; } +}; + +} // namespace rpc +} // namespace bcos \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/Web3JsonRpcImpl.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/Web3JsonRpcImpl.cpp new file mode 100644 index 0000000000..f0e76452b4 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/Web3JsonRpcImpl.cpp @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3JsonRpcImpl.cpp + * @author: kyonGuo + * @date 2024/3/21 + */ + +#include "Web3JsonRpcImpl.h" +#include + +using namespace bcos; +using namespace bcos::rpc; + +void Web3JsonRpcImpl::onRPCRequest(std::string_view _requestBody, Sender _sender) +{ + Json::Value request; + Json::Value response; + try + { + if (auto const& [valid, msg] = parseRequestAndValidate(_requestBody, request); !valid) + { + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidRequest, msg)); + } + response["id"] = request["id"]; + if (auto const handler = m_endpointsMapping.findHandler(request["method"].asString()); + handler.has_value()) + { + if (c_fileLogLevel == TRACE) [[unlikely]] + { + WEB3_LOG(TRACE) << LOG_BADGE("Web3Request") + << LOG_KV("request", printJson(request)); + } + task::wait([](Web3JsonRpcImpl* self, EndpointsMapping::Handler _handler, + Json::Value _request, Sender sender) -> task::Task { + Json::Value resp; + try + { + // FIXME)): throw exception here will core dump + Json::Value const& params = _request["params"]; + + co_await (self->m_endpoints.*_handler)(params, resp); + resp["id"] = _request["id"]; + } + catch (const JsonRpcException& e) + { + buildJsonError(_request, e.code(), e.msg(), resp); + } + catch (bcos::Error const& e) + { + buildJsonError(_request, InternalError, e.errorMessage(), resp); + } + catch (const std::exception& e) + { + buildJsonError(_request, InternalError, boost::diagnostic_information(e), resp); + } + auto&& respBytes = toBytesResponse(resp); + if (c_fileLogLevel == TRACE) [[unlikely]] + { + std::string method = _request["method"].asString(); + WEB3_LOG(TRACE) + << LOG_BADGE("Web3Response") << LOG_KV("method", method) + << LOG_KV("response", + std::string_view((const char*)(respBytes.data()), respBytes.size())); + } + sender(std::move(respBytes)); + }(this, handler.value(), std::move(request), _sender)); + return; + } + BOOST_THROW_EXCEPTION(JsonRpcException(MethodNotFound, "Method not found")); + } + catch (const JsonRpcException& e) + { + buildJsonError(request, e.code(), e.msg(), response); + } + catch (...) + { + buildJsonError(request, InternalError, "Internal error", response); + } + auto&& resp = toBytesResponse(response); + WEB3_LOG(DEBUG) << LOG_BADGE("onRPCRequest") << LOG_DESC("response with exception") + << LOG_KV("request", _requestBody) + << LOG_KV("response", std::string_view((const char*)resp.data(), resp.size())); + _sender(std::move(resp)); +} + +std::tuple Web3JsonRpcImpl::parseRequestAndValidate( + std::string_view request, Json::Value& root) +{ + if (Json::Reader jsonReader; !jsonReader.parse(request.begin(), request.end(), root)) + { + return {false, "Parse json failed"}; + } + if (auto result{JsonValidator::validate(root)}; !std::get(result)) + { + return result; + } + return {true, ""}; +} + +bcos::bytes Web3JsonRpcImpl::toBytesResponse(Json::Value const& jResp) +{ + auto builder = Json::StreamWriterBuilder(); + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + std::unique_ptr writer(builder.newStreamWriter()); + + bcos::bytes out; + boost::iostreams::stream outputStream(out); + + writer->write(jResp, &outputStream); + writer.reset(); + return out; +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/Web3JsonRpcImpl.h b/bcos-rpc/bcos-rpc/web3jsonrpc/Web3JsonRpcImpl.h new file mode 100644 index 0000000000..38b17a89ae --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/Web3JsonRpcImpl.h @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3JsonRpcImpl.h + * @author: kyonGuo + * @date 2024/3/21 + */ + +#pragma once +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ +class Web3JsonRpcImpl : public std::enable_shared_from_this +{ +public: + using Ptr = std::shared_ptr; + using Sender = std::function; + Web3JsonRpcImpl(std::string _groupId, bcos::rpc::GroupManager::Ptr _groupManager, + bcos::gateway::GatewayInterface::Ptr _gatewayInterface, + std::shared_ptr _wsService, FilterSystem::Ptr filterSystem) + : m_groupManager(std::move(_groupManager)), + m_gatewayInterface(std::move(_gatewayInterface)), + m_wsService(std::move(_wsService)), + m_groupId(std::move(_groupId)), + m_endpoints(m_groupManager->getNodeService(m_groupId, ""), filterSystem) + {} + ~Web3JsonRpcImpl() = default; + + void onRPCRequest(std::string_view _requestBody, Sender _sender); + +private: + static std::tuple parseRequestAndValidate( + std::string_view request, Json::Value& root); + static bcos::bytes toBytesResponse(Json::Value const& jResp); + // Note: only use in one group + GroupManager::Ptr m_groupManager; + bcos::gateway::GatewayInterface::Ptr m_gatewayInterface; + std::shared_ptr m_wsService; + std::string m_groupId; + Endpoints m_endpoints; + EndpointsMapping m_endpointsMapping; +}; +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Endpoints.h b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Endpoints.h new file mode 100644 index 0000000000..a2fa630d4c --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Endpoints.h @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Endpoints.h + * @author: kyonGuo + * @date 2024/3/28 + */ + +#pragma once +#include "EthEndpoint.h" +#include "NetEndpoint.h" +#include "Web3Endpoint.h" + +#include + +namespace bcos::rpc +{ +class Endpoints : protected EthEndpoint, NetEndpoint, Web3Endpoint +{ +public: + explicit Endpoints(NodeService::Ptr _nodeService, FilterSystem::Ptr filterSystem) + : EthEndpoint(_nodeService, filterSystem), + NetEndpoint(_nodeService), + Web3Endpoint(_nodeService) + {} + + ~Endpoints() override = default; + Endpoints(const Endpoints&) = delete; + Endpoints& operator=(const Endpoints&) = delete; + friend class EndpointsMapping; +}; +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EndpointsMapping.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EndpointsMapping.cpp new file mode 100644 index 0000000000..6094f43baf --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EndpointsMapping.cpp @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file EndpointsMapping.cpp + * @author: kyonGuo + * @date 2024/3/28 + */ + +#include "EndpointsMapping.h" + +#include "EthMethods.h" + +#include + +namespace bcos::rpc +{ +std::optional EndpointsMapping::findHandler( + const std::string& _method) const +{ + auto it = m_handlers.find(_method); + if (it == m_handlers.end()) + { + return std::nullopt; + } + return it->second; +} + +void EndpointsMapping::addHandlers() +{ + addEthHandlers(); + addNetHandlers(); + addWeb3Handlers(); + for (auto& [method, _] : m_handlers) + { + WEB3_LOG(INFO) << LOG_BADGE("initHandler") << LOG_KV("method", method); + } + WEB3_LOG(INFO) << LOG_BADGE("initHandler") << LOG_KV("size", m_handlers.size()); +} + +void EndpointsMapping::addEthHandlers() +{ + // clang-format off + m_handlers[methodString(EthMethod::eth_protocolVersion)] = &Endpoints::protocolVersion; + m_handlers[methodString(EthMethod::eth_syncing)] = &Endpoints::syncing; + m_handlers[methodString(EthMethod::eth_coinbase)] = &Endpoints::coinbase; + m_handlers[methodString(EthMethod::eth_chainId)] = &Endpoints::chainId; + m_handlers[methodString(EthMethod::eth_mining)] = &Endpoints::mining; + m_handlers[methodString(EthMethod::eth_hashrate)] = &Endpoints::hashrate; + m_handlers[methodString(EthMethod::eth_gasPrice)] = &Endpoints::gasPrice; + m_handlers[methodString(EthMethod::eth_accounts)] = &Endpoints::accounts; + m_handlers[methodString(EthMethod::eth_blockNumber)] = &Endpoints::blockNumber; + m_handlers[methodString(EthMethod::eth_getBalance)] = &Endpoints::getBalance; + m_handlers[methodString(EthMethod::eth_getStorageAt)] = &Endpoints::getStorageAt; + m_handlers[methodString(EthMethod::eth_getTransactionCount)] = &Endpoints::getTransactionCount; + m_handlers[methodString(EthMethod::eth_getBlockTransactionCountByHash)] = &Endpoints::getBlockTxCountByHash; + m_handlers[methodString(EthMethod::eth_getBlockTransactionCountByNumber)] = &Endpoints::getBlockTxCountByNumber; + m_handlers[methodString(EthMethod::eth_getUncleCountByBlockHash)] = &Endpoints::getUncleCountByBlockHash; + m_handlers[methodString(EthMethod::eth_getUncleCountByBlockNumber)] = &Endpoints::getUncleCountByBlockNumber; + m_handlers[methodString(EthMethod::eth_getCode)] = &Endpoints::getCode; + m_handlers[methodString(EthMethod::eth_sign)] = &Endpoints::sign; + m_handlers[methodString(EthMethod::eth_sendTransaction)] = &Endpoints::sendTransaction; + m_handlers[methodString(EthMethod::eth_signTransaction)] = &Endpoints::signTransaction; + m_handlers[methodString(EthMethod::eth_sendRawTransaction)] = &Endpoints::sendRawTransaction; + m_handlers[methodString(EthMethod::eth_call)] = &Endpoints::call; + m_handlers[methodString(EthMethod::eth_estimateGas)] = &Endpoints::estimateGas; + m_handlers[methodString(EthMethod::eth_getBlockByHash)] = &Endpoints::getBlockByHash; + m_handlers[methodString(EthMethod::eth_getBlockByNumber)] = &Endpoints::getBlockByNumber; + m_handlers[methodString(EthMethod::eth_getTransactionByHash)] = &Endpoints::getTransactionByHash; + m_handlers[methodString(EthMethod::eth_getTransactionByBlockHashAndIndex)] = &Endpoints::getTransactionByBlockHashAndIndex; + m_handlers[methodString(EthMethod::eth_getTransactionByBlockNumberAndIndex)] = &Endpoints::getTransactionByBlockNumberAndIndex; + m_handlers[methodString(EthMethod::eth_getTransactionReceipt)] = &Endpoints::getTransactionReceipt; + m_handlers[methodString(EthMethod::eth_getUncleByBlockHashAndIndex)] = &Endpoints::getUncleByBlockHashAndIndex; + m_handlers[methodString(EthMethod::eth_getUncleByBlockNumberAndIndex)] = &Endpoints::getUncleByBlockNumberAndIndex; + m_handlers[methodString(EthMethod::eth_newFilter)] = &Endpoints::newFilter; + m_handlers[methodString(EthMethod::eth_newBlockFilter)] = &Endpoints::newBlockFilter; + m_handlers[methodString(EthMethod::eth_newPendingTransactionFilter)] = &Endpoints::newPendingTransactionFilter; + m_handlers[methodString(EthMethod::eth_uninstallFilter)] = &Endpoints::uninstallFilter; + m_handlers[methodString(EthMethod::eth_getFilterChanges)] = &Endpoints::getFilterChanges; + m_handlers[methodString(EthMethod::eth_getFilterLogs)] = &Endpoints::getFilterLogs; + m_handlers[methodString(EthMethod::eth_getLogs)] = &Endpoints::getLogs; + // clang-format on +} + +void EndpointsMapping::addNetHandlers() +{ + // clang-format off + m_handlers[methodString(EthMethod::net_version)] = &Endpoints::verison; + m_handlers[methodString(EthMethod::net_peerCount)] = &Endpoints::peerCount; + m_handlers[methodString(EthMethod::net_listening)] = &Endpoints::listening; + // clang-format on +} + +void EndpointsMapping::addWeb3Handlers() +{ + // clang-format off + m_handlers[methodString(EthMethod::web3_clientVersion)] = &Endpoints::clientVersion; + m_handlers[methodString(EthMethod::web3_sha3)] = &Endpoints::sha3; + // clang-format on +} +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EndpointsMapping.h b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EndpointsMapping.h new file mode 100644 index 0000000000..bb5673a441 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EndpointsMapping.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file EndpointsMapping.h + * @author: kyonGuo + * @date 2024/3/28 + */ + +#pragma once + +#include "Endpoints.h" + +#include +#include + +namespace bcos::rpc +{ +class EndpointsMapping +{ +public: + using Handler = task::Task (Endpoints::*)(const Json::Value&, Json::Value&); + EndpointsMapping() { addHandlers(); }; + ~EndpointsMapping() = default; + EndpointsMapping(const EndpointsMapping&) = delete; + EndpointsMapping& operator=(const EndpointsMapping&) = delete; + + [[nodiscard]] std::optional findHandler(const std::string& _method) const; + +private: + void addHandlers(); + void addEthHandlers(); + void addNetHandlers(); + void addWeb3Handlers(); + + std::unordered_map m_handlers; +}; +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthEndpoint.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthEndpoint.cpp new file mode 100644 index 0000000000..decf6d9a53 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthEndpoint.cpp @@ -0,0 +1,814 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file EthEndpoint.cpp + * @author: kyonGuo + * @date 2024/3/21 + */ + +#include "EthEndpoint.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace bcos; +using namespace bcos::rpc; + +task::Task EthEndpoint::protocolVersion(const Json::Value&, Json::Value&) +{ + // TODO: impl this + BOOST_THROW_EXCEPTION( + JsonRpcException(MethodNotFound, "This API has not been implemented yet!")); + co_return; +} +task::Task EthEndpoint::syncing(const Json::Value&, Json::Value& response) +{ + auto const sync = m_nodeService->sync(); + auto status = sync->getSyncStatus(); + Json::Value result; + if (!status.has_value()) + { + result = false; + } + else + { + result = Json::objectValue; + auto [currentBlock, highestBlock] = status.value(); + result["startingBlock"] = "0x0"; + result["currentBlock"] = toQuantity(currentBlock); + result["highestBlock"] = toQuantity(highestBlock); + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::coinbase(const Json::Value&, Json::Value&) +{ + BOOST_THROW_EXCEPTION( + JsonRpcException(MethodNotFound, "This API has not been implemented yet!")); + co_return; +} +task::Task EthEndpoint::chainId(const Json::Value&, Json::Value& response) +{ + auto const ledger = m_nodeService->ledger(); + auto config = co_await ledger::getSystemConfig(*ledger, ledger::SYSTEM_KEY_WEB3_CHAIN_ID); + Json::Value result; + if (config.has_value()) + { + try + { + auto [chainId, _] = config.value(); + result = toQuantity(std::stoull(chainId)); + } + catch (...) + { + result = "0x0"; // 0x0 for default + } + } + else + { + result = "0x0"; // 0 for default + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::mining(const Json::Value&, Json::Value& response) +{ + Json::Value result = false; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::hashrate(const Json::Value&, Json::Value& response) +{ + Json::Value result = "0x0"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::gasPrice(const Json::Value&, Json::Value& response) +{ + // result: gasPrice(QTY) + auto const ledger = m_nodeService->ledger(); + auto config = co_await ledger::getSystemConfig(*ledger, ledger::SYSTEM_KEY_TX_GAS_PRICE); + Json::Value result; + if (config.has_value()) + { + auto [gasPrice, _] = config.value(); + auto const value = std::stoull(gasPrice, nullptr, 16); + result = toQuantity(value < LowestGasPrice ? LowestGasPrice : value); + } + else + { + result = "0x5208"; // 21000 + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::accounts(const Json::Value&, Json::Value& response) +{ + Json::Value result = Json::arrayValue; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::blockNumber(const Json::Value&, Json::Value& response) +{ + auto ledger = m_nodeService->ledger(); + auto number = co_await ledger::getCurrentBlockNumber(*ledger); + Json::Value result = toQuantity(number); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getBalance(const Json::Value& request, Json::Value& response) +{ + // params: address(DATA), blockNumber(QTY|TAG) + // result: balance(QTY) + auto address = toView(request[0u]); + if (address.starts_with("0x") || address.starts_with("0X")) + { + address.remove_prefix(2); + } + std::string addressStr(address); + boost::algorithm::to_lower(addressStr); + // TODO)): blockNumber is ignored nowadays + auto const blockTag = toView(request[1u]); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << "eth_getBalance" << LOG_KV("address", address) + << LOG_KV("blockTag", blockTag) << LOG_KV("blockNumber", blockNumber); + } + auto const ledger = m_nodeService->ledger(); + u256 balance = 0; + if (auto const entry = co_await ledger::getStorageAt( + *ledger, addressStr, bcos::executor::ACCOUNT_BALANCE, /*blockNumber*/ 0); + entry.has_value()) + { + auto const balanceStr = std::string(entry.value().get()); + balance = u256(balanceStr); + } + else + { + WEB3_LOG(TRACE) << LOG_DESC("getBalance failed, return 0 by defualt") + << LOG_KV("address", address); + } + Json::Value result = toQuantity(std::move(balance)); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getStorageAt(const Json::Value& request, Json::Value& response) +{ + // params: address(DATA), position(QTY), blockNumber(QTY|TAG) + // result: value(DATA) + auto address = toView(request[0u]); + if (address.starts_with("0x") || address.starts_with("0X")) + { + address.remove_prefix(2); + } + std::string addressStr(address); + boost::algorithm::to_lower(addressStr); + auto position = toView(request[1u]); + std::string positionStr = + std::string(position.starts_with("0x") ? position.substr(2) : position); + if (position.size() % 2 != 0) + { + positionStr.insert(0, "0"); + } + const auto posistionBytes = FixedBytes<32>(positionStr, FixedBytes<32>::FromHex); + // TODO)): blockNumber is ignored nowadays + auto const blockTag = toView(request[2u]); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << "eth_getStorageAt" << LOG_KV("address", address) + << LOG_KV("pos", positionStr) << LOG_KV("blockTag", blockTag) + << LOG_KV("blockNumber", blockNumber); + } + auto const ledger = m_nodeService->ledger(); + Json::Value result; + if (auto const entry = co_await ledger::getStorageAt( + *ledger, addressStr, posistionBytes.toRawString(), /*blockNumber*/ 0); + entry.has_value()) + { + auto const value = entry.value().get(); + result = toHex(value, "0x"); + } + else + { + // empty value + result = "0x0000000000000000000000000000000000000000000000000000000000000000"; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getTransactionCount(const Json::Value& request, Json::Value& response) +{ + // params: address(DATA), blockNumber(QTY|TAG) + // result: nonce(QTY) + auto address = toView(request[0u]); + if (address.starts_with("0x") || address.starts_with("0X")) + { + address.remove_prefix(2); + } + std::string addressStr(address); + boost::algorithm::to_lower(addressStr); + // TODO)): blockNumber is ignored nowadays + auto const blockTag = toView(request[1u]); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << "eth_getTransactionCount" << LOG_KV("address", address) + << LOG_KV("blockTag", blockTag) << LOG_KV("blockNumber", blockNumber); + } + auto const ledger = m_nodeService->ledger(); + uint64_t nonce = 0; + if (auto const entry = co_await ledger::getStorageAt( + *ledger, addressStr, bcos::ledger::ACCOUNT_TABLE_FIELDS::NONCE, /*blockNumber*/ 0); + entry.has_value()) + { + nonce = std::stoull(std::string(entry.value().get())); + } + else + { + WEB3_LOG(TRACE) << LOG_DESC("get address nonce failed, return random value by defualt") + << LOG_KV("address", address); + static thread_local std::mt19937 generator(std::random_device{}()); + std::uniform_int_distribution dis(0, 255); + std::array randomFixedBytes; + for (auto& element : randomFixedBytes) + { + element = dis(generator); + } + const auto* firstNum = (uint32_t*)randomFixedBytes.data(); + nonce = *firstNum; + } + Json::Value result = toQuantity(nonce); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getBlockTxCountByHash( + const Json::Value& request, Json::Value& response) +{ + // params: blockHash(DATA) + // result: transactionCount(QTY) + auto const hashStr = toView(request[0U]); + auto hash = crypto::HashType(hashStr, crypto::HashType::FromHex); + auto const ledger = m_nodeService->ledger(); + Json::Value result; + try + { + auto number = co_await ledger::getBlockNumber(*ledger, std::move(hash)); + auto block = + co_await ledger::getBlockData(*ledger, number, bcos::ledger::TRANSACTIONS_HASH); + result = toQuantity(block->transactionsHashSize()); + } + catch (...) + { + result = "0x0"; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getBlockTxCountByNumber( + const Json::Value& request, Json::Value& response) +{ + // params: blockNumber(QTY|TAG) + // result: transactionCount(QTY) + auto const number = fromQuantity(std::string(toView(request[0U]))); + auto const ledger = m_nodeService->ledger(); + Json::Value result; + try + { + auto const block = + co_await ledger::getBlockData(*ledger, number, bcos::ledger::TRANSACTIONS_HASH); + result = toQuantity(block->transactionsHashSize()); + } + catch (...) + { + result = "0x0"; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getUncleCountByBlockHash(const Json::Value&, Json::Value& response) +{ + Json::Value result = "0x0"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getUncleCountByBlockNumber(const Json::Value&, Json::Value& response) +{ + Json::Value result = "0x0"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getCode(const Json::Value& request, Json::Value& response) +{ + // params: address(DATA), blockNumber(QTY|TAG) + // result: code(DATA) + auto address = toView(request[0u]); + if (address.starts_with("0x") || address.starts_with("0X")) + { + address.remove_prefix(2); + } + std::string addressStr(address); + boost::algorithm::to_lower(addressStr); + // TODO)): blockNumber is ignored nowadays + auto const blockTag = toView(request[1u]); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << "eth_getCode" << LOG_KV("address", address) + << LOG_KV("blockTag", blockTag) << LOG_KV("blockNumber", blockNumber); + } + auto const scheduler = m_nodeService->scheduler(); + struct Awaitable + { + bcos::scheduler::SchedulerInterface::Ptr m_scheduler; + std::string m_address; + std::variant m_result{}; + constexpr static bool await_ready() noexcept { return false; } + void await_suspend(CO_STD::coroutine_handle<> handle) noexcept + { + m_scheduler->getCode(m_address, [this, handle](auto&& error, auto&& code) { + if (error) + { + m_result.emplace(std::move(error)); + } + else + { + m_result.emplace(std::move(code)); + } + handle.resume(); + }); + } + bcos::bytes await_resume() noexcept + { + if (std::holds_alternative(m_result)) + { + BOOST_THROW_EXCEPTION(*std::get(m_result)); + } + return std::move(std::get(m_result)); + } + }; + auto const code = co_await Awaitable{ + .m_scheduler = scheduler, + .m_address = std::move(addressStr), + }; + Json::Value result = toHexStringWithPrefix(code); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::sign(const Json::Value&, Json::Value& response) +{ + // params: address(DATA), message(DATA) + // result: signature(DATA) + Json::Value result = "0x00"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::signTransaction(const Json::Value&, Json::Value& response) +{ + // params: transaction(TX), address(DATA) + // result: signedTransaction(DATA) + Json::Value result = "0x00"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::sendTransaction(const Json::Value&, Json::Value& response) +{ + // params: transaction(TX) + // result: transactionHash(DATA) + Json::Value result = "0x0000000000000000000000000000000000000000000000000000000000000000"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::sendRawTransaction(const Json::Value& request, Json::Value& response) +{ + // params: signedTransaction(DATA) + // result: transactionHash(DATA) + auto txpool = m_nodeService->txpool(); + if (!txpool) [[unlikely]] + { + BOOST_THROW_EXCEPTION( + JsonRpcException(JsonRpcError::InternalError, "TXPool not available!")); + } + auto rawTx = toView(request[0U]); + auto rawTxBytes = fromHexWithPrefix(rawTx); + auto bytesRef = bcos::ref(rawTxBytes); + Web3Transaction web3Tx; + if (auto const error = codec::rlp::decode(bytesRef, web3Tx); error != nullptr) [[unlikely]] + { + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParams, error->errorMessage())); + } + auto tarsTx = web3Tx.takeToTarsTransaction(); + + auto tx = std::make_shared( + [m_tx = std::move(tarsTx)]() mutable { return &m_tx; }); + // for web3.eth.sendRawTransaction, return the hash of raw transaction + auto web3TxHash = bcos::crypto::keccak256Hash(bcos::ref(rawTxBytes)); + tx->mutableInner().extraTransactionHash.assign(web3TxHash.begin(), web3TxHash.end()); + + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << LOG_DESC("sendRawTransaction") << web3Tx.toString(); + } + txpool->broadcastTransaction(*tx); + auto const txResult = co_await txpool->submitTransaction(std::move(tx)); + auto const hash = std::move(web3TxHash); + if (txResult->status() == 0) + { + Json::Value result = hash.hexPrefixed(); + buildJsonContent(result, response); + } + else + { + protocol::TransactionStatus status = + static_cast(txResult->status()); + Json::Value errorData = Json::objectValue; + errorData["txHash"] = hash.hexPrefixed(); + auto output = toHex(txResult->transactionReceipt()->output(), "0x"); + auto msg = fmt::format("VM Exception while processing transaction, reason: {}, msg: {}", + protocol::toString(status), output); + errorData["message"] = msg; + errorData["data"] = std::move(output); + buildJsonErrorWithData(errorData, InternalError, std::move(msg), response); + } + if (c_fileLogLevel == TRACE) [[unlikely]] + { + WEB3_LOG(TRACE) << LOG_DESC("sendRawTransaction finished") + << LOG_KV("status", txResult->status()) + << LOG_KV("hash", hash.hexPrefixed()) << LOG_KV("rsp", printJson(response)); + } + co_return; +} +task::Task EthEndpoint::call(const Json::Value& request, Json::Value& response) +{ + // params: transaction(TX), blockNumber(QTY|TAG) + // result: data(DATA) + auto scheduler = m_nodeService->scheduler(); + if (!scheduler) + { + BOOST_THROW_EXCEPTION( + JsonRpcException(JsonRpcError::InternalError, "Scheduler not available!")); + } + auto [valid, call] = decodeCallRequest(request[0u]); + if (!valid) + { + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParams, "Invalid call request!")); + } + auto const blockTag = toView(request[1u]); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << LOG_DESC("eth_call") << LOG_KV("call", call) + << LOG_KV("blockTag", blockTag) << LOG_KV("blockNumber", blockNumber); + } + auto&& tx = call.takeToTransaction(m_nodeService->blockFactory()->transactionFactory()); + // TODO: ignore params blockNumber here, use it after historical data is available + + // MOVE it into a new file + struct Awaitable + { + bcos::scheduler::SchedulerInterface& m_scheduler; + bcos::protocol::Transaction::Ptr m_tx; + std::variant m_result{}; + constexpr static bool await_ready() noexcept { return false; } + void await_suspend(CO_STD::coroutine_handle<> handle) noexcept + { + m_scheduler.call(m_tx, [this, handle](Error::Ptr&& error, auto&& result) { + if (error) + { + m_result.emplace(std::move(error)); + } + else + { + m_result.emplace(std::move(result)); + } + handle.resume(); + }); + } + protocol::TransactionReceipt::Ptr await_resume() noexcept + { + if (std::holds_alternative(m_result)) + { + BOOST_THROW_EXCEPTION(*std::get(m_result)); + } + return std::move(std::get(m_result)); + } + }; + auto const result = co_await Awaitable{.m_scheduler = *scheduler, .m_tx = std::move(tx)}; + + auto output = toHexStringWithPrefix(result->output()); + if (result->status() == static_cast(protocol::TransactionStatus::None)) + { + response["jsonrpc"] = "2.0"; + response["result"] = std::move(output); + } + else + { + // https://docs.infura.io/api/networks/ethereum/json-rpc-methods/eth_call#returns + Json::Value jsonResult = Json::objectValue; + jsonResult["code"] = result->status(); + jsonResult["message"] = result->message(); + jsonResult["data"] = std::move(output); + response["jsonrpc"] = "2.0"; + response["error"] = std::move(jsonResult); + } + co_return; +} +task::Task EthEndpoint::estimateGas(const Json::Value& request, Json::Value& response) +{ + // params: transaction(TX), blockNumber(QTY|TAG) + // result: gas(QTY) + auto const tx = request[0u]; + auto const blockTag = toView(request[1u]); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + if (c_fileLogLevel == TRACE) + { + WEB3_LOG(TRACE) << LOG_DESC("eth_estimateGas") << LOG_KV("tx", printJson(tx)) + << LOG_KV("blockTag", blockTag) << LOG_KV("blockNumber", blockNumber); + } + // FIXME)): fake now + Json::Value result = "0x1"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getBlockByHash(const Json::Value& request, Json::Value& response) +{ + // params: blockHash(DATA), fullTransaction(Boolean) + // result: block(BLOCK) + auto const blockHash = toView(request[0u]); + auto const fullTransaction = request[1u].asBool(); + auto const ledger = m_nodeService->ledger(); + Json::Value result = Json::objectValue; + try + { + auto const number = co_await ledger::getBlockNumber( + *ledger, crypto::HashType(blockHash, crypto::HashType::FromHex)); + auto flag = bcos::ledger::HEADER; + flag |= fullTransaction ? bcos::ledger::TRANSACTIONS : bcos::ledger::TRANSACTIONS_HASH; + auto block = co_await ledger::getBlockData(*ledger, number, flag); + combineBlockResponse(result, std::move(block), fullTransaction); + } + catch (...) + { + result = Json::nullValue; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getBlockByNumber(const Json::Value& request, Json::Value& response) +{ + // params: blockNumber(QTY|TAG), fullTransaction(Boolean) + // result: block(BLOCK) + auto const blockTag = toView(request[0u]); + auto const fullTransaction = request[1u].asBool(); + Json::Value result = Json::objectValue; + try + { + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + auto const ledger = m_nodeService->ledger(); + auto flag = bcos::ledger::HEADER; + flag |= fullTransaction ? bcos::ledger::TRANSACTIONS : bcos::ledger::TRANSACTIONS_HASH; + auto block = co_await ledger::getBlockData(*ledger, blockNumber, flag); + combineBlockResponse(result, std::move(block), fullTransaction); + } + catch (...) + { + result = Json::nullValue; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getTransactionByHash( + const Json::Value& request, Json::Value& response) +{ + // params: transactionHash(DATA) + // result: transaction(TX) + auto const txHash = toView(request[0u]); + auto const hash = crypto::HashType(txHash, crypto::HashType::FromHex); + auto hashList = std::make_shared(); + hashList->push_back(hash); + auto const ledger = m_nodeService->ledger(); + Json::Value result = Json::objectValue; + try + { + auto const txs = co_await ledger::getTransactions(*ledger, std::move(hashList)); + auto receipt = co_await ledger::getReceipt(*ledger, hash); + if (!receipt || !txs || txs->empty()) + { + result = Json::nullValue; + buildJsonContent(result, response); + co_return; + } + auto block = co_await ledger::getBlockData(*ledger, receipt->blockNumber(), + bcos::ledger::HEADER | bcos::ledger::TRANSACTIONS_HASH); + combineTxResponse(result, txs->at(0), std::move(receipt), std::move(block)); + } + catch (...) + { + result = Json::nullValue; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getTransactionByBlockHashAndIndex( + const Json::Value& request, Json::Value& response) +{ + // params: blockHash(DATA), transactionIndex(QTY) + // result: transaction(TX) + auto const blockHash = toView(request[0u]); + auto const transactionIndex = fromQuantity(std::string(toView(request[1u]))); + auto const hash = crypto::HashType(blockHash, crypto::HashType::FromHex); + auto const ledger = m_nodeService->ledger(); + Json::Value result = Json::objectValue; + auto const number = co_await ledger::getBlockNumber(*ledger, hash); + // will not throw exception in getBlockNumber if not found + if (number <= 0) [[unlikely]] + { + result = Json::nullValue; + buildJsonContent(result, response); + co_return; + } + auto block = co_await ledger::getBlockData( + *ledger, number, bcos::ledger::TRANSACTIONS | bcos::ledger::HEADER); + if (!block || transactionIndex >= block->transactionsSize()) [[unlikely]] + { + result = Json::nullValue; + buildJsonContent(result, response); + co_return; + } + auto tx = block->transaction(transactionIndex); + combineTxResponse(result, std::move(tx), nullptr, std::move(block)); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getTransactionByBlockNumberAndIndex( + const Json::Value& request, Json::Value& response) +{ + // params: blockNumber(QTY|TAG), transactionIndex(QTY) + // result: transaction(TX) + auto const blockTag = toView(request[0u]); + auto const transactionIndex = fromQuantity(std::string(toView(request[1u]))); + auto [blockNumber, _] = co_await getBlockNumberByTag(blockTag); + auto const ledger = m_nodeService->ledger(); + Json::Value result = Json::objectValue; + try + { + auto block = co_await ledger::getBlockData( + *ledger, blockNumber, bcos::ledger::TRANSACTIONS | bcos::ledger::HEADER); + if (!block || transactionIndex >= block->transactionsSize()) [[unlikely]] + { + BOOST_THROW_EXCEPTION(JsonRpcException(InvalidParams, "Invalid transaction index!")); + } + auto tx = block->transaction(transactionIndex); + combineTxResponse(result, std::move(tx), nullptr, std::move(block)); + } + catch (...) + { + result = Json::nullValue; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getTransactionReceipt( + const Json::Value& request, Json::Value& response) +{ + // params: transactionHash(DATA) + // result: transactionReceipt(RECEIPT) + auto const hashStr = toView(request[0U]); + auto const hash = crypto::HashType(hashStr, crypto::HashType::FromHex); + auto const ledger = m_nodeService->ledger(); + Json::Value result = Json::objectValue; + try + { + auto receipt = co_await ledger::getReceipt(*ledger, hash); + auto hashList = std::make_shared(); + hashList->push_back(hash); + auto txs = co_await ledger::getTransactions(*ledger, std::move(hashList)); + if (!receipt || !txs || txs->empty()) + { + BOOST_THROW_EXCEPTION( + JsonRpcException(InvalidParams, "Invalid transaction hash: " + hash.hexPrefixed())); + } + auto block = co_await ledger::getBlockData(*ledger, receipt->blockNumber(), + bcos::ledger::HEADER | bcos::ledger::TRANSACTIONS_HASH); + combineReceiptResponse(result, std::move(receipt), txs->at(0), std::move(block)); + } + catch (...) + { + result = Json::nullValue; + buildJsonContent(result, response); + co_return; + } + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getUncleByBlockHashAndIndex(const Json::Value&, Json::Value& response) +{ + Json::Value result = "null"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getUncleByBlockNumberAndIndex( + const Json::Value&, Json::Value& response) +{ + Json::Value result = "null"; + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::newFilter(const Json::Value& request, Json::Value& response) +{ + // params: filter(FILTER) + // result: filterId(QTY) + Json::Value jParams = request[0U]; + auto params = m_filterSystem->requestFactory()->create(); + params->fromJson(jParams); + Json::Value result = co_await m_filterSystem->newFilter(params); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::newBlockFilter(const Json::Value&, Json::Value& response) +{ + // result: filterId(QTY) + Json::Value result = co_await m_filterSystem->newBlockFilter(); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::newPendingTransactionFilter(const Json::Value&, Json::Value& response) +{ + // result: filterId(QTY) + Json::Value result = co_await m_filterSystem->newPendingTxFilter(); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::uninstallFilter(const Json::Value& request, Json::Value& response) +{ + // params: filterId(QTY) + // result: success(Boolean) + auto const id = fromBigQuantity(toView(request[0U])); + Json::Value result = co_await m_filterSystem->uninstallFilter(id); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getFilterChanges(const Json::Value& request, Json::Value& response) +{ + // params: filterId(QTY) + // result: logs(ARRAY) + auto const id = fromBigQuantity(toView(request[0U])); + Json::Value result = co_await m_filterSystem->getFilterChanges(id); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getFilterLogs(const Json::Value& request, Json::Value& response) +{ + // params: filterId(QTY) + // result: logs(ARRAY) + auto const id = fromBigQuantity(toView(request[0U])); + Json::Value result = co_await m_filterSystem->getFilterLogs(id); + buildJsonContent(result, response); + co_return; +} +task::Task EthEndpoint::getLogs(const Json::Value& request, Json::Value& response) +{ + // params: filter(FILTER) + // result: logs(ARRAY) + Json::Value jParams = request[0U]; + auto params = m_filterSystem->requestFactory()->create(); + params->fromJson(jParams); + Json::Value result = co_await m_filterSystem->getLogs(params); + buildJsonContent(result, response); + co_return; +} +task::Task> EthEndpoint::getBlockNumberByTag( + std::string_view blockTag) +{ + auto ledger = m_nodeService->ledger(); + auto latest = co_await ledger::getCurrentBlockNumber(*ledger); + auto [number, _] = bcos::rpc::getBlockNumberByTag(latest, blockTag); + co_return std::make_tuple(number, std::cmp_equal(latest, number)); +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthEndpoint.h b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthEndpoint.h new file mode 100644 index 0000000000..95ea0b1e00 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthEndpoint.h @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file EthEndpoint.h + * @author: kyonGuo + * @date 2024/3/21 + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ + +/** + * eth entry point to match 'eth_' methods + */ +class EthEndpoint +{ +public: + EthEndpoint(NodeService::Ptr nodeService, FilterSystem::Ptr filterSystem) + : m_nodeService(std::move(nodeService)), m_filterSystem(std::move(filterSystem)) + {} + virtual ~EthEndpoint() = default; + +protected: + task::Task protocolVersion(const Json::Value&, Json::Value&); + task::Task syncing(const Json::Value&, Json::Value&); + task::Task coinbase(const Json::Value&, Json::Value&); + task::Task chainId(const Json::Value&, Json::Value&); + task::Task mining(const Json::Value&, Json::Value&); + task::Task hashrate(const Json::Value&, Json::Value&); + task::Task gasPrice(const Json::Value&, Json::Value&); + task::Task accounts(const Json::Value&, Json::Value&); + task::Task blockNumber(const Json::Value&, Json::Value&); + task::Task getBalance(const Json::Value&, Json::Value&); + task::Task getStorageAt(const Json::Value&, Json::Value&); + task::Task getTransactionCount(const Json::Value&, Json::Value&); + task::Task getBlockTxCountByHash(const Json::Value&, Json::Value&); + task::Task getBlockTxCountByNumber(const Json::Value&, Json::Value&); + task::Task getUncleCountByBlockHash(const Json::Value&, Json::Value&); + task::Task getUncleCountByBlockNumber(const Json::Value&, Json::Value&); + task::Task getCode(const Json::Value&, Json::Value&); + task::Task sign(const Json::Value&, Json::Value&); + task::Task signTransaction(const Json::Value&, Json::Value&); + task::Task sendTransaction(const Json::Value&, Json::Value&); + task::Task sendRawTransaction(const Json::Value&, Json::Value&); + task::Task call(const Json::Value&, Json::Value&); + task::Task estimateGas(const Json::Value&, Json::Value&); + task::Task getBlockByHash(const Json::Value&, Json::Value&); + task::Task getBlockByNumber(const Json::Value&, Json::Value&); + task::Task getTransactionByHash(const Json::Value&, Json::Value&); + task::Task getTransactionByBlockHashAndIndex(const Json::Value&, Json::Value&); + task::Task getTransactionByBlockNumberAndIndex(const Json::Value&, Json::Value&); + task::Task getTransactionReceipt(const Json::Value&, Json::Value&); + task::Task getUncleByBlockHashAndIndex(const Json::Value&, Json::Value&); + task::Task getUncleByBlockNumberAndIndex(const Json::Value&, Json::Value&); + task::Task newFilter(const Json::Value&, Json::Value&); + task::Task newBlockFilter(const Json::Value&, Json::Value&); + task::Task newPendingTransactionFilter(const Json::Value&, Json::Value&); + task::Task uninstallFilter(const Json::Value&, Json::Value&); + task::Task getFilterChanges(const Json::Value&, Json::Value&); + task::Task getFilterLogs(const Json::Value&, Json::Value&); + task::Task getLogs(const Json::Value&, Json::Value&); + task::Task> getBlockNumberByTag( + std::string_view blockTag); + +private: + NodeService::Ptr m_nodeService; + FilterSystem::Ptr m_filterSystem; +}; + +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthMethods.h b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthMethods.h new file mode 100644 index 0000000000..f6fb3420a2 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/EthMethods.h @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file EthMethods.h + * @author: kyonGuo + * @date 2024/3/27 + */ + +#pragma once + +#include +#include +namespace bcos::rpc +{ +enum class EthMethod +{ + web3_clientVersion, + web3_sha3, + net_version, + net_listening, + net_peerCount, + eth_protocolVersion, + eth_syncing, + eth_coinbase, + eth_chainId, + eth_mining, + eth_hashrate, + eth_gasPrice, + eth_accounts, + eth_blockNumber, + eth_getBalance, + eth_getStorageAt, + eth_getTransactionCount, + eth_getBlockTransactionCountByHash, + eth_getBlockTransactionCountByNumber, + eth_getUncleCountByBlockHash, + eth_getUncleCountByBlockNumber, + eth_getCode, + eth_sign, + eth_sendTransaction, + eth_signTransaction, + eth_sendRawTransaction, + eth_call, + eth_estimateGas, + eth_getBlockByHash, + eth_getBlockByNumber, + eth_getTransactionByHash, + eth_getTransactionByBlockHashAndIndex, + eth_getTransactionByBlockNumberAndIndex, + eth_getTransactionReceipt, + eth_getUncleByBlockHashAndIndex, + eth_getUncleByBlockNumberAndIndex, + eth_newFilter, + eth_newBlockFilter, + eth_newPendingTransactionFilter, + eth_uninstallFilter, + eth_getFilterChanges, + eth_getFilterLogs, + eth_getLogs +}; + +[[maybe_unused]] static std::string methodString(EthMethod _method) +{ + return std::string(magic_enum::enum_name(_method)); +} + +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/NetEndpoint.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/NetEndpoint.cpp new file mode 100644 index 0000000000..134152005f --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/NetEndpoint.cpp @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file NetEndpoint.cpp + * @author: kyonGuo + * @date 2024/3/21 + */ + +#include "NetEndpoint.h" + +#include +#include +#include + +using namespace bcos; +using namespace bcos::rpc; +task::Task NetEndpoint::verison(const Json::Value&, Json::Value& response) +{ + auto const ledger = m_nodeService->ledger(); + auto config = co_await ledger::getSystemConfig(*ledger, ledger::SYSTEM_KEY_WEB3_CHAIN_ID); + Json::Value result; + if (config.has_value()) + { + try + { + auto [chainId, _] = config.value(); + result = toQuantity(std::stoull(chainId)); + } + catch (...) + { + result = "0x4ee8"; // 20200 + } + } + else + { + result = "0x4ee8"; // 20200 + } + buildJsonContent(result, response); + co_return; +} +task::Task NetEndpoint::listening(const Json::Value&, Json::Value& response) +{ + Json::Value result = true; + buildJsonContent(result, response); + co_return; +} +task::Task NetEndpoint::peerCount(const Json::Value&, Json::Value& response) +{ + auto const sync = m_nodeService->sync(); + auto const status = sync->getPeerStatus(); + Json::Value result = Json::UInt64(status.size()); + buildJsonContent(result, response); + co_return; +} diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/NetEndpoint.h b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/NetEndpoint.h new file mode 100644 index 0000000000..e39b572ac1 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/NetEndpoint.h @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file NetEndpoint.h + * @author: kyonGuo + * @date 2024/3/21 + */ + +#pragma once +#include "bcos-rpc/groupmgr/GroupManager.h" +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ +class NetEndpoint +{ +public: + explicit NetEndpoint(NodeService::Ptr nodeService) : m_nodeService(std::move(nodeService)) {} + virtual ~NetEndpoint() = default; + +protected: + task::Task verison(const Json::Value&, Json::Value&); + task::Task listening(const Json::Value&, Json::Value&); + task::Task peerCount(const Json::Value&, Json::Value&); + +private: + NodeService::Ptr m_nodeService; +}; + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Web3Endpoint.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Web3Endpoint.cpp new file mode 100644 index 0000000000..1a0df3c340 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Web3Endpoint.cpp @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3Endpoint.cpp + * @author: kyonGuo + * @date 2024/3/21 + */ + +#include "Web3Endpoint.h" +#include "include/BuildInfo.h" +#include +#include +using namespace bcos::rpc; +bcos::task::Task Web3Endpoint::clientVersion( + const Json::Value& request, Json::Value& response) +{ + std::string version = "FISCO-BCOS-Web3RPC/" + std::string(FISCO_BCOS_PROJECT_VERSION) + "-" + + std::string(FISCO_BCOS_BUILD_TYPE) + "/" + + std::string(FISCO_BCOS_BUILD_PLATFORM) + "/" + + std::string(FISCO_BCOS_COMMIT_HASH); + Json::Value result = version; + buildJsonContent(result, response); + co_return; +} +bcos::task::Task Web3Endpoint::sha3(const Json::Value& request, Json::Value& response) +{ + // sha3 in eth means keccak256, not sha3-256, ref: + // https://ethereum.org/zh/developers/docs/apis/json-rpc/#web3_sha3 + auto const msg = toView(request[0U]); + auto const bytes = fromHexWithPrefix(msg); + crypto::hasher::openssl::OpenSSL_Keccak256_Hasher hasher; + hasher.update(bytesConstRef(bytes.data(), bytes.size())); + crypto::HashType out; + hasher.final(out); + Json::Value result = out.hexPrefixed(); + buildJsonContent(result, response); + co_return; +} diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Web3Endpoint.h b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Web3Endpoint.h new file mode 100644 index 0000000000..670fbc3aed --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/endpoints/Web3Endpoint.h @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3Endpoint.h + * @author: kyonGuo + * @date 2024/3/21 + */ + +#pragma once +#include "bcos-rpc/groupmgr/GroupManager.h" +#include +#include +#include +#include + +namespace bcos::rpc +{ +class Web3Endpoint +{ +public: + explicit Web3Endpoint(NodeService::Ptr nodeService) : m_nodeService(std::move(nodeService)) {} + virtual ~Web3Endpoint() = default; + +protected: + task::Task clientVersion(const Json::Value&, Json::Value&); + task::Task sha3(const Json::Value&, Json::Value&); + +private: + NodeService::Ptr m_nodeService; +}; + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/BlockResponse.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/BlockResponse.h new file mode 100644 index 0000000000..f0b5a5476e --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/BlockResponse.h @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file BlockResponse.h + * @author: kyonGuo + * @date 2024/4/11 + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ +[[maybe_unused]] static void combineBlockResponse( + Json::Value& result, bcos::protocol::Block::Ptr&& block, bool fullTxs = false) +{ + result["number"] = toQuantity(block->blockHeader()->number()); + result["hash"] = block->blockHeader()->hash().hexPrefixed(); + for (const auto& info : block->blockHeader()->parentInfo()) + { + result["parentHash"] = info.blockHash.hexPrefixed(); + } + result["nonce"] = "0x0000000000000000"; + // result["sha3Uncles"] = "0x"; + // result["logsBloom"] = "0x"; + result["transactionsRoot"] = block->blockHeader()->txsRoot().hexPrefixed(); + result["stateRoot"] = block->blockHeader()->stateRoot().hexPrefixed(); + result["receiptsRoot"] = block->blockHeader()->receiptsRoot().hexPrefixed(); + result["miner"] = Address().hexPrefixed(); + result["difficulty"] = "0x0"; + result["totalDifficulty"] = "0x0"; + result["extraData"] = toHexStringWithPrefix(block->blockHeader()->extraData()); + result["size"] = "0xffff"; + result["gasLimit"] = toQuantity(3000000000ull); + result["gasUsed"] = toQuantity((uint64_t)block->blockHeader()->gasUsed()); + result["timestamp"] = toQuantity(block->blockHeader()->timestamp()); + if (fullTxs) + { + Json::Value txList = Json::arrayValue; + for (size_t i = 0; i < block->transactionsSize(); i++) + { + Json::Value txJson = Json::objectValue; + auto tx = block->transaction(i); + combineTxResponse(txJson, std::move(tx), nullptr, block); + txList.append(txJson); + } + result["transactions"] = std::move(txList); + } + else + { + Json::Value txHashesList = Json::arrayValue; + for (size_t i = 0; i < block->transactionsHashSize(); i++) + { + txHashesList.append(block->transactionHash(i).hexPrefixed()); + } + result["transactions"] = std::move(txHashesList); + } + result["uncles"] = Json::Value(Json::arrayValue); +} +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/Bloom.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Bloom.cpp new file mode 100644 index 0000000000..c3fa645894 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Bloom.cpp @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Bloom.cpp + * @author: kyonGuo + * @date 2024/4/12 + */ + +#include "Bloom.h" + +using namespace bcos; +using namespace bcos::rpc; + +void rpc::bytesToBloom(concepts::ByteBuffer auto const& _bytes, Bloom& _bloom) +{ + auto hash = crypto::keccak256Hash(RefDataContainer(_bytes.data(), _bytes.size())); + static_assert(sizeof(crypto::HashType) == 32); + static_assert(sizeof(bcos::byte) == 1); + static_assert(sizeof(unsigned short) == 2); + for (size_t i = 0; i < 6; i += 2) + { + // bitPosition = ((first byte) & 0x07 << 8) + (second byte) + const unsigned short bitPosition = + static_cast((hash[i] & LOWER_3_BITS) << 8) + hash[i + 1]; + const size_t positionInBytes = BloomBytesSize - 1 - bitPosition / 8; + _bloom[positionInBytes] |= 1 << (bitPosition % 8); + } +} + +Bloom rpc::getLogsBloom(Logs const& logs) +{ + Bloom bloom{}; + for (auto const& log : logs) + { + bytesToBloom(log.address, bloom); + for (auto& topic : log.topics) + { + bytesToBloom(topic, bloom); + } + } + return bloom; +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/Bloom.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Bloom.h new file mode 100644 index 0000000000..1d40beb8b3 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Bloom.h @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Bloom.h + * @author: kyonGuo + * @date 2024/4/12 + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ +constexpr size_t BloomBytesSize = 256; +constexpr uint8_t LOWER_3_BITS = 0b00000111; +using Bloom = std::array; + +void bytesToBloom(bcos::concepts::ByteBuffer auto const& _bytes, Bloom& _bloom); + +Bloom getLogsBloom(Logs const& logs); + +inline std::string_view toStringView(Bloom const& bloom) +{ + return {reinterpret_cast(bloom.data()), bloom.size()}; +} + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/CallRequest.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/model/CallRequest.cpp new file mode 100644 index 0000000000..9b4db35f7b --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/CallRequest.cpp @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file CallRequest.cpp + * @author: kyonGuo + * @date 2024/4/11 + */ + +#include "CallRequest.h" + +using namespace bcos; +using namespace bcos::rpc; + +bcos::protocol::Transaction::Ptr CallRequest::takeToTransaction( + bcos::protocol::TransactionFactory::Ptr const& factory) noexcept +{ + auto tx = factory->createTransaction(0, std::move(this->to), std::move(this->data), "", 0, {}, + {}, 0, "", value.value_or(""), gasPrice.value_or(""), gas.value_or(0), + maxFeePerGas.value_or(""), maxPriorityFeePerGas.value_or("")); + if (from.has_value()) + { + auto sender = fromHexWithPrefix(from.value()); + tx->forceSender(std::move(sender)); + } + return tx; +} + + +std::tuple rpc::decodeCallRequest(Json::Value const& _root) noexcept +{ + CallRequest _request; + if (!_root.isObject()) + { + return {false, _request}; + } + if (!_root.isMember("data")) + { + return {false, _request}; + } + _request.to = _root.isMember("to") ? _root["to"].asString() : ""; + _request.data = bcos::fromHexWithPrefix(_root["data"].asString()); + if (_root.isMember("from")) + { + _request.from = _root["from"].asString(); + } + if (_root.isMember("gas")) + { + _request.gas = fromQuantity(_root["gas"].asString()); + } + if (_root.isMember("gasPrice")) + { + _request.gasPrice = _root["gasPrice"].asString(); + } + if (_root.isMember("value")) + { + _request.value = _root["value"].asString(); + } + if (_root.isMember("maxPriorityFeePerGas")) + { + _request.maxPriorityFeePerGas = _root["maxPriorityFeePerGas"].asString(); + } + if (_root.isMember("maxFeePerGas")) + { + _request.maxFeePerGas = _root["maxFeePerGas"].asString(); + } + return {true, std::move(_request)}; +} \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/CallRequest.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/CallRequest.h new file mode 100644 index 0000000000..118fe667b0 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/CallRequest.h @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file CallRequest.h + * @author: kyonGuo + * @date 2024/4/11 + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ +struct CallRequest +{ + // Address + std::optional from{}; + // Address + std::string to{}; + bcos::bytes data{}; + // Quantity + std::optional gas{}; + // Quantity + std::optional gasPrice{}; + // Quantity + std::optional value{}; + std::optional maxPriorityFeePerGas{}; + std::optional maxFeePerGas{}; + + + friend std::ostream& operator<<(std::ostream& _out, const CallRequest& _in) + { + _out << "from: " << _in.from.value_or("null") << ", "; + _out << "to: " << _in.to << ", "; + _out << "data: " << bcos::toHex(_in.data) << ", "; + _out << "gas: " << _in.gas.value_or(0) << ", "; + _out << "gasPrice: " << _in.gasPrice.value_or("") << ", "; + _out << "value: " << _in.value.value_or("") << ", "; + _out << "maxPriorityFeePerGas: " << _in.maxPriorityFeePerGas.value_or("") << ", "; + _out << "maxFeePerGas: " << _in.maxFeePerGas.value_or("") << ", "; + return _out; + } + bcos::protocol::Transaction::Ptr takeToTransaction( + bcos::protocol::TransactionFactory::Ptr const&) noexcept; +}; +[[maybe_unused]] std::tuple decodeCallRequest(Json::Value const& _root) noexcept; +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/Log.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Log.h new file mode 100644 index 0000000000..43e1af9414 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Log.h @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Log.h + * @author: kyonGuo + * @date 2024/4/11 + */ + +#pragma once + +#include +#include +#include + +namespace bcos::rpc +{ +struct Log +{ + bool removed = false; + bcos::bytes address{}; + h256s topics{}; + bcos::bytes data{}; + protocol::BlockNumber number{0}; + crypto::HashType blockHash{}; + crypto::HashType transactionHash{}; + uint32_t txIndex{0}; + uint32_t logIndex{0}; + + friend std::ostream& operator<<(std::ostream& _out, const Log& _in) + { + _out << "address: " << toHex(_in.address) << ", "; + _out << "topics: ["; + for (auto& topic : _in.topics) + { + _out << topic.hex() << ", "; + } + _out << "], "; + _out << "data: " << bcos::toHex(_in.data) << ", "; + _out << "number: " << _in.number << ", "; + _out << "blockHash: " << _in.blockHash.hex() << ", "; + _out << "transactionHash: " << _in.transactionHash.hex() << ", "; + _out << "txIndex: " << _in.txIndex << ", "; + _out << "logIndex: " << _in.logIndex << ", "; + return _out; + } +}; +using Logs = std::vector; +[[maybe_unused]] static std::ostream& operator<<(std::ostream& _out, const Logs& _in) +{ + for (const auto& log : _in) + { + _out << log; + } + return _out; +} +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/ReceiptResponse.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/ReceiptResponse.h new file mode 100644 index 0000000000..f558265db2 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/ReceiptResponse.h @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file ReceiptResponse.h + * @author: kyonGuo + * @date 2024/4/11 + */ + +#pragma once +#include "Bloom.h" + + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace bcos::rpc +{ +[[maybe_unused]] static void combineReceiptResponse(Json::Value& result, + protocol::TransactionReceipt::ConstPtr&& receipt, bcos::protocol::Transaction::ConstPtr&& tx, + bcos::protocol::Block::Ptr&& block) +{ + if (!result.isObject()) + { + return; + } + uint8_t status = (receipt->status() == 0 ? 1 : 0); + result["status"] = toQuantity(status); + result["transactionHash"] = tx->hash().hexPrefixed(); + size_t transactionIndex = 0; + crypto::HashType blockHash; + uint64_t blockNumber = 0; + if (block) + { + blockHash = block->blockHeader()->hash(); + blockNumber = block->blockHeader()->number(); + for (; transactionIndex < block->transactionsHashSize(); transactionIndex++) + { + if (block->transactionHash(transactionIndex) == tx->hash()) + { + break; + } + } + } + result["transactionIndex"] = toQuantity(transactionIndex); + result["blockHash"] = blockHash.hexPrefixed(); + result["blockNumber"] = toQuantity(blockNumber); + auto from = toHex(tx->sender()); + toChecksumAddress(from, bcos::crypto::keccak256Hash(bcos::bytesConstRef(from)).hex()); + result["from"] = "0x" + std::move(from); + if (tx->to().empty()) + { + result["to"] = Json::nullValue; + } + else + { + auto toView = tx->to(); + auto to = std::string(toView.starts_with("0x") ? toView.substr(2) : toView); + toChecksumAddress(to, bcos::crypto::keccak256Hash(bcos::bytesConstRef(to)).hex()); + result["to"] = "0x" + std::move(to); + } + result["cumulativeGasUsed"] = "0x0"; + result["effectiveGasPrice"] = + receipt->effectiveGasPrice().empty() ? "0x0" : std::string(receipt->effectiveGasPrice()); + result["gasUsed"] = toQuantity((uint64_t)receipt->gasUsed()); + if (receipt->contractAddress().empty()) + { + result["contractAddress"] = Json::nullValue; + } + else + { + auto contractAddress = std::string(receipt->contractAddress()); + toChecksumAddress(contractAddress, + bcos::crypto::keccak256Hash(bcos::bytesConstRef(contractAddress)).hex()); + result["contractAddress"] = "0x" + std::move(contractAddress); + } + result["logs"] = Json::arrayValue; + auto mutableReceipt = const_cast(receipt.get()); + auto receiptLog = mutableReceipt->takeLogEntries(); + for (size_t i = 0; i < receiptLog.size(); i++) + { + Json::Value log; + auto address = std::string(receiptLog[i].address()); + toChecksumAddress(address, bcos::crypto::keccak256Hash(bcos::bytesConstRef(address)).hex()); + log["address"] = "0x" + std::move(address); + log["topics"] = Json::arrayValue; + for (const auto& topic : receiptLog[i].topics()) + { + log["topics"].append(topic.hexPrefixed()); + } + log["data"] = toHexStringWithPrefix(receiptLog[i].data()); + log["logIndex"] = toQuantity(i); + log["blockNumber"] = toQuantity(blockNumber); + log["blockHash"] = blockHash.hexPrefixed(); + log["transactionIndex"] = toQuantity(transactionIndex); + log["transactionHash"] = tx->hash().hexPrefixed(); + log["removed"] = false; + result["logs"].append(std::move(log)); + } + Logs logs; + logs.reserve(receiptLog.size()); + for (size_t i = 0; i < receiptLog.size(); i++) + { + rpc::Log log{.address = std::move(receiptLog[i].takeAddress()), + .topics = std::move(receiptLog[i].takeTopics()), + .data = std::move(receiptLog[i].takeData())}; + log.logIndex = i; + logs.push_back(std::move(log)); + } + auto logsBloom = getLogsBloom(logs); + result["logsBloom"] = toHexStringWithPrefix(logsBloom); + auto type = TransactionType::Legacy; + if (!tx->extraTransactionBytes().empty()) + { + if (auto firstByte = tx->extraTransactionBytes()[0]; + firstByte < bcos::codec::rlp::BYTES_HEAD_BASE) + { + type = static_cast(firstByte); + } + } + result["type"] = toQuantity(static_cast(type)); +} +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/TransactionResponse.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/TransactionResponse.h new file mode 100644 index 0000000000..aad28b6e11 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/TransactionResponse.h @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file TransactionResponse.h + * @author: kyonGuo + * @date 2024/4/16 + */ + +#pragma once +#include "Web3Transaction.h" + + +#include +#include +#include +#include +#include +#include +#include + +namespace bcos::rpc +{ +// block and receipt are nullable +static void combineTxResponse(Json::Value& result, bcos::protocol::Transaction::ConstPtr&& tx, + protocol::TransactionReceipt::ConstPtr&& receipt, bcos::protocol::Block::Ptr const& block) +{ + if (!result.isObject()) + { + return; + } + size_t transactionIndex = 0; + crypto::HashType blockHash; + uint64_t blockNumber = 0; + if (block) + { + blockHash = block->blockHeader()->hash(); + blockNumber = block->blockHeader()->number(); + if (block->transactionsSize() == 0) + { + for (; transactionIndex < block->transactionsHashSize(); transactionIndex++) + { + if (block->transactionHash(transactionIndex) == tx->hash()) + { + break; + } + } + } + else + { + for (; transactionIndex < block->transactionsSize(); transactionIndex++) + { + if (block->transaction(transactionIndex)->hash() == tx->hash()) + { + break; + } + } + } + } + result["blockHash"] = blockHash.hexPrefixed(); + result["blockNumber"] = toQuantity(blockNumber); + result["transactionIndex"] = toQuantity(transactionIndex); + auto from = toHex(tx->sender()); + toChecksumAddress(from, bcos::crypto::keccak256Hash(bcos::bytesConstRef(from)).hex()); + result["from"] = "0x" + std::move(from); + if (tx->to().empty()) + { + result["to"] = Json::nullValue; + } + else + { + auto toView = tx->to(); + auto to = std::string(toView.starts_with("0x") ? toView.substr(2) : toView); + toChecksumAddress(to, bcos::crypto::keccak256Hash(bcos::bytesConstRef(to)).hex()); + result["to"] = "0x" + std::move(to); + } + result["gas"] = toQuantity(tx->gasLimit()); + auto gasPrice = tx->gasPrice(); + if (receipt && !receipt->effectiveGasPrice().empty()) + { + gasPrice = receipt->effectiveGasPrice(); + } + // FIXME)): return will case coredump in executor + result["gasPrice"] = std::string(gasPrice.empty() ? "0x0" : gasPrice); + result["hash"] = tx->hash().hexPrefixed(); + result["input"] = toHexStringWithPrefix(tx->input()); + + if (tx->type() == bcos::protocol::TransactionType::BCOSTransaction) [[unlikely]] + { + result["type"] = toQuantity(UINT32_MAX); + result["nonce"] = tx->nonce(); + result["value"] = std::string(tx->value().empty() ? "0x0" : tx->value()); + result["maxPriorityFeePerGas"] = + std::string(tx->maxPriorityFeePerGas().empty() ? "0x0" : tx->maxPriorityFeePerGas()); + result["maxFeePerGas"] = + std::string(tx->maxFeePerGas().empty() ? "0x0" : tx->maxFeePerGas()); + result["chainId"] = "0x0"; + } + else [[likely]] + { + Web3Transaction web3Tx; + auto extraBytesRef = bcos::bytesRef(const_cast(tx->extraTransactionBytes().data()), + tx->extraTransactionBytes().size()); + codec::rlp::decodeFromPayload(extraBytesRef, web3Tx); + result["nonce"] = toQuantity(web3Tx.nonce); + result["type"] = toQuantity(static_cast(web3Tx.type)); + result["value"] = toQuantity(web3Tx.value); + if (web3Tx.type >= TransactionType::EIP2930) + { + result["accessList"] = Json::arrayValue; + result["accessList"].resize(web3Tx.accessList.size()); + for (size_t i = 0; i < web3Tx.accessList.size(); i++) + { + auto& accessList = web3Tx.accessList[i]; + Json::Value access = Json::objectValue; + access["address"] = accessList.account.hexPrefixed(); + access["storageKeys"] = Json::arrayValue; + access["storageKeys"].resize(accessList.storageKeys.size()); + for (size_t j = 0; j < accessList.storageKeys.size(); j++) + { + Json::Value storageKey = accessList.storageKeys[j].hexPrefixed(); + access["storageKeys"].append(std::move(storageKey)); + } + result["accessList"].append(std::move(access)); + } + } + if (web3Tx.type >= TransactionType::EIP1559) + { + result["maxPriorityFeePerGas"] = toQuantity(web3Tx.maxPriorityFeePerGas); + result["maxFeePerGas"] = toQuantity(web3Tx.maxFeePerGas); + } + result["chainId"] = toQuantity(web3Tx.chainId.value_or(0)); + if (web3Tx.type >= TransactionType::EIP4844) + { + result["maxFeePerBlobGas"] = web3Tx.maxFeePerBlobGas.str(); + result["blobVersionedHashes"] = Json::arrayValue; + result["blobVersionedHashes"].resize(web3Tx.blobVersionedHashes.size()); + for (size_t i = 0; i < web3Tx.blobVersionedHashes.size(); i++) + { + Json::Value hash = web3Tx.blobVersionedHashes[i].hexPrefixed(); + result["blobVersionedHashes"].append(std::move(hash)); + } + } + } + result["r"] = toQuantity(tx->signatureData().getCroppedData(0, 32)); + result["s"] = toQuantity(tx->signatureData().getCroppedData(32, 32)); + result["v"] = toQuantity(tx->signatureData().getCroppedData(64, 1)); +} +} // namespace bcos::rpc diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3FilterRequest.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3FilterRequest.h new file mode 100644 index 0000000000..bcda0a88f0 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3FilterRequest.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file FilterRequest.h + * @author: kyonGuo + * @date 2024/4/11 + */ + +#pragma once +#include +#include + +namespace bcos::rpc +{ + +class Web3FilterRequest : public FilterRequest +{ +public: + Web3FilterRequest() = default; + ~Web3FilterRequest() = default; + Web3FilterRequest(const FilterRequest& oth) : FilterRequest(oth) {} + virtual int32_t InvalidParamsCode() override { return Web3JsonRpcError::Web3DefautError; } +}; + +class Web3FilterRequestFactory : public FilterRequestFactory +{ +public: + Web3FilterRequestFactory() = default; + ~Web3FilterRequestFactory() = default; + FilterRequest::Ptr create() override { return std::make_shared(); } + FilterRequest::Ptr create(const FilterRequest& req) override + { + return std::make_shared(req); + } +}; + +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3Transaction.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3Transaction.cpp new file mode 100644 index 0000000000..ad4f0e86fc --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3Transaction.cpp @@ -0,0 +1,495 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3Transaction.cpp + * @author: kyonGuo + * @date 2024/4/8 + */ + +#include "Web3Transaction.h" + +#include +#include +#include +#include + +namespace bcos +{ +namespace rpc +{ +using codec::rlp::decode; +using codec::rlp::encode; +using codec::rlp::header; +using codec::rlp::length; +bcos::bytes Web3Transaction::encodeForSign() const +{ + bcos::bytes out; + if (type == TransactionType::Legacy) + { + // rlp([nonce, gasPrice, gasLimit, to, value, data]) + codec::rlp::encodeHeader(out, codec::rlp::headerForSign(*this)); + codec::rlp::encode(out, nonce); + // for legacy tx, it means gas price + codec::rlp::encode(out, maxFeePerGas); + codec::rlp::encode(out, gasLimit); + if (to != Address()) + { + codec::rlp::encode(out, to.ref()); + } + else + { + out.push_back(codec::rlp::BYTES_HEAD_BASE); + } + codec::rlp::encode(out, value); + codec::rlp::encode(out, data); + if (chainId) + { + // EIP-155 + codec::rlp::encode(out, chainId.value()); + codec::rlp::encode(out, 0u); + codec::rlp::encode(out, 0u); + } + } + else + { + // EIP2930: 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]) + + // EIP1559: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, destination, amount, data, access_list]) + + // EIP4844: 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]) + out.push_back(static_cast(type)); + codec::rlp::encodeHeader(out, codec::rlp::headerForSign(*this)); + codec::rlp::encode(out, chainId.value_or(0)); + codec::rlp::encode(out, nonce); + if (type != TransactionType::EIP2930) + { + codec::rlp::encode(out, maxPriorityFeePerGas); + } + // for EIP2930 it means gasPrice; for EIP1559 and EIP4844, it means max priority fee per gas + codec::rlp::encode(out, maxFeePerGas); + codec::rlp::encode(out, gasLimit); + codec::rlp::encode(out, to.ref()); + codec::rlp::encode(out, value); + codec::rlp::encode(out, data); + codec::rlp::encode(out, accessList); + if (type == TransactionType::EIP4844) + { + codec::rlp::encode(out, maxFeePerBlobGas); + codec::rlp::encode(out, blobVersionedHashes); + } + } + return out; +} + +bcos::crypto::HashType Web3Transaction::txHash() const +{ + bcos::bytes encoded{}; + codec::rlp::encode(encoded, *this); + return bcos::crypto::keccak256Hash(bcos::ref(encoded)); +} + +bcos::crypto::HashType Web3Transaction::hashForSign() const +{ + auto encodeForSign = this->encodeForSign(); + return bcos::crypto::keccak256Hash(bcos::ref(encodeForSign)); +} + +bcostars::Transaction Web3Transaction::takeToTarsTransaction() +{ + bcostars::Transaction tarsTx{}; + tarsTx.data.to = (this->to == Address()) ? "" : this->to.hexPrefixed(); + tarsTx.data.input.reserve(this->data.size()); + RANGES::move(this->data, std::back_inserter(tarsTx.data.input)); + + tarsTx.data.value = "0x" + this->value.str(0, std::ios_base::hex); + tarsTx.data.gasLimit = this->gasLimit; + if (static_cast(this->type) >= static_cast(TransactionType::EIP1559)) + { + tarsTx.data.maxFeePerGas = "0x" + this->maxFeePerGas.str(0, std::ios_base::hex); + tarsTx.data.maxPriorityFeePerGas = + "0x" + this->maxPriorityFeePerGas.str(0, std::ios_base::hex); + } + else + { + tarsTx.data.gasPrice = "0x" + this->maxPriorityFeePerGas.str(0, std::ios_base::hex); + } + tarsTx.type = static_cast(bcos::protocol::TransactionType::Web3Transacion); + auto hashForSign = this->hashForSign(); + auto encodedForSign = this->encodeForSign(); + // FISCO BCOS signature is r||s||v + tarsTx.signature.reserve(crypto::SECP256K1_SIGNATURE_LEN); + RANGES::move(this->signatureR, std::back_inserter(tarsTx.signature)); + RANGES::move(this->signatureS, std::back_inserter(tarsTx.signature)); + tarsTx.signature.push_back(static_cast(this->signatureV)); + + tarsTx.extraTransactionBytes.reserve(encodedForSign.size()); + RANGES::move(encodedForSign, std::back_inserter(tarsTx.extraTransactionBytes)); + + const bcos::crypto::Secp256k1Crypto signatureImpl; + bcos::crypto::Keccak256 hashImpl; + auto signRef = bcos::bytesConstRef( + reinterpret_cast(tarsTx.signature.data()), tarsTx.signature.size()); + auto [_, sender] = signatureImpl.recoverAddress(hashImpl, hashForSign, signRef); + tarsTx.data.nonce = toHexStringWithPrefix(sender) + toQuantity(this->nonce); + tarsTx.data.chainID = std::to_string(this->chainId.value_or(0)); + tarsTx.dataHash.reserve(crypto::HashType::SIZE); + RANGES::move(hashForSign, std::back_inserter(tarsTx.dataHash)); + tarsTx.sender.reserve(sender.size()); + RANGES::move(sender, std::back_inserter(tarsTx.sender)); + return tarsTx; +} +} // namespace rpc + +namespace codec::rlp +{ +using namespace bcos::rpc; +Header header(const AccessListEntry& entry) noexcept +{ + auto len = length(entry.storageKeys); + return {.isList = true, .payloadLength = Address::SIZE + 1 + len}; +} + +size_t length(AccessListEntry const& entry) noexcept +{ + auto head = header(entry); + return lengthOfLength(head.payloadLength) + head.payloadLength; +} +Header headerTxBase(const Web3Transaction& tx) noexcept +{ + Header h{.isList = true}; + + if (tx.type != TransactionType::Legacy) + { + h.payloadLength += length(tx.chainId.value_or(0)); + } + + h.payloadLength += length(tx.nonce); + if (tx.type == TransactionType::EIP1559 || tx.type == TransactionType::EIP4844) + { + h.payloadLength += length(tx.maxPriorityFeePerGas); + } + h.payloadLength += length(tx.maxFeePerGas); + h.payloadLength += length(tx.gasLimit); + h.payloadLength += (tx.to != Address()) ? (Address::SIZE + 1) : 1; + h.payloadLength += length(tx.value); + h.payloadLength += length(tx.data); + + if (tx.type != TransactionType::Legacy) + { + h.payloadLength += codec::rlp::length(tx.accessList); + if (tx.type == TransactionType::EIP4844) + { + h.payloadLength += length(tx.maxFeePerBlobGas); + h.payloadLength += length(tx.blobVersionedHashes); + } + } + + return h; +} +Header header(Web3Transaction const& tx) noexcept +{ + auto header = headerTxBase(tx); + header.payloadLength += (tx.type == TransactionType::Legacy) ? length(tx.getSignatureV()) : 1; + header.payloadLength += length(tx.signatureR); + header.payloadLength += length(tx.signatureS); + return header; +} +Header headerForSign(Web3Transaction const& tx) noexcept +{ + auto header = headerTxBase(tx); + if (tx.type == TransactionType::Legacy && tx.chainId) + { + header.payloadLength += length(tx.chainId.value()) + 2; + } + return header; +} +size_t length(Web3Transaction const& tx) noexcept +{ + auto head = header(tx); + auto len = lengthOfLength(head.payloadLength) + head.payloadLength; + len = (tx.type == TransactionType::Legacy) ? len : lengthOfLength(len + 1) + len + 1; + return len; +} +void encode(bcos::bytes& out, const AccessListEntry& entry) noexcept +{ + encodeHeader(out, header(entry)); + encode(out, entry.account.ref()); + encode(out, entry.storageKeys); +} +void encode(bcos::bytes& out, const Web3Transaction& tx) noexcept +{ + if (tx.type == TransactionType::Legacy) + { + // rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]) + encodeHeader(out, header(tx)); + encode(out, tx.nonce); + // for legacy tx, it means gas price + encode(out, tx.maxFeePerGas); + encode(out, tx.gasLimit); + encode(out, tx.to.ref()); + encode(out, tx.value); + encode(out, tx.data); + encode(out, tx.getSignatureV()); + encode(out, tx.signatureR); + encode(out, tx.signatureS); + } + else + { + // EIP2930: 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, + // signatureYParity, signatureR, signatureS]) + + // EIP1559: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, + // signature_s]) + + // EIP4844: 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, + // signature_y_parity, signature_r, signature_s]) + out.push_back(static_cast(tx.type)); + encodeHeader(out, header(tx)); + encode(out, tx.chainId.value_or(0)); + encode(out, tx.nonce); + if (tx.type != TransactionType::EIP2930) + { + encode(out, tx.maxPriorityFeePerGas); + } + // for EIP2930 it means gasPrice; for EIP1559 and EIP4844, it means max priority fee per gas + encode(out, tx.maxFeePerGas); + encode(out, tx.gasLimit); + encode(out, tx.to.ref()); + encode(out, tx.value); + encode(out, tx.data); + encode(out, tx.accessList); + if (tx.type == TransactionType::EIP4844) + { + encode(out, tx.maxFeePerBlobGas); + encode(out, tx.blobVersionedHashes); + } + encode(out, tx.signatureV); + encode(out, tx.signatureR); + encode(out, tx.signatureS); + } +} +bcos::Error::UniquePtr decode(bcos::bytesRef& in, AccessListEntry& out) noexcept +{ + return decode(in, out.account, out.storageKeys); +} +bcos::Error::UniquePtr decode(bcos::bytesRef& in, Web3Transaction& out) noexcept +{ + if (in.empty()) + { + return BCOS_ERROR_UNIQUE_PTR(InputTooShort, "Input too short"); + } + Error::UniquePtr decodeError = nullptr; + if (auto const& firstByte = in[0]; 0 < firstByte && firstByte < BYTES_HEAD_BASE) + { + // EIP-2718: Transaction Type + // EIP2930: 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, + // signatureYParity, signatureR, signatureS]) + + // EIP1559: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, + // signature_s]) + + // EIP4844: 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, + // signature_y_parity, signature_r, signature_s]) + + out.type = static_cast(firstByte); + in = in.getCroppedData(1); + auto&& [e, header] = decodeHeader(in); + if (e != nullptr) + { + return std::move(e); + } + if (!header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(UnexpectedString, "Unexpected String"); + } + uint64_t chainId = 0; + if (auto error = decodeItems(in, chainId, out.nonce, out.maxPriorityFeePerGas); + error != nullptr) + { + return error; + } + out.chainId.emplace(chainId); + if (out.type == TransactionType::EIP2930) + { + out.maxFeePerGas = out.maxPriorityFeePerGas; + } + else if (auto error = decode(in, out.maxFeePerGas); error != nullptr) + { + return error; + } + + if (auto error = decodeItems(in, out.gasLimit, out.to, out.value, out.data, out.accessList); + error != nullptr) + { + return error; + } + + if (out.type == TransactionType::EIP4844) + { + if (auto error = decodeItems(in, out.maxFeePerBlobGas, out.blobVersionedHashes); + error != nullptr) + { + return error; + } + } + + decodeError = decodeItems(in, out.signatureV, out.signatureR, out.signatureS); + } + else + { + // rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]) + auto&& [error, header] = decodeHeader(in); + if (error != nullptr) + { + return std::move(error); + } + if (!header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(UnexpectedList, "Unexpected list"); + } + out.type = TransactionType::Legacy; + decodeError = decodeItems(in, out.nonce, out.maxPriorityFeePerGas, out.gasLimit, out.to, + out.value, out.data, out.signatureV, out.signatureR, out.signatureS); + out.maxFeePerGas = out.maxPriorityFeePerGas; + // TODO: EIP-155 chainId decode from encoded bytes for sign + auto v = out.signatureV; + if (v == 27 || v == 28) + { + // pre EIP-155 + out.chainId = std::nullopt; + out.signatureV = v - 27; + } + else if (v == 0 || v == 1) + { + out.chainId = std::nullopt; + return decodeError; + } + else if (v < 35) + { + return BCOS_ERROR_UNIQUE_PTR(InvalidVInSignature, "Invalid V in signature"); + } + else + { + // https://eips.ethereum.org/EIPS/eip-155 + // Find chain_id and y_parity ∈ {0, 1} such that + // v = chain_id * 2 + 35 + y_parity + out.signatureV = (v - 35) % 2; + out.chainId = ((v - 35) >> 1); + } + } + // rehandle signature and chainId + if (out.signatureR.size() < crypto::SECP256K1_SIGNATURE_R_LEN) + { + out.signatureR.insert( + out.signatureR.begin(), crypto::SECP256K1_SIGNATURE_R_LEN - out.signatureR.size(), 0); + } + if (out.signatureS.size() < crypto::SECP256K1_SIGNATURE_S_LEN) + { + out.signatureS.insert(out.signatureS.begin(), + crypto::SECP256K1_SIGNATURE_S_LEN - out.signatureS.size(), bcos::byte(0)); + } + return decodeError; +} + +bcos::Error::UniquePtr decodeFromPayload(bcos::bytesRef& in, rpc::Web3Transaction& out) noexcept +{ + if (in.empty()) + { + return BCOS_ERROR_UNIQUE_PTR(InputTooShort, "Input too short"); + } + Error::UniquePtr decodeError = nullptr; + if (auto const& firstByte = in[0]; 0 < firstByte && firstByte < BYTES_HEAD_BASE) + { + // EIP-2718: Transaction Type + // EIP2930: 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, + // signatureYParity, signatureR, signatureS]) + + // EIP1559: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, + // signature_s]) + + // EIP4844: 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, + // gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, + // signature_y_parity, signature_r, signature_s]) + + out.type = static_cast(firstByte); + in = in.getCroppedData(1); + auto&& [e, header] = decodeHeader(in); + if (e != nullptr) + { + return std::move(e); + } + if (!header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(UnexpectedString, "Unexpected String"); + } + uint64_t chainId = 0; + if (auto error = decodeItems(in, chainId, out.nonce, out.maxPriorityFeePerGas); + error != nullptr) + { + return error; + } + out.chainId.emplace(chainId); + if (out.type == TransactionType::EIP2930) + { + out.maxFeePerGas = out.maxPriorityFeePerGas; + } + else if (auto error = decode(in, out.maxFeePerGas); error != nullptr) + { + return error; + } + + if (auto error = decodeItems(in, out.gasLimit, out.to, out.value, out.data, out.accessList); + error != nullptr) + { + return error; + } + + if (out.type == TransactionType::EIP4844) + { + if (auto error = decodeItems(in, out.maxFeePerBlobGas, out.blobVersionedHashes); + error != nullptr) + { + return error; + } + } + return nullptr; + } + // rlp([nonce, gasPrice, gasLimit, to, value, data, chainId]) + auto&& [error, header] = decodeHeader(in); + if (error != nullptr) + { + return std::move(error); + } + if (!header.isList) + { + return BCOS_ERROR_UNIQUE_PTR(UnexpectedList, "Unexpected list"); + } + out.type = TransactionType::Legacy; + uint64_t chainId = 0; + decodeError = decodeItems(in, out.nonce, out.maxPriorityFeePerGas, out.gasLimit, out.to, + out.value, out.data, chainId); + out.chainId.emplace(chainId); + return decodeError; +} + +} // namespace codec::rlp +} // namespace bcos diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3Transaction.h b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3Transaction.h new file mode 100644 index 0000000000..ac10693bcc --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/model/Web3Transaction.h @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2022 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3Transaction.h + * @author: kyonGuo + * @date 2024/4/8 + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace bcos +{ +namespace rpc +{ +// EIP-2718 transaction type +// https://github.com/ethereum/eth1.0-specs/tree/master/lists/signature-types +enum class TransactionType : uint8_t +{ + Legacy = 0, + EIP2930 = 1, // https://eips.ethereum.org/EIPS/eip-2930 + EIP1559 = 2, // https://eips.ethereum.org/EIPS/eip-1559 + EIP4844 = 3, // https://eips.ethereum.org/EIPS/eip-4844 +}; + +[[maybe_unused]] constexpr auto operator<=>(TransactionType const& ltype, auto rtype) + requires std::same_as || + std::unsigned_integral +{ + return static_cast(ltype) <=> static_cast(rtype); +} + +[[maybe_unused]] static std::ostream& operator<<(std::ostream& _out, const TransactionType& _in) +{ + _out << magic_enum::enum_name(_in); + return _out; +} + +// EIP-2930: Access lists +struct AccessListEntry +{ + Address account{}; + std::vector storageKeys{}; + friend bool operator==(const AccessListEntry& lhs, const AccessListEntry& rhs) noexcept + { + return lhs.account == rhs.account && lhs.storageKeys == rhs.storageKeys; + } +}; + +class Web3Transaction +{ +public: + Web3Transaction() = default; + ~Web3Transaction() = default; + Web3Transaction(const Web3Transaction&) = delete; + Web3Transaction(Web3Transaction&&) = default; + Web3Transaction& operator=(const Web3Transaction&) = delete; + Web3Transaction& operator=(Web3Transaction&&) = default; + + // encode for sign, rlp(tx_payload) + bcos::bytes encodeForSign() const; + // tx hash = keccak256(rlp(tx_payload,v,r,s)) + bcos::crypto::HashType txHash() const; + // hash for sign = keccak256(rlp(tx_payload)) + bcos::crypto::HashType hashForSign() const; + bcostars::Transaction takeToTarsTransaction(); + uint64_t getSignatureV() const + { + // EIP-155: Simple replay attack protection + if (chainId.has_value()) + { + return chainId.value() * 2 + 35 + signatureV; + } + return signatureV + 27; + } + + std::string sender() const + { + bcos::bytes sign{}; + sign.reserve(crypto::SECP256K1_SIGNATURE_LEN); + sign.insert(sign.end(), signatureR.begin(), signatureR.end()); + sign.insert(sign.end(), signatureS.begin(), signatureS.end()); + sign.push_back(signatureV); + bcos::crypto::Keccak256 hashImpl; + auto encodeForSign = this->encodeForSign(); + auto hash = bcos::crypto::keccak256Hash(ref(encodeForSign)); + const bcos::crypto::Secp256k1Crypto signatureImpl; + auto [_, addr] = signatureImpl.recoverAddress(hashImpl, hash, ref(sign)); + return toHexStringWithPrefix(addr); + } + + std::string toString() const noexcept + { + std::stringstream stringstream{}; + stringstream << " chainId: " << this->chainId.value_or(0) + << " type: " << static_cast(this->type) << " to: " << this->to + << " data: " << toHex(this->data) << " value: " << this->value + << " nonce: " << this->nonce << " gasLimit: " << this->gasLimit + << " maxPriorityFeePerGas: " << this->maxPriorityFeePerGas + << " maxFeePerGas: " << this->maxFeePerGas + << " maxFeePerBlobGas: " << this->maxFeePerBlobGas + << " blobVersionedHashes: " << this->blobVersionedHashes + << " signatureR: " << toHex(this->signatureR) + << " signatureS: " << toHex(this->signatureS) + << " signatureV: " << this->signatureV; + return stringstream.str(); + } + + std::optional chainId{std::nullopt}; // nullopt means a pre-EIP-155 transaction + TransactionType type{TransactionType::Legacy}; + Address to{}; + bcos::bytes data{}; + u256 value{0}; + uint64_t nonce{0}; + uint64_t gasLimit{0}; + // EIP-2930: Optional access lists + std::vector accessList{}; + // EIP-1559: Fee market change for ETH 1.0 chain + u256 maxPriorityFeePerGas{0}; // for legacy tx, it stands for gasPrice + u256 maxFeePerGas{0}; + // EIP-4844: Shard Blob Transactions + u256 maxFeePerBlobGas{0}; + h256s blobVersionedHashes{}; + // TODO)) blob + bcos::bytes signatureR{}; + bcos::bytes signatureS{}; + uint64_t signatureV{0}; +}; +} // namespace rpc +namespace codec::rlp +{ +Header header(const rpc::AccessListEntry& entry) noexcept; +void encode(bcos::bytes& out, const rpc::AccessListEntry&) noexcept; +size_t length(const rpc::AccessListEntry&) noexcept; + +size_t length(const rpc::Web3Transaction&) noexcept; +Header headerForSign(const rpc::Web3Transaction& tx) noexcept; +Header headerTxBase(const rpc::Web3Transaction& tx) noexcept; +Header header(const rpc::Web3Transaction& tx) noexcept; +void encode(bcos::bytes& out, const rpc::Web3Transaction&) noexcept; +bcos::Error::UniquePtr decode(bcos::bytesRef& in, rpc::AccessListEntry&) noexcept; +bcos::Error::UniquePtr decode(bcos::bytesRef& in, rpc::Web3Transaction&) noexcept; +bcos::Error::UniquePtr decodeFromPayload(bcos::bytesRef& in, rpc::Web3Transaction&) noexcept; +} // namespace codec::rlp +} // namespace bcos diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/utils/Common.h b/bcos-rpc/bcos-rpc/web3jsonrpc/utils/Common.h new file mode 100644 index 0000000000..b3fcaef4ce --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/utils/Common.h @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Common.h + * @author: kyonGuo + * @date 2024/3/29 + */ + +#pragma once + +#include +#include + +namespace bcos::rpc +{ +constexpr const uint64_t LowestGasPrice{21000}; +enum Web3JsonRpcError : int32_t +{ + Web3DefautError = -32000, +}; +} // namespace bcos::rpc \ No newline at end of file diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/utils/util.cpp b/bcos-rpc/bcos-rpc/web3jsonrpc/utils/util.cpp new file mode 100644 index 0000000000..8c187a60e6 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/utils/util.cpp @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file util.cpp + * @author: kyonGuo + * @date 2024/3/29 + */ + +#include "util.h" + +using namespace bcos::rpc; + +void bcos::rpc::buildJsonContent(Json::Value& result, Json::Value& response) +{ + response["jsonrpc"] = "2.0"; + response["result"].swap(result); +} + +void bcos::rpc::buildJsonError( + Json::Value const& request, int32_t code, std::string message, Json::Value& response) +{ + response["jsonrpc"] = "2.0"; + // maybe request not init + response["id"] = request.isMember("id") ? request["id"] : Json::Value::null; + Json::Value error; + error["code"] = code; + error["message"] = std::move(message); + response["error"] = std::move(error); +} + +void bcos::rpc::buildJsonErrorWithData( + Json::Value& data, int32_t code, std::string message, Json::Value& response) +{ + response["jsonrpc"] = "2.0"; + // maybe request not init + Json::Value error = Json::objectValue; + error["code"] = code; + error["message"] = std::move(message); + error["data"].swap(data); + response["error"] = std::move(error); +} diff --git a/bcos-rpc/bcos-rpc/web3jsonrpc/utils/util.h b/bcos-rpc/bcos-rpc/web3jsonrpc/utils/util.h new file mode 100644 index 0000000000..d51d847466 --- /dev/null +++ b/bcos-rpc/bcos-rpc/web3jsonrpc/utils/util.h @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file util.h + * @author: kyonGuo + * @date 2024/3/29 + */ + +#pragma once +#include +#include +#include + +namespace bcos::rpc +{ +void buildJsonContent(Json::Value& result, Json::Value& response); +void buildJsonError( + Json::Value const& request, int32_t code, std::string message, Json::Value& response); +void buildJsonErrorWithData( + Json::Value& data, int32_t code, std::string message, Json::Value& response); +inline auto printJson(const Json::Value& value) +{ + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + return Json::writeString(builder, value); +} +inline std::string_view toView(const Json::Value& value) +{ + const char* begin = nullptr; + const char* end = nullptr; + if (!value.getString(&begin, &end)) + { + return {}; + } + std::string_view view(begin, end - begin); + return view; +} +} // namespace bcos::rpc diff --git a/bcos-rpc/test/CMakeLists.txt b/bcos-rpc/test/CMakeLists.txt index d8e257ac3d..b647c52e33 100644 --- a/bcos-rpc/test/CMakeLists.txt +++ b/bcos-rpc/test/CMakeLists.txt @@ -26,5 +26,6 @@ add_executable(${TEST_BINARY_NAME} ${SOURCES}) target_include_directories(${TEST_BINARY_NAME} PRIVATE .) target_compile_options(${TEST_BINARY_NAME} PRIVATE -Wno-unused-variable) -target_link_libraries(${TEST_BINARY_NAME} ${RPC_TARGET} ${TOOL_TARGET} Boost::unit_test_framework) -add_test(NAME ${TEST_BINARY_NAME} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} COMMAND ${TEST_BINARY_NAME}) \ No newline at end of file +target_link_libraries(${TEST_BINARY_NAME} ${TXPOOL_TARGET} ${LEDGER_TARGET} ${RPC_TARGET} ${TOOL_TARGET} Boost::unit_test_framework) +#add_test(NAME ${TEST_BINARY_NAME} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} COMMAND ${TEST_BINARY_NAME}) +config_test_cases("" "${SOURCES}" ${TEST_BINARY_NAME} "") \ No newline at end of file diff --git a/bcos-rpc/test/unittests/common/RPCFixture.h b/bcos-rpc/test/unittests/common/RPCFixture.h index 5f0a3f4723..f71cc7c320 100644 --- a/bcos-rpc/test/unittests/common/RPCFixture.h +++ b/bcos-rpc/test/unittests/common/RPCFixture.h @@ -35,9 +35,15 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include #include #include @@ -82,7 +88,18 @@ class RPCFixture : public TestPromptFixture cryptoSuite, blockHeaderFactory, txFactory, receiptFactory); m_ledger = std::make_shared(m_blockFactory, 20, 10, 10); m_ledger->setSystemConfig(ledger::SYSTEM_KEY_TX_COUNT_LIMIT, "1000"); - txPool = std::make_shared(); + + auto nodeId = std::make_shared( + h256("1110000000000000000000000000000000000000000000000000000000000000").asBytes()); + m_frontService = std::make_shared(nodeId); + auto txResultFactory = std::make_shared(); + + auto txPoolFactory = std::make_shared(nodeId, cryptoSuite, txResultFactory, + m_blockFactory, m_frontService, m_ledger, "group0", "chain0", 100000000); + + txPool = txPoolFactory->createTxPool(); + txPool->init(); + txPool->start(); scheduler = std::make_shared(m_ledger, m_blockFactory); nodeService = std::make_shared( @@ -109,8 +126,9 @@ class RPCFixture : public TestPromptFixture std::string chainId = "test-chain"; RpcFactory::Ptr factory; + FakeFrontService::Ptr m_frontService; FakeLedger::Ptr m_ledger; - FakeTxPool::Ptr txPool; + TxPool::Ptr txPool; FakeScheduler::Ptr scheduler; BlockFactory::Ptr m_blockFactory; @@ -129,6 +147,11 @@ class RPCFixture : public TestPromptFixture " ; enable compression for p2p message, default: true\n" " ; enable_compression=false\n" "\n" + "[web3_rpc]\n" + " enable=true\n" + " listen_ip=127.0.0.1\n" + " listen_port=8555\n" + "\n" "[rpc]\n" " listen_ip=0.0.0.0\n" " listen_port=20200\n" diff --git a/bcos-rpc/test/unittests/rpc/BloomTest.cpp b/bcos-rpc/test/unittests/rpc/BloomTest.cpp new file mode 100644 index 0000000000..4faa47b49c --- /dev/null +++ b/bcos-rpc/test/unittests/rpc/BloomTest.cpp @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file BloomTest.cpp + * @author: kyonGuo + * @date 2024/4/29 + */ + + +#include "../common/RPCFixture.h" +#include +#include +#include +#include +#include +#include +#include +using namespace bcos; +using namespace bcos::rpc; +using namespace bcos::codec::rlp; +using namespace std::literals; +using namespace std::string_view_literals; +namespace bcos::test +{ +BOOST_FIXTURE_TEST_SUITE(testBloom, RPCFixture) +BOOST_AUTO_TEST_CASE(testLogsToBloom) +{ + // case silkworm + { + Logs logs{}; + logs.push_back({.address = 0x22341ae42d6dd7384bc8584e50419ea3ac75b83f_bytes, + .topics = {0x04491edcd115127caedbd478e2e7895ed80c7847e903431f94f9cfa579cad47f_hash}}); + logs.push_back({ + .address = 0xe7fb22dfef11920312e4989a3a2b81e2ebf05986_bytes, + .topics = + { + 0x7f1fef85c4b037150d3675218e0cdb7cf38fea354759471e309f3354918a442f_hash, + 0xd85629c7eaae9ea4a10234fed31bc0aeda29b2683ebe0c1882499d272621f6b6_hash, + }, + .data = 0x2d690516512020171c1ec870f6ff45398cc8609250326be89915fb538e7b_bytes, + }); + auto const bloom = rpc::getLogsBloom(logs); + auto const bloomHex = toHex(bloom); + // clang-format off + auto constexpr expected = "00000000000000000081000000000000000000000000000000000002000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000040000080000000400000000000000000000000000000000000000000000000000000000000000000000010000010000000000000000000000000000000001400000000000000008000000000000000000000000000000000"sv; + // clang-format on + BOOST_CHECK_EQUAL(bloomHex, expected); + } + // case hardhat + { + Logs logs{}; + logs.push_back({.address = 0x5fbdb2315678afecb367f032d93f642f64180aa3_bytes, + .topics = { + 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0_hash, + 0x0000000000000000000000000000000000000000000000000000000000000000_hash, + 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266_hash, + }}); + // clang-format off + auto constexpr expected = "00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000040020000000000000100000800000000000000000000000000000000400000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000040000000200000000000000000000000002000000000000000000020000000000000000000000000000000000000000000000000000000000000000000"sv; + // clang-format on + auto const bloom = rpc::getLogsBloom(logs); + auto const bloomHex = toHex(bloom); + BOOST_CHECK_EQUAL(bloomHex, expected); + } +} +BOOST_AUTO_TEST_SUITE_END() +}; // namespace bcos::test \ No newline at end of file diff --git a/bcos-rpc/test/unittests/rpc/Web3RpcTest.cpp b/bcos-rpc/test/unittests/rpc/Web3RpcTest.cpp new file mode 100644 index 0000000000..4f6e2ee2d3 --- /dev/null +++ b/bcos-rpc/test/unittests/rpc/Web3RpcTest.cpp @@ -0,0 +1,694 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3RpcTest.cpp + * @author: kyonGuo + * @date 2024/3/29 + */ + + +#include "../common/RPCFixture.h" +#include "bcos-crypto/encrypt/AESCrypto.h" +#include "bcos-crypto/signature/secp256k1/Secp256k1Crypto.h" +#include "bcos-crypto/signature/secp256k1/Secp256k1KeyPair.h" +#include "bcos-crypto/signature/sm2/SM2Crypto.h" +#include "bcos-crypto/signature/sm2/SM2KeyPair.h" +#include "bcos-framework/protocol/GlobalConfig.h" +#include "bcos-rpc/bcos-rpc/RpcFactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace bcos; +using namespace bcos::rpc; +using namespace bcos::crypto; +namespace bcos::test +{ +class Web3TestFixture : public RPCFixture +{ +public: + Web3TestFixture() + { + rpc = factory->buildLocalRpc(groupInfo, nodeService); + web3JsonRpc = rpc->web3JsonRpc(); + BOOST_CHECK(web3JsonRpc != nullptr); + } + Json::Value onRPCRequestWrapper(std::string_view request, rpc::Sender _diySender = nullptr) + { + Json::Value value; + Json::Reader reader; + if (!_diySender) + { + std::promise promise; + web3JsonRpc->onRPCRequest( + request, [&promise](bcos::bytes resp) { promise.set_value(std::move(resp)); }); + auto jsonBytes = promise.get_future().get(); + std::string_view json( + (char*)jsonBytes.data(), (char*)jsonBytes.data() + jsonBytes.size()); + reader.parse(json.begin(), json.end(), value); + } + else + { + web3JsonRpc->onRPCRequest(request, std::move(_diySender)); + } + return value; + } + static void validRespCheck(Json::Value const& resp) + { + BOOST_CHECK(!resp.isMember("error")); + BOOST_CHECK(resp.isMember("result")); + BOOST_CHECK(resp.isMember("id")); + BOOST_CHECK(resp.isMember("jsonrpc")); + BOOST_CHECK(resp["jsonrpc"].asString() == "2.0"); + }; + + Rpc::Ptr rpc; + Web3JsonRpcImpl::Ptr web3JsonRpc; +}; +BOOST_FIXTURE_TEST_SUITE(testWeb3RPC, Web3TestFixture) +BOOST_AUTO_TEST_CASE(handleInvalidTest) +{ + // no method + { + const auto request = R"({"jsonrpc":"2.0","id":1})"; + auto response = onRPCRequestWrapper(request); + BOOST_CHECK(response.isMember("error")); + BOOST_CHECK(response["error"]["code"].asInt() == InvalidRequest); + } + + // invalid json + { + const auto request = R"({{"jsonrpc":"2.0","id":1, "method":"eth_blockNumber","params":[]})"; + auto response = onRPCRequestWrapper(request); + BOOST_CHECK(response.isMember("error")); + BOOST_CHECK(response["error"]["code"].asInt() == InvalidRequest); + } + + // invalid params type + { + const auto request = R"({"jsonrpc":"2.0","id":1, "method":"eth_blockNumber","params":{}})"; + auto response = onRPCRequestWrapper(request); + BOOST_CHECK(response.isMember("error")); + BOOST_CHECK(response["error"]["code"].asInt() == InvalidRequest); + } + + // invalid method + { + const auto request = R"({"jsonrpc":"2.0","id":1, "method":"eth_AAA","params":[]})"; + auto response = onRPCRequestWrapper(request); + BOOST_CHECK(response.isMember("error")); + BOOST_CHECK(response["error"]["code"].asInt() == MethodNotFound); + BOOST_CHECK(response["error"]["message"].asString() == "Method not found"); + } + + // not impl method + { + const auto request = R"({"jsonrpc":"2.0","id":1, "method":"eth_coinbase","params":[]})"; + auto response = onRPCRequestWrapper(request); + std::string s = response.toStyledString(); + BOOST_CHECK(response.isMember("error")); + BOOST_CHECK(response["error"]["code"].asInt() == MethodNotFound); + BOOST_CHECK( + response["error"]["message"].asString() == "This API has not been implemented yet!"); + } +} + +BOOST_AUTO_TEST_CASE(handleValidTest) +{ + // method eth_syncing + // { + // const auto request = + // R"({"jsonrpc":"2.0","id":1132123, "method":"eth_syncing","params":[]})"; + // auto response = onRPCRequestWrapper(request); + // validRespCheck(response); + // BOOST_CHECK(response["id"].asInt64() == 1132123); + // BOOST_CHECK(response["result"].asBool() == false); + // } + + // method eth_chainId + { + const auto request = R"({"jsonrpc":"2.0","id":123, "method":"eth_chainId","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 123); + BOOST_CHECK(fromQuantity(response["result"].asString()) == 0); + } + + // method eth_mining + { + const auto request = R"({"jsonrpc":"2.0","id":3214, "method":"eth_mining","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 3214); + BOOST_CHECK(response["result"].asBool() == false); + } + + // method eth_hashrate + { + const auto request = R"({"jsonrpc":"2.0","id":3214, "method":"eth_hashrate","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 3214); + BOOST_CHECK(fromQuantity(response["result"].asString()) == 0); + } + + // method eth_gasPrice + { + m_ledger->setSystemConfig(SYSTEM_KEY_TX_GAS_PRICE, "0x99e670"); + const auto request = + R"({"jsonrpc":"2.0","id":541321, "method":"eth_gasPrice","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 541321); + auto b = fromQuantity(response["result"].asString()); + std::cout << b << std::endl; + BOOST_CHECK(b == 10086000); + BOOST_CHECK(fromQuantity(response["result"].asString()) == 10086000); + } + + // method eth_accounts + { + const auto request = R"({"jsonrpc":"2.0","id":3214, "method":"eth_accounts","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 3214); + BOOST_CHECK(response["result"].size() == 0); + } + + // method eth_blockNumber + { + const auto request = + R"({"jsonrpc":"2.0","id":996886, "method":"eth_blockNumber","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 996886); + auto const blkNum = toQuantity(m_ledger->blockNumber()); + BOOST_CHECK(response["result"].asString() == blkNum); + } + + // method eth_blockNumber + { + const auto request = + R"({"id":1655516568316917,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asUInt64() == 1655516568316917); + auto const blkNum = toQuantity(m_ledger->blockNumber()); + BOOST_CHECK(response["result"].asString() == blkNum); + } + + // method eth_blockNumber + { + const auto request = + R"({"id":"e4df2b99-f80b-4e23-91c7-b9471b46af26","jsonrpc":"2.0","method":"eth_blockNumber","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asString() == "e4df2b99-f80b-4e23-91c7-b9471b46af26"); + auto const blkNum = toQuantity(m_ledger->blockNumber()); + BOOST_CHECK(response["result"].asString() == blkNum); + } +} + +BOOST_AUTO_TEST_CASE(handleWeb3NamespaceValidTest) +{ + auto validRespCheck = [](Json::Value const& resp) { + BOOST_CHECK(!resp.isMember("error")); + BOOST_CHECK(resp.isMember("result")); + BOOST_CHECK(resp.isMember("id")); + BOOST_CHECK(resp.isMember("jsonrpc")); + BOOST_CHECK(resp["jsonrpc"].asString() == "2.0"); + }; + + // method web3_clientVersion + { + const auto request = + R"({"jsonrpc":"2.0","id":24243, "method":"web3_clientVersion","params":[]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 24243); + std::string result = response["result"].asString(); + BOOST_CHECK(result.find("FISCO-BCOS-Web3RPC") != std::string::npos); + } + + // method web3_sha3 + { + const auto request = + R"({"jsonrpc":"2.0","id":321314, "method":"web3_sha3","params":["0x68656c6c6f20776f726c64"]})"; + auto response = onRPCRequestWrapper(request); + validRespCheck(response); + BOOST_CHECK(response["id"].asInt64() == 321314); + BOOST_CHECK(response["result"].asString() == + "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"); + } +} + +BOOST_AUTO_TEST_CASE(handleLegacyTxTest) +{ + // method eth_sendRawTransaction + auto testLegacyTx = [&](std::string const& rawTx, std::string const& expectHash) { + // clang-format off + const std::string request = R"({"jsonrpc":"2.0","id":1132123, "method":"eth_sendRawTransaction","params":[")" + rawTx + R"("]})"; + // clang-format on + auto rawTxBytes = fromHexWithPrefix(rawTx); + auto rawTxBytesRef = bcos::ref(rawTxBytes); + Web3Transaction rawWeb3Tx; + codec::rlp::decode(rawTxBytesRef, rawWeb3Tx); + onRPCRequestWrapper(request, [](auto&&) {}); + // validRespCheck(response); + // BOOST_CHECK(response["id"].asInt64() == 1132123); + // BOOST_CHECK(response["result"].asString() == expectHash); + std::vector hashes = {HashType(expectHash)}; + task::wait([](Web3TestFixture* self, decltype(hashes) m_hashes, + decltype(rawWeb3Tx) rawWeb3Tx) -> task::Task { + auto txs = co_await self->txPool->getTransactions(m_hashes); + BOOST_CHECK(txs.size() == 1); + BOOST_CHECK(txs[0]->hash() == m_hashes[0]); + BOOST_CHECK(txs[0]->type() == 1); + BOOST_CHECK(!txs[0]->extraTransactionBytes().empty()); + auto ref = bytesRef(const_cast(txs[0]->extraTransactionBytes().data()), + txs[0]->extraTransactionBytes().size()); + auto const chainId = txs[0]->chainId(); + BOOST_CHECK(chainId == std::to_string(rawWeb3Tx.chainId.value_or(0))); + Web3Transaction tx; + bcos::codec::rlp::decode(ref, tx); + BOOST_CHECK(tx.type == rawWeb3Tx.type); + BOOST_CHECK(tx.data == rawWeb3Tx.data); + BOOST_CHECK(tx.nonce == rawWeb3Tx.nonce); + BOOST_CHECK(tx.to == rawWeb3Tx.to); + BOOST_CHECK(tx.value == rawWeb3Tx.value); + BOOST_CHECK(tx.maxFeePerGas == rawWeb3Tx.maxFeePerGas); + BOOST_CHECK(tx.maxPriorityFeePerGas == rawWeb3Tx.maxPriorityFeePerGas); + BOOST_CHECK(tx.gasLimit == rawWeb3Tx.gasLimit); + BOOST_CHECK(tx.accessList == rawWeb3Tx.accessList); + }(this, std::move(hashes), std::move(rawWeb3Tx))); + }; + + // clang-format off + // https://etherscan.io/tx/0x6f55e12280765243993c76e80e244ecad548aff22380c8e89e060a60deab4b28 + testLegacyTx( + "0xf902ee83031ae9850256506a88831b40d094d56e4eab23cb81f43168f9f45211eb027b9ac7cc80b90284b143044b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000650000000000000000000000004d73adb72bc3dd368966edd0f0b2148401a178e200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000661d084300000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000084704316e5000000000000000000000000000000000000000000000000000000000000006e740e551c6ee78d757128b8e476b390f891c54e96538e1bb4469a62105220215a0000000000000000000000000000000000000000000000000000000000000014740e551c6ee78d757128b8e476b390f891c54e96538e1bb4469a62105220215a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082ce44cffc92754f8825e1c60cadc5f54a504b769ee616e9f28a9797c2a4b84d11542a1aa4a06a6aa60ee062a36c4fb467145b8914959e42323a13068e2475bff41c67af8dd96034675776cb78036711c1f320b82085df5ee005ffca77959f29a0a07a226241787f06b57483d509e0befd84e5ac84d960e881c7b0853ee1d7c63dff1b00000000000000000000000000000000000000000000000000000000000026a04932608e9743aaa76626f082cedb5da11ff8a1ebe6b5a62a372c81393b5912aea012f36de80608ba3b0f72f3e8f299379ce2802e64b1cbb55275ad9aaa81190b44", + "0x6f55e12280765243993c76e80e244ecad548aff22380c8e89e060a60deab4b28"); + // https://etherscan.io/tx/0x4cbe0f1e383e4aec60cedd0e8dc01832efd160415d82557a72853ec1a58e615b + testLegacyTx("0xf9014d8203ca8504a817c800830f42408080b8f9606060405260008054600160a060020a0319163317905560d78060226000396000f36060604052361560275760e060020a600035046341c0e1b58114606e578063e5225381146096575b60d56000341115606c57346060908152605890600160a060020a033316907f90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a1590602090a35b565b60d5600054600160a060020a0390811633919091161415606c57600054600160a060020a0316ff5b60d5600054600160a060020a0390811633919091161415606c5760008054600160a060020a039081169190301631606082818181858883f15050505050565b001ca0c11d1e703f19bd676fcd8cb0c2f6d9febd4c117692215e92a8933f0c34e2ea85a03a9cd0c207899aa699d287025fa13073a461485a59a4fe0aa246db9a2428e7a7", + "0x4cbe0f1e383e4aec60cedd0e8dc01832efd160415d82557a72853ec1a58e615b"); + // https://etherscan.io/tx/0xdd2294fae0bce9409e3f52684b9947613147b9fe2fcb8efb648ca096728236f5 + testLegacyTx("0xf9014d8203bb8504a817c800830f42408080b8f9606060405260008054600160a060020a0319163317905560d78060226000396000f36060604052361560275760e060020a600035046341c0e1b58114606e578063e5225381146096575b60d56000341115606c57346060908152605890600160a060020a033316907f90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a1590602090a35b565b60d5600054600160a060020a0390811633919091161415606c57600054600160a060020a0316ff5b60d5600054600160a060020a0390811633919091161415606c5760008054600160a060020a039081169190301631606082818181858883f15050505050565b001ca0e97321e5cdb2d677b840337568b057cdf9748659f47fc92e3d29c5d418c36f01a02c2eb384060b1784c0c5b4029d704bc75ae9cbcd17994db535e2cc4863ec1973", + "0xdd2294fae0bce9409e3f52684b9947613147b9fe2fcb8efb648ca096728236f5"); + // clang-format on +} + +BOOST_AUTO_TEST_CASE(handleEIP1559TxTest) +{ + // method eth_sendRawTransaction + auto testEIP1559Tx = [&](std::string const& rawTx, std::string const& expectHash) { + // clang-format off + const std::string request = R"({"jsonrpc":"2.0","id":1132123, "method":"eth_sendRawTransaction","params":[")" + rawTx + R"("]})"; + // clang-format on + auto rawTxBytes = fromHexWithPrefix(rawTx); + auto rawTxBytesRef = bcos::ref(rawTxBytes); + Web3Transaction rawWeb3Tx; + codec::rlp::decode(rawTxBytesRef, rawWeb3Tx); + onRPCRequestWrapper(request, [](auto&&) {}); + // validRespCheck(response); + // BOOST_CHECK(response["id"].asInt64() == 1132123); + // BOOST_CHECK(response["result"].asString() == expectHash); + std::vector hashes = {HashType(expectHash)}; + task::wait([&rawWeb3Tx]( + Web3TestFixture* self, decltype(hashes) m_hashes) -> task::Task { + auto txs = co_await self->txPool->getTransactions(m_hashes); + BOOST_CHECK(txs.size() == 1); + BOOST_CHECK(txs[0] != nullptr); + BOOST_CHECK(txs[0]->hash() == m_hashes[0]); + BOOST_CHECK(txs[0]->type() == 1); + BOOST_CHECK(!txs[0]->extraTransactionBytes().empty()); + auto ref = bytesRef(const_cast(txs[0]->extraTransactionBytes().data()), + txs[0]->extraTransactionBytes().size()); + Web3Transaction tx; + bcos::codec::rlp::decode(ref, tx); + BOOST_CHECK(tx.type == rawWeb3Tx.type); + BOOST_CHECK(tx.data == rawWeb3Tx.data); + BOOST_CHECK(tx.nonce == rawWeb3Tx.nonce); + BOOST_CHECK(tx.to == rawWeb3Tx.to); + BOOST_CHECK(tx.value == rawWeb3Tx.value); + BOOST_CHECK(tx.maxFeePerGas == rawWeb3Tx.maxFeePerGas); + BOOST_CHECK(tx.maxPriorityFeePerGas == rawWeb3Tx.maxPriorityFeePerGas); + BOOST_CHECK(tx.gasLimit == rawWeb3Tx.gasLimit); + BOOST_CHECK(tx.accessList == rawWeb3Tx.accessList); + BOOST_CHECK(tx.chainId == rawWeb3Tx.chainId); + }(this, std::move(hashes))); + return rawWeb3Tx; + }; + + // clang-format off + // https://etherscan.io/tx/0x5b2f242c755ec9f9ed36628331991bd6c90b712b5867a0c7f3a4516caf09cc68 + testEIP1559Tx( + "0x02f871018308b3e6808501cd2ec1d7826ac194ba1951df0c0a52af23857c5ab48b4c43a57e7ed1872700f2d0ba3db080c001a069be171dfa805790a28f1bfcd131eb2aa8f345f601c4a3659de4ae8d624a7b89a06e0f6ed7d035397547aeac0e5130847570f4b607350f71c1391b7cb7f9dd604c", + "0x5b2f242c755ec9f9ed36628331991bd6c90b712b5867a0c7f3a4516caf09cc68"); + // https://etherscan.io/tx/0x698f4dc5e9948d62733d84c4adce663e06fe9c2a42c1bce9dc675fc0ae66cfdc + testEIP1559Tx("0x02f8b00181c4830ecd1085026856b07782b744944c9edd5852cd905f086c759e8383e09bff1e68b380b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a0170f981789f3265ec823403335db3c9da85a191720417b9d29253cbd56e6a3c9a05f8dd3422141cc9c158a776069769fd6b6ac35d1a9c6a2b3d3722b28fd5f9759", + "0x698f4dc5e9948d62733d84c4adce663e06fe9c2a42c1bce9dc675fc0ae66cfdc"); + // https://etherscan.io/tx/0x91d638aec203ed00f60ac4b89916d6a79532a6acc24088286891c978d66106ce + testEIP1559Tx("0x02f8b3018310468983e4e1c085746a528800830249f094a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000a4909347fe9cfd582a382253fc4a5710d32c016b0000000000000000000000000000000000000000000000000000000042968240c080a06d687ed8689c5f022c97cb4be0053d01e2d7c03236fc4bc5765169a29600fa2ca023bee5a2365088addf6ac5844d6ccfebdeddb979165aaa7371813eb1b3a89e37", + "0x91d638aec203ed00f60ac4b89916d6a79532a6acc24088286891c978d66106ce"); + // clang-format on +} + +BOOST_AUTO_TEST_CASE(handleEIP4844TxTest) +{ + // method eth_sendRawTransaction + auto testEIP4844Tx = [&](std::string const& rawTx, std::string const& expectHash) { + // clang-format off + const std::string request = R"({"jsonrpc":"2.0","id":1132123, "method":"eth_sendRawTransaction","params":[")" + rawTx + R"("]})"; + // clang-format on + auto rawTxBytes = fromHexWithPrefix(rawTx); + auto rawTxBytesRef = bcos::ref(rawTxBytes); + Web3Transaction rawWeb3Tx; + codec::rlp::decode(rawTxBytesRef, rawWeb3Tx); + onRPCRequestWrapper(request, [](auto&&) {}); + // validRespCheck(response); + // BOOST_CHECK(response["id"].asInt64() == 1132123); + // BOOST_CHECK(response["result"].asString() == expectHash); + std::vector hashes = {HashType(expectHash)}; + task::wait([&rawWeb3Tx]( + Web3TestFixture* self, decltype(hashes) m_hashes) -> task::Task { + auto txs = co_await self->txPool->getTransactions(m_hashes); + BOOST_CHECK(txs.size() == 1); + BOOST_CHECK(txs[0]->hash() == m_hashes[0]); + BOOST_CHECK(txs[0]->type() == 1); + BOOST_CHECK(!txs[0]->extraTransactionBytes().empty()); + auto ref = bytesRef(const_cast(txs[0]->extraTransactionBytes().data()), + txs[0]->extraTransactionBytes().size()); + Web3Transaction tx; + bcos::codec::rlp::decode(ref, tx); + BOOST_CHECK(tx.type == rawWeb3Tx.type); + BOOST_CHECK(tx.data == rawWeb3Tx.data); + BOOST_CHECK(tx.nonce == rawWeb3Tx.nonce); + BOOST_CHECK(tx.to == rawWeb3Tx.to); + BOOST_CHECK(tx.value == rawWeb3Tx.value); + BOOST_CHECK(tx.maxFeePerGas == rawWeb3Tx.maxFeePerGas); + BOOST_CHECK(tx.maxPriorityFeePerGas == rawWeb3Tx.maxPriorityFeePerGas); + BOOST_CHECK(tx.gasLimit == rawWeb3Tx.gasLimit); + BOOST_CHECK(tx.accessList == rawWeb3Tx.accessList); + BOOST_CHECK(tx.chainId == rawWeb3Tx.chainId); + BOOST_CHECK(tx.maxFeePerBlobGas == rawWeb3Tx.maxFeePerBlobGas); + BOOST_CHECK(tx.blobVersionedHashes == rawWeb3Tx.blobVersionedHashes); + }(this, std::move(hashes))); + return rawWeb3Tx; + }; + + // clang-format off + // https://etherscan.io/tx/0x637b7e88d7a0992b62a973acd41736ee2b63be1c86d0ffeb683747964daea892 + testEIP4844Tx( + "0x03f902fd018309a7ee8405f5e1008522ecb25c008353ec6094c662c410c0ecf747543f5ba90660f6abebd9c8c480b90264b72d42a100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000d06ad5334c4498113f3cc1548635e7c32bb72562421fddef1e60d165d8035ebe2031fe8a2addb2bec3c5c43b6168e3dac21f84cddbfae7d9e24977fbee8038772000000000000000000000000000000000000000000000000000000000009a7ed04ef432aa69fe0dd81c5d5d07464120008bbaede9cf73c4ca149f0be56acfbf605ba2078240f1585f96424c2d1ee48211da3b3f9177bf2b9880b4fc91d59e9a200000000000000000000000000000000000000000000000000000000000000010000000000000000df5459e0fefcbcfb5585179c4dfde48bc334f5836ad2821f0000000000000000883e48453eb072b0e184b27522f52d8324ee5d6ee778b05002c8df8c56bb3f47ab53b12be77ca1192abf4821a3e2b32c24d34af793fbcdca00000000000000000000000000000000cf420df13fc18924e41f7b0bd34de6a6000000000000000000000000000000000f389c6a65227d0e916c9670e8398e0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003088cf8a7c3410fc132c573b94603dd2cf6e25555808a487c925ae595f6e2d1907907ba57a3fb42729eb1de453ebeda6bc00000000000000000000000000000000c08522ecb25c00e1a00131e940049304bb30d0da71fcccc542712238c5e2aa6ad96cf7eb5fa8ef974880a0f9ea6a59f3f160f36e1e2248085ec9d3bc3797462b4e5fd2c53f6753ddc88cc3a06e0aa62f613475a881564eb9269762b466560d4870835a198e12f6d5e05a9eaa", + "0x637b7e88d7a0992b62a973acd41736ee2b63be1c86d0ffeb683747964daea892"); + // https://etherscan.io/tx/0x82d366c93c5fb2cbe8f1b6f0f19c260d11a958875560a398d75784ce9f4d6adf + testEIP4844Tx("0x03f9013b018310fcdb847735940085041e2a063282520894ff000000000000000000000000000000000000108080c0843b9aca00f8c6a001a74d77682287763fc9a460f1c0a2f686daa8bdbb8757564c0fc05f54bbd6d5a0015d0db22bb787710c9d112373a0a49132646f0813c478d15ed247c148982172a00185ece9183ab795a0bfd3326d5b64848d9839992ac3f3ce4a55afb6e7ba4520a0013d1e438e3fb85830cb49bad9598a1ac720b778796c67a00ed8e20eebde05c5a001ff9dae3fb9320795caa1b474a9f667f2022e0e3d8a678ebfe163d7801908c1a001622f10864730dcd603857f00824ad18e070c64b57f9878738db1f85cfad33880a04539a1636f5c431241be963c898bc59491ed7c01853b75ed9e9e9bc47d0aa9ada031880afe746833518e6ea271ecd6a3943fa6da09ba1f889531729a1c8270e7cc", + "0x82d366c93c5fb2cbe8f1b6f0f19c260d11a958875560a398d75784ce9f4d6adf"); + // https://etherscan.io/tx/0xa8fd95a70b4f2b6cea8c52bcb782b5c3f806a0d5250cf75a1d97a6e899f09979 + testEIP4844Tx("0x03f9013a0182a8f98477359400850432ec4812825208946f54ca6f6ede96662024ffd61bfd18f3f4e34dff8080c0843b9aca00f8c6a001fb60d5b0abeff9e3d47099386a31eed62cd55d54aa146b42d10eb81b0a9b2aa00156b2193030eb7c9496d8586492934a57a5ebd0fac816ef1ea01dc4d09498c5a001bd78704a4b015adec86a654a123045312b083641ababec0e60ef17be4453e7a001b59b9a8b8d35bd6afcff91c6afc56ebcaf4a62f6e83f5e57aac4484f022cc4a00110aac506aff4957e40d4640f0763015b84bbb8c23f50f1b13d501067bea878a001f100a97a70563cd623e588c976155ffe5999d1e9258302d969964d7524bd0280a08c1cff27365c5fe0a6ebfe5e010b3460747489302547f3ba5b181390fad485ada02af2f430e0dfad48ba78853b59486ea7425835c3a7034bb22702234f8994288f", + "0xa8fd95a70b4f2b6cea8c52bcb782b5c3f806a0d5250cf75a1d97a6e899f09979"); + // clang-format on +} +BOOST_AUTO_TEST_CASE(logMatcherTest) +{ + // clang-format off + h256 A(fromHexWithPrefix(std::string("0x7ac7a04970319ae8fc5b92fe177d000fee3c00c92f8e78aae13d6571f17c351f"))); + h256 B(fromHexWithPrefix(std::string("0x1fe46fe782fa5618721cdf61de2e50c0639f4b26f6568f9c67b128f5610ced68"))); + h256 C(fromHexWithPrefix(std::string("0x0c5ace674d8e621803f806d73d9d03a49b2694f13186b8d657c9df689fe4e478"))); + h256 D(fromHexWithPrefix(std::string("0xf0173383cebedea9e486292201bc460817279651f0b05c46c0ad1f5e094e62d9"))); + h256 E(fromHexWithPrefix(std::string("0x7b0682ce692786e3ad67f5ff45e7fe63c1dbed629f5f016c605b814d905b6447"))); + h256 F(fromHexWithPrefix(std::string("0xa0f422234f24bda551aee9753bb806b078efa605b128061af1be03e7ce54e64d"))); + h256 G(fromHexWithPrefix(std::string("0xba2b5bce04969801bc79406f18a929f465e83afbb138834d5ef55a0112018a8f"))); + h256 H(fromHexWithPrefix(std::string("0x8518c89a1be62e684eac2fcfad1682ea6fbccff0e436fb7c8ce19c64ad569f79"))); + bytes address1(fromHexWithPrefix(std::string("0x6849f21d1e455e9f0712b1e99fa4fcd23758e8f1"))); + bytes address2(fromHexWithPrefix(std::string("0xc75ebd24f8ae4f17be209b30dc8dea4fadcf67ce"))); + // clang-format on + + LogMatcher matcher; + + // 1.[] "anything" + { + // [A, B] + protocol::LogEntry log1(address1, {A, B}, bytes()); + // [C, D] + protocol::LogEntry log2(address1, {C, D}, bytes()); + auto params1 = std::make_shared(); + auto params2 = std::make_shared(); + params2->addAddress(toHexStringWithPrefix(address1)); + BOOST_CHECK(matcher.matches(params1, log1)); + BOOST_CHECK(matcher.matches(params1, log2)); + BOOST_CHECK(!matcher.matches(params2, log1)); + BOOST_CHECK(!matcher.matches(params2, log2)); + } + // 2.[A] "A in first position (and anything after)" + { + auto params = std::make_shared(); + params->resizeTopic(1); + params->addTopic(0, toHexStringWithPrefix(A)); + protocol::LogEntry log; + // [A] + log = protocol::LogEntry(address1, {A}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, B] + log = protocol::LogEntry(address1, {A, B}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [C] + log = protocol::LogEntry(address1, {C}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [C, D] + log = protocol::LogEntry(address1, {C}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + // 3.[null, B] "anything in first position AND B in second position (and anything after)" + { + auto params = std::make_shared(); + params->resizeTopic(2); + params->addTopic(1, toHexStringWithPrefix(B)); + protocol::LogEntry log; + // matched + // [A, B] + log = protocol::LogEntry(address1, {A, B}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [C, B] + log = protocol::LogEntry(address1, {C, B}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, B, F] + log = protocol::LogEntry(address1, {A, B, F}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, B, E] + log = protocol::LogEntry(address1, {A, B, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [C, B, F] + log = protocol::LogEntry(address1, {C, B, F}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [C, B, E] + log = protocol::LogEntry(address1, {C, B, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // mismatched + // [A, G] + log = protocol::LogEntry(address1, {A, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [C, G] + log = protocol::LogEntry(address1, {C, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [A, G, F] + log = protocol::LogEntry(address1, {A, G, F}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [A, G, E] + log = protocol::LogEntry(address1, {A, G, E}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [C, G, F] + log = protocol::LogEntry(address1, {C, G, F}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [C, G, E] + log = protocol::LogEntry(address1, {C, G, E}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + // 4.[A, B] "A in first position AND B in second position (and anything after)" + { + auto params = std::make_shared(); + params->resizeTopic(2); + params->addTopic(0, toHexStringWithPrefix(A)); + params->addTopic(1, toHexStringWithPrefix(B)); + protocol::LogEntry log; + + // matched + // [A, B] + log = protocol::LogEntry(address1, {A, B}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, B, C] + log = protocol::LogEntry(address1, {A, B, C}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, B, E] + log = protocol::LogEntry(address1, {A, B, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + + // mismatched + // [A] + log = protocol::LogEntry(address1, {A}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [A, C] + log = protocol::LogEntry(address1, {A, C}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [A, C, F] + log = protocol::LogEntry(address1, {A, C, F}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B] + log = protocol::LogEntry(address1, {B}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B, C] + log = protocol::LogEntry(address1, {B, C}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B, C, F] + log = protocol::LogEntry(address1, {B, C, F}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + // 5.[[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything + // after)" + { + auto params = std::make_shared(); + params->resizeTopic(2); + params->addTopic(0, toHexStringWithPrefix(A)); + params->addTopic(0, toHexStringWithPrefix(B)); + params->addTopic(1, toHexStringWithPrefix(C)); + params->addTopic(1, toHexStringWithPrefix(D)); + protocol::LogEntry log; + + // matched + { + // [A, C] + log = protocol::LogEntry(address1, {A, C}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, D] + log = protocol::LogEntry(address1, {A, D}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [B, C] + log = protocol::LogEntry(address1, {B, C}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [B, D] + log = protocol::LogEntry(address1, {B, D}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + } + { + // [A, C, E] + log = protocol::LogEntry(address1, {A, C, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, D, E] + log = protocol::LogEntry(address1, {A, D, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [B, C, E] + log = protocol::LogEntry(address1, {B, C, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [B, D, E] + log = protocol::LogEntry(address1, {B, D, E}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + } + { + // [A, C, F] + log = protocol::LogEntry(address1, {A, C, F}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [A, D, F] + log = protocol::LogEntry(address1, {A, D, F}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [B, C, F] + log = protocol::LogEntry(address1, {B, C, F}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + // [B, D, F] + log = protocol::LogEntry(address1, {B, D, F}, bytes()); + BOOST_CHECK(matcher.matches(params, log)); + } + // mismatched + { + // [A] + log = protocol::LogEntry(address1, {A}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B] + log = protocol::LogEntry(address1, {B}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + { + // [E, C] + log = protocol::LogEntry(address1, {E, C}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [E, D] + log = protocol::LogEntry(address1, {E, D}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [F, C] + log = protocol::LogEntry(address1, {F, C}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [F, D] + log = protocol::LogEntry(address1, {F, D}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + { + // [A, E] + log = protocol::LogEntry(address1, {A, E}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [A, F] + log = protocol::LogEntry(address1, {A, F}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B, E] + log = protocol::LogEntry(address1, {B, E}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B, F] + log = protocol::LogEntry(address1, {B, F}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + { + // [E, C, G] + log = protocol::LogEntry(address1, {E, C, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [E, D, G] + log = protocol::LogEntry(address1, {E, D, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [F, C, G] + log = protocol::LogEntry(address1, {F, C, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [F, D, G] + log = protocol::LogEntry(address1, {F, D, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + { + // [A, E, G] + log = protocol::LogEntry(address1, {A, E, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [A, F, G] + log = protocol::LogEntry(address1, {A, F, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B, E, G] + log = protocol::LogEntry(address1, {B, E, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + // [B, F, G] + log = protocol::LogEntry(address1, {B, F, G}, bytes()); + BOOST_CHECK(!matcher.matches(params, log)); + } + } +} +BOOST_AUTO_TEST_SUITE_END() +} // namespace bcos::test \ No newline at end of file diff --git a/bcos-rpc/test/unittests/rpc/Web3TypeTest.cpp b/bcos-rpc/test/unittests/rpc/Web3TypeTest.cpp new file mode 100644 index 0000000000..d6e6225d05 --- /dev/null +++ b/bcos-rpc/test/unittests/rpc/Web3TypeTest.cpp @@ -0,0 +1,334 @@ +/** + * Copyright (C) 2024 FISCO BCOS. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @file Web3TypeTest.cpp + * @author: kyonGuo + * @date 2024/4/9 + */ + +#include "../common/RPCFixture.h" +#include +#include +#include +#include +#include + +using namespace bcos; +using namespace bcos::rpc; +using namespace bcos::codec::rlp; +namespace bcos::test +{ +static const std::vector s_accessList{ + {Address("0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae"), + { + HashType("0x0000000000000000000000000000000000000000000000000000000000000003"), + HashType("0x0000000000000000000000000000000000000000000000000000000000000007"), + }}, + {Address("0xbb9bc244d798123fde783fcc1c72d3bb8c189413"), {}}, +}; +BOOST_FIXTURE_TEST_SUITE(testWeb3Type, RPCFixture) +BOOST_AUTO_TEST_CASE(testLegacyTransactionDecode) +{ + // clang-format off + constexpr std::string_view rawTx = "0xf89b0c8504a817c80082520894727fc6a68321b754475c668a6abfb6e9e71c169a888ac7230489e80000afa9059cbb000000000213ed0f886efd100b67c7e4ec0a85a7d20dc971600000000000000000000015af1d78b58c400026a0be67e0a07db67da8d446f76add590e54b6e92cb6b8f9835aeb67540579a27717a02d690516512020171c1ec870f6ff45398cc8609250326be89915fb538e7bd718"; + // constexpr std::string_view rawTx = "0xf8ac82017c8504a817c800835fefd89409d07ecb4d6f32e91503c04b192e3bdeb7f857f480b8442c7128d700000000000000000000000000000000000000000000000000009bc24e89949a00000000000000000000000000000000000000000000000000000000000000001ba0cd372eb41b6b4e9e576233bb29c1492e0329fac1331f492a69e4a1b586a1a28ba032950cc4184ca8b0d45b24d13345157b4153d7ccc0d187dbab018be07726d186"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(!e); + BOOST_CHECK(tx.type == rpc::TransactionType::Legacy); + BOOST_CHECK(tx.chainId.has_value()); + BOOST_CHECK_EQUAL(tx.chainId.value(), 1); + BOOST_CHECK_EQUAL(tx.nonce, 12); + BOOST_CHECK_EQUAL(tx.nonce, 12); + BOOST_CHECK_EQUAL(tx.maxPriorityFeePerGas, 20000000000); + BOOST_CHECK_EQUAL(tx.maxFeePerGas, 20000000000); + BOOST_CHECK_EQUAL(tx.gasLimit, 21000); + BOOST_CHECK_EQUAL(tx.to, Address("0x727fc6a68321b754475c668a6abfb6e9e71c169a")); + BOOST_CHECK_EQUAL(tx.value, 10000000000000000000ull); + BOOST_CHECK_EQUAL(toHex(tx.data), + "a9059cbb000000000213ed0f886efd100b67c7e4ec0a85a7d20dc9" + "71600000000000000000000015af1d78b58c4000"); + BOOST_CHECK_EQUAL( + toHex(tx.signatureR), "be67e0a07db67da8d446f76add590e54b6e92cb6b8f9835aeb67540579a27717"); + BOOST_CHECK_EQUAL( + toHex(tx.signatureS), "2d690516512020171c1ec870f6ff45398cc8609250326be89915fb538e7bd718"); + BOOST_CHECK_EQUAL(tx.getSignatureV(), tx.chainId.value() * 2 + 35 + 1); + + auto hash = tx.txHash().hexPrefixed(); + BOOST_CHECK_EQUAL(hash, bcos::crypto::keccak256Hash(ref(bytes)).hexPrefixed()); + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); +} + +BOOST_AUTO_TEST_CASE(testEIP2930Transaction) +{ + // clang-format off + constexpr std::string_view rawTx = "0x01f8f205078506fc23ac008357b58494811a752c8cd697e3cb27279c330ed1ada745a8d7881bc16d674ec80000906ebaf477f83e051589c1188bcc6ddccdf872f85994de0b295669a9fd93d5f28d9ec85e40f4cb697baef842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007d694bb9bc244d798123fde783fcc1c72d3bb8c189413c080a036b241b061a36a32ab7fe86c7aa9eb592dd59018cd0443adc0903590c16b02b0a05edcc541b4741c5cc6dd347c5ed9577ef293a62787b4510465fadbfe39ee4094"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(e == nullptr); + BOOST_CHECK(tx.type == rpc::TransactionType::EIP2930); + BOOST_CHECK(tx.chainId.has_value()); + BOOST_CHECK_EQUAL(tx.chainId.value(), 5); + BOOST_CHECK_EQUAL(tx.nonce, 7); + BOOST_CHECK_EQUAL(tx.maxPriorityFeePerGas, 30000000000); + BOOST_CHECK_EQUAL(tx.maxFeePerGas, 30000000000); + BOOST_CHECK_EQUAL(tx.gasLimit, 5748100); + BOOST_CHECK_EQUAL(tx.to, Address("0x811a752c8cd697e3cb27279c330ed1ada745a8d7")); + BOOST_CHECK_EQUAL(tx.value, 2000000000000000000ull); + BOOST_CHECK_EQUAL(toHex(tx.data), "6ebaf477f83e051589c1188bcc6ddccd"); + BOOST_CHECK_EQUAL(tx.getSignatureV(), tx.chainId.value() * 2 + 35); + BOOST_CHECK_EQUAL( + toHex(tx.signatureR), "36b241b061a36a32ab7fe86c7aa9eb592dd59018cd0443adc0903590c16b02b0"); + BOOST_CHECK_EQUAL( + toHex(tx.signatureS), "5edcc541b4741c5cc6dd347c5ed9577ef293a62787b4510465fadbfe39ee4094"); + BOOST_CHECK(tx.accessList == s_accessList); + + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); +} + +BOOST_AUTO_TEST_CASE(testEIP1559Transaction) +{ + // clang-format off + constexpr std::string_view rawTx = "0x02f8f805078502540be4008506fc23ac008357b58494811a752c8cd697e3cb27279c330ed1ada745a8d7881bc16d674ec80000906ebaf477f83e051589c1188bcc6ddccdf872f85994de0b295669a9fd93d5f28d9ec85e40f4cb697baef842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007d694bb9bc244d798123fde783fcc1c72d3bb8c189413c080a036b241b061a36a32ab7fe86c7aa9eb592dd59018cd0443adc0903590c16b02b0a05edcc541b4741c5cc6dd347c5ed9577ef293a62787b4510465fadbfe39ee4094"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(e == nullptr); + BOOST_CHECK(tx.type == rpc::TransactionType::EIP1559); + BOOST_CHECK(tx.chainId.has_value()); + BOOST_CHECK_EQUAL(tx.chainId.value(), 5); + BOOST_CHECK_EQUAL(tx.nonce, 7); + BOOST_CHECK_EQUAL(tx.maxPriorityFeePerGas, 10000000000); + BOOST_CHECK_EQUAL(tx.maxFeePerGas, 30000000000); + BOOST_CHECK_EQUAL(tx.gasLimit, 5748100); + BOOST_CHECK_EQUAL(tx.to, Address("0x811a752c8cd697e3cb27279c330ed1ada745a8d7")); + BOOST_CHECK_EQUAL(tx.value, 2000000000000000000ull); + BOOST_CHECK_EQUAL(toHex(tx.data), "6ebaf477f83e051589c1188bcc6ddccd"); + BOOST_CHECK_EQUAL(tx.getSignatureV(), tx.chainId.value() * 2 + 35); + BOOST_CHECK_EQUAL( + toHex(tx.signatureR), "36b241b061a36a32ab7fe86c7aa9eb592dd59018cd0443adc0903590c16b02b0"); + BOOST_CHECK_EQUAL( + toHex(tx.signatureS), "5edcc541b4741c5cc6dd347c5ed9577ef293a62787b4510465fadbfe39ee4094"); + BOOST_CHECK(tx.accessList == s_accessList); + + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); +} + +BOOST_AUTO_TEST_CASE(testEIP4844Transaction) +{ + // clang-format off + constexpr std::string_view rawTx = "0x03f9012705078502540be4008506fc23ac008357b58494811a752c8cd697e3cb27279c330ed1ada745a8d7808204f7f872f85994de0b295669a9fd93d5f28d9ec85e40f4cb697baef842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007d694bb9bc244d798123fde783fcc1c72d3bb8c189413c07bf842a0c6bdd1de713471bd6cfa62dd8b5a5b42969ed09e26212d3377f3f8426d8ec210a08aaeccaf3873d07cef005aca28c39f8a9f8bdb1ec8d79ffc25afc0a4fa2ab73601a036b241b061a36a32ab7fe86c7aa9eb592dd59018cd0443adc0903590c16b02b0a05edcc541b4741c5cc6dd347c5ed9577ef293a62787b4510465fadbfe39ee4094"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(e == nullptr); + BOOST_CHECK(tx.type == rpc::TransactionType::EIP4844); + BOOST_CHECK(tx.chainId.has_value()); + BOOST_CHECK_EQUAL(tx.chainId.value(), 5); + BOOST_CHECK_EQUAL(tx.nonce, 7); + BOOST_CHECK_EQUAL(tx.maxPriorityFeePerGas, 10000000000); + BOOST_CHECK_EQUAL(tx.maxFeePerGas, 30000000000); + BOOST_CHECK_EQUAL(tx.gasLimit, 5748100); + BOOST_CHECK_EQUAL(tx.to, Address("0x811a752c8cd697e3cb27279c330ed1ada745a8d7")); + BOOST_CHECK_EQUAL(tx.value, 0); + BOOST_CHECK_EQUAL(toHex(tx.data), "04f7"); + BOOST_CHECK_EQUAL(tx.maxFeePerBlobGas, 123); + BOOST_CHECK_EQUAL(tx.blobVersionedHashes.size(), 2); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[0]), + "c6bdd1de713471bd6cfa62dd8b5a5b42969ed09e26212d3377f3f8426d8ec210"); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[1]), + "8aaeccaf3873d07cef005aca28c39f8a9f8bdb1ec8d79ffc25afc0a4fa2ab736"); + BOOST_CHECK_EQUAL(tx.getSignatureV(), tx.chainId.value() * 2 + 35 + 1); + BOOST_CHECK_EQUAL( + toHex(tx.signatureR), "36b241b061a36a32ab7fe86c7aa9eb592dd59018cd0443adc0903590c16b02b0"); + BOOST_CHECK_EQUAL( + toHex(tx.signatureS), "5edcc541b4741c5cc6dd347c5ed9577ef293a62787b4510465fadbfe39ee4094"); + BOOST_CHECK(tx.accessList == s_accessList); + + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); +} + +BOOST_AUTO_TEST_CASE(recoverAddress) +{ + // clang-format off + constexpr std::string_view rawTx = "0xf8ac82017c8504a817c800835fefd89409d07ecb4d6f32e91503c04b192e3bdeb7f857f480b8442c7128d700000000000000000000000000000000000000000000000000009bc24e89949a00000000000000000000000000000000000000000000000000000000000000001ba0cd372eb41b6b4e9e576233bb29c1492e0329fac1331f492a69e4a1b586a1a28ba032950cc4184ca8b0d45b24d13345157b4153d7ccc0d187dbab018be07726d186"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(!e); + BOOST_CHECK(tx.type == rpc::TransactionType::Legacy); + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); + + auto encodeForSign = tx.encodeForSign(); + bcos::bytes sign{}; + sign.insert(sign.end(), tx.signatureR.begin(), tx.signatureR.end()); + sign.insert(sign.end(), tx.signatureS.begin(), tx.signatureS.end()); + sign.push_back(tx.signatureV); + auto hashImpl = std::make_shared(); + auto hash = bcos::crypto::keccak256Hash(ref(encodeForSign)); + auto signatureImpl = std::make_shared(); + auto [re, addr] = signatureImpl->recoverAddress(*hashImpl, hash, ref(sign)); + BOOST_CHECK(re); + auto address = toHexStringWithPrefix(addr); + BOOST_CHECK_EQUAL(address, "0xec5e7dec9d2d6bfa1f2221ace01ae3deb6906fb0"); +} + +BOOST_AUTO_TEST_CASE(EIP1559Recover) +{ + // https://etherscan.io/tx/0xcf6b53ec88659fc86e854af2e8453fa519ca261f949ef291e33c5f44ead870dc + // clang-format off + constexpr std::string_view rawTx = "0x02f8720183015b148085089a36ae8682520894e10f39a0dfb9e380b6d176eb7183af32b68028d78806e9ba3bd88b600080c080a032ab966d1c9cc2be6952713a1599a95a14f6e92c9f62d7fa40aa62d8b764ffcfa060bdbe7b8e66a0c681a90d4da0c7c0a4ba9321d49fc5c65bfddb847539e35d56"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(e == nullptr); + BOOST_CHECK(tx.type == rpc::TransactionType::EIP1559); + BOOST_CHECK(tx.chainId.has_value()); + BOOST_CHECK_EQUAL(tx.chainId.value(), 1); + BOOST_CHECK_EQUAL(tx.nonce, 88852); + BOOST_CHECK_EQUAL(tx.maxPriorityFeePerGas, 0); + BOOST_CHECK_EQUAL(tx.maxFeePerGas, 36947013254u); + BOOST_CHECK_EQUAL(tx.gasLimit, 21000); + BOOST_CHECK_EQUAL(tx.to.hexPrefixed(), "0xe10f39a0dfb9e380b6d176eb7183af32b68028d7"); + BOOST_CHECK_EQUAL(tx.value, 498134000000000000ull); + BOOST_CHECK_EQUAL(toHex(tx.data), ""); + BOOST_CHECK_EQUAL(tx.getSignatureV(), tx.chainId.value() * 2 + 35); + BOOST_CHECK_EQUAL(tx.txHash().hexPrefixed(), + "0xcf6b53ec88659fc86e854af2e8453fa519ca261f949ef291e33c5f44ead870dc"); + auto txHash = bcos::crypto::keccak256Hash(ref(bytes)).hexPrefixed(); + BOOST_CHECK_EQUAL(txHash, "0xcf6b53ec88659fc86e854af2e8453fa519ca261f949ef291e33c5f44ead870dc"); + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); + + auto encodeForSign = tx.encodeForSign(); + bcos::bytes sign{}; + sign.insert(sign.end(), tx.signatureR.begin(), tx.signatureR.end()); + sign.insert(sign.end(), tx.signatureS.begin(), tx.signatureS.end()); + sign.push_back(tx.signatureV); + auto hashImpl = std::make_shared(); + auto hash = bcos::crypto::keccak256Hash(ref(encodeForSign)); + auto signatureImpl = std::make_shared(); + auto [re, addr] = signatureImpl->recoverAddress(*hashImpl, hash, ref(sign)); + BOOST_CHECK(re); + auto address = toHexStringWithPrefix(addr); + BOOST_CHECK_EQUAL(address, "0x595063172c85b1e8ac2fe74fcb6b7dc26844cc2d"); +} + +BOOST_AUTO_TEST_CASE(EIP4844Recover) +{ + // https://etherscan.io/tx/0x8bb97c1480b533396b0940a0f94ef5974c4989954f52d928e06e38d363bbd560 + // clang-format off + constexpr std::string_view rawTx = "0x03f9049f0183082ef8843b9aca008537942bfdb083036a2b941c479675ad559dc151f6ec7ed3fbf8cee79582b680b8a43e5aa082000000000000000000000000000000000000000000000000000000000008f7060000000000000000000000000000000000000000000000000000000000168763000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb5000000000000000000000000000000000000000000000000000000000a8cc7c7000000000000000000000000000000000000000000000000000000000a8ccabef902c0f8dd941c479675ad559dc151f6ec7ed3fbf8cee79582b6f8c6a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660f90141948315177ab297ba92a06054ce80a67ed4dbd7ed3af90129a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873fbd8da0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873fbd8ea0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f379294a1f89b94e64a54e2533fd126c2e452c5fab544d80e2e4eb5f884a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0e85fd79f89ff278fc57d40aecb7947873df9f0beac531c8f71a98f630e1eab62a07686888b19bb7b75e46bb1aa328b65150743f4899443d722f0adf8e252ccda410af8c6a001f8198b33db3461035e1621dd12498e57cf26efe9578b39054fbe5efdf83032a00152295a881b358db5dcf58b54661ee60f595de7f57eb93030a5d9e57bcae30ea0014ea3a3d4fc547ccb6974c5c4deb7778b755b0b3d56be88c54ef3a39d209b4ca001b378a4a2a4a3806740ec38b5672d213c78bbcae34550d014a265fc262fe06ea001b83eca80127748b71bcaa6a8c9edbfd5a9fb47933032891c27e07668f48867a001904e6186ecd84f6897659777846d5510bfbeb2863a93d8432f0fcf89c3e2c901a028bc2660c742d25de1f9af5550bfb734ac81c1e0d703c285447684872430635aa01788719406012ded6dd859a3a0218cb0acccd3f30a93da6796abc19ba3192fcf"; + // clang-format on + auto bytes = fromHexWithPrefix(rawTx); + auto bRef = bcos::ref(bytes); + Web3Transaction tx{}; + auto e = codec::rlp::decode(bRef, tx); + BOOST_CHECK(e == nullptr); + BOOST_CHECK(tx.type == rpc::TransactionType::EIP4844); + BOOST_CHECK(tx.chainId.has_value()); + BOOST_CHECK_EQUAL(tx.chainId.value(), 1); + BOOST_CHECK_EQUAL(tx.nonce, 536312); + BOOST_CHECK_EQUAL(tx.maxPriorityFeePerGas, 1000000000); + BOOST_CHECK_EQUAL(tx.maxFeePerGas, 238709112240); + BOOST_CHECK_EQUAL(tx.gasLimit, 223787); + BOOST_CHECK_EQUAL(tx.to.hexPrefixed(), "0x1c479675ad559dc151f6ec7ed3fbf8cee79582b6"); + BOOST_CHECK_EQUAL(tx.value, 0); + // clang-format off + BOOST_CHECK_EQUAL(toHex(tx.data), "3e5aa082000000000000000000000000000000000000000000000000000000000008f7060000000000000000000000000000000000000000000000000000000000168763000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb5000000000000000000000000000000000000000000000000000000000a8cc7c7000000000000000000000000000000000000000000000000000000000a8ccabe"); + // clang-format on + BOOST_CHECK_EQUAL(tx.getSignatureV(), tx.chainId.value() * 2 + 36); + BOOST_CHECK_EQUAL(tx.txHash().hexPrefixed(), + "0x8bb97c1480b533396b0940a0f94ef5974c4989954f52d928e06e38d363bbd560"); + + BOOST_CHECK_EQUAL(tx.blobVersionedHashes.size(), 6); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[0]), + "01f8198b33db3461035e1621dd12498e57cf26efe9578b39054fbe5efdf83032"); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[1]), + "0152295a881b358db5dcf58b54661ee60f595de7f57eb93030a5d9e57bcae30e"); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[2]), + "014ea3a3d4fc547ccb6974c5c4deb7778b755b0b3d56be88c54ef3a39d209b4c"); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[3]), + "01b378a4a2a4a3806740ec38b5672d213c78bbcae34550d014a265fc262fe06e"); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[4]), + "01b83eca80127748b71bcaa6a8c9edbfd5a9fb47933032891c27e07668f48867"); + BOOST_CHECK_EQUAL(toHex(tx.blobVersionedHashes[5]), + "01904e6186ecd84f6897659777846d5510bfbeb2863a93d8432f0fcf89c3e2c9"); + + BOOST_CHECK_EQUAL(tx.accessList.size(), 3); + BOOST_CHECK_EQUAL( + tx.accessList[0].account.hexPrefixed(), "0x1c479675ad559dc151f6ec7ed3fbf8cee79582b6"); + BOOST_CHECK_EQUAL(tx.accessList[0].storageKeys.size(), 6); + BOOST_CHECK_EQUAL(toHex(tx.accessList[0].storageKeys[5]), + "a10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660"); + BOOST_CHECK_EQUAL( + tx.accessList[1].account.hexPrefixed(), "0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a"); + BOOST_CHECK_EQUAL(tx.accessList[1].storageKeys.size(), 9); + BOOST_CHECK_EQUAL( + tx.accessList[2].account.hexPrefixed(), "0xe64a54e2533fd126c2e452c5fab544d80e2e4eb5"); + BOOST_CHECK_EQUAL(tx.accessList[2].storageKeys.size(), 4); + + bcos::bytes encoded{}; + codec::rlp::encode(encoded, tx); + auto rawTx2 = toHexStringWithPrefix(encoded); + BOOST_CHECK_EQUAL(rawTx, rawTx2); + + auto encodeForSign = tx.encodeForSign(); + bcos::bytes sign{}; + sign.insert(sign.end(), tx.signatureR.begin(), tx.signatureR.end()); + sign.insert(sign.end(), tx.signatureS.begin(), tx.signatureS.end()); + sign.push_back(tx.signatureV); + auto hashImpl = std::make_shared(); + auto hash = bcos::crypto::keccak256Hash(ref(encodeForSign)); + auto signatureImpl = std::make_shared(); + auto [re, addr] = signatureImpl->recoverAddress(*hashImpl, hash, ref(sign)); + BOOST_CHECK(re); + auto address = toHexStringWithPrefix(addr); + BOOST_CHECK_EQUAL(address, "0xc1b634853cb333d3ad8663715b08f41a3aec47cc"); +} + +BOOST_AUTO_TEST_SUITE_END() +} // namespace bcos::test \ No newline at end of file diff --git a/bcos-scheduler/src/BlockExecutive.cpp b/bcos-scheduler/src/BlockExecutive.cpp index d0a64c68a9..947de24795 100644 --- a/bcos-scheduler/src/BlockExecutive.cpp +++ b/bcos-scheduler/src/BlockExecutive.cpp @@ -1010,13 +1010,14 @@ void BlockExecutive::onDmcExecuteFinish( auto dmcChecksum = m_dmcRecorder->dumpAndClearChecksum(); if (m_staticCall) { - DMC_LOG(TRACE) << LOG_BADGE("Stat") << "DMCExecute.6:" << "\t " << LOG_BADGE("DMCRecorder") - << " DMCExecute for call finished " << LOG_KV("blockNumber", number()) - << LOG_KV("checksum", dmcChecksum); + DMC_LOG(TRACE) << LOG_BADGE("Stat") << "DMCExecute.6:" + << "\t " << LOG_BADGE("DMCRecorder") << " DMCExecute for call finished " + << LOG_KV("blockNumber", number()) << LOG_KV("checksum", dmcChecksum); } else { - DMC_LOG(INFO) << LOG_BADGE("Stat") << "DMCExecute.6:" << "\t " << LOG_BADGE("DMCRecorder") + DMC_LOG(INFO) << LOG_BADGE("Stat") << "DMCExecute.6:" + << "\t " << LOG_BADGE("DMCRecorder") << " DMCExecute for transaction finished " << LOG_KV("blockNumber", number()) << LOG_KV("checksum", dmcChecksum); diff --git a/bcos-scheduler/src/SerialBlockExecutive.h b/bcos-scheduler/src/SerialBlockExecutive.h index 79c90bf937..c2b17e4414 100644 --- a/bcos-scheduler/src/SerialBlockExecutive.h +++ b/bcos-scheduler/src/SerialBlockExecutive.h @@ -68,7 +68,7 @@ class SerialBlockExecutive : public BlockExecutive void onExecuteFinish( std::function callback) override; - void serialPrepareExecutor() override { + void serialPrepareExecutor() override{ // do nothing }; diff --git a/bcos-scheduler/test/mock/MockLedger3.h b/bcos-scheduler/test/mock/MockLedger3.h index 0a780ab1f2..a94681c086 100644 --- a/bcos-scheduler/test/mock/MockLedger3.h +++ b/bcos-scheduler/test/mock/MockLedger3.h @@ -128,6 +128,10 @@ class MockLedger3 : public bcos::ledger::LedgerInterface { _onGetConfig(nullptr, "0", commitBlockNumber); } + else if (_key == ledger::SYSTEM_KEY_WEB3_CHAIN_ID) + { + _onGetConfig(nullptr, "20200", commitBlockNumber); + } else if (RANGES::count(ledger::Features::featureKeys(), _key) > 0) { _onGetConfig(BCOS_ERROR_PTR(-1, "Not found!"), "0", commitBlockNumber); diff --git a/bcos-scheduler/test/testChecksumAddress.cpp b/bcos-scheduler/test/testChecksumAddress.cpp index 3fe2cddcb1..636d0875ee 100644 --- a/bcos-scheduler/test/testChecksumAddress.cpp +++ b/bcos-scheduler/test/testChecksumAddress.cpp @@ -49,10 +49,17 @@ struct ChecksumFixture bcos::crypto::Hash::Ptr hashImpl; - void check(const std::string& addr) + void check(const std::string& addr, uint64_t chainId = 0) { auto ret = addr; - toCheckSumAddress(ret, hashImpl); + if (chainId != 0) + { + toCheckSumAddressWithChainId(ret, hashImpl, chainId); + } + else + { + toCheckSumAddress(ret, hashImpl); + } BOOST_CHECK_EQUAL(addr, ret); } }; @@ -69,5 +76,72 @@ BOOST_AUTO_TEST_CASE(checksumAddress) check("3C589CB0Be25f651b0563e052DEa63d3844C33e6"); } +BOOST_AUTO_TEST_CASE(checkEIP1191) +{ + // test case from https://eips.ethereum.org/EIPS/eip-1191 + auto const eth_mainnet = std::to_array({ + "27b1fdb04752bbc536007a920d24acb045561c26", + "3599689E6292b81B2d85451025146515070129Bb", + "42712D45473476b98452f434e72461577D686318", + "52908400098527886E0F7030069857D2E4169EE7", + "5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "6549f4939460DE12611948b3f82b88C3C8975323", + "66f9664f97F2b50F62D13eA064982f936dE76657", + "8617E340B3D01FA5F11F306F4090FD50E238070D", + "88021160C5C792225E4E5452585947470010289D", + "D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + "dbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + "de709f2102306220921060314715629080e2fb77", + "fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + }); + + for (auto& addr : eth_mainnet) + { + check(addr, 1); + } + + auto const rsk_mainnet = std::to_array({ + "27b1FdB04752BBc536007A920D24ACB045561c26", + "3599689E6292B81B2D85451025146515070129Bb", + "42712D45473476B98452f434E72461577d686318", + "52908400098527886E0F7030069857D2E4169ee7", + "5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", + "6549F4939460DE12611948B3F82B88C3C8975323", + "66F9664f97f2B50F62d13EA064982F936de76657", + "8617E340b3D01Fa5f11f306f4090fd50E238070D", + "88021160c5C792225E4E5452585947470010289d", + "D1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", + "DBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", + "De709F2102306220921060314715629080e2FB77", + "Fb6916095cA1Df60bb79ce92cE3EA74c37c5d359", + }); + + for (auto& addr : rsk_mainnet) + { + check(addr, 30); + } + + auto const rsk_testnet = std::to_array({ + "27B1FdB04752BbC536007a920D24acB045561C26", + "3599689e6292b81b2D85451025146515070129Bb", + "42712D45473476B98452F434E72461577D686318", + "52908400098527886E0F7030069857D2e4169EE7", + "5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", + "6549f4939460dE12611948b3f82b88C3c8975323", + "66f9664F97F2b50f62d13eA064982F936DE76657", + "8617e340b3D01fa5F11f306F4090Fd50e238070d", + "88021160c5C792225E4E5452585947470010289d", + "d1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", + "dbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", + "DE709F2102306220921060314715629080e2Fb77", + "Fb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", + }); + + for (auto& addr : rsk_testnet) + { + check(addr, 31); + } +} + BOOST_AUTO_TEST_SUITE_END() } // namespace bcos::test \ No newline at end of file diff --git a/bcos-storage/bcos-storage/RocksDBStorage2.h b/bcos-storage/bcos-storage/RocksDBStorage2.h index 14e0fce6d7..806d5d3406 100644 --- a/bcos-storage/bcos-storage/RocksDBStorage2.h +++ b/bcos-storage/bcos-storage/RocksDBStorage2.h @@ -28,13 +28,9 @@ namespace bcos::storage2::rocksdb template concept Resolver = requires(ResolverType&& resolver) { - { - resolver.encode(std::declval()) - }; - { - resolver.decode(std::string_view{}) - } -> std::convertible_to; - }; + { resolver.encode(std::declval()) }; + { resolver.decode(std::string_view{}) } -> std::convertible_to; +}; // clang-format off struct RocksDBException : public bcos::Error {}; @@ -129,7 +125,7 @@ class RocksDBStorage2 friend auto tag_invoke(storage2::tag_t /*unused*/, RocksDBStorage2& storage, RANGES::input_range auto&& keys) -> task::AwaitableValue(keys)))> + std::forward(keys)))> { return {executeReadSome(storage, std::forward(keys))}; } @@ -404,7 +400,7 @@ class RocksDBStorage2 friend task::AwaitableValue tag_invoke( bcos::storage2::tag_t /*unused*/, RocksDBStorage2& storage, - auto&& startKey) + storage2::RANGE_SEEK_TYPE /*unused*/, auto&& startKey) { auto encodedKey = storage.m_keyResolver.encode(startKey); ::rocksdb::Slice slice(RANGES::data(encodedKey), RANGES::size(encodedKey)); diff --git a/bcos-storage/test/unittest/TestRocksDBStorage2.cpp b/bcos-storage/test/unittest/TestRocksDBStorage2.cpp index 609eea2a01..8e6e74e263 100644 --- a/bcos-storage/test/unittest/TestRocksDBStorage2.cpp +++ b/bcos-storage/test/unittest/TestRocksDBStorage2.cpp @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(merge) BOOST_CHECK_EQUAL(i, 100); StateKeyView seekKeyView{"Table~5"sv, "Key~5"sv}; - auto seekIt2 = co_await storage2::range(rocksDB, seekKeyView); + auto seekIt2 = co_await storage2::range(rocksDB, storage2::RANGE_SEEK, seekKeyView); int j = 54; while (auto keyValue = co_await seekIt2.next()) diff --git a/bcos-sync/bcos-sync/BlockSync.cpp b/bcos-sync/bcos-sync/BlockSync.cpp index 2650c3c7bf..dc5df88d61 100644 --- a/bcos-sync/bcos-sync/BlockSync.cpp +++ b/bcos-sync/bcos-sync/BlockSync.cpp @@ -287,11 +287,21 @@ bool BlockSync::shouldSyncing() return true; } -bool BlockSync::isSyncing() +bool BlockSync::isSyncing() const { return (m_state == SyncState::Downloading); } +std::optional> +BlockSync::getSyncStatus() const +{ + if (!isSyncing()) + { + return std::nullopt; + } + return std::make_tuple(m_config->blockNumber(), m_config->knownHighestNumber()); +} + void BlockSync::maintainDownloadingBuffer() { if (m_downloadingQueue->size() == 0) @@ -998,3 +1008,14 @@ void BlockSync::asyncGetSyncInfo(std::function _o std::string statusStr = fastWriter.write(syncInfo); _onGetSyncInfo(nullptr, statusStr); } + +std::vector BlockSync::getPeerStatus() +{ + std::vector statuses{}; + statuses.reserve(m_syncStatus->peersSize()); + m_syncStatus->foreachPeer([&statuses](auto&& status) { + statuses.emplace_back(status); + return true; + }); + return statuses; +} diff --git a/bcos-sync/bcos-sync/BlockSync.h b/bcos-sync/bcos-sync/BlockSync.h index b652ca3d65..16aae731a9 100644 --- a/bcos-sync/bcos-sync/BlockSync.h +++ b/bcos-sync/bcos-sync/BlockSync.h @@ -54,6 +54,8 @@ class BlockSync : public BlockSyncInterface, // for rpc void asyncGetSyncInfo(std::function _onGetSyncInfo) override; + std::vector getPeerStatus() override; + // consensus notify sync module committed block number void asyncNotifyCommittedIndex( bcos::protocol::BlockNumber _number, std::function _onRecv) override @@ -87,6 +89,11 @@ class BlockSync : public BlockSyncInterface, c_FaultyNodeBlockDelta = _delta; } + bool isSyncing() const override; + + virtual std::optional> + getSyncStatus() const override; + protected: virtual void asyncNotifyBlockSyncMessage(Error::Ptr _error, bcos::crypto::NodeIDPtr _nodeID, bytesConstRef _data, std::function _sendResponse, @@ -105,7 +112,6 @@ class BlockSync : public BlockSyncInterface, bcos::crypto::NodeIDPtr _nodeID, BlockSyncMsgInterface::Ptr _syncMsg); virtual bool shouldSyncing(); - virtual bool isSyncing(); virtual void tryToRequestBlocks(); virtual void onDownloadTimeout(); // block execute and submit diff --git a/bcos-sync/bcos-sync/state/SyncPeerStatus.cpp b/bcos-sync/bcos-sync/state/SyncPeerStatus.cpp index 258ff86de6..5ddab55e17 100644 --- a/bcos-sync/bcos-sync/state/SyncPeerStatus.cpp +++ b/bcos-sync/bcos-sync/state/SyncPeerStatus.cpp @@ -167,7 +167,7 @@ void SyncPeerStatus::deletePeer(PublicPtr _peer) void SyncPeerStatus::foreachPeerRandom(std::function const& _f) const { - std::vector peersStatusList; + std::vector peersStatusList{}; { if (m_peersStatus.empty()) { @@ -176,17 +176,21 @@ void SyncPeerStatus::foreachPeerRandom(std::function cons peersStatusList.reserve(m_peersStatus.size()); // Get nodeid list - for (const auto& peer : m_peersStatus) + for (const auto& [_, peer] : m_peersStatus) { - peersStatusList.emplace_back(peer.second); + peersStatusList.emplace_back(peer); } } + if (peersStatusList.empty()) [[unlikely]] + { + return; + } // Random nodeid list for (size_t i = peersStatusList.size() - 1; i > 0; --i) { size_t select = rand() % (i + 1); - swap(peersStatusList[i], peersStatusList[select]); + std::swap(peersStatusList[i], peersStatusList[select]); } // access _f() according to the random list for (auto const& peerStatus : peersStatusList) diff --git a/bcos-table/src/KeyPageStorage.cpp b/bcos-table/src/KeyPageStorage.cpp index 3de241b3af..a898169d2b 100644 --- a/bcos-table/src/KeyPageStorage.cpp +++ b/bcos-table/src/KeyPageStorage.cpp @@ -563,8 +563,8 @@ void KeyPageStorage::rollback(const Recoder& recoder) } // if data not exist, create an empty one -auto KeyPageStorage::getData(std::string_view tableView, std::string_view key, - bool mustExist) -> std::tuple> +auto KeyPageStorage::getData(std::string_view tableView, std::string_view key, bool mustExist) + -> std::tuple> { // find from cache auto [bucket, lock] = getBucket(tableView, key); @@ -685,8 +685,8 @@ auto KeyPageStorage::getData(std::string_view tableView, std::string_view key, } } -auto KeyPageStorage::getEntryFromPage(std::string_view table, - std::string_view key) -> std::pair> +auto KeyPageStorage::getEntryFromPage(std::string_view table, std::string_view key) + -> std::pair> { // key is empty means the data is TableMeta auto [error, data] = getData(table, TABLE_META_KEY); @@ -801,8 +801,8 @@ auto KeyPageStorage::getEntryFromPage(std::string_view table, return std::make_pair(nullptr, std::nullopt); } -auto KeyPageStorage::setEntryToPage( - std::string table, std::string key, Entry entry) -> Error::UniquePtr +auto KeyPageStorage::setEntryToPage(std::string table, std::string key, Entry entry) + -> Error::UniquePtr { auto [error, data] = getData(table, TABLE_META_KEY); if (error) @@ -1004,8 +1004,8 @@ auto KeyPageStorage::setEntryToPage( return nullptr; } -auto KeyPageStorage::getRawEntryFromStorage(std::string_view table, - std::string_view key) -> std::pair> +auto KeyPageStorage::getRawEntryFromStorage(std::string_view table, std::string_view key) + -> std::pair> { auto prev = getPrev(); // prev must not null if (!prev) @@ -1022,8 +1022,8 @@ auto KeyPageStorage::getRawEntryFromStorage(std::string_view table, }); return getPromise.get_future().get(); } -auto KeyPageStorage::getSysTableRawEntry(std::string_view table, - std::string_view key) -> std::pair> +auto KeyPageStorage::getSysTableRawEntry(std::string_view table, std::string_view key) + -> std::pair> { auto [bucket, lock] = getBucket(table, key); boost::ignore_unused(lock); @@ -1045,8 +1045,8 @@ auto KeyPageStorage::getSysTableRawEntry(std::string_view table, return std::make_pair(std::move(error), entryOption); } -auto KeyPageStorage::importExistingEntry( - std::string_view table, std::string_view key, Entry entry) -> Entry +auto KeyPageStorage::importExistingEntry(std::string_view table, std::string_view key, Entry entry) + -> Entry { if (m_readOnly) { diff --git a/bcos-table/src/StateStorageFactory.h b/bcos-table/src/StateStorageFactory.h index b6d5a150f4..145d2ac910 100644 --- a/bcos-table/src/StateStorageFactory.h +++ b/bcos-table/src/StateStorageFactory.h @@ -50,7 +50,7 @@ constexpr static const std::array IGNORED_ARRAY{ bcos::storage::StorageInterface::SYS_TABLES, }; -constexpr static const std::array IGNORED_ARRAY_310{bcos::ledger::SYS_CONFIG, +constexpr static const std::array IGNORED_ARRAY_310{bcos::ledger::SYS_CONFIG, bcos::ledger::SYS_CONSENSUS, bcos::storage::StorageInterface::SYS_TABLES, bcos::ledger::SYS_CODE_BINARY, bcos::ledger::SYS_CONTRACT_ABI}; diff --git a/bcos-tars-protocol/bcos-tars-protocol/Common.h b/bcos-tars-protocol/bcos-tars-protocol/Common.h index 4af59803af..d44d09540e 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/Common.h +++ b/bcos-tars-protocol/bcos-tars-protocol/Common.h @@ -429,4 +429,20 @@ inline bcos::protocol::LogEntry toBcosLogEntry(bcostars::LogEntry const& _logEnt return bcos::protocol::LogEntry(bcos::bytes(_logEntry.address.begin(), _logEntry.address.end()), topics, bcos::bytes(_logEntry.data.begin(), _logEntry.data.end())); } + +inline bcos::protocol::LogEntry takeToBcosLogEntry(bcostars::LogEntry&& _logEntry) +{ + std::vector topics; + for (auto&& topicIt : _logEntry.topic) + { + bcos::h256 topic; + RANGES::move(topicIt.begin(), topicIt.end(), topic.mutableData().data()); + topics.push_back(std::move(topic)); + } + bcos::bytes address; + RANGES::move(std::move(_logEntry.address), std::back_inserter(address)); + bcos::bytes data; + RANGES::move(std::move(_logEntry.data), std::back_inserter(data)); + return {std::move(address), std::move(topics), std::move(data)}; +} } // namespace bcostars \ No newline at end of file diff --git a/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.cpp b/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.cpp index 12997a25f6..e227c85bbe 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.cpp +++ b/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.cpp @@ -143,6 +143,11 @@ void BlockSyncServiceClient::asyncGetSyncInfo( m_proxy->async_asyncGetSyncInfo(new Callback(_onGetSyncInfo)); } +std::vector BlockSyncServiceClient::getPeerStatus() +{ + return {}; +} + void BlockSyncServiceClient::notifyConnectedNodes(bcos::crypto::NodeIDSet const& _connectedNodes, std::function _onRecvResponse) { diff --git a/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.h b/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.h index d05574d4f9..f32687020c 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.h +++ b/bcos-tars-protocol/bcos-tars-protocol/client/PBFTServiceClient.h @@ -207,6 +207,8 @@ class BlockSyncServiceClient : virtual public bcos::sync::BlockSyncInterface, void asyncGetSyncInfo( std::function _onGetSyncInfo) override; + std::vector getPeerStatus() override; + // called by the frontService to dispatch message void asyncNotifyBlockSyncMessage(bcos::Error::Ptr _error, std::string const& _uuid, bcos::crypto::NodeIDPtr _nodeID, bcos::bytesConstRef _data, diff --git a/bcos-tars-protocol/bcos-tars-protocol/impl/TarsHashable.h b/bcos-tars-protocol/bcos-tars-protocol/impl/TarsHashable.h index 0d8dc1b7c8..fb6cf8cbb0 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/impl/TarsHashable.h +++ b/bcos-tars-protocol/bcos-tars-protocol/impl/TarsHashable.h @@ -6,6 +6,7 @@ #include "bcos-tars-protocol/tars/Block.h" #include "bcos-tars-protocol/tars/Transaction.h" #include "bcos-tars-protocol/tars/TransactionReceipt.h" +#include #include #include @@ -16,6 +17,15 @@ void tag_invoke(bcos::concepts::hash::tag_t /*u Transaction const& transaction, bcos::crypto::hasher::Hasher auto&& hasher, bcos::concepts::bytebuffer::ByteBuffer auto& out) { + if (transaction.type >= static_cast(bcos::protocol::TransactionType::Web3Transacion)) + [[unlikely]] + { + if (!transaction.extraTransactionHash.empty()) [[likely]] + { + bcos::concepts::bytebuffer::assignTo(transaction.extraTransactionHash, out); + } + return; + } if (!transaction.dataHash.empty()) { bcos::concepts::bytebuffer::assignTo(transaction.dataHash, out); diff --git a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionFactoryImpl.h b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionFactoryImpl.h index 601abd6907..f63b3aee4d 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionFactoryImpl.h +++ b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionFactoryImpl.h @@ -71,25 +71,30 @@ class TransactionFactoryImpl : public bcos::protocol::TransactionFactory } } - auto originDataHash = std::move(transaction->mutableInner().dataHash); - transaction->mutableInner().dataHash.clear(); - transaction->calculateHash(*m_cryptoSuite->hashImpl()); - - // check if hash matching - if (checkHash && !originDataHash.empty() && - (originDataHash != transaction->mutableInner().dataHash)) [[unlikely]] + // other transaction type, do not need to check hash, skip + if (transaction->type() == + static_cast(bcos::protocol::TransactionType::BCOSTransaction)) { - bcos::crypto::HashType originHashResult( - (bcos::byte*)originDataHash.data(), originDataHash.size()); - bcos::crypto::HashType hashResult( - (bcos::byte*)transaction->mutableInner().dataHash.data(), - transaction->mutableInner().dataHash.size()); - - BCOS_LOG(WARNING) << LOG_DESC("the transaction hash does not match") - << LOG_KV("originHash", originHashResult.hex()) - << LOG_KV("realHash", hashResult.hex()); - BOOST_THROW_EXCEPTION(std::invalid_argument( - "transaction hash mismatching, maybe transaction version not support.")); + auto originDataHash = std::move(transaction->mutableInner().dataHash); + transaction->mutableInner().dataHash.clear(); + transaction->calculateHash(*m_cryptoSuite->hashImpl()); + + // check if hash matching + if (checkHash && !originDataHash.empty() && + (originDataHash != transaction->mutableInner().dataHash)) [[unlikely]] + { + bcos::crypto::HashType originHashResult( + (bcos::byte*)originDataHash.data(), originDataHash.size()); + bcos::crypto::HashType hashResult( + (bcos::byte*)transaction->mutableInner().dataHash.data(), + transaction->mutableInner().dataHash.size()); + + BCOS_LOG(WARNING) << LOG_DESC("the transaction hash does not match") + << LOG_KV("originHash", originHashResult.hex()) + << LOG_KV("realHash", hashResult.hex()); + BOOST_THROW_EXCEPTION(std::invalid_argument( + "transaction hash mismatching, maybe transaction version not support.")); + } } if (checkSig) diff --git a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.cpp b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.cpp index e012f6f932..38a2633b7a 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.cpp +++ b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.cpp @@ -47,11 +47,17 @@ void TransactionImpl::encode(bcos::bytes& txData) const bcos::crypto::HashType TransactionImpl::hash() const { - if (m_inner()->dataHash.empty()) + if (m_inner()->dataHash.empty() && m_inner()->extraTransactionHash.empty()) { BOOST_THROW_EXCEPTION(EmptyTransactionHash{}); } + if (type() == static_cast(bcos::protocol::TransactionType::Web3Transacion)) + { + bcos::crypto::HashType hashResult((bcos::byte*)m_inner()->extraTransactionHash.data(), + m_inner()->extraTransactionHash.size()); + return hashResult; + } bcos::crypto::HashType hashResult( (bcos::byte*)m_inner()->dataHash.data(), m_inner()->dataHash.size()); @@ -174,6 +180,16 @@ void bcostars::protocol::TransactionImpl::setExtraData(std::string const& _extra { m_inner()->extraData = _extraData; } +uint8_t bcostars::protocol::TransactionImpl::type() const +{ + return static_cast(m_inner()->type); +} +bcos::bytesConstRef TransactionImpl::extraTransactionBytes() const +{ + return {reinterpret_cast(m_inner()->extraTransactionBytes.data()), + m_inner()->extraTransactionBytes.size()}; +} + const bcostars::Transaction& bcostars::protocol::TransactionImpl::inner() const { return *m_inner(); diff --git a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.h b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.h index 5de8a5d7d2..9c80b8b165 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.h +++ b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionImpl.h @@ -89,6 +89,9 @@ class TransactionImpl : public bcos::protocol::Transaction std::string_view extraData() const override; void setExtraData(std::string const& _extraData) override; + uint8_t type() const override; + bcos::bytesConstRef extraTransactionBytes() const override; + const bcostars::Transaction& inner() const; bcostars::Transaction& mutableInner(); void setInner(bcostars::Transaction inner); diff --git a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.cpp b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.cpp index d36a31f4e0..bcb9025f84 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.cpp +++ b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.cpp @@ -97,6 +97,21 @@ gsl::span bcostars::protocol::TransactionReceipt return {m_logEntries.data(), m_logEntries.size()}; } +bcos::protocol::LogEntries&& bcostars::protocol::TransactionReceiptImpl::takeLogEntries() +{ + if (m_logEntries.empty()) + { + auto& innter = mutableInner(); + m_logEntries.reserve(innter.data.logEntries.size()); + for (auto& it : innter.data.logEntries) + { + auto bcosLogEntry = takeToBcosLogEntry(std::move(it)); + m_logEntries.push_back(std::move(bcosLogEntry)); + } + return std::move(m_logEntries); + } + return std::move(m_logEntries); +} bcos::protocol::BlockNumber bcostars::protocol::TransactionReceiptImpl::blockNumber() const { return m_inner()->data.blockNumber; diff --git a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.h b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.h index e75b3ca5cd..f488201376 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.h +++ b/bcos-tars-protocol/bcos-tars-protocol/protocol/TransactionReceiptImpl.h @@ -63,6 +63,7 @@ class TransactionReceiptImpl : public bcos::protocol::TransactionReceipt int32_t status() const override; bcos::bytesConstRef output() const override; gsl::span logEntries() const override; + bcos::protocol::LogEntries&& takeLogEntries() override; bcos::protocol::BlockNumber blockNumber() const override; std::string_view effectiveGasPrice() const override; void setEffectiveGasPrice(std::string effectiveGasPrice) override; diff --git a/bcos-tars-protocol/bcos-tars-protocol/tars/Transaction.tars b/bcos-tars-protocol/bcos-tars-protocol/tars/Transaction.tars index f5aaf1b2fa..bb5b2c5ae1 100644 --- a/bcos-tars-protocol/bcos-tars-protocol/tars/Transaction.tars +++ b/bcos-tars-protocol/bcos-tars-protocol/tars/Transaction.tars @@ -18,12 +18,15 @@ module bcostars { struct Transaction { 1 optional TransactionData data; - 2 optional vector dataHash; + 2 optional vector dataHash; // type == 0, hash(data); type == 1, hash(extraTransactionBytes) 3 optional vector signature; 4 optional long importTime; 5 optional int attribute; // 6 optional string source; 7 optional vector sender; 8 optional string extraData; + 9 optional byte type; + 10 optional vector extraTransactionBytes; + 11 optional vector extraTransactionHash; // hash(rlp(10|3)) }; }; diff --git a/bcos-tool/bcos-tool/Exceptions.h b/bcos-tool/bcos-tool/Exceptions.h index 2b6738bcb0..527b8b6b52 100644 --- a/bcos-tool/bcos-tool/Exceptions.h +++ b/bcos-tool/bcos-tool/Exceptions.h @@ -27,6 +27,7 @@ namespace tool DERIVE_BCOS_EXCEPTION(LedgerConfigFetcherException); DERIVE_BCOS_EXCEPTION(InvalidConfig); DERIVE_BCOS_EXCEPTION(InvalidVersion); +/// NOTE: only use it when the compatibility version >= 3.6.0 DERIVE_BCOS_EXCEPTION(InvalidSetFeature); class ExceptionHolder diff --git a/bcos-tool/bcos-tool/LedgerConfigFetcher.cpp b/bcos-tool/bcos-tool/LedgerConfigFetcher.cpp index 0cdb0623a9..714f11bf83 100644 --- a/bcos-tool/bcos-tool/LedgerConfigFetcher.cpp +++ b/bcos-tool/bcos-tool/LedgerConfigFetcher.cpp @@ -21,6 +21,7 @@ #include "LedgerConfigFetcher.h" #include "Exceptions.h" #include "VersionConverter.h" +#include #include #include #include @@ -325,4 +326,21 @@ void LedgerConfigFetcher::fetchNotifyRotateFlagInfo() TOOL_LOG(INFO) << LOG_DESC("fetchNotifyRotateFlagInfo success") << LOG_KV("value", std::get<0>(ret)); m_ledgerConfig->setNotifyRotateFlagInfo(boost::lexical_cast(std::get<0>(ret))); +} + +void LedgerConfigFetcher::fetchChainId() +{ + auto [chainIdStr, _] = fetchSystemConfigNoException(SYSTEM_KEY_WEB3_CHAIN_ID, {"0", 0}); + try + { + auto chainId = boost::lexical_cast(chainIdStr); + TOOL_LOG(INFO) << LOG_DESC("fetchChainId success") << LOG_KV("chainId", chainIdStr); + m_ledgerConfig->setChainId(bcos::toEvmC(chainId)); + } + catch (...) + { + TOOL_LOG(TRACE) << LOG_DESC("fetchChainId failed") << LOG_KV("chainId", chainIdStr); + evmc_uint256be chainId{}; + m_ledgerConfig->setChainId(std::move(chainId)); + } } \ No newline at end of file diff --git a/bcos-tool/bcos-tool/LedgerConfigFetcher.h b/bcos-tool/bcos-tool/LedgerConfigFetcher.h index 8b4fc61360..62bddb72c6 100644 --- a/bcos-tool/bcos-tool/LedgerConfigFetcher.h +++ b/bcos-tool/bcos-tool/LedgerConfigFetcher.h @@ -56,6 +56,7 @@ class LedgerConfigFetcher : public std::enable_shared_from_this("rpc.listen_ip", "0.0.0.0"); int listenPort = _pt.get("rpc.listen_port", 20200); int threadCount = _pt.get("rpc.thread_count", 8); + int filterTimeout = _pt.get("rpc.filter_timeout", 300); + int maxProcessBlock = _pt.get("rpc.filter_max_process_block", 10); bool smSsl = _pt.get("rpc.sm_ssl", false); bool disableSsl = _pt.get("rpc.disable_ssl", false); bool needRetInput = _pt.get("rpc.return_input_params", true); @@ -371,6 +378,8 @@ void NodeConfig::loadRpcConfig(boost::property_tree::ptree const& _pt) m_rpcThreadPoolSize = threadCount; m_rpcDisableSsl = disableSsl; m_rpcSmSsl = smSsl; + m_rpcFilterTimeout = filterTimeout; + m_rpcMaxProcessBlock = maxProcessBlock; g_BCOSConfig.setNeedRetInput(needRetInput); NodeConfig_LOG(INFO) << LOG_DESC("loadRpcConfig") << LOG_KV("listenIP", listenIP) @@ -379,6 +388,37 @@ void NodeConfig::loadRpcConfig(boost::property_tree::ptree const& _pt) << LOG_KV("needRetInput", needRetInput); } +void NodeConfig::loadWeb3RpcConfig(boost::property_tree::ptree const& _pt) +{ + /* + [web3_rpc] + enable=false + listen_ip=127.0.0.1 + listen_port=8545 + thread_count=16 + ; 300s + filter_timeout=300 + filter_max_process_block=10 + */ + const std::string listenIP = _pt.get("web3_rpc.listen_ip", "127.0.0.1"); + const int listenPort = _pt.get("web3_rpc.listen_port", 8545); + const int threadCount = _pt.get("web3_rpc.thread_count", 8); + const int filterTimeout = _pt.get("web3_rpc.filter_timeout", 300); + const int maxProcessBlock = _pt.get("web3_rpc.filter_max_process_block", 10); + const bool enableWeb3Rpc = _pt.get("web3_rpc.enable", false); + + m_web3RpcListenIP = listenIP; + m_web3RpcListenPort = listenPort; + m_web3RpcThreadSize = threadCount; + m_enableWeb3Rpc = enableWeb3Rpc; + m_web3FilterTimeout = filterTimeout; + m_web3MaxProcessBlock = maxProcessBlock; + + NodeConfig_LOG(INFO) << LOG_DESC("loadWeb3RpcConfig") << LOG_KV("enableWeb3Rpc", enableWeb3Rpc) + << LOG_KV("listenIP", listenIP) << LOG_KV("listenPort", listenPort) + << LOG_KV("listenPort", listenPort); +} + void NodeConfig::loadGatewayConfig(boost::property_tree::ptree const& _pt) { /* @@ -559,6 +599,18 @@ void NodeConfig::loadChainConfig(boost::property_tree::ptree const& _pt, bool _e << LOG_KV("blockLimit", m_blockLimit); } +void NodeConfig::NodeConfig::loadWeb3ChainConfig(boost::property_tree::ptree const& _pt) +{ + m_genesisConfig.m_web3ChainID = _pt.get("web3.chain_id", "0"); + if (!isNumStr(m_genesisConfig.m_web3ChainID)) + { + BOOST_THROW_EXCEPTION( + InvalidConfig() << errinfo_comment("The web3ChainId must be number string")); + } + NodeConfig_LOG(INFO) << LOG_DESC("loadWeb3ChainConfig") + << LOG_KV("web3ChainID", m_genesisConfig.m_web3ChainID); +} + void NodeConfig::loadSecurityConfig(boost::property_tree::ptree const& _pt) { m_privateKeyPath = _pt.get("security.private_key_path", "node.pem"); diff --git a/bcos-tool/bcos-tool/NodeConfig.h b/bcos-tool/bcos-tool/NodeConfig.h index 496e961857..9ff25a684a 100644 --- a/bcos-tool/bcos-tool/NodeConfig.h +++ b/bcos-tool/bcos-tool/NodeConfig.h @@ -178,9 +178,19 @@ class NodeConfig const std::string& rpcListenIP() const { return m_rpcListenIP; } uint16_t rpcListenPort() const { return m_rpcListenPort; } uint32_t rpcThreadPoolSize() const { return m_rpcThreadPoolSize; } + uint32_t rpcFilterTimeout() const { return m_rpcFilterTimeout; } + uint32_t rpcMaxProcessBlock() const { return m_rpcMaxProcessBlock; } bool rpcSmSsl() const { return m_rpcSmSsl; } bool rpcDisableSsl() const { return m_rpcDisableSsl; } + // the web3 rpc configurations + bool enableWeb3Rpc() const { return m_enableWeb3Rpc; } + const std::string& web3RpcListenIP() const { return m_web3RpcListenIP; } + uint16_t web3RpcListenPort() const { return m_web3RpcListenPort; } + uint32_t web3RpcThreadSize() const { return m_web3RpcThreadSize; } + uint32_t web3FilterTimeout() const { return m_web3FilterTimeout; } + uint32_t web3MaxProcessBlock() const { return m_web3MaxProcessBlock; } + // the gateway configurations const std::string& p2pListenIP() const { return m_p2pListenIP; } uint16_t p2pListenPort() const { return m_p2pListenPort; } @@ -275,7 +285,9 @@ class NodeConfig protected: virtual void loadChainConfig(boost::property_tree::ptree const& _pt, bool _enforceGroupId); + virtual void loadWeb3ChainConfig(boost::property_tree::ptree const& _pt); virtual void loadRpcConfig(boost::property_tree::ptree const& _pt); + virtual void loadWeb3RpcConfig(boost::property_tree::ptree const& _pt); virtual void loadGatewayConfig(boost::property_tree::ptree const& _pt); virtual void loadCertConfig(boost::property_tree::ptree const& _pt); virtual void loadTxPoolConfig(boost::property_tree::ptree const& _pt); @@ -417,9 +429,19 @@ class NodeConfig std::string m_rpcListenIP; uint16_t m_rpcListenPort; uint32_t m_rpcThreadPoolSize; + uint32_t m_rpcFilterTimeout; + uint32_t m_rpcMaxProcessBlock; bool m_rpcSmSsl; bool m_rpcDisableSsl = false; + // config fro web3 rpc + bool m_enableWeb3Rpc = false; + std::string m_web3RpcListenIP; + uint16_t m_web3RpcListenPort; + uint32_t m_web3RpcThreadSize; + uint32_t m_web3FilterTimeout; + uint32_t m_web3MaxProcessBlock; + // config for gateway std::string m_p2pListenIP; uint16_t m_p2pListenPort; diff --git a/bcos-txpool/bcos-txpool/TxPool.cpp b/bcos-txpool/bcos-txpool/TxPool.cpp index 75a1718171..139f0c13d8 100644 --- a/bcos-txpool/bcos-txpool/TxPool.cpp +++ b/bcos-txpool/bcos-txpool/TxPool.cpp @@ -115,6 +115,12 @@ task::Task TxPool::submitTransaction( co_return co_await m_txpoolStorage->submitTransaction(std::move(transaction)); } +task::Task TxPool::submitTransactionWithoutReceipt( + protocol::Transaction::Ptr transaction) +{ + co_return co_await m_txpoolStorage->submitTransactionWithoutReceipt(std::move(transaction)); +} + task::Task TxPool::submitTransactionWithHook( protocol::Transaction::Ptr transaction, std::function onTxSubmitted) { @@ -556,6 +562,8 @@ void TxPool::init() // init syncConfig TXPOOL_LOG(INFO) << LOG_DESC("init sync config"); auto txsSyncConfig = m_transactionSync->config(); + txsSyncConfig->setConsensusNodeList(ledgerConfig->consensusNodeList()); + txsSyncConfig->setObserverList(ledgerConfig->observerNodeList()); m_transactionSync->config()->setMaxResponseTxsToNodesWithEmptyTxs( ledgerConfig->blockTxCountLimit()); TXPOOL_LOG(INFO) << LOG_DESC("init sync config success"); diff --git a/bcos-txpool/bcos-txpool/TxPool.h b/bcos-txpool/bcos-txpool/TxPool.h index dc38b55769..6a92eff7f3 100644 --- a/bcos-txpool/bcos-txpool/TxPool.h +++ b/bcos-txpool/bcos-txpool/TxPool.h @@ -47,6 +47,9 @@ class TxPool : public TxPoolInterface, public std::enable_shared_from_this submitTransaction( protocol::Transaction::Ptr transaction) override; + task::Task submitTransactionWithoutReceipt( + protocol::Transaction::Ptr transaction) override; + task::Task submitTransactionWithHook( protocol::Transaction::Ptr transaction, std::function onTxSubmitted) override; diff --git a/bcos-txpool/bcos-txpool/txpool/interfaces/TxPoolStorageInterface.h b/bcos-txpool/bcos-txpool/txpool/interfaces/TxPoolStorageInterface.h index a9866985a5..b062e996d8 100644 --- a/bcos-txpool/bcos-txpool/txpool/interfaces/TxPoolStorageInterface.h +++ b/bcos-txpool/bcos-txpool/txpool/interfaces/TxPoolStorageInterface.h @@ -39,6 +39,8 @@ class TxPoolStorageInterface virtual task::Task submitTransaction( protocol::Transaction::Ptr transaction) = 0; + virtual task::Task submitTransactionWithoutReceipt( + protocol::Transaction::Ptr transaction) = 0; virtual std::vector getTransactions( RANGES::any_view hashes) = 0; diff --git a/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.cpp b/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.cpp index bbb81f45a1..6c2ec4de75 100644 --- a/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.cpp +++ b/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.cpp @@ -20,6 +20,8 @@ */ #include "bcos-txpool/txpool/storage/MemoryStorage.h" #include "bcos-utilities/Common.h" + +#include #include #include #include @@ -171,6 +173,69 @@ task::Task MemoryStorage::submitTransact co_return co_await awaitable; } +task::Task MemoryStorage::submitTransactionWithoutReceipt( + protocol::Transaction::Ptr transaction) +{ + transaction->setImportTime(utcTime()); + struct Awaitable + { + [[maybe_unused]] constexpr bool await_ready() { return false; } + [[maybe_unused]] void await_suspend(CO_STD::coroutine_handle<> handle) + { + try + { + auto result = m_self->verifyAndSubmitTransaction( + std::move(m_transaction), nullptr, true, true); + + if (result != TransactionStatus::None) + { + TXPOOL_LOG(DEBUG) + << "Submit transaction failed! " + << LOG_KV("TxHash", m_transaction ? m_transaction->hash().hex() : "") + << LOG_KV("result", result); + m_submitResult.emplace( + BCOS_ERROR_PTR((int32_t)result, bcos::protocol::toString(result))); + } + else + { + auto res = std::make_shared(); + res->setStatus(static_cast(result)); + m_submitResult.emplace(std::move(res)); + } + handle.resume(); + } + catch (std::exception& e) + { + TXPOOL_LOG(WARNING) << "Unexpected exception: " << boost::diagnostic_information(e); + m_submitResult.emplace( + BCOS_ERROR_PTR((int32_t)TransactionStatus::Malformed, "Unknown exception")); + handle.resume(); + } + } + bcos::protocol::TransactionSubmitResult::Ptr await_resume() + { + if (std::holds_alternative(m_submitResult)) + { + BOOST_THROW_EXCEPTION(*std::get(m_submitResult)); + } + + return std::move( + std::get(m_submitResult)); + } + + protocol::Transaction::Ptr m_transaction; + std::shared_ptr m_self; + std::variant + m_submitResult; + }; + + Awaitable awaitable{.m_transaction = std::move(transaction), + .m_self = shared_from_this(), + .m_submitResult = {}}; + co_return co_await awaitable; +} + + std::vector MemoryStorage::getTransactions( RANGES::any_view hashes) { @@ -238,15 +303,16 @@ TransactionStatus MemoryStorage::enforceSubmitTransaction(Transaction::Ptr _tx) } if (result == TransactionStatus::NonceCheckFail) [[unlikely]] { - if (tx) + if (!tx) { - TXPOOL_LOG(WARNING) << LOG_DESC("enforce to seal failed for nonce check failed: ") - << tx->hash().abridged() << LOG_KV("batchId", tx->batchId()) - << LOG_KV("batchHash", tx->batchHash().abridged()) - << LOG_KV("importBatchId", _tx->batchId()) - << LOG_KV("importBatchHash", _tx->batchHash().abridged()); + TXPOOL_LOG(WARNING) + << LOG_DESC("enforce to seal failed for nonce check failed: ") + // << tx->hash().hex() << LOG_KV("batchId", tx->batchId()) + // << LOG_KV("batchHash", tx->batchHash().abridged()) + << LOG_KV("importTxHash", txHash) << LOG_KV("importBatchId", _tx->batchId()) + << LOG_KV("importBatchHash", _tx->batchHash().abridged()); + return TransactionStatus::NonceCheckFail; } - return TransactionStatus::NonceCheckFail; } // tx already in txpool @@ -346,6 +412,8 @@ TransactionStatus MemoryStorage::verifyAndSubmitTransaction( m_inRateCollector.update(1, true); if (result == TransactionStatus::None) { + auto const txImportTime = transaction->importTime(); + auto const txHash = transaction->hash().hex(); if (txSubmitCallback) { transaction->setSubmitCallback(std::move(txSubmitCallback)); @@ -358,6 +426,11 @@ TransactionStatus MemoryStorage::verifyAndSubmitTransaction( { result = insertWithoutLock(std::move(transaction)); } + if (c_fileLogLevel == TRACE) + { + TXPOOL_LOG(TRACE) << LOG_DESC("submitTxTime") << LOG_KV("txHash", txHash) + << LOG_KV("insertTime", utcTime() - txImportTime); + } } return result; @@ -1135,6 +1208,7 @@ std::shared_ptr MemoryStorage::batchVerifyProposal(Block::Ptr _block) << LOG_DESC("batchVerifyProposal unexpected wrong tx") << LOG_KV("blkNum", header->number()) << LOG_KV("blkHash", header->hash().abridged()) + << LOG_KV("txHash", accessor->value()->hash().hexPrefixed()) << LOG_KV("txBatchId", accessor->value()->batchId()) << LOG_KV("txBatchHash", accessor->value()->batchHash().abridged()); // NOTE: In certain scenarios, a bug may occur here: The leader generates the @@ -1316,8 +1390,8 @@ void MemoryStorage::batchImportTxs(TransactionsPtr _txs) auto ret = verifyAndSubmitTransaction(tx, nullptr, false, false); if (ret != TransactionStatus::None) { - TXPOOL_LOG(TRACE) << LOG_DESC("batchImportTxs failed") - << LOG_KV("tx", tx->hash().abridged()) << LOG_KV("msg", ret); + TXPOOL_LOG(TRACE) << LOG_DESC("batchImportTxs failed") << LOG_KV("tx", tx->hash().hex()) + << LOG_KV("msg", ret); continue; } successCount++; diff --git a/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.h b/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.h index c6bdcb928b..e4ba9e2441 100644 --- a/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.h +++ b/bcos-txpool/bcos-txpool/txpool/storage/MemoryStorage.h @@ -64,6 +64,9 @@ class MemoryStorage : public TxPoolStorageInterface, task::Task submitTransaction( protocol::Transaction::Ptr transaction) override; + task::Task submitTransactionWithoutReceipt( + protocol::Transaction::Ptr transaction) override; + std::vector getTransactions( RANGES::any_view hashes) override; diff --git a/bcos-txpool/bcos-txpool/txpool/validator/LedgerNonceChecker.cpp b/bcos-txpool/bcos-txpool/txpool/validator/LedgerNonceChecker.cpp index 8457246951..5612236433 100644 --- a/bcos-txpool/bcos-txpool/txpool/validator/LedgerNonceChecker.cpp +++ b/bcos-txpool/bcos-txpool/txpool/validator/LedgerNonceChecker.cpp @@ -41,7 +41,7 @@ TransactionStatus LedgerNonceChecker::checkNonce(Transaction::ConstPtr _tx, bool { return status; } - if (m_checkBlockLimit) + if (m_checkBlockLimit && _tx->type() == static_cast(TransactionType::BCOSTransaction)) { // check blockLimit return checkBlockLimit(_tx); } diff --git a/bcos-txpool/bcos-txpool/txpool/validator/TxValidator.cpp b/bcos-txpool/bcos-txpool/txpool/validator/TxValidator.cpp index 1a8cd1f246..03e7226562 100644 --- a/bcos-txpool/bcos-txpool/txpool/validator/TxValidator.cpp +++ b/bcos-txpool/bcos-txpool/txpool/validator/TxValidator.cpp @@ -31,11 +31,13 @@ TransactionStatus TxValidator::verify(bcos::protocol::Transaction::ConstPtr _tx) return TransactionStatus::InvalidSignature; } // check groupId and chainId - if (_tx->groupId() != m_groupId) [[unlikely]] + if (_tx->groupId() != m_groupId && + _tx->type() == static_cast(TransactionType::BCOSTransaction)) [[unlikely]] { return TransactionStatus::InvalidGroupId; } - if (_tx->chainId() != m_chainId) [[unlikely]] + if (_tx->chainId() != m_chainId && + _tx->type() == static_cast(TransactionType::BCOSTransaction)) [[unlikely]] { return TransactionStatus::InvalidChainId; } diff --git a/bcos-utilities/bcos-utilities/DataConvertUtility.h b/bcos-utilities/bcos-utilities/DataConvertUtility.h index f9047286d7..13912a55c4 100644 --- a/bcos-utilities/bcos-utilities/DataConvertUtility.h +++ b/bcos-utilities/bcos-utilities/DataConvertUtility.h @@ -20,7 +20,9 @@ #include "Common.h" #include "Error.h" +#include "Ranges.h" #include +#include #include #include #include @@ -34,6 +36,7 @@ namespace bcos { template + requires RANGES::range && RANGES::sized_range Out toHex(const Binary& binary, std::string_view prefix = std::string_view()) { Out out; @@ -48,6 +51,51 @@ Out toHex(const Binary& binary, std::string_view prefix = std::string_view()) return out; } +template +Out toHex(std::unsigned_integral auto number, std::string_view prefix = std::string_view()) +{ + std::basic_string bytes(8, '\0'); + boost::endian::store_big_u64(bytes.data(), number); + return toHex(bytes, prefix); +} + +template +concept Binary = RANGES::contiguous_range; +static std::string toQuantity(const Binary auto& binary) +{ + if (binary.empty()) + { + return "0x0"; + } + auto&& hex = toHex(binary); + auto it = hex.begin(); + while ((it + 1) != hex.end()) + { + if (*it != '0') + { + break; + } + it++; + } + std::string out = "0x"; + out.reserve(2 + std::distance(it, hex.end())); + out.insert(out.end(), it, hex.end()); + return out; +} + +template +concept Number = std::is_integral_v; +static std::string toQuantity(Number auto number) +{ + std::basic_string bytes(8, '\0'); + boost::endian::store_big_u64(bytes.data(), number); + return toQuantity(bytes); +} + +template +concept BigNumber = !std::is_integral_v && std::convertible_to; +static std::string toQuantity(BigNumber auto number); + template Out fromHex(const Hex& hex, std::string_view prefix = std::string_view()) { @@ -79,6 +127,16 @@ Out fromHexWithPrefix(const Hex& hex) return fromHex(hex, "0x"); } +inline uint64_t fromQuantity(std::string const& quantity) +{ + return std::stoull(quantity, nullptr, 16); +} + +inline u256 fromBigQuantity(std::string_view quantity) +{ + return u256(quantity); +} + /** * @brief convert the specified bytes data into hex string * @@ -368,4 +426,15 @@ inline std::string toString(uint8_t const& _u) o << static_cast(_u); return o.str(); } + +std::string toQuantity(BigNumber auto number) +{ + if (number == 0) + { + return "0x0"; + } + auto bytes = toCompactBigEndian(number); + return toQuantity(bytes); +} + } // namespace bcos diff --git a/bcos-utilities/bcos-utilities/FixedBytes.h b/bcos-utilities/bcos-utilities/FixedBytes.h index b419da201d..8650775548 100644 --- a/bcos-utilities/bcos-utilities/FixedBytes.h +++ b/bcos-utilities/bcos-utilities/FixedBytes.h @@ -292,7 +292,7 @@ class FixedBytes byte* data() { return m_data.data(); } /// @returns a constant byte pointer to the object's data. byte const* data() const { return m_data.data(); } - + auto& mutableData() { return m_data; } /// @returns begin iterator. auto begin() const -> typename std::array::const_iterator { return m_data.begin(); } /// @returns end iterator. diff --git a/bcos-utilities/bcos-utilities/RecursiveLambda.h b/bcos-utilities/bcos-utilities/RecursiveLambda.h new file mode 100644 index 0000000000..8644725796 --- /dev/null +++ b/bcos-utilities/bcos-utilities/RecursiveLambda.h @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace bcos +{ + +inline constexpr auto recursiveLambda(auto&& lambda) +{ + return [lambdaImpl = std::forward(lambda)](auto&&... args) -> decltype(auto) { + return lambdaImpl(lambdaImpl, std::forward(args)...); + }; +}; + +} // namespace bcos \ No newline at end of file diff --git a/bcos-utilities/bcos-utilities/RefDataContainer.h b/bcos-utilities/bcos-utilities/RefDataContainer.h index fbd507056d..d9486a47eb 100644 --- a/bcos-utilities/bcos-utilities/RefDataContainer.h +++ b/bcos-utilities/bcos-utilities/RefDataContainer.h @@ -18,6 +18,7 @@ * @date 2021-02-24 */ #pragma once +#include #include #include #include @@ -107,6 +108,12 @@ class RefDataContainer return std::string_view((char const*)m_dataPointer, m_dataCount * sizeof(T)); } + template + String toStringLike() const + { + return String((char const*)m_dataPointer, m_dataCount * sizeof(T)); + } + bool empty() const { return (m_dataCount == 0); } RefDataContainer getCroppedData(size_t _startIndex, size_t _count) const { diff --git a/bcos-utilities/testutils/TestPromptFixture.h b/bcos-utilities/testutils/TestPromptFixture.h index 6f5c2f9da7..40e9978fc7 100644 --- a/bcos-utilities/testutils/TestPromptFixture.h +++ b/bcos-utilities/testutils/TestPromptFixture.h @@ -19,6 +19,7 @@ #pragma once #include "bcos-utilities/Common.h" +#include #include #include @@ -26,6 +27,14 @@ namespace bcos { namespace test { +inline bcos::bytes operator""_bytes(const char* s) noexcept +{ + return fromHexWithPrefix(std::string_view(s)); +} +inline bcos::crypto::HashType operator""_hash(const char* s) noexcept +{ + return bcos::crypto::HashType(std::string_view(s), bcos::crypto::HashType::FromHex); +} class TestPrompt { public: diff --git a/cmake/CompilerSettings.cmake b/cmake/CompilerSettings.cmake index b14575018b..d55cff939d 100644 --- a/cmake/CompilerSettings.cmake +++ b/cmake/CompilerSettings.cmake @@ -76,7 +76,7 @@ if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR("${CMAKE_CXX_COMPILER_ID}" MATC endif() # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") + set(CMAKE_CXX_FLAGS_DEBUG "-Og -g") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") if(ONLY_CPP_SDK) set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") diff --git a/concepts/bcos-concepts/Basic.h b/concepts/bcos-concepts/Basic.h index b9b70a240c..55477cd5ae 100644 --- a/concepts/bcos-concepts/Basic.h +++ b/concepts/bcos-concepts/Basic.h @@ -18,6 +18,10 @@ concept ByteBuffer = std::is_standard_layout_v>> && (sizeof(RANGES::range_value_t>) == 1); +template +concept StringLike = std::same_as, std::string> || + std::same_as, std::string_view>; + template concept PointerLike = requires(Pointer pointer) { *pointer; diff --git a/concepts/bcos-concepts/Serialize.h b/concepts/bcos-concepts/Serialize.h index 815925d800..0ca87fcfaa 100644 --- a/concepts/bcos-concepts/Serialize.h +++ b/concepts/bcos-concepts/Serialize.h @@ -16,6 +16,10 @@ concept HasADLEncode = requires(Object const& object, Buffer& output) { impl_encode(object, output); }; template concept HasADLDecode = requires(Object object, const Buffer& input) { impl_decode(input, object); }; +template +concept HasADLPreEncode = requires(Object object) { impl_pre_encode(object); }; +template +concept HasADLAfterDecode = requires(Object object) { impl_after_decode(object); }; struct encode { @@ -50,10 +54,32 @@ struct decode impl_decode(input, object); } }; + +struct PreEncode +{ + template + void operator()(Object& object) const + requires HasADLPreEncode + { + impl_pre_encode(object); + } +}; + +struct AfterDecode +{ + template + void operator()(Object& object) const + requires HasADLAfterDecode + { + impl_after_decode(object); + } +}; } // namespace detail constexpr inline detail::encode encode{}; constexpr inline detail::decode decode{}; +constexpr inline detail::PreEncode pre_encode{}; +constexpr inline detail::AfterDecode after_decode{}; template concept Serializable = true; diff --git a/libinitializer/BaselineSchedulerInitializer.cpp b/libinitializer/BaselineSchedulerInitializer.cpp index d3fb91f613..c6ceb3f4a3 100644 --- a/libinitializer/BaselineSchedulerInitializer.cpp +++ b/libinitializer/BaselineSchedulerInitializer.cpp @@ -49,6 +49,18 @@ bcos::task::Task bcos::transaction_scheduler::BaselineSchedulerInitializer } } +using MutableStorage = bcos::storage2::memory_storage::MemoryStorage< + bcos::transaction_executor::StateKey, bcos::transaction_executor::StateValue, + bcos::storage2::memory_storage::Attribute(bcos::storage2::memory_storage::ORDERED | + bcos::storage2::memory_storage::LOGICAL_DELETION)>; +using CacheStorage = + bcos::storage2::memory_storage::MemoryStorage>; + std::tuple()>, std::function)>> bcos::transaction_scheduler::BaselineSchedulerInitializer::build(::rocksdb::DB& rocksDB, @@ -60,18 +72,6 @@ bcos::transaction_scheduler::BaselineSchedulerInitializer::build(::rocksdb::DB& { struct Data { - using MutableStorage = - storage2::memory_storage::MemoryStorage; - using CacheStorage = storage2::memory_storage::MemoryStorage>; - CacheStorage m_cacheStorage; storage2::rocksdb::RocksDBStorage2()); + return buildBaselineHolder(std::make_shared>()); } return buildBaselineHolder(std::make_shared()); } diff --git a/libinitializer/Initializer.cpp b/libinitializer/Initializer.cpp index f774ff8efe..3f7148da7f 100644 --- a/libinitializer/Initializer.cpp +++ b/libinitializer/Initializer.cpp @@ -206,6 +206,7 @@ void Initializer::init(bcos::protocol::NodeArchitectureType _nodeArchType, // build ledger auto ledger = LedgerInitializer::build(m_protocolInitializer->blockFactory(), storage, m_nodeConfig); + ledger->setKeyPageSize(m_nodeConfig->keyPageSize()); m_ledger = ledger; bcos::protocol::ExecutionMessageFactory::Ptr executionMessageFactory = nullptr; diff --git a/libinitializer/PBFTInitializer.cpp b/libinitializer/PBFTInitializer.cpp index cac523a78f..b8eae331e6 100644 --- a/libinitializer/PBFTInitializer.cpp +++ b/libinitializer/PBFTInitializer.cpp @@ -177,6 +177,9 @@ void PBFTInitializer::initChainNodeInfo( m_nodeInfo->setFeatureKeys( ledger::Features::featureKeys() | RANGES::views::transform([](std::string_view view) { return std::string(view); })); + m_nodeInfo->setSupportConfigs( + ledger::SystemConfigs::supportConfigs() | + RANGES::views::transform([](std::string_view view) { return std::string(view); })); m_groupInfo->appendNodeInfo(m_nodeInfo); INITIALIZER_LOG(INFO) << LOG_DESC("PBFTInitializer::initChainNodeInfo") << LOG_KV("nodeType", m_nodeInfo->nodeType()) diff --git a/lightnode/bcos-lightnode/rpc/LightNodeRPC.h b/lightnode/bcos-lightnode/rpc/LightNodeRPC.h index a7dec94db4..d81bb61a2e 100644 --- a/lightnode/bcos-lightnode/rpc/LightNodeRPC.h +++ b/lightnode/bcos-lightnode/rpc/LightNodeRPC.h @@ -161,7 +161,8 @@ class LightNodeRPC : public bcos::rpc::JsonRpcInterface boost::algorithm::hex_lower( txHash.begin(), txHash.end(), std::back_inserter(txHashStr)); - LIGHTNODE_LOG(INFO) << "RPC send transaction request: " << "0x" << txHashStr; + LIGHTNODE_LOG(INFO) << "RPC send transaction request: " + << "0x" << txHashStr; bcostars::TransactionReceipt receipt; co_await self->remoteTransactionPool().submitTransaction( @@ -571,7 +572,41 @@ class LightNodeRPC : public bcos::rpc::JsonRpcInterface Json::Value value; _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); } - + void newBlockFilter(std::string_view, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } + void newPendingTransactionFilter(std::string_view, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } + void newFilter(std::string_view, const Json::Value&, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } + void uninstallFilter(std::string_view, std::string_view, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } + void getFilterChanges(std::string_view, std::string_view, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } + void getFilterLogs(std::string_view, std::string_view, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } + void getLogs(std::string_view, const Json::Value&, RespFunc _respFunc) override + { + Json::Value value; + _respFunc(BCOS_ERROR_PTR(-1, "Unspported method!"), value); + } void getGroupBlockNumber(RespFunc respFunc) override { LIGHTNODE_LOG(INFO) << "RPC get group block number request"; diff --git a/tools/BcosAirBuilder/build_chain.sh b/tools/BcosAirBuilder/build_chain.sh index 06e22f6711..3aa1478a1f 100755 --- a/tools/BcosAirBuilder/build_chain.sh +++ b/tools/BcosAirBuilder/build_chain.sh @@ -39,7 +39,7 @@ ca_dir="" prometheus_dir="" config_path="" docker_mode= -default_version="v3.8.0" +default_version="v3.9.0" compatibility_version=${default_version} default_mtail_version="3.0.0-rc49" compatibility_mtail_version=${default_mtail_version} @@ -60,20 +60,22 @@ download_timeout=240 make_tar= default_group="group0" default_chainid="chain0" +default_web3_chainid="20200" use_ipv6="" # for modifying multipy ca node modify_node_path="" multi_ca_path="" consensus_type="pbft" supported_consensus=(pbft rpbft) +log_level="info" # for pro or max default setting bcos_builder_package=BcosBuilder.tgz -bcos_builder_version=v3.8.0 +bcos_builder_version=v3.9.0 use_exist_binary="false" download_specific_binary_flag="false" download_service_binary_type="cdn" -service_binary_version="v3.8.0" +service_binary_version="v3.9.0" download_service_binary_path="binary" download_service_binary_path_flag="false" service_type="all" @@ -576,6 +578,7 @@ air -c [Required when expand node] Specify the path of the expanded node config.ini, config.genesis and p2p connection file nodes.json -d [Required when expand node] When expanding the node, specify the path where the CA certificate and private key are located -D Default off. If set -D, build with docker + -E Default off. If set -E, enable debug log -a [Optional] when Auth mode Specify the admin account address. -w [Optional] Whether to use the wasm virtual machine engine, default is false -R [Optional] Whether to use serial execute,default is true @@ -646,7 +649,7 @@ EOF } parse_params() { - while getopts "l:C:c:o:e:t:p:d:g:G:L:v:i:I:M:k:zwDshHmn:R:a:N:u:y:r:V:6T:" option; do + while getopts "l:C:c:o:e:t:p:d:g:G:L:v:i:I:M:k:zwDshHmEn:R:a:N:u:y:r:V:6T:" option; do case $option in 6) use_ipv6="true" && default_listen_ip="::" ;; @@ -698,6 +701,9 @@ parse_params() { m) monitor_mode="true" ;; + E) + log_level="debug" + ;; n) node_key_dir="${OPTARG}" dir_must_exists "${node_key_dir}" @@ -1373,6 +1379,12 @@ generate_config_ini() { ; return input params in sendTransaction() return, default: true ; return_input_params=false +[web3_rpc] + enable=false + listen_ip=0.0.0.0 + listen_port=8545 + thread_count=8 + [cert] ; directory the certificates located in ca_path=./conf @@ -1527,7 +1539,7 @@ generate_common_ini() { enable_console_output = false log_path=./log ; info debug trace - level=info + level=${log_level} ; MB max_log_file_size=1024 ; rotate the log every hour @@ -1581,6 +1593,12 @@ generate_sm_config_ini() { ; return input params in sendTransaction() return, default: true ; return_input_params=false +[web3_rpc] + enable=false + listen_ip=0.0.0.0 + listen_port=8545 + thread_count=8 + [cert] ; directory the certificates located in ca_path=./conf @@ -1708,6 +1726,9 @@ generate_genesis_config() { ; the chain id, should nerver be changed chain_id=${default_chainid} +[web3] + chain_id=${default_web3_chainid} + [consensus] ; consensus algorithm now support PBFT(consensus_type=pbft), rPBFT(consensus_type=rpbft) consensus_type=${consensus_type} diff --git a/tools/BcosBuilder/max/conf/config-build-example.toml b/tools/BcosBuilder/max/conf/config-build-example.toml index a7832be122..893aa78c92 100644 --- a/tools/BcosBuilder/max/conf/config-build-example.toml +++ b/tools/BcosBuilder/max/conf/config-build-example.toml @@ -36,7 +36,7 @@ consensus_type = "pbft" # transaction gas limit gas_limit = "3000000000" # compatible version, can be dynamically upgraded through setSystemConfig -compatibility_version="3.8.0" +compatibility_version="3.9.0" [[agency]] name = "agencyA" diff --git a/tools/BcosBuilder/max/conf/config-deploy-example.toml b/tools/BcosBuilder/max/conf/config-deploy-example.toml index d837496744..4725a0c514 100644 --- a/tools/BcosBuilder/max/conf/config-deploy-example.toml +++ b/tools/BcosBuilder/max/conf/config-deploy-example.toml @@ -36,7 +36,7 @@ consensus_type = "pbft" # transaction gas limit gas_limit = "3000000000" # compatible version, can be dynamically upgraded through setSystemConfig -compatibility_version="3.8.0" +compatibility_version="3.9.0" [[agency]] name = "agencyA" diff --git a/tools/BcosBuilder/pro/conf/config-build-example.toml b/tools/BcosBuilder/pro/conf/config-build-example.toml index c668a02bb9..8a0e545580 100644 --- a/tools/BcosBuilder/pro/conf/config-build-example.toml +++ b/tools/BcosBuilder/pro/conf/config-build-example.toml @@ -34,7 +34,7 @@ consensus_type = "pbft" # transaction gas limit gas_limit = "3000000000" # compatible version, can be dynamically upgraded through setSystemConfig -compatibility_version="3.8.0" +compatibility_version="3.9.0" [[agency]] name = "agencyA" diff --git a/tools/BcosBuilder/pro/conf/config-deploy-example.toml b/tools/BcosBuilder/pro/conf/config-deploy-example.toml index 47001d54e4..4f671a6937 100644 --- a/tools/BcosBuilder/pro/conf/config-deploy-example.toml +++ b/tools/BcosBuilder/pro/conf/config-deploy-example.toml @@ -36,7 +36,7 @@ consensus_type = "pbft" # transaction gas limit gas_limit = "3000000000" # compatible version, can be dynamically upgraded through setSystemConfig -compatibility_version="3.8.0" +compatibility_version="3.9.0" [[agency]] name = "agencyA" diff --git a/tools/BcosBuilder/src/common/utilities.py b/tools/BcosBuilder/src/common/utilities.py index 724caf00d7..3fc63d01f0 100644 --- a/tools/BcosBuilder/src/common/utilities.py +++ b/tools/BcosBuilder/src/common/utilities.py @@ -99,7 +99,7 @@ class CommandInfo: network_add_vxlan = "add-vxlan" download_binary = "download_binary" download_type = ["cdn", "git"] - default_binary_version = "v3.8.0" + default_binary_version = "v3.9.0" command_list = [gen_config, upload, deploy, upgrade, undeploy, expand, start, stop] service_command_list_str = ', '.join(command_list) diff --git a/tools/BcosBuilder/src/tpl/config.genesis b/tools/BcosBuilder/src/tpl/config.genesis index ceefc46846..8f10bd9e81 100644 --- a/tools/BcosBuilder/src/tpl/config.genesis +++ b/tools/BcosBuilder/src/tpl/config.genesis @@ -22,7 +22,7 @@ [version] ; compatible version, can be dynamically upgraded through setSystemConfig - compatibility_version=3.8.0 + compatibility_version=3.9.0 [tx] ; transaction gas limit diff --git a/tools/storage-tool/storageTool.cpp b/tools/storage-tool/storageTool.cpp index 0fdda7f76b..006ee493bd 100644 --- a/tools/storage-tool/storageTool.cpp +++ b/tools/storage-tool/storageTool.cpp @@ -156,8 +156,8 @@ StateStorageInterface::Ptr createKeyPageStorage( void print( std::string_view tableName, std::string_view key, std::string_view value, bool hex = false) { - cout << "[tableName=" << tableName << "]" << " [key=" << key - << "] [value=" << (hex ? toHex(value) : value) << "]" << endl; + cout << "[tableName=" << tableName << "]" + << " [key=" << key << "] [value=" << (hex ? toHex(value) : value) << "]" << endl; } void writeKV(std::ofstream& output, std::string_view key, std::string_view value, bool hex = false) diff --git a/transaction-executor/bcos-transaction-executor/RollbackableStorage.h b/transaction-executor/bcos-transaction-executor/RollbackableStorage.h index 0fef1911bb..afc11f8637 100644 --- a/transaction-executor/bcos-transaction-executor/RollbackableStorage.h +++ b/transaction-executor/bcos-transaction-executor/RollbackableStorage.h @@ -76,9 +76,10 @@ class Rollbackable storage.m_storage.get(), std::forward(keys)); } - friend auto tag_invoke(storage2::tag_t /*unused*/, Rollbackable& storage, - auto&& key) -> task::Task>> + friend auto tag_invoke( + storage2::tag_t /*unused*/, Rollbackable& storage, auto&& key) + -> task::Task>> { co_return co_await storage2::readOne( storage.m_storage.get(), std::forward(key)); @@ -138,7 +139,7 @@ class Rollbackable friend auto tag_invoke(bcos::storage2::tag_t /*unused*/, Rollbackable& storage, auto&&... args) -> task::Task, decltype(args)...>>> + std::add_lvalue_reference_t, decltype(args)...>>> { co_return co_await storage2::range( storage.m_storage.get(), std::forward(args)...); diff --git a/transaction-executor/bcos-transaction-executor/TransactionExecutorImpl.h b/transaction-executor/bcos-transaction-executor/TransactionExecutorImpl.h index cf11a9fe0b..fb0941879b 100644 --- a/transaction-executor/bcos-transaction-executor/TransactionExecutorImpl.h +++ b/transaction-executor/bcos-transaction-executor/TransactionExecutorImpl.h @@ -132,7 +132,8 @@ class TransactionExecutorImpl if (c_fileLogLevel <= LogLevel::TRACE) { TRANSACTION_EXECUTOR_LOG(TRACE) - << "Execte transaction finished" << ", gasUsed: " << receipt->gasUsed() + << "Execte transaction finished" + << ", gasUsed: " << receipt->gasUsed() << ", newContractAddress: " << receipt->contractAddress() << ", logEntries: " << receipt->logEntries().size() << ", status: " << receipt->status() << ", output: " << toHex(receipt->output()) diff --git a/transaction-executor/bcos-transaction-executor/vm/EVMHostInterface.h b/transaction-executor/bcos-transaction-executor/vm/EVMHostInterface.h index 8427558f33..affa99df5a 100644 --- a/transaction-executor/bcos-transaction-executor/vm/EVMHostInterface.h +++ b/transaction-executor/bcos-transaction-executor/vm/EVMHostInterface.h @@ -23,6 +23,7 @@ #include "bcos-concepts/ByteBuffer.h" #include "bcos-executor/src/Common.h" +#include "bcos-framework/protocol/Exceptions.h" #include #include #include @@ -150,7 +151,7 @@ struct EVMHostInterface .block_timestamp = hostContext.timestamp(), .block_gas_limit = hostContext.blockGasLimit(), .block_prev_randao = {}, - .chain_id = {}, + .chain_id = hostContext.chainId(), .block_base_fee = {}, }; return result; diff --git a/transaction-executor/bcos-transaction-executor/vm/HostContext.h b/transaction-executor/bcos-transaction-executor/vm/HostContext.h index 2f168c3d25..a680bf5c3d 100644 --- a/transaction-executor/bcos-transaction-executor/vm/HostContext.h +++ b/transaction-executor/bcos-transaction-executor/vm/HostContext.h @@ -121,15 +121,15 @@ template class HostContext : public evmc_host_context { private: - Storage& m_rollbackableStorage; - protocol::BlockHeader const& m_blockHeader; - const evmc_address& m_origin; + std::reference_wrapper m_rollbackableStorage; + std::reference_wrapper m_blockHeader; + std::reference_wrapper m_origin; std::string_view m_abi; int m_contextID; - int64_t& m_seq; - PrecompiledManager const& m_precompiledManager; - ledger::LedgerConfig const& m_ledgerConfig; - crypto::Hash const& m_hashImpl; + std::reference_wrapper m_seq; + std::reference_wrapper m_precompiledManager; + std::reference_wrapper m_ledgerConfig; + std::reference_wrapper m_hashImpl; std::variant m_message; Account m_myAccount; @@ -155,7 +155,7 @@ class HostContext : public evmc_host_context auto getMyAccount() { - return Account>(m_rollbackableStorage, message().recipient); + return Account>(m_rollbackableStorage.get(), message().recipient); } inline constexpr static struct InnerConstructor @@ -182,7 +182,8 @@ class HostContext : public evmc_host_context m_precompiledManager(precompiledManager), m_ledgerConfig(ledgerConfig), m_hashImpl(hashImpl), - m_message(getMessage(message, m_blockHeader.number(), m_contextID, m_seq, m_hashImpl)), + m_message( + getMessage(message, m_blockHeader.get().number(), m_contextID, m_seq, m_hashImpl)), m_myAccount(getMyAccount()) {} @@ -214,7 +215,7 @@ class HostContext : public evmc_host_context task::Task> code(const evmc_address& address) { - if (auto executable = co_await getExecutable(m_rollbackableStorage, address); + if (auto executable = co_await getExecutable(m_rollbackableStorage.get(), address); executable && executable->m_code) { co_return executable->m_code; @@ -224,7 +225,7 @@ class HostContext : public evmc_host_context task::Task codeSizeAt(const evmc_address& address) { - if (auto const* precompiled = m_precompiledManager.getPrecompiled(address)) + if (auto const* precompiled = m_precompiledManager.get().getPrecompiled(address)) { co_return transaction_executor::size(*precompiled); } @@ -238,7 +239,7 @@ class HostContext : public evmc_host_context task::Task codeHashAt(const evmc_address& address) { - Account account(m_rollbackableStorage, address); + Account account(m_rollbackableStorage.get(), address); co_return co_await ledger::account::codeHash(account); } @@ -259,11 +260,15 @@ class HostContext : public evmc_host_context BOOST_THROW_EXCEPTION(std::runtime_error("Unsupported method!")); co_return h256{}; } - int64_t blockNumber() const { return m_blockHeader.number(); } - uint32_t blockVersion() const { return m_blockHeader.version(); } - int64_t timestamp() const { return m_blockHeader.timestamp(); } + int64_t blockNumber() const { return m_blockHeader.get().number(); } + uint32_t blockVersion() const { return m_blockHeader.get().version(); } + int64_t timestamp() const { return m_blockHeader.get().timestamp(); } evmc_address const& origin() const { return m_origin; } - int64_t blockGasLimit() const { return std::get<0>(m_ledgerConfig.gasLimit()); } + int64_t blockGasLimit() const { return std::get<0>(m_ledgerConfig.get().gasLimit()); } + evmc_uint256be chainId() const + { + return m_ledgerConfig.get().chainId().value_or(evmc_uint256be{}); + } /// Revert any changes made (by any of the other calls). void log(const evmc_address& address, h256s topics, bytesConstRef data) @@ -301,20 +306,20 @@ class HostContext : public evmc_host_context if (c_fileLogLevel <= LogLevel::TRACE) [[unlikely]] { HOST_CONTEXT_LOG(TRACE) - << "HostContext execute, kind: " << ref.kind << " seq:" << m_seq + << "HostContext execute, kind: " << ref.kind << " seq:" << m_seq.get() << " sender:" << address2HexString(ref.sender) << " recipient:" << address2HexString(ref.recipient) << " gas:" << ref.gas; } - auto savepoint = m_rollbackableStorage.current(); + auto savepoint = m_rollbackableStorage.get().current(); std::optional evmResult; - if (m_ledgerConfig.authCheckStatus() != 0U) + if (m_ledgerConfig.get().authCheckStatus() != 0U) { - HOST_CONTEXT_LOG(DEBUG) - << "Checking auth..." << m_ledgerConfig.authCheckStatus() << " gas: " << ref.gas; + HOST_CONTEXT_LOG(DEBUG) << "Checking auth..." << m_ledgerConfig.get().authCheckStatus() + << " gas: " << ref.gas; - if (auto result = checkAuth(m_rollbackableStorage, m_blockHeader, ref, m_origin, - buildLegacyExternalCaller(), m_precompiledManager, m_contextID, m_seq, + if (auto result = checkAuth(m_rollbackableStorage.get(), m_blockHeader, ref, m_origin, + buildLegacyExternalCaller(), m_precompiledManager.get(), m_contextID, m_seq, m_hashImpl)) { HOST_CONTEXT_LOG(DEBUG) << "Auth check failed"; @@ -338,7 +343,7 @@ class HostContext : public evmc_host_context // If the call to system contract failed, the gasUsed is cleared to zero if (evmResult->status_code != EVMC_SUCCESS) { - co_await m_rollbackableStorage.rollback(savepoint); + co_await m_rollbackableStorage.get().rollback(savepoint); if (auto hexAddress = address2FixedArray(ref.code_address); bcos::precompiled::c_systemTxsAddress.find(concepts::bytebuffer::toView( @@ -385,9 +390,9 @@ class HostContext : public evmc_host_context << " recipient:" << address2HexString(message.recipient) << " gas:" << message.gas; } - HostContext hostcontext(innerConstructor, m_rollbackableStorage, m_blockHeader, message, - m_origin, {}, m_contextID, m_seq, m_precompiledManager, m_ledgerConfig, m_hashImpl, - interface); + HostContext hostcontext(innerConstructor, m_rollbackableStorage.get(), m_blockHeader, + message, m_origin, {}, m_contextID, m_seq, m_precompiledManager.get(), m_ledgerConfig, + m_hashImpl, interface); try { @@ -432,11 +437,11 @@ class HostContext : public evmc_host_context task::Task executeCreate() { - if (m_blockHeader.number() != 0) + if (m_blockHeader.get().number() != 0) { - createAuthTable(m_rollbackableStorage, m_blockHeader, message(), m_origin, + createAuthTable(m_rollbackableStorage.get(), m_blockHeader, message(), m_origin, co_await ledger::account::path(m_myAccount), buildLegacyExternalCaller(), - m_precompiledManager, m_contextID, m_seq); + m_precompiledManager.get(), m_contextID, m_seq); } auto& ref = message(); @@ -446,7 +451,7 @@ class HostContext : public evmc_host_context if (result.status_code == 0) { auto code = bytesConstRef(result.output_data, result.output_size); - auto codeHash = m_hashImpl.hash(code); + auto codeHash = m_hashImpl.get().hash(code); co_await ledger::account::setCode( m_myAccount, code.toBytes(), std::string(m_abi), codeHash); @@ -473,10 +478,10 @@ class HostContext : public evmc_host_context if (message().kind != EVMC_DELEGATECALL) { if (auto const* precompiled = - m_precompiledManager.getPrecompiled(message().code_address)) + m_precompiledManager.get().getPrecompiled(message().code_address)) { if (auto flag = transaction_executor::featureFlag(*precompiled); - !flag || m_ledgerConfig.features().get(*flag)) + !flag || m_ledgerConfig.get().features().get(*flag)) { m_preparedPrecompiled = precompiled; co_return; @@ -484,7 +489,7 @@ class HostContext : public evmc_host_context } } - m_executable = co_await getExecutable(m_rollbackableStorage, message().code_address); + m_executable = co_await getExecutable(m_rollbackableStorage.get(), message().code_address); if (m_executable && hasPrecompiledPrefix(m_executable->m_code->data())) { if (std::holds_alternative(m_message)) @@ -523,7 +528,8 @@ class HostContext : public evmc_host_context << LOG_KV("code", code); } - if (auto const* precompiled = m_precompiledManager.getPrecompiled(message.recipient)) + if (auto const* precompiled = + m_precompiledManager.get().getPrecompiled(message.recipient)) { m_preparedPrecompiled = precompiled; } @@ -541,14 +547,16 @@ class HostContext : public evmc_host_context if (m_preparedPrecompiled != nullptr) { co_return transaction_executor::callPrecompiled(*m_preparedPrecompiled, - m_rollbackableStorage, m_blockHeader, ref, m_origin, buildLegacyExternalCaller(), - m_precompiledManager, m_contextID, m_seq, m_ledgerConfig.authCheckStatus()); + m_rollbackableStorage.get(), m_blockHeader, ref, m_origin, + buildLegacyExternalCaller(), m_precompiledManager.get(), m_contextID, m_seq, + m_ledgerConfig.get().authCheckStatus()); } else { if (!m_executable) { - m_executable = co_await getExecutable(m_rollbackableStorage, ref.code_address); + m_executable = + co_await getExecutable(m_rollbackableStorage.get(), ref.code_address); } if (!m_executable) diff --git a/transaction-scheduler/bcos-transaction-scheduler/BaselineScheduler.h b/transaction-scheduler/bcos-transaction-scheduler/BaselineScheduler.h index 251369c9f8..7df398a7da 100644 --- a/transaction-scheduler/bcos-transaction-scheduler/BaselineScheduler.h +++ b/transaction-scheduler/bcos-transaction-scheduler/BaselineScheduler.h @@ -310,8 +310,8 @@ class BaselineScheduler : public scheduler::SchedulerInterface } auto now = current(); - scheduler.m_multiLayerStorage.get().newMutable(); - auto view = scheduler.m_multiLayerStorage.get().fork(true); + auto view = scheduler.m_multiLayerStorage.get().fork(); + view.newMutable(); auto transactions = co_await getTransactions(scheduler.m_txpool.get(), *block); ledger::LedgerConfig::Ptr ledgerConfig; @@ -329,8 +329,8 @@ class BaselineScheduler : public scheduler::SchedulerInterface auto executedBlockHeader = scheduler.m_blockHeaderFactory.get().populateBlockHeader(blockHeader); bool sysBlock = false; - finishExecute(scheduler.m_multiLayerStorage.get().mutableStorage(), receipts, - *executedBlockHeader, *block, transactions, sysBlock, scheduler.m_hashImpl.get()); + finishExecute(view.mutableStorage(), receipts, *executedBlockHeader, *block, + transactions, sysBlock, scheduler.m_hashImpl.get()); if (verify && (executedBlockHeader->hash() != blockHeader->hash())) { @@ -354,13 +354,12 @@ class BaselineScheduler : public scheduler::SchedulerInterface } BASELINE_SCHEDULER_LOG(ERROR) << message; - scheduler.m_multiLayerStorage.get().removeMutable(); co_return std::make_tuple( BCOS_ERROR_UNIQUE_PTR(scheduler::SchedulerError::InvalidBlocks, message), nullptr, false); } - scheduler.m_multiLayerStorage.get().pushMutableToImmutableFront(); + scheduler.m_multiLayerStorage.get().pushView(std::move(view)); scheduler.m_lastExecutedBlockNumber = blockHeader->number(); std::unique_lock resultsLock(scheduler.m_resultsMutex); @@ -449,7 +448,7 @@ class BaselineScheduler : public scheduler::SchedulerInterface resultsLock.unlock(); result.m_block->setBlockHeader(header); - auto lastStorage = scheduler.m_multiLayerStorage.get().lastImmutableStorage(); + auto lastStorage = scheduler.m_multiLayerStorage.get().backStorage(); if (result.m_block->blockHeaderConst()->number() != 0) { ittapi::Report report(ittapi::ITT_DOMAINS::instance().BASE_SCHEDULER, @@ -458,8 +457,7 @@ class BaselineScheduler : public scheduler::SchedulerInterface co_await ledger::prewriteBlock(scheduler.m_ledger.get(), result.m_transactions, result.m_block, false, *lastStorage); } - auto mergedStorage = - co_await scheduler.m_multiLayerStorage.get().mergeAndPopImmutableBack(); + auto mergedStorage = co_await scheduler.m_multiLayerStorage.get().mergeBackStorage(); co_await ledger::storeTransactionsAndReceipts( scheduler.m_ledger.get(), result.m_transactions, result.m_block); @@ -580,8 +578,8 @@ class BaselineScheduler : public scheduler::SchedulerInterface { task::wait([](decltype(this) self, protocol::Transaction::Ptr transaction, decltype(callback) callback) -> task::Task { - auto view = self->m_multiLayerStorage.get().fork(false); - view.newTemporaryMutable(); + auto view = self->m_multiLayerStorage.get().fork(); + view.newMutable(); auto blockHeader = self->m_blockHeaderFactory.get().createBlockHeader(); ledger::LedgerConfig::Ptr ledgerConfig; { @@ -622,7 +620,7 @@ class BaselineScheduler : public scheduler::SchedulerInterface { task::wait([](decltype(this) self, std::string_view contract, decltype(callback) callback) -> task::Task { - auto view = self->m_multiLayerStorage.get().fork(false); + auto view = self->m_multiLayerStorage.get().fork(); auto contractAddress = unhexAddress(contract); ledger::account::EVMAccount account(view, contractAddress); auto code = co_await ledger::account::code(account); @@ -642,7 +640,7 @@ class BaselineScheduler : public scheduler::SchedulerInterface { task::wait([](decltype(this) self, std::string_view contract, decltype(callback) callback) -> task::Task { - auto view = self->m_multiLayerStorage.get().fork(false); + auto view = self->m_multiLayerStorage.get().fork(); auto contractAddress = unhexAddress(contract); ledger::account::EVMAccount account(view, contractAddress); auto abi = co_await ledger::account::abi(account); @@ -663,7 +661,7 @@ class BaselineScheduler : public scheduler::SchedulerInterface callback(nullptr); } - void stop() override {}; + void stop() override{}; void registerTransactionNotifier(std::function)> diff --git a/transaction-scheduler/bcos-transaction-scheduler/MultiLayerStorage.h b/transaction-scheduler/bcos-transaction-scheduler/MultiLayerStorage.h index 27c9e3ec44..f0a2b8d4cb 100644 --- a/transaction-scheduler/bcos-transaction-scheduler/MultiLayerStorage.h +++ b/transaction-scheduler/bcos-transaction-scheduler/MultiLayerStorage.h @@ -1,8 +1,9 @@ #pragma once #include "bcos-framework/storage2/Storage.h" +#include "bcos-task/TBBWait.h" #include "bcos-task/Trait.h" -#include "bcos-task/Wait.h" #include "bcos-utilities/Error.h" +#include "bcos-utilities/RecursiveLambda.h" #include #include #include @@ -23,275 +24,250 @@ struct UnsupportedMethod : public bcos::Error {}; template requires((std::is_void_v || (!std::is_void_v))) -class MultiLayerStorage +class View { -private: +public: constexpr static bool withCacheStorage = !std::is_void_v; using KeyType = std::remove_cvref_t; using ValueType = std::remove_cvref_t; - static_assert(std::same_as); - static_assert(std::same_as); + using MutableStorage = MutableStorageType; std::shared_ptr m_mutableStorage; std::deque> m_immutableStorages; - std::mutex m_listMutex; - std::mutex m_mergeMutex; - std::reference_wrapper> m_backendStorage; [[no_unique_address]] std::conditional_t>, std::monostate> m_cacheStorage; - // 同一时间只允许一个可以修改的view - // Only one view that can be modified is allowed at a time - std::mutex m_mutableMutex; - -public: - using MutableStorage = MutableStorageType; + View(BackendStorage& backendStorage) + requires(!withCacheStorage) + : m_backendStorage(backendStorage) + {} + View(BackendStorage& backendStorage, + std::conditional_t, + std::monostate> + cacheStorage) + requires(withCacheStorage) + : m_backendStorage(backendStorage), m_cacheStorage(cacheStorage) + {} - class View + static task::Task fillMissingValues( + auto& storage, RANGES::input_range auto&& keys, RANGES::input_range auto& values) { - friend class MultiLayerStorage; - - private: - std::shared_ptr m_mutableStorage; - std::deque> m_immutableStorages; - std::reference_wrapper> m_backendStorage; - [[no_unique_address]] std::conditional_t>, std::monostate> - m_cacheStorage; - std::unique_lock m_mutableLock; - - View(BackendStorage& backendStorage) - requires(!withCacheStorage) - : m_backendStorage(backendStorage) - {} - View(BackendStorage& backendStorage, - std::conditional_t, - std::monostate> - cacheStorage) - requires(withCacheStorage) - : m_backendStorage(backendStorage), m_cacheStorage(cacheStorage) - {} - - static task::Task fillMissingValues( - auto& storage, RANGES::input_range auto&& keys, RANGES::input_range auto& values) - { - using StoreKeyType = std::conditional_t< - std::is_lvalue_reference_v>, + using StoreKeyType = + std::conditional_t>, std::reference_wrapper, KeyType>; - std::vector>>> - missingKeyValues; - for (auto&& [key, value] : - RANGES::views::zip(std::forward(keys), values)) - { - if (!value) - { - missingKeyValues.emplace_back( - std::forward(key), std::ref(value)); - } - } - auto gotValues = - co_await storage2::readSome(storage, missingKeyValues | RANGES::views::keys); - - size_t count = 0; - for (auto&& [from, to] : - RANGES::views::zip(gotValues, missingKeyValues | RANGES::views::values)) + std::vector>>> + missingKeyValues; + for (auto&& [key, value] : RANGES::views::zip(std::forward(keys), values)) + { + if (!value) { - if (from) - { - to.get() = std::move(from); - ++count; - } + missingKeyValues.emplace_back(std::forward(key), std::ref(value)); } - - co_return count == RANGES::size(gotValues); } + auto gotValues = + co_await storage2::readSome(storage, missingKeyValues | RANGES::views::keys); - public: - using MutableStorage = MutableStorageType; - - friend auto tag_invoke(storage2::tag_t /*unused*/, View& storage, - RANGES::input_range auto&& keys) - -> task::Task>> - requires RANGES::sized_range && - RANGES::sized_range>> + size_t count = 0; + for (auto&& [from, to] : + RANGES::views::zip(gotValues, missingKeyValues | RANGES::views::values)) { - task::AwaitableReturnType - values(RANGES::size(keys)); - if (storage.m_mutableStorage && - co_await fillMissingValues(*storage.m_mutableStorage, keys, values)) - { - co_return values; - } - else + if (from) { - values.resize(RANGES::size(keys)); - } - - for (auto& immutableStorage : storage.m_immutableStorages) - { - if (co_await fillMissingValues(*immutableStorage, keys, values)) - { - co_return values; - } + to.get() = std::move(from); + ++count; } + } - if constexpr (withCacheStorage) - { - if (co_await fillMissingValues(storage.m_cacheStorage.get(), keys, values)) - { - co_return values; - } - } + co_return count == RANGES::size(gotValues); + } - co_await fillMissingValues(storage.m_backendStorage.get(), keys, values); + friend auto tag_invoke( + storage2::tag_t /*unused*/, View& view, RANGES::input_range auto&& keys) + -> task::Task>> + requires RANGES::sized_range && + RANGES::sized_range>> + { + task::AwaitableReturnType + values(RANGES::size(keys)); + if (view.m_mutableStorage && + co_await fillMissingValues(*view.m_mutableStorage, keys, values)) + { co_return values; } + else + { + values.resize(RANGES::size(keys)); + } - friend auto tag_invoke(storage2::tag_t /*unused*/, View& storage, - RANGES::input_range auto&& keys, storage2::DIRECT_TYPE /*unused*/) - -> task::Task>> + for (auto& immutableStorage : view.m_immutableStorages) { - if (storage.m_mutableStorage) + if (co_await fillMissingValues(*immutableStorage, keys, values)) { - co_return co_await storage2::readSome( - *storage.m_mutableStorage, std::forward(keys)); + co_return values; } + } - for (auto& immutableStorage : storage.m_immutableStorages) + if constexpr (withCacheStorage) + { + if (co_await fillMissingValues(view.m_cacheStorage.get(), keys, values)) { - co_return co_await storage2::readSome( - *immutableStorage, std::forward(keys)); + co_return values; } + } - if constexpr (withCacheStorage) - { - co_return co_await storage2::readSome( - storage.m_cacheStorage.get(), std::forward(keys)); - } + co_await fillMissingValues(view.m_backendStorage.get(), keys, values); + co_return values; + } + friend auto tag_invoke(storage2::tag_t /*unused*/, View& view, + RANGES::input_range auto&& keys, storage2::DIRECT_TYPE /*unused*/) + -> task::Task>> + { + if (view.m_mutableStorage) + { co_return co_await storage2::readSome( - storage.m_backendStorage.get(), std::forward(keys)); + *view.m_mutableStorage, std::forward(keys)); } - friend auto tag_invoke( - storage2::tag_t /*unused*/, View& storage, auto&& key) - -> task::Task>> + for (auto& immutableStorage : view.m_immutableStorages) { - if (storage.m_mutableStorage) - { - if (auto value = co_await storage2::readOne(*storage.m_mutableStorage, key)) - { - co_return value; - } - } + co_return co_await storage2::readSome( + *immutableStorage, std::forward(keys)); + } - for (auto& immutableStorage : storage.m_immutableStorages) - { - if (auto value = co_await storage2::readOne(*immutableStorage, key)) - { - co_return value; - } - } + if constexpr (withCacheStorage) + { + co_return co_await storage2::readSome( + view.m_cacheStorage.get(), std::forward(keys)); + } + + co_return co_await storage2::readSome( + view.m_backendStorage.get(), std::forward(keys)); + } - if constexpr (withCacheStorage) + friend auto tag_invoke(storage2::tag_t /*unused*/, View& view, auto&& key) + -> task::Task>> + { + if (view.m_mutableStorage) + { + if (auto value = co_await storage2::readOne(*view.m_mutableStorage, key)) { - if (auto value = co_await storage2::readOne(storage.m_cacheStorage.get(), key)) - { - co_return value; - } + co_return value; } - - co_return co_await storage2::readOne(storage.m_backendStorage.get(), key); } - friend auto tag_invoke(storage2::tag_t /*unused*/, View& storage, - auto&& key, storage2::DIRECT_TYPE /*unused*/) - -> task::Task>> + for (auto& immutableStorage : view.m_immutableStorages) { - if (storage.m_mutableStorage) + if (auto value = co_await storage2::readOne(*immutableStorage, key)) { - co_return co_await storage2::readOne( - *storage.m_mutableStorage, std::forward(key)); + co_return value; } + } - for (auto& immutableStorage : storage.m_immutableStorages) + if constexpr (withCacheStorage) + { + if (auto value = co_await storage2::readOne(view.m_cacheStorage.get(), key)) { - co_return co_await storage2::readOne( - *immutableStorage, std::forward(key)); + co_return value; } + } - if constexpr (withCacheStorage) - { - co_return co_await storage2::readOne( - storage.m_cacheStorage.get(), std::forward(key)); - } + co_return co_await storage2::readOne(view.m_backendStorage.get(), key); + } + friend auto tag_invoke(storage2::tag_t /*unused*/, View& view, auto&& key, + storage2::DIRECT_TYPE /*unused*/) + -> task::Task>> + { + if (view.m_mutableStorage) + { co_return co_await storage2::readOne( - storage.m_backendStorage.get(), std::forward(key)); + *view.m_mutableStorage, std::forward(key)); } - friend task::Task tag_invoke(storage2::tag_t /*unused*/, - View& storage, RANGES::input_range auto&& keys, RANGES::input_range auto&& values) + for (auto& immutableStorage : view.m_immutableStorages) { - co_await storage2::writeSome(storage.mutableStorage(), - std::forward(keys), std::forward(values)); + co_return co_await storage2::readOne( + *immutableStorage, std::forward(key)); } - friend auto tag_invoke(storage2::tag_t /*unused*/, View& storage, - auto&& key, auto&& value) -> task::Task + if constexpr (withCacheStorage) { - co_await storage2::writeOne(storage.mutableStorage(), std::forward(key), - std::forward(value)); + co_return co_await storage2::readOne( + view.m_cacheStorage.get(), std::forward(key)); } - friend task::Task tag_invoke( - storage2::tag_t /*unused*/, View& toStorage, auto&& fromStorage) - { - co_await storage2::merge( - toStorage.mutableStorage(), std::forward(fromStorage)); - } + co_return co_await storage2::readOne( + view.m_backendStorage.get(), std::forward(key)); + } - friend task::Task tag_invoke(storage2::tag_t /*unused*/, - View& storage, RANGES::input_range auto&& keys, auto&&... args) - { - co_await storage2::removeSome(storage.mutableStorage(), - std::forward(keys), std::forward(args)...); - } + friend task::Task tag_invoke(storage2::tag_t /*unused*/, View& view, + RANGES::input_range auto&& keys, RANGES::input_range auto&& values) + { + co_await storage2::writeSome(view.mutableStorage(), std::forward(keys), + std::forward(values)); + } + + friend auto tag_invoke(storage2::tag_t /*unused*/, View& view, auto&& key, + auto&& value) -> task::Task + { + co_await storage2::writeOne(view.mutableStorage(), std::forward(key), + std::forward(value)); + } - class Iterator + friend task::Task tag_invoke( + storage2::tag_t /*unused*/, View& toView, auto&& fromStorage) + { + co_await storage2::merge( + toView.mutableStorage(), std::forward(fromStorage)); + } + + friend task::Task tag_invoke(storage2::tag_t /*unused*/, View& view, + RANGES::input_range auto&& keys, auto&&... args) + { + co_await storage2::removeSome(view.mutableStorage(), std::forward(keys), + std::forward(args)...); + } + + class Iterator + { + private: + using StorageIterator = + std::variant>>, + task::AwaitableReturnType>>>; + using RangeValue = + std::optional>; + std::vector> m_iterators; + + task::Task forwardIterators(auto&& iterators) { - private: - using StorageIterator = - std::variant>>, - task::AwaitableReturnType>>>; - using RangeValue = std::optional< - std::tuple>; - std::vector> m_iterators; - - task::Task forwardIterators(auto&& iterators) + for (auto& it : iterators) { - for (auto& it : iterators) - { - auto& [variantIterator, item] = it; - item = co_await std::visit( - [](auto& input) -> task::Task { + auto& [variantIterator, item] = it; + item = co_await std::visit( + bcos::recursiveLambda( + [&](auto const& self, auto& input) -> task::Task { RangeValue item; auto rangeValue = co_await input.next(); if (rangeValue) { auto&& [key, value] = *rangeValue; - if constexpr (std::is_pointer_v) + if constexpr (std::is_pointer_v>) { + if (!value) + { + co_return co_await self(self, input); + } item.emplace(key, *value); } else @@ -300,274 +276,249 @@ class MultiLayerStorage } } co_return item; - }, - variantIterator); - } + }), + variantIterator); } + } - public: - task::Task init(View& view, auto&&... args) + public: + task::Task init(View& view, auto&&... args) + { + if (view.m_mutableStorage) { - if (view.m_mutableStorage) - { - m_iterators.emplace_back(co_await storage2::range(*view.m_mutableStorage, - std::forward(args)...), - RangeValue{}); - } - for (auto& storage : view.m_immutableStorages) - { - m_iterators.emplace_back( - co_await storage2::range(*storage, std::forward(args)...), - RangeValue{}); - } - m_iterators.emplace_back(co_await storage2::range(view.m_backendStorage.get(), + m_iterators.emplace_back(co_await storage2::range(*view.m_mutableStorage, std::forward(args)...), RangeValue{}); - co_await forwardIterators(m_iterators); + } + for (auto& storage : view.m_immutableStorages) + { + m_iterators.emplace_back( + co_await storage2::range(*storage, std::forward(args)...), + RangeValue{}); + } + m_iterators.emplace_back(co_await storage2::range(view.m_backendStorage.get(), + std::forward(args)...), + RangeValue{}); + co_await forwardIterators(m_iterators); + } + + task::Task next() + { + // 基于合并排序,找到所有迭代器的最小值,推进迭代器并返回值 + // Based on merge sort, find the minimum value of all iterators, advance the + // iterator and return its value + auto iterators = m_iterators | RANGES::views::filter([](auto const& rangeValue) { + return std::get<1>(rangeValue).has_value(); + }); + if (RANGES::empty(iterators)) + { + co_return std::nullopt; } - task::Task next() + std::vector*> minIterators; + for (auto& it : iterators) { - // 基于合并排序,找到所有迭代器的最小值,推进迭代器并返回值 - // Based on merge sort, find the minimum value of all iterators, advance the - // iterator and return its value - auto iterators = m_iterators | RANGES::views::filter([](auto const& rangeValue) { - return std::get<1>(rangeValue).has_value(); - }); - if (RANGES::empty(iterators)) + if (minIterators.empty()) { - co_return std::nullopt; + minIterators.emplace_back(std::addressof(it)); } - - std::vector*> minIterators; - for (auto& it : iterators) + else { - if (minIterators.empty()) + auto& [variantIterator, value] = it; + auto& key = std::get<0>(*value); + auto& existsKey = std::get<0>(*std::get<1>(*minIterators[0])); + + if (key < existsKey) { + minIterators.clear(); minIterators.emplace_back(std::addressof(it)); } - else + else if (key == existsKey) { - auto& [variantIterator, value] = it; - auto& key = std::get<0>(*value); - auto& existsKey = std::get<0>(*std::get<1>(*minIterators[0])); - - if (key < existsKey) - { - minIterators.clear(); - minIterators.emplace_back(std::addressof(it)); - } - else if (key == existsKey) - { - minIterators.emplace_back(std::addressof(it)); - } + minIterators.emplace_back(std::addressof(it)); } } + } - RangeValue result = std::get<1>(*minIterators[0]); - co_await forwardIterators( - minIterators | - RANGES::views::transform([](auto* iterator) -> auto& { return *iterator; })); - co_return result; - }; + RangeValue result = std::get<1>(*minIterators[0]); + co_await forwardIterators( + minIterators | + RANGES::views::transform([](auto* iterator) -> auto& { return *iterator; })); + co_return result; }; + }; - friend task::Task tag_invoke( - bcos::storage2::tag_t /*unused*/, View& storage, auto&&... args) - { - Iterator iterator; - co_await iterator.init(storage, std::forward(args)...); - co_return iterator; - } + friend task::Task tag_invoke( + bcos::storage2::tag_t /*unused*/, View& view, auto&&... args) + { + Iterator iterator; + co_await iterator.init(view, std::forward(args)...); + co_return iterator; + } - MutableStorageType& mutableStorage() + MutableStorageType& mutableStorage() + { + if (!m_mutableStorage) { - if (!m_mutableStorage) - { - BOOST_THROW_EXCEPTION(NotExistsMutableStorageError{}); - } - return *m_mutableStorage; + BOOST_THROW_EXCEPTION(NotExistsMutableStorageError{}); } + return *m_mutableStorage; + } - View(const View&) = delete; - View& operator=(const View&) = delete; - View(View&&) noexcept = default; - View& operator=(View&&) noexcept = default; - ~View() noexcept { release(); } + View(const View&) = delete; + View& operator=(const View&) = delete; + View(View&&) noexcept = default; + View& operator=(View&&) noexcept = default; + ~View() noexcept = default; - using Key = KeyType; - using Value = ValueType; + using Key = KeyType; + using Value = ValueType; - void release() + template + void newMutable(Args&&... args) + { + if (m_mutableStorage) { - if (m_mutableLock.owns_lock()) - { - m_mutableLock.unlock(); - } + BOOST_THROW_EXCEPTION(DuplicateMutableStorageError{}); } - template - void newTemporaryMutable(Args... args) - { - if (m_mutableStorage) - { - BOOST_THROW_EXCEPTION(DuplicateMutableStorageError{}); - } + m_mutableStorage = + std::make_shared(std::forward(args)...); + } - m_mutableStorage = std::make_shared(args...); - } + BackendStorage& backendStorage() & { return m_backendStorage; } +}; - BackendStorage& backendStorage() & { return m_backendStorage; } - }; +template + requires((std::is_void_v || (!std::is_void_v))) +class MultiLayerStorage +{ +private: + constexpr static bool withCacheStorage = !std::is_void_v; + using KeyType = std::remove_cvref_t; + using ValueType = std::remove_cvref_t; + using ViewType = View; + + std::deque> m_storages; + std::mutex m_listMutex; + std::mutex m_mergeMutex; + + std::reference_wrapper> m_backendStorage; + [[no_unique_address]] std::conditional_t>, std::monostate> + m_cacheStorage; + +public: + using MutableStorage = MutableStorageType; using Key = KeyType; using Value = ValueType; - explicit MultiLayerStorage(BackendStorage& backendStorage) + explicit MultiLayerStorage(BackendStorage& backendStorage) noexcept requires(!withCacheStorage) : m_backendStorage(backendStorage) - {} + { + static_assert(std::same_as); + static_assert( + std::same_as); + } MultiLayerStorage(BackendStorage& backendStorage, std::conditional_t, std::monostate> - cacheStorage) + cacheStorage) noexcept requires(withCacheStorage) : m_backendStorage(backendStorage), m_cacheStorage(cacheStorage) - {} + { + static_assert(std::same_as); + static_assert( + std::same_as); + } MultiLayerStorage(const MultiLayerStorage&) = delete; - MultiLayerStorage(MultiLayerStorage&&) noexcept = delete; + MultiLayerStorage(MultiLayerStorage&&) noexcept = default; MultiLayerStorage& operator=(const MultiLayerStorage&) = delete; - MultiLayerStorage& operator=(MultiLayerStorage&&) noexcept = delete; + MultiLayerStorage& operator=(MultiLayerStorage&&) noexcept = default; ~MultiLayerStorage() noexcept = default; - View fork(bool withMutable) + ViewType fork() { std::unique_lock lock(m_listMutex); if constexpr (withCacheStorage) { - View view(m_backendStorage, m_cacheStorage); - if (withMutable) - { - view.m_mutableLock = {m_mutableMutex, std::try_to_lock}; - if (!view.m_mutableLock.owns_lock()) - { - BOOST_THROW_EXCEPTION(DuplicateMutableViewError{}); - } - view.m_mutableStorage = m_mutableStorage; - } - view.m_immutableStorages = m_immutableStorages; - + ViewType view(m_backendStorage, m_cacheStorage); + view.m_immutableStorages = m_storages; return view; } else { - View view(m_backendStorage); - if (withMutable) - { - view.m_mutableLock = {m_mutableMutex, std::try_to_lock}; - if (!view.m_mutableLock.owns_lock()) - { - BOOST_THROW_EXCEPTION(DuplicateMutableViewError{}); - } - view.m_mutableStorage = m_mutableStorage; - } - view.m_immutableStorages = m_immutableStorages; - + ViewType view(m_backendStorage); + view.m_immutableStorages = m_storages; return view; } } - template - void newMutable(Args... args) - { - std::unique_lock lock(m_listMutex); - if (m_mutableStorage) - { - BOOST_THROW_EXCEPTION(DuplicateMutableStorageError{}); - } - - m_mutableStorage = std::make_shared(args...); - } - - void removeMutable() - { - std::unique_lock lock(m_listMutex); - m_mutableStorage.reset(); - } - - void pushMutableToImmutableFront() + void pushView(ViewType&& view) { - if (!m_mutableStorage) + if (!view.m_mutableStorage) { - BOOST_THROW_EXCEPTION(NotExistsMutableStorageError{}); + return; } std::unique_lock lock(m_listMutex); - m_immutableStorages.push_front(std::move(m_mutableStorage)); - m_mutableStorage.reset(); + m_storages.push_front(std::move(view.m_mutableStorage)); } - task::Task> mergeAndPopImmutableBack() + task::Task> mergeBackStorage() { std::unique_lock mergeLock(m_mergeMutex); - std::unique_lock immutablesLock(m_listMutex); - if (m_immutableStorages.empty()) + std::unique_lock listLock(m_listMutex); + if (m_storages.empty()) { BOOST_THROW_EXCEPTION(NotExistsImmutableStorageError{}); } - auto immutableStorage = m_immutableStorages.back(); - immutablesLock.unlock(); + auto backStoragePtr = m_storages.back(); + auto const& backStorage = *backStoragePtr; + listLock.unlock(); if constexpr (withCacheStorage) { tbb::parallel_invoke( [&]() { - task::syncWait(storage2::merge(m_backendStorage.get(), *immutableStorage)); + task::tbb::syncWait(storage2::merge(m_backendStorage.get(), backStorage)); }, - [&]() { - task::syncWait(storage2::merge(m_cacheStorage.get(), *immutableStorage)); - }); + [&]() { task::tbb::syncWait(storage2::merge(m_cacheStorage.get(), backStorage)); }); } else { - co_await storage2::merge(m_backendStorage.get(), *immutableStorage); + co_await storage2::merge(m_backendStorage.get(), backStorage); } - immutablesLock.lock(); - m_immutableStorages.pop_back(); + listLock.lock(); + m_storages.pop_back(); - co_return immutableStorage; + co_return backStoragePtr; } - std::shared_ptr frontImmutableStorage() + std::shared_ptr frontStorage() { std::unique_lock immutablesLock(m_listMutex); - if (m_immutableStorages.empty()) + if (m_storages.empty()) { BOOST_THROW_EXCEPTION(NotExistsImmutableStorageError{}); } - return m_immutableStorages.front(); + return m_storages.front(); } - std::shared_ptr lastImmutableStorage() + std::shared_ptr backStorage() { std::unique_lock immutablesLock(m_listMutex); - if (m_immutableStorages.empty()) + if (m_storages.empty()) { BOOST_THROW_EXCEPTION(NotExistsImmutableStorageError{}); } - return m_immutableStorages.back(); + return m_storages.back(); } - MutableStorageType& mutableStorage() - { - if (!m_mutableStorage) - { - BOOST_THROW_EXCEPTION(NotExistsMutableStorageError{}); - } - return *m_mutableStorage; - } BackendStorage& backendStorage() { return m_backendStorage; } }; diff --git a/transaction-scheduler/bcos-transaction-scheduler/ReadWriteSetStorage.h b/transaction-scheduler/bcos-transaction-scheduler/ReadWriteSetStorage.h index d0b2c1825d..60370084c3 100644 --- a/transaction-scheduler/bcos-transaction-scheduler/ReadWriteSetStorage.h +++ b/transaction-scheduler/bcos-transaction-scheduler/ReadWriteSetStorage.h @@ -105,9 +105,9 @@ class ReadWriteSetStorage } friend auto tag_invoke(bcos::storage2::tag_t /*unused*/, - ReadWriteSetStorage& storage, - auto&&... args) -> task::Task, decltype(args)...>>> + ReadWriteSetStorage& storage, auto&&... args) + -> task::Task, decltype(args)...>>> { co_return co_await storage2::range( storage.m_storage.get(), std::forward(args)...); diff --git a/transaction-scheduler/bcos-transaction-scheduler/SchedulerParallelImpl.h b/transaction-scheduler/bcos-transaction-scheduler/SchedulerParallelImpl.h index 36d872d880..d1d29d3aa0 100644 --- a/transaction-scheduler/bcos-transaction-scheduler/SchedulerParallelImpl.h +++ b/transaction-scheduler/bcos-transaction-scheduler/SchedulerParallelImpl.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -26,112 +27,123 @@ namespace bcos::transaction_scheduler #define PARALLEL_SCHEDULER_LOG(LEVEL) BCOS_LOG(LEVEL) << LOG_BADGE("PARALLEL_SCHEDULER") -class SchedulerParallelImpl +template +struct StorageTrait +{ + using LocalStorageView = View; + using LocalReadWriteSetStorage = + ReadWriteSetStorage; +}; + +template +struct ExecutionContext +{ + ExecutionContext(int contextID, std::reference_wrapper transaction, + std::reference_wrapper receipt, + std::optional coro, typename CoroType::Iterator iterator) + : contextID(contextID), + transaction(transaction), + receipt(receipt), + coro(std::move(coro)), + iterator(std::move(iterator)) + {} + int contextID; + std::reference_wrapper transaction; + std::reference_wrapper receipt; + std::optional coro; + typename CoroType::Iterator iterator; +}; + +template +class ChunkStatus { private: - template - struct StorageTrait - { - using LocalStorage = MultiLayerStorage; - using LocalStorageView = - std::invoke_result_t; - using LocalReadWriteSetStorage = - ReadWriteSetStorage; - }; - - template - class ChunkStatus + int64_t m_chunkIndex = 0; + std::reference_wrapper m_hasRAW; + ContextRange m_contextRange; + std::reference_wrapper m_executor; + typename StorageTrait::LocalStorageView m_storageView; + typename StorageTrait::LocalReadWriteSetStorage m_readWriteSetStorage; + +public: + ChunkStatus(int64_t chunkIndex, boost::atomic_flag const& hasRAW, ContextRange contextRange, + Executor& executor, auto& storage) + : m_chunkIndex(chunkIndex), + m_hasRAW(hasRAW), + m_contextRange(std::move(contextRange)), + m_executor(executor), + m_storageView(storage), + m_readWriteSetStorage(m_storageView) { - private: - auto forkAndMutable(auto& storage) - { - storage.newMutable(); - auto view = storage.fork(true); - return view; - } + m_storageView.newMutable(); + } - int64_t m_chunkIndex = 0; - std::reference_wrapper m_hasRAW; - ContextRange m_contextRange; - std::reference_wrapper m_executor; - typename StorageTrait::LocalStorage m_localStorage; - typename StorageTrait::LocalStorageView m_localStorageView; - typename StorageTrait::LocalReadWriteSetStorage m_localReadWriteSetStorage; - - public: - ChunkStatus(int64_t chunkIndex, boost::atomic_flag const& hasRAW, ContextRange contextRange, - Executor& executor, auto& storage) - : m_chunkIndex(chunkIndex), - m_hasRAW(hasRAW), - m_contextRange(std::move(contextRange)), - m_executor(executor), - m_localStorage(storage), - m_localStorageView(forkAndMutable(m_localStorage)), - m_localReadWriteSetStorage(m_localStorageView) - {} - - int64_t chunkIndex() const { return m_chunkIndex; } - auto count() const { return RANGES::size(m_contextRange); } - auto& localStorage() & { return m_localStorage; } - auto& readWriteSetStorage() & { return m_localReadWriteSetStorage; } - - void executeStep1( - protocol::BlockHeader const& blockHeader, ledger::LedgerConfig const& ledgerConfig) + int64_t chunkIndex() const { return m_chunkIndex; } + auto count() const { return RANGES::size(m_contextRange); } + auto& storageView() & { return m_storageView; } + auto& readWriteSetStorage() & { return m_readWriteSetStorage; } + + void executeStep1( + protocol::BlockHeader const& blockHeader, ledger::LedgerConfig const& ledgerConfig) + { + ittapi::Report report(ittapi::ITT_DOMAINS::instance().PARALLEL_SCHEDULER, + ittapi::ITT_DOMAINS::instance().EXECUTE_CHUNK1); + for (auto&& [index, context] : RANGES::views::enumerate(m_contextRange)) { - ittapi::Report report(ittapi::ITT_DOMAINS::instance().PARALLEL_SCHEDULER, - ittapi::ITT_DOMAINS::instance().EXECUTE_CHUNK1); - for (auto&& [index, context] : RANGES::views::enumerate(m_contextRange)) + if (m_hasRAW.get().test()) { - if (m_hasRAW.get().test()) - { - PARALLEL_SCHEDULER_LOG(DEBUG) - << "Chunk: " << m_chunkIndex << " aborted in step1, executed " << index - << " transactions"; - break; - } - - context.coro.emplace(transaction_executor::execute3Step(m_executor.get(), - m_localReadWriteSetStorage, blockHeader, context.transaction.get(), - context.contextID, ledgerConfig, task::tbb::syncWait)); - context.iterator = context.coro->begin(); - context.receipt.get() = *context.iterator; + PARALLEL_SCHEDULER_LOG(DEBUG) + << "Chunk: " << m_chunkIndex << " aborted in step1, executed " << index + << " transactions"; + break; } + + context.coro.emplace(transaction_executor::execute3Step(m_executor.get(), + m_readWriteSetStorage, blockHeader, context.transaction.get(), context.contextID, + ledgerConfig, task::tbb::syncWait)); + context.iterator = context.coro->begin(); + context.receipt.get() = *context.iterator; } + } - void executeStep2() + void executeStep2() + { + ittapi::Report report(ittapi::ITT_DOMAINS::instance().PARALLEL_SCHEDULER, + ittapi::ITT_DOMAINS::instance().EXECUTE_CHUNK2); + for (auto&& [index, context] : RANGES::views::enumerate(m_contextRange)) { - ittapi::Report report(ittapi::ITT_DOMAINS::instance().PARALLEL_SCHEDULER, - ittapi::ITT_DOMAINS::instance().EXECUTE_CHUNK2); - for (auto&& [index, context] : RANGES::views::enumerate(m_contextRange)) + if (m_hasRAW.get().test()) + { + PARALLEL_SCHEDULER_LOG(DEBUG) + << "Chunk: " << m_chunkIndex << " aborted in step2, executed " << index + << " transactions"; + break; + } + if (!context.receipt.get() && context.iterator != context.coro->end()) { - if (m_hasRAW.get().test()) - { - PARALLEL_SCHEDULER_LOG(DEBUG) - << "Chunk: " << m_chunkIndex << " aborted in step2, executed " << index - << " transactions"; - break; - } - if (!context.receipt.get() && context.iterator != context.coro->end()) - { - context.receipt.get() = *(++context.iterator); - } + context.receipt.get() = *(++context.iterator); } } + } - void executeStep3() + void executeStep3() + { + ittapi::Report report(ittapi::ITT_DOMAINS::instance().PARALLEL_SCHEDULER, + ittapi::ITT_DOMAINS::instance().EXECUTE_CHUNK3); + for (auto& context : m_contextRange) { - ittapi::Report report(ittapi::ITT_DOMAINS::instance().PARALLEL_SCHEDULER, - ittapi::ITT_DOMAINS::instance().EXECUTE_CHUNK3); - for (auto& context : m_contextRange) + if (!context.receipt.get() && context.iterator != context.coro->end()) { - if (!context.receipt.get() && context.iterator != context.coro->end()) - { - context.receipt.get() = *(++context.iterator); - } + context.receipt.get() = *(++context.iterator); } } - }; + } +}; +template +class SchedulerParallelImpl +{ +private: constexpr static auto DEFAULT_TRANSACTION_GRAIN_SIZE = 16L; GC m_gc; size_t m_grainSize = DEFAULT_TRANSACTION_GRAIN_SIZE; @@ -155,18 +167,18 @@ class SchedulerParallelImpl const auto count = RANGES::size(contexts); ReadWriteSetStorage writeSet(storage); - using Chunk = SchedulerParallelImpl::ChunkStatus, + using Chunk = ChunkStatus, std::decay_t, decltype(RANGES::subrange>(contexts))>; - using ChunkStorage = typename std::decay_t::MutableStorage; boost::atomic_flag hasRAW; - ChunkStorage lastStorage; - const auto contextChunks = RANGES::views::chunk(contexts, chunkSize); + MutableStorage lastStorage; + auto contextChunks = RANGES::views::chunk(contexts, chunkSize); std::atomic_size_t offset = 0; std::atomic_size_t chunkIndex = 0; + tbb::task_group_context context; // 五级流水线:分片准备、并行执行、检测RAW冲突&合并读写集、生成回执、合并storage // Five-stage pipeline: shard preparation, parallel execution, detection of RAW // conflicts & merging read/write sets, generating receipts, and merging storage @@ -246,8 +258,8 @@ class SchedulerParallelImpl return chunk; }) & - tbb::make_filter, void>( - tbb::filter_mode::serial_in_order, [&](std::unique_ptr chunk) { + tbb::make_filter, void>(tbb::filter_mode::serial_in_order, + [&](std::unique_ptr chunk) { if (chunk) { ittapi::Report report1( @@ -261,10 +273,15 @@ class SchedulerParallelImpl << "Merging storage... " << chunk->chunkIndex() << " | " << chunk->count(); task::tbb::syncWait(storage2::merge( - lastStorage, std::move(chunk->localStorage().mutableStorage()))); + lastStorage, std::move(chunk->storageView().mutableStorage()))); scheduler.m_gc.collect(std::move(chunk)); } - })); + else + { + context.cancel_group_execution(); + } + }), + context); task::tbb::syncWait(mergeLastStorage(scheduler, storage, std::move(lastStorage))); scheduler.m_gc.collect(std::move(writeSet)); @@ -280,16 +297,6 @@ class SchedulerParallelImpl return 0; } - template - struct ExecutionContext - { - int contextID{}; - std::reference_wrapper transaction; - std::reference_wrapper receipt; - std::optional coro; - typename CoroType::Iterator iterator; - }; - friend task::Task> tag_invoke( tag_t /*unused*/, SchedulerParallelImpl& scheduler, auto& storage, auto& executor, protocol::BlockHeader const& blockHeader, @@ -303,22 +310,20 @@ class SchedulerParallelImpl std::vector receipts(count); using Storage = std::decay_t; - using CoroType = std::invoke_result_t::LocalReadWriteSetStorage>, - protocol::BlockHeader const&, protocol::Transaction const&, int, - ledger::LedgerConfig const&, task::tbb::SyncWait>; + using CoroType = + std::invoke_result_t::LocalReadWriteSetStorage>, + protocol::BlockHeader const&, protocol::Transaction const&, int, + ledger::LedgerConfig const&, task::tbb::SyncWait>; std::vector, tbb::cache_aligned_allocator>> contexts; contexts.reserve(RANGES::size(transactions)); for (auto index : RANGES::views::iota(0, (int)RANGES::size(transactions))) { - contexts.emplace_back(ExecutionContext{.contextID = index, - .transaction = transactions[index], - .receipt = receipts[index], - .coro = {}, - .iterator = {}}); + contexts.emplace_back( + ExecutionContext{index, transactions[index], receipts[index], {}, {}}); } constexpr static auto DEFAULT_PARALLEL_ARENA = 8; diff --git a/transaction-scheduler/benchmark/benchmarkMultiLayerStorage.cpp b/transaction-scheduler/benchmark/benchmarkMultiLayerStorage.cpp index 5b4eb67e24..32355d3470 100644 --- a/transaction-scheduler/benchmark/benchmarkMultiLayerStorage.cpp +++ b/transaction-scheduler/benchmark/benchmarkMultiLayerStorage.cpp @@ -18,11 +18,10 @@ struct Fixture void prepareData(int64_t count, int layer = 0) { - multiLayerStorage.newMutable(); - // Write count data task::syncWait([this](int64_t count) -> task::Task { - auto view = multiLayerStorage.fork(true); + auto view = multiLayerStorage.fork(); + view.newMutable(); allKeys = RANGES::views::iota(0, count) | RANGES::views::transform([](int num) { auto key = fmt::format("key: {}", num); return transaction_executor::StateKey{"test_table"sv, std::string_view(key)}; @@ -36,12 +35,14 @@ struct Fixture }); co_await storage2::writeSome(view, allKeys, allValues); + multiLayerStorage.pushView(std::move(view)); }(count)); for (auto i = 0; i < layer; ++i) { - multiLayerStorage.pushMutableToImmutableFront(); - multiLayerStorage.newMutable(); + auto view = multiLayerStorage.fork(); + view.newMutable(); + multiLayerStorage.pushView(std::move(view)); } } @@ -64,7 +65,7 @@ static void read1(benchmark::State& state) int i = 0; task::syncWait([&](benchmark::State& state) -> task::Task { - auto view = fixture.multiLayerStorage.fork(false); + auto view = fixture.multiLayerStorage.fork(); for (auto const& it : state) { [[maybe_unused]] auto data = @@ -84,7 +85,7 @@ static void read10(benchmark::State& state) int i = 0; task::syncWait([&](benchmark::State& state) -> task::Task { - auto view = fixture.multiLayerStorage.fork(false); + auto view = fixture.multiLayerStorage.fork(); for (auto const& it : state) { [[maybe_unused]] auto data = @@ -99,11 +100,11 @@ static void read10(benchmark::State& state) static void write1(benchmark::State& state) { Fixture fixture; - fixture.multiLayerStorage.newMutable(); int i = 0; task::syncWait([&](benchmark::State& state) -> task::Task { - auto view = fixture.multiLayerStorage.fork(true); + auto view = fixture.multiLayerStorage.fork(); + view.newMutable(); for (auto const& it : state) { storage::Entry entry; diff --git a/transaction-scheduler/benchmark/benchmarkScheduler.cpp b/transaction-scheduler/benchmark/benchmarkScheduler.cpp index 43e30362e3..1223a1db56 100644 --- a/transaction-scheduler/benchmark/benchmarkScheduler.cpp +++ b/transaction-scheduler/benchmark/benchmarkScheduler.cpp @@ -52,7 +52,7 @@ struct Fixture if constexpr (parallel) { - m_scheduler.emplace(); + m_scheduler.emplace>(); } else { @@ -91,8 +91,8 @@ struct Fixture RANGES::single_view(std::addressof(createTransaction)) | RANGES::views::transform([](auto* ptr) -> auto const& { return *ptr; }); - m_multiLayerStorage.newMutable(); - auto view = m_multiLayerStorage.fork(true); + auto view = m_multiLayerStorage.fork(); + view.newMutable(); ledger::LedgerConfig ledgerConfig; auto receipts = co_await transaction_scheduler::executeBlock(scheduler, view, @@ -103,8 +103,8 @@ struct Fixture receipts[0]->status(), receipts[0]->message()); co_return; } - m_multiLayerStorage.pushMutableToImmutableFront(); - co_await m_multiLayerStorage.mergeAndPopImmutableBack(); + m_multiLayerStorage.pushView(std::move(view)); + co_await m_multiLayerStorage.mergeBackStorage(); m_contractAddress = receipts[0]->contractAddress(); }()); @@ -233,15 +233,15 @@ struct Fixture RANGES::to< std::vector>>(); - auto view = m_multiLayerStorage.fork(true); + auto view = m_multiLayerStorage.fork(); + view.newMutable(); ledger::LedgerConfig ledgerConfig; auto receipts = co_await transaction_scheduler::executeBlock(scheduler, view, m_executor, blockHeader, - checkTransactions | RANGES::views::transform([ - ](const std::unique_ptr& transaction) - -> auto& { - return *transaction; - }), + checkTransactions | + RANGES::views::transform( + [](const std::unique_ptr& + transaction) -> auto& { return *transaction; }), ledgerConfig); auto balances = receipts | @@ -276,7 +276,8 @@ struct Fixture bcos::bytes m_helloworldBytecodeBinary; TransactionExecutorImpl m_executor; - std::variant m_scheduler; + std::variant> + m_scheduler; std::string m_contractAddress; std::vector
m_addresses; @@ -303,8 +304,8 @@ static void issue(benchmark::State& state) else { task::syncWait([&](benchmark::State& state) -> task::Task { - fixture.m_multiLayerStorage.newMutable(true); - auto view = fixture.m_multiLayerStorage.fork(true); + auto view = fixture.m_multiLayerStorage.fork(); + view.newMutable(); for (auto const& it : state) { bcostars::protocol::BlockHeaderImpl blockHeader( @@ -319,15 +320,14 @@ static void issue(benchmark::State& state) co_await transaction_scheduler::executeBlock(scheduler, view, fixture.m_executor, blockHeader, fixture.m_transactions | - RANGES::views::transform([ - ](const std::unique_ptr& - transaction) -> auto& { - return *transaction; - }), + RANGES::views::transform( + [](const std::unique_ptr< + bcostars::protocol::TransactionImpl>& transaction) + -> auto& { return *transaction; }), ledgerConfig); } - view.release(); + fixture.m_multiLayerStorage.pushView(std::move(view)); auto balances = co_await fixture.balances(); for (auto& balance : balances) { @@ -338,7 +338,7 @@ static void issue(benchmark::State& state) balance.template convert_to()))); } } - fixture.m_multiLayerStorage.pushMutableToImmutableFront(); + co_await fixture.m_multiLayerStorage.mergeBackStorage(); }(state)); } }, @@ -373,16 +373,15 @@ static void transfer(benchmark::State& state) blockHeader.setNumber(0); blockHeader.setVersion((uint32_t)bcos::protocol::BlockVersion::V3_1_VERSION); - fixture.m_multiLayerStorage.newMutable(); - auto view = fixture.m_multiLayerStorage.fork(true); + auto view = fixture.m_multiLayerStorage.fork(); + view.newMutable(); ledger::LedgerConfig ledgerConfig; [[maybe_unused]] auto receipts = co_await transaction_scheduler::executeBlock( scheduler, view, fixture.m_executor, blockHeader, - fixture.m_transactions | RANGES::views::transform([ - ](const std::unique_ptr& transaction) - -> auto& { - return *transaction; - }), + fixture.m_transactions | + RANGES::views::transform( + [](const std::unique_ptr& + transaction) -> auto& { return *transaction; }), ledgerConfig); fixture.m_transactions.clear(); @@ -403,16 +402,15 @@ static void transfer(benchmark::State& state) co_await transaction_scheduler::executeBlock(scheduler, view, fixture.m_executor, blockHeader, fixture.m_transactions | - RANGES::views::transform([ - ](const std::unique_ptr& - transaction) -> auto& { - return *transaction; - }), + RANGES::views::transform( + [](const std::unique_ptr< + bcostars::protocol::TransactionImpl>& transaction) + -> auto& { return *transaction; }), ledgerConfig); } // Check - view.release(); + fixture.m_multiLayerStorage.pushView(std::move(view)); auto balances = co_await fixture.balances(); for (auto&& range : balances | RANGES::views::chunk(2)) { @@ -433,8 +431,7 @@ static void transfer(benchmark::State& state) to.template convert_to()))); } } - - fixture.m_multiLayerStorage.pushMutableToImmutableFront(); + co_await fixture.m_multiLayerStorage.mergeBackStorage(); }(state)); } }, @@ -460,8 +457,8 @@ static void conflictTransfer(benchmark::State& state) else { int i = 0; - fixture.m_multiLayerStorage.newMutable(); - auto view = fixture.m_multiLayerStorage.fork(true); + auto view = fixture.m_multiLayerStorage.fork(); + view.newMutable(); task::syncWait([&](benchmark::State& state) -> task::Task { // First issue @@ -474,11 +471,10 @@ static void conflictTransfer(benchmark::State& state) ledger::LedgerConfig ledgerConfig; [[maybe_unused]] auto receipts = co_await transaction_scheduler::executeBlock( scheduler, view, fixture.m_executor, blockHeader, - fixture.m_transactions | RANGES::views::transform([ - ](const std::unique_ptr& transaction) - -> auto& { - return *transaction; - }), + fixture.m_transactions | + RANGES::views::transform( + [](const std::unique_ptr& + transaction) -> auto& { return *transaction; }), ledgerConfig); fixture.m_transactions.clear(); @@ -499,16 +495,15 @@ static void conflictTransfer(benchmark::State& state) co_await transaction_scheduler::executeBlock(scheduler, view, fixture.m_executor, blockHeader, fixture.m_transactions | - RANGES::views::transform([ - ](const std::unique_ptr& - transaction) -> auto& { - return *transaction; - }), + RANGES::views::transform( + [](const std::unique_ptr< + bcostars::protocol::TransactionImpl>& transaction) + -> auto& { return *transaction; }), ledgerConfig); } // Check - view.release(); + fixture.m_multiLayerStorage.pushView(std::move(view)); auto balances = co_await fixture.balances(); for (auto&& [balance, index] : RANGES::views::zip(balances, RANGES::views::iota(0LU))) @@ -541,7 +536,7 @@ static void conflictTransfer(benchmark::State& state) } } } - fixture.m_multiLayerStorage.pushMutableToImmutableFront(); + co_await fixture.m_multiLayerStorage.mergeBackStorage(); }(state)); } }, diff --git a/transaction-scheduler/tests/testMultiLayerStorage.cpp b/transaction-scheduler/tests/testMultiLayerStorage.cpp index 2d6b947ab4..7905280156 100644 --- a/transaction-scheduler/tests/testMultiLayerStorage.cpp +++ b/transaction-scheduler/tests/testMultiLayerStorage.cpp @@ -35,7 +35,7 @@ BOOST_FIXTURE_TEST_SUITE(TestMultiLayerStorage, TestMultiLayerStorageFixture) BOOST_AUTO_TEST_CASE(noMutable) { task::syncWait([this]() -> task::Task { - auto view = multiLayerStorage.fork(true); + auto view = multiLayerStorage.fork(); storage::Entry entry; BOOST_CHECK_THROW(co_await storage2::writeOne( view, StateKey{"test_table"sv, "test_key"sv}, std::move(entry)), @@ -48,11 +48,8 @@ BOOST_AUTO_TEST_CASE(noMutable) BOOST_AUTO_TEST_CASE(readWriteMutable) { task::syncWait([this]() -> task::Task { - BOOST_CHECK_THROW( - multiLayerStorage.pushMutableToImmutableFront(), NotExistsMutableStorageError); - - multiLayerStorage.newMutable(); - auto view = std::make_optional(multiLayerStorage.fork(true)); + auto view = std::make_optional(multiLayerStorage.fork()); + view->newMutable(); StateKey key{"test_table"sv, "test_key"sv}; storage::Entry entry; @@ -63,10 +60,9 @@ BOOST_AUTO_TEST_CASE(readWriteMutable) auto values = co_await storage2::readSome(*view, keyViews); BOOST_CHECK_EQUAL(values[0]->get(), entry.get()); + BOOST_CHECK_NO_THROW(multiLayerStorage.pushView(std::move(*view))); - BOOST_CHECK_NO_THROW(multiLayerStorage.pushMutableToImmutableFront()); - view.reset(); - auto view2 = multiLayerStorage.fork(true); + auto view2 = multiLayerStorage.fork(); BOOST_CHECK_THROW( co_await storage2::writeOne(view2, key, entry), NotExistsMutableStorageError); @@ -77,13 +73,11 @@ BOOST_AUTO_TEST_CASE(readWriteMutable) BOOST_AUTO_TEST_CASE(merge) { task::syncWait([this]() -> task::Task { - BOOST_CHECK_THROW( - multiLayerStorage.pushMutableToImmutableFront(), NotExistsMutableStorageError); - - multiLayerStorage.newMutable(); - auto view = std::make_optional(multiLayerStorage.fork(true)); - auto toKey = RANGES::views::transform( - [](int num) { return StateKey{"test_table"sv, fmt::format("key: {}", num)}; }); + auto view = std::make_optional(multiLayerStorage.fork()); + view->newMutable(); + auto toKey = RANGES::views::transform([](int num) { + return StateKey{"test_table"sv, fmt::format("key: {}", num)}; + }); auto toValue = RANGES::views::transform([](int num) { storage::Entry entry; entry.set(fmt::format("value: {}", num)); @@ -95,13 +89,12 @@ BOOST_AUTO_TEST_CASE(merge) RANGES::iota_view(0, 100) | toValue); BOOST_CHECK_THROW( - co_await multiLayerStorage.mergeAndPopImmutableBack(), NotExistsImmutableStorageError); + co_await multiLayerStorage.mergeBackStorage(), NotExistsImmutableStorageError); - multiLayerStorage.pushMutableToImmutableFront(); - co_await multiLayerStorage.mergeAndPopImmutableBack(); - view.reset(); + multiLayerStorage.pushView(std::move(*view)); + co_await multiLayerStorage.mergeBackStorage(); - auto view2 = multiLayerStorage.fork(false); + auto view2 = multiLayerStorage.fork(); auto keys = RANGES::iota_view(0, 100) | toKey; auto values = co_await storage2::readSome(view2, keys); @@ -111,12 +104,11 @@ BOOST_AUTO_TEST_CASE(merge) } BOOST_CHECK_EQUAL(RANGES::size(values), 100); - multiLayerStorage.newMutable(); - - auto view3 = multiLayerStorage.fork(true); + auto view3 = multiLayerStorage.fork(); + view3.newMutable(); co_await storage2::removeSome(view3, RANGES::iota_view(20, 30) | toKey); - multiLayerStorage.pushMutableToImmutableFront(); - co_await multiLayerStorage.mergeAndPopImmutableBack(); + multiLayerStorage.pushView(std::move(view3)); + co_await multiLayerStorage.mergeBackStorage(); auto values2 = co_await storage2::readSome(view3, keys); for (auto&& [index, value] : RANGES::views::enumerate(values2)) @@ -135,21 +127,10 @@ BOOST_AUTO_TEST_CASE(merge) }()); } -BOOST_AUTO_TEST_CASE(oneMutable) -{ - multiLayerStorage.newMutable(); - auto view1 = std::make_optional(multiLayerStorage.fork(true)); - auto view2 = multiLayerStorage.fork(false); - BOOST_CHECK_THROW(auto view3 = multiLayerStorage.fork(true), DuplicateMutableViewError); - - view1.reset(); - auto view4 = multiLayerStorage.fork(true); -} - BOOST_AUTO_TEST_CASE(rangeMulti) { - using MutableStorage = - memory_storage::MemoryStorage; + using MutableStorage = memory_storage::MemoryStorage; using BackendStorage = memory_storage::MemoryStorage; @@ -160,21 +141,21 @@ BOOST_AUTO_TEST_CASE(rangeMulti) MultiLayerStorage myMultiLayerStorage(backendStorage); - myMultiLayerStorage.newMutable(); - auto view1 = myMultiLayerStorage.fork(true); + auto view1 = myMultiLayerStorage.fork(); + view1.newMutable(); co_await storage2::writeSome(view1, RANGES::views::iota(2, 6), RANGES::views::repeat(1)); - view1.release(); - myMultiLayerStorage.pushMutableToImmutableFront(); + co_await storage2::removeOne(view1, 2); + myMultiLayerStorage.pushView(std::move(view1)); - myMultiLayerStorage.newMutable(); - auto view2 = myMultiLayerStorage.fork(true); + auto view2 = myMultiLayerStorage.fork(); + view2.newMutable(); co_await storage2::writeSome(view2, RANGES::views::iota(4, 8), RANGES::views::repeat(2)); auto resultList = co_await storage2::readSome(view2, RANGES::views::iota(0, 8)); auto vecList = resultList | RANGES::views::transform([](auto input) { return *input; }) | RANGES::to(); BOOST_CHECK_EQUAL(resultList.size(), 8); - auto expectList = std::vector({0, 0, 1, 1, 2, 2, 2, 2}); + auto expectList = std::vector({0, 0, 0, 1, 2, 2, 2, 2}); BOOST_CHECK_EQUAL_COLLECTIONS( vecList.begin(), vecList.end(), expectList.begin(), expectList.end()); diff --git a/transaction-scheduler/tests/testSchedulerParallel.cpp b/transaction-scheduler/tests/testSchedulerParallel.cpp index 4f9414e596..56899ac40d 100644 --- a/transaction-scheduler/tests/testSchedulerParallel.cpp +++ b/transaction-scheduler/tests/testSchedulerParallel.cpp @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(simple) { task::syncWait([&, this]() -> task::Task { MockExecutor executor; - SchedulerParallelImpl scheduler; + SchedulerParallelImpl scheduler; bcostars::protocol::BlockHeaderImpl blockHeader( [inner = bcostars::BlockHeader()]() mutable { return std::addressof(inner); }); @@ -85,8 +85,8 @@ BOOST_AUTO_TEST_CASE(simple) }) | RANGES::to>>(); - multiLayerStorage.newMutable(); - auto view = multiLayerStorage.fork(true); + auto view = multiLayerStorage.fork(); + view.newMutable(); ledger::LedgerConfig ledgerConfig; auto receipts = co_await bcos::transaction_scheduler::executeBlock(scheduler, view, executor, blockHeader, @@ -139,16 +139,19 @@ BOOST_AUTO_TEST_CASE(conflict) { task::syncWait([&, this]() -> task::Task { MockConflictExecutor executor; - SchedulerParallelImpl scheduler; + SchedulerParallelImpl scheduler; + + auto view1 = multiLayerStorage.fork(); + view1.newMutable(); + multiLayerStorage.pushView(std::move(view1)); - multiLayerStorage.newMutable(); constexpr static int INITIAL_VALUE = 100000; for (auto i : RANGES::views::iota(0LU, MOCK_USER_COUNT)) { StateKey key{"t_test"sv, boost::lexical_cast(i)}; storage::Entry entry; entry.set(boost::lexical_cast(INITIAL_VALUE)); - co_await storage2::writeOne(multiLayerStorage.mutableStorage(), key, std::move(entry)); + co_await storage2::writeOne(*multiLayerStorage.frontStorage(), key, std::move(entry)); } bcostars::protocol::BlockHeaderImpl blockHeader( @@ -167,15 +170,17 @@ BOOST_AUTO_TEST_CASE(conflict) auto transactionRefs = transactions | RANGES::views::transform([](auto& ptr) -> auto& { return *ptr; }); - auto view = multiLayerStorage.fork(true); + auto view = multiLayerStorage.fork(); + view.newMutable(); ledger::LedgerConfig ledgerConfig; auto receipts = co_await bcos::transaction_scheduler::executeBlock( scheduler, view, executor, blockHeader, transactionRefs, ledgerConfig); + multiLayerStorage.pushView(std::move(view)); for (auto i : RANGES::views::iota(0LU, MOCK_USER_COUNT)) { StateKey key{"t_test"sv, boost::lexical_cast(i)}; - auto entry = co_await storage2::readOne(multiLayerStorage.mutableStorage(), key); + auto entry = co_await storage2::readOne(*multiLayerStorage.frontStorage(), key); BOOST_CHECK_EQUAL(boost::lexical_cast(entry->get()), INITIAL_VALUE); } for (auto const& receipt : receipts) diff --git a/transaction-scheduler/tests/testSchedulerSerial.cpp b/transaction-scheduler/tests/testSchedulerSerial.cpp index ca600531a4..0c4eb4c915 100644 --- a/transaction-scheduler/tests/testSchedulerSerial.cpp +++ b/transaction-scheduler/tests/testSchedulerSerial.cpp @@ -79,7 +79,8 @@ BOOST_AUTO_TEST_CASE(executeBlock) RANGES::to>>(); MockExecutor executor; - auto view = multiLayerStorage.fork(true); + auto view = multiLayerStorage.fork(); + view.newMutable(); ledger::LedgerConfig ledgerConfig; auto receipts = co_await bcos::transaction_scheduler::executeBlock(scheduler, view, executor, blockHeader, diff --git a/vcpkg.json b/vcpkg.json index c973fff77a..cb22c6dfbd 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "fiscobcos", - "version-string": "3.8.0", + "version-string": "3.9.0", "homepage": "https://github.com/FISCO-BCOS/FISCO-BCOS", "description": "FISCO BCOS", "dependencies": [