Skip to content

Commit

Permalink
Use common block mapping code between contract and EVM node.
Browse files Browse the repository at this point in the history
  • Loading branch information
arhag committed Mar 14, 2023
1 parent 920ddf5 commit 16c1ae0
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 43 deletions.
74 changes: 34 additions & 40 deletions cmd/block_conversion_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,30 @@
#include "channels.hpp"
#include "abi_utils.hpp"
#include "utils.hpp"
#include "contract_common/evm_common/block_mapping.hpp"

#include <fstream>

#include <silkworm/types/transaction.hpp>
#include <silkworm/trie/vector_root.hpp>
#include <silkworm/common/endian.hpp>

struct block_mapping {

uint64_t genesis_timestamp = 1667688089000000; //us
uint64_t block_interval = 1000000; //us

void set_genesis_timestamp(uint64_t timestamp){
genesis_timestamp = timestamp;
}

void set_block_interval(uint64_t interval) {
block_interval = interval;
}

inline uint32_t timestamp_to_evm_block(uint64_t timestamp) {
if( timestamp < genesis_timestamp ) {
SILK_CRIT << "Invalid timestamp [" << timestamp << ", genesis: " << genesis_timestamp;
assert(timestamp >= genesis_timestamp);
}
return 1 + (timestamp - genesis_timestamp)/block_interval;
}

inline uint64_t evm_block_to_timestamp(uint32_t block_num) {
return genesis_timestamp + block_num * block_interval;
}

};
using sys = sys_plugin;
class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversion_plugin_impl> {
public:
block_conversion_plugin_impl()
: evm_blocks_channel(appbase::app().get_channel<channels::evm_blocks>()){}


uint32_t timestamp_to_evm_block_num(uint64_t timestamp) const {
if (timestamp < bm.value().genesis_timestamp) {
SILK_CRIT << "Invalid timestamp " << timestamp << ", genesis: " << bm->genesis_timestamp;
assert(timestamp >= bm->genesis_timestamp);
}

return bm->timestamp_to_evm_block_num(timestamp);
}

void load_head() {
auto head_header = appbase::app().get_plugin<engine_plugin>().get_head_canonical_header();
if (!head_header) {
Expand All @@ -67,11 +52,20 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
return;
}

bm.set_block_interval(silkworm::endian::load_big_u64(genesis_header->nonce.data())*1e3);
bm.set_genesis_timestamp(genesis_header->timestamp*1e6);
uint64_t block_interval_ms = silkworm::endian::load_big_u64(genesis_header->nonce.data());
// TODO: Consider redefining genesis nonce to be in units of seconds rather than milliseconds? Or just hardcode to 1 second?

if (block_interval_ms == 0 || block_interval_ms % 1000 != 0) {
SILK_CRIT << "Genesis nonce is invalid. Must be a positive multiple of 1000 representing the block interval in milliseconds. "
"Instead got: " << block_interval_ms;
sys::error("Invalid genesis nonce");
return;
}

bm.emplace(genesis_header->timestamp, block_interval_ms/1e3);

SILK_INFO << "Block interval: " << bm.block_interval;
SILK_INFO << "Genesis timestamp: " << bm.genesis_timestamp;
SILK_INFO << "Block interval: " << bm->block_interval;
SILK_INFO << "Genesis timestamp: " << bm->genesis_timestamp;
}

evmc::bytes32 compute_transaction_root(const silkworm::BlockBody& body) {
Expand All @@ -94,7 +88,7 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
new_block.header.difficulty = 1;
new_block.header.number = num;
new_block.header.gas_limit = 0x7ffffffffff;
new_block.header.timestamp = bm.evm_block_to_timestamp(num)/1e6;
new_block.header.timestamp = bm.value().evm_block_num_to_evm_timestamp(num)/1e6;
new_block.header.transactions_root = silkworm::kEmptyRoot;
//new_block.header.mix_hash
//new_block.header.nonce
Expand Down Expand Up @@ -136,13 +130,13 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
//SILK_INFO << "--Enter Block #" << b->block_num;

// Keep the last block before genesis timestamp
if (b->timestamp <= bm.genesis_timestamp) {
SILK_DEBUG << "Before genesis: " << bm.genesis_timestamp << " Block #" << b->block_num << " timestamp: " << b->timestamp;
if (b->timestamp <= bm.value().genesis_timestamp) {
SILK_DEBUG << "Before genesis: " << bm->genesis_timestamp << " Block #" << b->block_num << " timestamp: " << b->timestamp;
native_blocks.clear();
native_blocks.push_back(*b);
return;
}

// Add received native block to our local reversible chain.
// If the received native block can't be linked we remove the forked blocks until the fork point.
// Also we remove the forked block transactions from the corresponding evm block as they where previously included
Expand All @@ -151,7 +145,7 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
//SILK_DEBUG << "Fork at Block #" << b->block_num;
const auto& forked_block = native_blocks.back();

while( bm.timestamp_to_evm_block(forked_block.timestamp) < evm_blocks.back().header.number )
while( timestamp_to_evm_block_num(forked_block.timestamp) < evm_blocks.back().header.number )
evm_blocks.pop_back();

auto& last_evm_block = evm_blocks.back();
Expand All @@ -160,7 +154,7 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
});

native_blocks.pop_back();
if( native_blocks.size() && bm.timestamp_to_evm_block(native_blocks.back().timestamp) == last_evm_block.header.number )
if( native_blocks.size() && timestamp_to_evm_block_num(native_blocks.back().timestamp) == last_evm_block.header.number )
set_upper_bound(last_evm_block, native_blocks.back());
}

Expand All @@ -172,7 +166,7 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi

// Process the last native block received.
// We extend the evm chain if necessary up until the block where the received block belongs
auto evm_num = bm.timestamp_to_evm_block(b->timestamp);
auto evm_num = timestamp_to_evm_block_num(b->timestamp);
//SILK_INFO << "Expecting evm number: " << evm_num;

while(evm_blocks.back().header.number < evm_num) {
Expand Down Expand Up @@ -213,11 +207,11 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
}

if( lib_timestamp ) {
auto evm_lib = bm.timestamp_to_evm_block(*lib_timestamp) - 1;
auto evm_lib = timestamp_to_evm_block_num(*lib_timestamp) - 1;
//SILK_DEBUG << "EVM LIB: #" << evm_lib;

// Remove irreversible native blocks
while(bm.timestamp_to_evm_block(native_blocks.front().timestamp) < evm_lib) {
while(timestamp_to_evm_block_num(native_blocks.front().timestamp) < evm_lib) {
//SILK_DEBUG << "Remove IRR native: #" << native_blocks.front().block_num;
native_blocks.pop_front();
}
Expand All @@ -244,7 +238,7 @@ class block_conversion_plugin_impl : std::enable_shared_from_this<block_conversi
std::list<silkworm::Block> evm_blocks;
channels::evm_blocks::channel_type& evm_blocks_channel;
channels::native_blocks::channel_type::handle native_blocks_subscription;
block_mapping bm;
std::optional<evm_common::block_mapping> bm;
};

block_conversion_plugin::block_conversion_plugin() : my(new block_conversion_plugin_impl()) {}
Expand Down
52 changes: 52 additions & 0 deletions cmd/contract_common/evm_common/block_mapping.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include <cstdint>

namespace evm_common {

struct block_mapping {

/**
* @brief Construct object that maps from Antelope timestamp to EVM block number and timestamp
*
* @param genesis_timestamp_sec - the EVM genesis timestamp in seconds
* @param block_interval_sec - time interval between consecutive EVM blocks in seconds (must be positive)
*
* @note The timestamp of the Antelope block containing the init action rounded down to the nearest second must equal genesis_timestamp_sec.
*/
block_mapping(uint64_t genesis_timestamp_sec, uint32_t block_interval_sec = 1)
: block_interval(block_interval_sec == 0 ? 1 : block_interval_sec),
genesis_timestamp(genesis_timestamp_sec)
{}

const uint64_t block_interval; // seconds
const uint64_t genesis_timestamp; // seconds

/**
* @brief Map Antelope timestamp to EVM block num
*
* @param timestamp - Antelope timestamp in microseconds
* @return mapped EVM block number (returns 0 for all timestamps prior to the genesis timestamp)
*/
inline uint32_t timestamp_to_evm_block_num(uint64_t timestamp_us) const {
uint64_t timestamp = timestamp_us / 1e6; // map Antelope block timestamp to EVM block timestamp
if( timestamp < genesis_timestamp ) {
// There should not be any transactions prior to the init action.
// But any entity with an associated timestamp prior to the genesis timestamp can be considered as part of the genesis block.
return 0;
}
return 1 + (timestamp - genesis_timestamp) / block_interval;
}

/**
* @brief Map EVM block num to EVM block timestamp
*
* @param block_num - EVM block number
* @return EVM block timestamp associated with the given EVM block number
*/
inline uint64_t evm_block_num_to_evm_timestamp(uint32_t block_num) const {
return genesis_timestamp + block_num * block_interval;
}
};

} // namespace evm_common
1 change: 1 addition & 0 deletions contract/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ add_contract( evm_contract evm_runtime ${SOURCES})

target_include_directories( evm_runtime PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../include
${CMAKE_CURRENT_SOURCE_DIR}/../../cmd/contract_common
${CMAKE_CURRENT_SOURCE_DIR}/../external/intx/include
${CMAKE_CURRENT_SOURCE_DIR}/../external/ethash/include
${CMAKE_CURRENT_SOURCE_DIR}/../external/GSL/include
Expand Down
10 changes: 7 additions & 3 deletions contract/src/actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <evm_runtime/intrinsics.hpp>
#include <evm_runtime/eosio.token.hpp>

#include <evm_common/block_mapping.hpp>

#include <silkworm/consensus/trust/engine.hpp>
// included here so NDEBUG is defined to disable assert macro
#include <silkworm/execution/processor.cpp>
Expand Down Expand Up @@ -46,7 +48,7 @@ void evm_contract::init(const uint64_t chainid) {
_config.set({
.version = 0,
.chainid = chainid,
.genesis_time = current_time_point()
.genesis_time = eosio::current_time_point() // implicitly converts from Antelope timestamp to EVM compatible timestamp
}, get_self());
}

Expand Down Expand Up @@ -121,11 +123,13 @@ void evm_contract::pushtx( eosio::name ram_payer, const bytes& rlptx ) {
check( found_chain_config.has_value(), "failed to find expected chain config" );
eosio::require_auth(ram_payer);

evm_common::block_mapping bm(_config.get().genesis_time.sec_since_epoch());

Block block;
block.header.difficulty = 1;
block.header.gas_limit = 0x7ffffffffff;
block.header.timestamp = eosio::current_time_point().sec_since_epoch();
block.header.number = 1 + (block.header.timestamp - _config.get().genesis_time.sec_since_epoch()); // same logic with block_mapping in TrustEVM
block.header.number = bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count());
block.header.timestamp = bm.evm_block_num_to_evm_timestamp(block.header.number);

silkworm::consensus::TrustEngine engine{*found_chain_config->second};
push_trx( ram_payer, block, rlptx, engine, *found_chain_config->second );
Expand Down
2 changes: 2 additions & 0 deletions contract/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ configure_file(${CMAKE_SOURCE_DIR}/contracts.hpp.in ${CMAKE_BINARY_DIR}/contract

include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}/../../cmd/contract_common
${CMAKE_SOURCE_DIR}/silkworm/node
${CMAKE_SOURCE_DIR}/silkworm/core
${CMAKE_SOURCE_DIR}/../../silkworm/third_party/evmone/lib
Expand All @@ -27,6 +28,7 @@ add_eosio_test_executable( unit_test
${CMAKE_SOURCE_DIR}/evm_runtime_tests.cpp
${CMAKE_SOURCE_DIR}/init_tests.cpp
${CMAKE_SOURCE_DIR}/native_token_tests.cpp
${CMAKE_SOURCE_DIR}/mapping_tests.cpp
${CMAKE_SOURCE_DIR}/main.cpp
${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/encode.cpp
${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/decode.cpp
Expand Down
120 changes: 120 additions & 0 deletions contract/tests/mapping_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "basic_evm_tester.hpp"

#include <evm_common/block_mapping.hpp>

#include <fc/io/raw_fwd.hpp>

using namespace eosio::testing;

using eosio::chain::time_point;
using eosio::chain::time_point_sec;
using eosio::chain::unsigned_int;

struct mapping_evm_tester : basic_evm_tester {
static constexpr eosio::chain::name evm_account_name = "evm"_n;

bool produce_blocks_until_timestamp_satisfied(std::function<bool(time_point)> cond, unsigned int limit = 10) {
for (unsigned int blocks_produced = 0; blocks_produced < limit && !cond(control->pending_block_time()); ++blocks_produced) {
produce_block();
}
return cond(control->pending_block_time());
}

struct config_table_row {
unsigned_int version;
uint64_t chainid;
time_point_sec genesis_time;
uint32_t status;
};

time_point_sec get_genesis_time() {
static constexpr eosio::chain::name config_singleton_name = "config"_n;
const vector<char> d =
get_row_by_account(evm_account_name, evm_account_name, config_singleton_name, config_singleton_name);
return fc::raw::unpack<config_table_row>(d).genesis_time;
}
};

FC_REFLECT(mapping_evm_tester::config_table_row, (version)(chainid)(genesis_time)(status))

BOOST_AUTO_TEST_SUITE(mapping_evm_tests)

BOOST_AUTO_TEST_CASE(block_mapping_tests) try {
using evm_common::block_mapping;

{
// Tests using default 1 second block interval

block_mapping bm(10);

BOOST_CHECK_EQUAL(bm.block_interval, 1); // Block interval should default to 1 second.
BOOST_CHECK_EQUAL(bm.genesis_timestamp, 10);

BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(9'500'000), 0);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(10'000'000), 1);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(10'500'000), 1);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(11'000'000), 2);

BOOST_CHECK_EQUAL(bm.evm_block_num_to_evm_timestamp(0), 10);
// An EVM transaction in an Antelope block with timestamp 10 ends up in EVM block 1 which has timestamp 11. This is intentional.
// It means that an EVM transaction included in the same block immediately after the init action will end up in a block
// that has a timestamp greater than the genesis timestamp.
BOOST_CHECK_EQUAL(bm.evm_block_num_to_evm_timestamp(1), 11);
BOOST_CHECK_EQUAL(bm.evm_block_num_to_evm_timestamp(2), 12);
}

{
// Tests using a 5 second block interval

block_mapping bm(123, 5);

BOOST_CHECK_EQUAL(bm.block_interval, 5);
BOOST_CHECK_EQUAL(bm.genesis_timestamp, 123);

BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(100'000'000), 0);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(122'000'000), 0);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(123'000'000), 1);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(124'000'000), 1);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(127'000'000), 1);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(127'999'999), 1);
BOOST_CHECK_EQUAL(bm.timestamp_to_evm_block_num(128'000'000), 2);

BOOST_CHECK_EQUAL(bm.evm_block_num_to_evm_timestamp(0), 123);
BOOST_CHECK_EQUAL(bm.evm_block_num_to_evm_timestamp(1), 128);
BOOST_CHECK_EQUAL(bm.evm_block_num_to_evm_timestamp(2), 133);
}

} FC_LOG_AND_RETHROW()


BOOST_FIXTURE_TEST_CASE(init_on_second_boundary, mapping_evm_tester) try {
auto timestamp_at_second_boundary = [](time_point timestamp) -> bool {
return timestamp.time_since_epoch().count() % 1'000'000 == 0;
};
BOOST_REQUIRE(produce_blocks_until_timestamp_satisfied(timestamp_at_second_boundary));

init(15555);
time_point_sec expected_genesis_time = control->pending_block_time(); // Rounds down to nearest second.

time_point_sec actual_genesis_time = get_genesis_time();
ilog("Genesis time: ${time}", ("time", actual_genesis_time));

BOOST_REQUIRE_EQUAL(actual_genesis_time.sec_since_epoch(), expected_genesis_time.sec_since_epoch());
} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(init_not_on_second_boundary, mapping_evm_tester) try {
auto timestamp_not_at_second_boundary = [](time_point timestamp) -> bool {
return timestamp.time_since_epoch().count() % 1'000'000 != 0;
};
BOOST_REQUIRE(produce_blocks_until_timestamp_satisfied(timestamp_not_at_second_boundary));

init(15555);
time_point_sec expected_genesis_time = control->pending_block_time(); // Rounds down to nearest second.

time_point_sec actual_genesis_time = get_genesis_time();
ilog("Genesis time: ${time}", ("time", actual_genesis_time));

BOOST_REQUIRE_EQUAL(actual_genesis_time.sec_since_epoch(), expected_genesis_time.sec_since_epoch());
} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 16c1ae0

Please sign in to comment.