Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Implement Trusted Producer Light Validation #5631

Merged
merged 6 commits into from
Sep 17, 2018
15 changes: 11 additions & 4 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1018,14 +1018,20 @@ struct controller_impl {


void push_block( const signed_block_ptr& b, controller::block_status s ) {
// idump((fc::json::to_pretty_string(*b)));
EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block");

auto reset_prod_light_validation = fc::make_scoped_exit([old_value=conf.trusted_producer_light_validation, this]() {
conf.trusted_producer_light_validation = old_value;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to see this reset to current value so it could be nested in the future.

      auto reset_prod_light_validation = fc::make_scoped_exit([old_value=conf.trusted_producer_light_validation, this]() {
         conf.trusted_producer_light_validation = old_value;
      });

try {
EOS_ASSERT( b, block_validate_exception, "trying to push empty block" );
EOS_ASSERT( s != controller::block_status::incomplete, block_validate_exception, "invalid block status for a completed block" );
emit( self.pre_accepted_block, b );
bool trust = !conf.force_all_checks && (s == controller::block_status::irreversible || s == controller::block_status::validated);
auto new_header_state = fork_db.add( b, trust );
if (conf.trusted_producers.count(b->producer)) {
conf.trusted_producer_light_validation = true;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting a flag, I wonder if we could just add this check in light_validation_allowed then the extra flag and reset would not be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be much more complicated (handling identifying if we are speculating or applying a block) and this gets called multiple times in a transaction pass, versus being called once for the block.

emit( self.accepted_block_header, new_header_state );
// on replay irreversible is not emitted by fork database, so emit it explicitly here
if( s == controller::block_status::irreversible )
Expand Down Expand Up @@ -1657,13 +1663,14 @@ bool controller::light_validation_allowed(bool replay_opts_disabled_by_policy) c
return false;
}

auto pb_status = my->pending->_block_status;
const auto pb_status = my->pending->_block_status;

// in a pending irreversible or previously validated block and we have forcing all checks
bool consider_skipping_on_replay = (pb_status == block_status::irreversible || pb_status == block_status::validated) && !replay_opts_disabled_by_policy;
const bool consider_skipping_on_replay = (pb_status == block_status::irreversible || pb_status == block_status::validated) && !replay_opts_disabled_by_policy;

// OR in a signed block and in light validation mode
bool consider_skipping_on_validate = (pb_status == block_status::complete && my->conf.block_validation_mode == validation_mode::LIGHT);
const bool consider_skipping_on_validate = (pb_status == block_status::complete &&
(my->conf.block_validation_mode == validation_mode::LIGHT || my->conf.trusted_producer_light_validation));

return consider_skipping_on_replay || consider_skipping_on_validate;
}
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ namespace eosio { namespace chain {
validation_mode block_validation_mode = validation_mode::FULL;

flat_set<account_name> resource_greylist;
flat_set<account_name> trusted_producers;
bool trusted_producer_light_validation = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont really like this "implementation detail" exposed in the config structure (The bool, the flat_set is appropriate here). There doesn't seem to be a reason why an external system would ever need to know about this. I think I would prefer that be part of the controller_impl class instead alongside in_trx_requiring_checks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also trusted_producers should be added to the reflection of this structure:

FC_REFLECT( eosio::chain::controller::config,

};

enum class block_status {
Expand Down
22 changes: 20 additions & 2 deletions libraries/testing/include/eosio/testing/tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,17 @@ namespace eosio { namespace testing {
try {
if( num_blocks_to_producer_before_shutdown > 0 )
produce_blocks( num_blocks_to_producer_before_shutdown );
BOOST_REQUIRE_EQUAL( validate(), true );
if (!skip_validate)
BOOST_REQUIRE_EQUAL( validate(), true );
} catch( const fc::exception& e ) {
wdump((e.to_detail_string()));
}
}
controller::config vcfg;

validating_tester() {
static controller::config default_config() {
fc::temp_directory tempdir;
controller::config vcfg;
vcfg.blocks_dir = tempdir.path() / std::string("v_").append(config::default_blocks_dir_name);
vcfg.state_dir = tempdir.path() / std::string("v_").append(config::default_state_dir_name);
vcfg.state_size = 1024*1024*8;
Expand All @@ -333,7 +336,13 @@ namespace eosio { namespace testing {
else if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--wavm"))
vcfg.wasm_runtime = chain::wasm_interface::vm_type::wavm;
}
return vcfg;
}

validating_tester(const flat_set<account_name>& trusted_producers = flat_set<account_name>()) {
vcfg = default_config();

vcfg.trusted_producers = trusted_producers;

validating_node = std::make_unique<controller>(vcfg);
validating_node->startup();
Expand Down Expand Up @@ -362,6 +371,14 @@ namespace eosio { namespace testing {
return sb;
}

signed_block_ptr produce_block_no_validation( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0 /*skip_missed_block_penalty*/ ) {
return _produce_block(skip_time, false, skip_flag | 2);
}

void validate_push_block(const signed_block_ptr& sb) {
validating_node->push_block( sb );
}

signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), uint32_t skip_flag = 0 /*skip_missed_block_penalty*/ )override {
control->abort_block();
auto sb = _produce_block(skip_time, true, skip_flag | 2);
Expand Down Expand Up @@ -393,6 +410,7 @@ namespace eosio { namespace testing {

unique_ptr<controller> validating_node;
uint32_t num_blocks_to_producer_before_shutdown = 0;
bool skip_validate = false;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions plugins/chain_plugin/chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip
"replace reversible block database with blocks imported from specified file and then exit")
("export-reversible-blocks", bpo::value<bfs::path>(),
"export reversible block database in portable format into specified file and then exit")
("trusted-producer", bpo::value<vector<string>>()->composing(), "Indicate a producer whose blocks headers signed by it will be fully validated, but transactions in those validated blocks will be trusted.")
;

}
Expand Down Expand Up @@ -332,6 +333,8 @@ void chain_plugin::plugin_initialize(const variables_map& options) {
LOAD_VALUE_SET( options, "contract-whitelist", my->chain_config->contract_whitelist );
LOAD_VALUE_SET( options, "contract-blacklist", my->chain_config->contract_blacklist );

LOAD_VALUE_SET( options, "trusted-producer", my->chain_config->trusted_producers );

if( options.count( "action-blacklist" )) {
const std::vector<std::string>& acts = options["action-blacklist"].as<std::vector<std::string>>();
auto& list = my->chain_config->action_blacklist;
Expand Down
114 changes: 113 additions & 1 deletion unittests/block_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,119 @@ BOOST_AUTO_TEST_CASE(block_with_invalid_tx_test)
[] (const fc::exception &e)->bool {
return e.code() == account_name_exists_exception::code_value ;
}) ;


}

std::pair<signed_block_ptr, signed_block_ptr> corrupt_trx_in_block(validating_tester& main, account_name act_name) {
// First we create a valid block with valid transaction
main.create_account(act_name);
signed_block_ptr b = main.produce_block_no_validation();

// Make a copy of the valid block and corrupt the transaction
auto copy_b = std::make_shared<signed_block>(*b);
auto signed_tx = copy_b->transactions.back().trx.get<packed_transaction>().get_signed_transaction();
// Corrupt one signature
signed_tx.signatures.clear();
signed_tx.sign(main.get_private_key(act_name, "active"), main.control->get_chain_id());

// Replace the valid transaction with the invalid transaction
auto invalid_packed_tx = packed_transaction(signed_tx);
copy_b->transactions.back().trx = invalid_packed_tx;

// Re-calculate the transaction merkle
vector<digest_type> trx_digests;
const auto& trxs = copy_b->transactions;
trx_digests.reserve( trxs.size() );
for( const auto& a : trxs )
trx_digests.emplace_back( a.digest() );
copy_b->transaction_mroot = merkle( move(trx_digests) );

// Re-sign the block
auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) );
auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state()->pending_schedule_hash) );
copy_b->producer_signature = main.get_private_key(b->producer, "active").sign(sig_digest);
return std::pair<signed_block_ptr, signed_block_ptr>(b, copy_b);
}

// verify that a block with a transaction with an incorrect signature, is blindly accepted from a trusted producer
BOOST_AUTO_TEST_CASE(trusted_producer_test)
{
flat_set<account_name> trusted_producers = { N(defproducera), N(defproducerc) };
validating_tester main(trusted_producers);
// only using validating_tester to keep the 2 chains in sync, not to validate that the validating_node matches the main node,
// since it won't be
main.skip_validate = true;

// First we create a valid block with valid transaction
std::set<account_name> producers = { N(defproducera), N(defproducerb), N(defproducerc), N(defproducerd) };
for (auto prod : producers)
main.create_account(prod);
auto b = main.produce_block();

std::vector<account_name> schedule(producers.cbegin(), producers.cend());
auto trace = main.set_producers(schedule);

while (b->producer != N(defproducera)) {
b = main.produce_block();
}

auto blocks = corrupt_trx_in_block(main, N(tstproducera));
main.validate_push_block( blocks.second );
}

// like trusted_producer_test, except verify that any entry in the trusted_producer list is accepted
BOOST_AUTO_TEST_CASE(trusted_producer_verify_2nd_test)
{
flat_set<account_name> trusted_producers = { N(defproducera), N(defproducerc) };
validating_tester main(trusted_producers);
// only using validating_tester to keep the 2 chains in sync, not to validate that the validating_node matches the main node,
// since it won't be
main.skip_validate = true;

// First we create a valid block with valid transaction
std::set<account_name> producers = { N(defproducera), N(defproducerb), N(defproducerc), N(defproducerd) };
for (auto prod : producers)
main.create_account(prod);
auto b = main.produce_block();

std::vector<account_name> schedule(producers.cbegin(), producers.cend());
auto trace = main.set_producers(schedule);

while (b->producer != N(defproducerc)) {
b = main.produce_block();
}

auto blocks = corrupt_trx_in_block(main, N(tstproducera));
main.validate_push_block( blocks.second );
}

// verify that a block with a transaction with an incorrect signature, is rejected if it is not from a trusted producer
BOOST_AUTO_TEST_CASE(untrusted_producer_test)
{
flat_set<account_name> trusted_producers = { N(defproducera), N(defproducerc) };
validating_tester main(trusted_producers);
// only using validating_tester to keep the 2 chains in sync, not to validate that the validating_node matches the main node,
// since it won't be
main.skip_validate = true;

// First we create a valid block with valid transaction
std::set<account_name> producers = { N(defproducera), N(defproducerb), N(defproducerc), N(defproducerd) };
for (auto prod : producers)
main.create_account(prod);
auto b = main.produce_block();

std::vector<account_name> schedule(producers.cbegin(), producers.cend());
auto trace = main.set_producers(schedule);

while (b->producer != N(defproducerb)) {
b = main.produce_block();
}

auto blocks = corrupt_trx_in_block(main, N(tstproducera));
BOOST_REQUIRE_EXCEPTION(main.validate_push_block( blocks.second ), fc::exception ,
[] (const fc::exception &e)->bool {
return e.code() == unsatisfied_authorization::code_value ;
}) ;
}

BOOST_AUTO_TEST_SUITE_END()