Skip to content

Commit

Permalink
Merge remote-tracking branch 'spring/savanna' into GH-13-disaster-test
Browse files Browse the repository at this point in the history
  • Loading branch information
heifner committed Apr 23, 2024
2 parents 6b8bf26 + 053b53b commit 31c2f86
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 33 deletions.
9 changes: 8 additions & 1 deletion libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,21 @@ block_state_ptr block_state::create_if_genesis_block(const block_state_legacy& b
}

block_state_ptr block_state::create_transition_block(
const block_header_state& prev,
const block_state& prev,
signed_block_ptr b,
const protocol_feature_set& pfs,
const validator_t& validator,
bool skip_validate_signee,
const std::optional<digest_type>& action_mroot_savanna) {
auto result_ptr = std::make_shared<block_state>(prev, b, pfs, validator, skip_validate_signee);

result_ptr->action_mroot = action_mroot_savanna.has_value() ? *action_mroot_savanna : digest_type();
// action_mroot_savanna can be empty in IRREVERSIBLE mode. Do not create valid structure
// if action_mroot is empty.
if( !result_ptr->action_mroot.empty() ) {
result_ptr->valid = prev.new_valid(*result_ptr, result_ptr->action_mroot, result_ptr->strong_digest);
}

return result_ptr;
}

Expand Down
51 changes: 28 additions & 23 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ struct controller_impl {
block_log blog;
std::optional<pending_state> pending;
block_handle chain_head;
block_state_ptr chain_head_trans_svnn_block; // chain_head's Savanna representation during transition
fork_database fork_db;
large_atomic<block_id_type> if_irreversible_block_id;
resource_limits_manager resource_limits;
Expand Down Expand Up @@ -1318,7 +1319,7 @@ struct controller_impl {
// legacy_branch is from head, all will be validated unless irreversible_mode(),
// IRREVERSIBLE applies (validates) blocks when irreversible, new_valid will be done after apply in log_irreversible
assert(read_mode == db_read_mode::IRREVERSIBLE || legacy->action_mroot_savanna);
if (legacy->action_mroot_savanna) {
if (legacy->action_mroot_savanna && !new_bsp->valid) {
// Create the valid structure for producing
new_bsp->valid = prev->new_valid(*new_bsp, *legacy->action_mroot_savanna, new_bsp->strong_digest);
}
Expand Down Expand Up @@ -1528,24 +1529,21 @@ struct controller_impl {
const bool skip_validate_signee = true; // validated already or not in replay_push_block according to conf.force_all_checks;
assert(!legacy_branch.empty()); // should have started with a block_state chain_head or we transition during replay
// transition to savanna
block_state_ptr prev;
block_state_ptr prev = chain_head_trans_svnn_block;
bool replay_not_from_snapshot = !chain_head_trans_svnn_block;
for (size_t i = 0; i < legacy_branch.size(); ++i) {
if (i == 0) {
if (i == 0 && replay_not_from_snapshot) {
assert(!prev);
prev = block_state::create_if_genesis_block(*legacy_branch[0]);
} else {
const auto& bspl = legacy_branch[i];
assert(bspl->action_mroot_savanna.has_value());
assert(read_mode == db_read_mode::IRREVERSIBLE || bspl->action_mroot_savanna.has_value());
auto new_bsp = block_state::create_transition_block(
*prev,
bspl->block,
protocol_features.get_protocol_feature_set(),
validator_t{}, skip_validate_signee,
bspl->action_mroot_savanna);
// legacy_branch is from head, all should be validated
assert(bspl->action_mroot_savanna);
// Create the valid structure for producing
new_bsp->valid = prev->new_valid(*new_bsp, *bspl->action_mroot_savanna, new_bsp->strong_digest);
prev = new_bsp;
}
}
Expand Down Expand Up @@ -2017,10 +2015,10 @@ struct controller_impl {
{
return apply<block_state_pair>(chain_head, overloaded{
[&](const block_state_legacy_ptr& head) -> block_state_pair {
if (fork_db.version_in_use() == fork_database::in_use_t::both) {
return fork_db.apply_s<block_state_pair>([&](const auto& forkdb) -> block_state_pair {
return { head, forkdb.head() };
});
if (head->header.contains_header_extension(instant_finality_extension::extension_id())) {
// During transition to Savanna, we need to build Transition Savanna block
// from Savanna Genesis block
return { head, get_transition_savanna_block(head) };
}
return block_state_pair{ head, {} };
},
Expand Down Expand Up @@ -2107,6 +2105,11 @@ struct controller_impl {
auto legacy_ptr = std::make_shared<block_state_legacy>(std::move(*block_state_data.bs_l));
chain_head = block_handle{legacy_ptr};
result.first = std::move(legacy_ptr);

// If we have both bs_l and bs, we are during Savanna transition
if (block_state_data.bs) {
chain_head_trans_svnn_block = std::make_shared<block_state>(std::move(*block_state_data.bs));
}
} else {
auto bs_ptr = std::make_shared<block_state>(std::move(*block_state_data.bs));
chain_head = block_handle{bs_ptr};
Expand Down Expand Up @@ -4418,10 +4421,8 @@ struct controller_impl {
}
}

// This is only used during Savanna transition, which is a one-time occurrence,
// and it is only used by SHiP..
// It is OK to calculate from Savanna Genesis block for each Transition block.
std::optional<finality_data_t> get_transition_block_finality_data(const block_state_legacy_ptr& head) const {
// Returns corresponding Transition Savanna block for a given Legacy block.
block_state_ptr get_transition_savanna_block(const block_state_legacy_ptr& head) const {
fork_database_legacy_t::branch_t legacy_branch;
block_state_legacy_ptr legacy_root;
fork_db.apply_l<void>([&](const auto& forkdb) {
Expand All @@ -4432,14 +4433,13 @@ struct controller_impl {
block_state_ptr prev;
auto bitr = legacy_branch.rbegin();

// get_transition_block_finality_data is called by SHiP as a result
// of receiving accepted_block signal. That is before
// the call to log_irreversible where root() is updated.
// This function can be called before log_irreversible is executed
// (where root() is updated), like in SHiP case where it is called
// as a result receiving accepted_block signal.
// Search both root and legacy_branch for the first block having
// instant_finality_extension -- the Savanna Genesis Block.
// Then start from the Savanna Genesis Block to create corresponding
// Savanna blocks.
// genesis_block already contains all information for finality_data.
if (legacy_root->header.contains_header_extension(instant_finality_extension::extension_id())) {
prev = block_state::create_if_genesis_block(*legacy_root);
} else {
Expand All @@ -4456,19 +4456,24 @@ struct controller_impl {
const bool skip_validate_signee = true; // validated already

for (; bitr != legacy_branch.rend(); ++bitr) {
assert((*bitr)->action_mroot_savanna.has_value());
assert(read_mode == db_read_mode::IRREVERSIBLE || (*bitr)->action_mroot_savanna.has_value());
auto new_bsp = block_state::create_transition_block(
*prev,
(*bitr)->block,
protocol_features.get_protocol_feature_set(),
validator_t{}, skip_validate_signee, (*bitr)->action_mroot_savanna);
validator_t{},
skip_validate_signee,
(*bitr)->action_mroot_savanna);

prev = new_bsp;
}

assert(prev);
return prev->get_finality_data();
return prev;
}

std::optional<finality_data_t> get_transition_block_finality_data(const block_state_legacy_ptr& head) const {
return get_transition_savanna_block(head)->get_finality_data();
}

std::optional<finality_data_t> head_finality_data() const {
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/block_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ struct block_state : public block_header_state { // block_header_state provi

// Constructs a Transition Savanna block state from a Legacy block state.
static std::shared_ptr<block_state> create_transition_block(
const block_header_state& prev,
const block_state& prev,
signed_block_ptr b,
const protocol_feature_set& pfs,
const validator_t& validator,
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/keosd_auto_launch_test.py ${CMAKE_CUR
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/db_modes_test.sh ${CMAKE_CURRENT_BINARY_DIR}/db_modes_test.sh COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/prod_preactivation_test.py ${CMAKE_CURRENT_BINARY_DIR}/prod_preactivation_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/release-build.sh ${CMAKE_CURRENT_BINARY_DIR}/release-build.sh COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/snapshot_in_svnn_transition_test.py ${CMAKE_CURRENT_BINARY_DIR}/snapshot_in_svnn_transition_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-label.sh ${CMAKE_CURRENT_BINARY_DIR}/version-label.sh COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/full-version-label.sh ${CMAKE_CURRENT_BINARY_DIR}/full-version-label.sh COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_producer_watermark_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_producer_watermark_test.py COPYONLY)
Expand Down Expand Up @@ -167,6 +168,8 @@ set_property(TEST p2p_dawn515_test PROPERTY LABELS nonparallelizable_tests)

add_test(NAME producer-preactivate-feature-test COMMAND tests/prod_preactivation_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST producer-preactivate-feature-test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME snapshot_in_svnn_transition_test COMMAND tests/snapshot_in_svnn_transition_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST snapshot_in_svnn_transition_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME nodeos_protocol_feature_test COMMAND tests/nodeos_protocol_feature_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST nodeos_protocol_feature_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME compute_transaction_test COMMAND tests/compute_transaction_test.py -v -p 2 -n 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
Expand Down
16 changes: 9 additions & 7 deletions tests/TestHarness/Cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ def parseClusterKeys(totalNodes):
Utils.Print(f'Found {len(producerKeys)} producer keys')
return producerKeys

def activateInstantFinality(self, biosFinalizer=True):
def activateInstantFinality(self, biosFinalizer=True, waitForFinalization=True):
# call setfinalizer
numFins = 0
for n in (self.nodes + [self.biosNode]):
Expand Down Expand Up @@ -1041,13 +1041,14 @@ def activateInstantFinality(self, biosFinalizer=True):
trans = self.biosNode.pushMessage("eosio", "setfinalizer", setFinStr, opts)
if trans is None or not trans[0]:
Utils.Print("ERROR: Failed to set finalizers")
return None
return None, 0
Node.validateTransaction(trans[1])
transId = Node.getTransId(trans[1])
if not self.biosNode.waitForTransFinalization(transId, timeout=21*12*3):
Utils.Print("ERROR: Failed to validate transaction %s got rolled into a LIB block on server port %d." % (transId, biosNode.port))
return None
return True
if waitForFinalization:
if not self.biosNode.waitForTransFinalization(transId, timeout=21*12*3):
Utils.Print("ERROR: Failed to validate transaction %s got rolled into a LIB block on server port %d." % (transId, biosNode.port))
return None, transId
return True, transId

def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True, activateIF=False, biosFinalizer=True):
"""Create 'prodCount' init accounts and deposits 10000000000 SYS in each. If prodCount is -1 will initialize all possible producers.
Expand Down Expand Up @@ -1113,7 +1114,8 @@ def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers,
return None

if activateIF:
if not self.activateInstantFinality(biosFinalizer=biosFinalizer):
success, transId = self.activateInstantFinality(biosFinalizer=biosFinalizer)
if not success:
Utils.Print("ERROR: Activate instant finality failed")
return None

Expand Down
121 changes: 121 additions & 0 deletions tests/snapshot_in_svnn_transition_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3

import signal

from TestHarness import Cluster, TestHelper, Utils, WalletMgr
from TestHarness.TestHelper import AppArgs

###############################################################
# snapshot_in_svnn_transition_test
#
# Tests snapshot during Savanna transition
# - Configures 5 producing nodes such that the test does not take too long while has enough
# transition time
# - Configures trx_generator to pump transactions into the test node
# - Take a snapshot right after setfinalizer is called
# - Wait until LIB advances
# - Kill the test node
# - Restart from the snapshot
#
###############################################################

Print=Utils.Print
errorExit=Utils.errorExit

appArgs = AppArgs()
args=TestHelper.parse_args({"-d","-s","--keep-logs","--dump-error-details","-v","--leave-running","--unshared"},
applicationSpecificArgs=appArgs)
pnodes=5 # Use 5 such that test does not take too long while has enough transition time
delay=args.d
topo=args.s
debug=args.v
prod_count = 1 # per node prod count
total_nodes=pnodes+1
dumpErrorDetails=args.dump_error_details

snapshotNodeId = 0
irrNodeId=pnodes

Utils.Debug=debug
testSuccessful=False

cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs)
walletMgr=WalletMgr(True)

try:
TestHelper.printSystemInfo("BEGIN")

cluster.setWalletMgr(walletMgr)

Print(f'producing nodes: {pnodes}, topology: {topo}, delay between nodes launch: {delay} second{"s" if delay != 1 else ""}')

numTrxGenerators=2
Print("Stand up cluster")
# For now do not load system contract as it does not support setfinalizer
specificExtraNodeosArgs = { irrNodeId: "--read-mode irreversible"}
if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, prodCount=prod_count, maximumP2pPerHost=total_nodes+numTrxGenerators, topo=topo, delay=delay, loadSystemContract=False,
activateIF=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False:
errorExit("Failed to stand up eos cluster.")

assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] != "eosio", "launch should have waited for production to change"

Print("Configure and launch txn generators")
targetTpsPerGenerator = 10
testTrxGenDurationSec=60*60
cluster.launchTrxGenerators(contractOwnerAcctName=cluster.eosioAccount.name, acctNamesList=[cluster.defproduceraAccount.name, cluster.defproducerbAccount.name],
acctPrivKeysList=[cluster.defproduceraAccount.activePrivateKey,cluster.defproducerbAccount.activePrivateKey], nodeId=cluster.getNode(0).nodeId,
tpsPerGenerator=targetTpsPerGenerator, numGenerators=numTrxGenerators, durationSec=testTrxGenDurationSec,
waitToComplete=False)

status = cluster.waitForTrxGeneratorsSpinup(nodeId=cluster.getNode(0).nodeId, numGenerators=numTrxGenerators)
assert status is not None and status is not False, "ERROR: Failed to spinup Transaction Generators"

nodeSnap=cluster.getNode(snapshotNodeId)
nodeIrr=cluster.getNode(irrNodeId)

# Active Savanna without waiting for activatation is finished so that we can take a
# snapshot during transition
success, transId = cluster.activateInstantFinality(biosFinalizer=False, waitForFinalization=False)
assert success, "Activate instant finality failed"

info = cluster.biosNode.getInfo(exitOnError=True)
snapshot_block_num = info["head_block_num"] + 1

Print(f'Schedule snapshot on snapshot node at block {snapshot_block_num}')
ret = nodeSnap.scheduleSnapshotAt(snapshot_block_num)
assert ret is not None, "Snapshot scheduling failed"
ret = nodeIrr.scheduleSnapshotAt(snapshot_block_num + 1) # intentionally different from nodeSnap
assert ret is not None, "Snapshot scheduling failed"

assert cluster.biosNode.waitForTransFinalization(transId, timeout=21*12*3), f'Failed to validate transaction {transId} got rolled into a LIB block on server port {cluster.biosNode.port}'
assert cluster.biosNode.waitForLibToAdvance(), "Lib should advance after instant finality activated"
assert cluster.biosNode.waitForProducer("defproducera"), "Did not see defproducera"
assert cluster.biosNode.waitForHeadToAdvance(blocksToAdvance=13), "Head did not advance 13 blocks to next producer"
assert cluster.biosNode.waitForLibToAdvance(), "Lib stopped advancing on biosNode"
assert cluster.getNode(snapshotNodeId).waitForLibToAdvance(), "Lib stopped advancing on snapshotNode"
assert cluster.getNode(irrNodeId).waitForLibToAdvance(), "Lib stopped advancing on irrNode"

info = cluster.biosNode.getInfo(exitOnError=True)
assert (info["head_block_num"] - info["last_irreversible_block_num"]) < 9, "Instant finality enabled LIB diff should be small"

def restartWithSnapshot(node):
Print("Shut down node")
node.kill(signal.SIGTERM)
Print("Restart node with snapshot")
node.removeState()
node.rmFromCmd('--p2p-peer-address')
isRelaunchSuccess = node.relaunch(chainArg=f"--snapshot {node.getLatestSnapshot()}")
assert isRelaunchSuccess, "Failed to relaunch node with snapshot"

Print("Restart snapshot node with snapshot")
restartWithSnapshot(nodeSnap)

Print("Restart Irreversible node with snapshot")
restartWithSnapshot(nodeIrr)

testSuccessful=True
finally:
TestHelper.shutdown(cluster, walletMgr, testSuccessful=testSuccessful, dumpErrorDetails=dumpErrorDetails)

exitCode = 0 if testSuccessful else 1
exit(exitCode)
3 changes: 2 additions & 1 deletion tests/transition_to_if.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
status = cluster.waitForTrxGeneratorsSpinup(nodeId=cluster.getNode(0).nodeId, numGenerators=numTrxGenerators)
assert status is not None and status is not False, "ERROR: Failed to spinup Transaction Generators"

assert cluster.activateInstantFinality(biosFinalizer=False), "Activate instant finality failed"
success, transId = cluster.activateInstantFinality(biosFinalizer=False)
assert success, "Activate instant finality failed"

assert cluster.biosNode.waitForLibToAdvance(), "Lib should advance after instant finality activated"
assert cluster.biosNode.waitForProducer("defproducera"), "Did not see defproducera"
Expand Down

0 comments on commit 31c2f86

Please sign in to comment.