-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IF: Test: Transition to instant-finality with multiple producers #2224
Changes from all commits
22086f6
902c4c9
59132ee
e7909e0
b309617
93ea676
bd9a93e
00a5615
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -481,6 +481,8 @@ def connectGroup(group, producerNodes, bridgeNodes) : | |
node = Node(self.host, self.port + nodeNum, nodeNum, Path(instance.data_dir_name), | ||
Path(instance.config_dir_name), eosdcmd, unstarted=instance.dont_start, | ||
launch_time=launcher.launch_time, walletMgr=self.walletMgr, nodeosVers=self.nodeosVers) | ||
node.keys = instance.keys | ||
node.isProducer = len(instance.producers) > 0 | ||
if nodeNum == Node.biosNodeId: | ||
self.biosNode = node | ||
else: | ||
|
@@ -993,34 +995,33 @@ def parseClusterKeys(totalNodes): | |
Utils.Print(f'Found {len(producerKeys)} producer keys') | ||
return producerKeys | ||
|
||
def activateInstantFinality(self, launcher, biosFinalizer, pnodes): | ||
def activateInstantFinality(self, biosFinalizer=True): | ||
# call setfinalizer | ||
numFins = 0 | ||
for n in launcher.network.nodes.values(): | ||
if not n.keys or not n.keys[0].blspubkey: | ||
for n in (self.nodes + [self.biosNode]): | ||
if not n or not n.keys or not n.keys[0].blspubkey: | ||
continue | ||
if not n.producers: | ||
if not n.isProducer: | ||
continue | ||
if n.index == Node.biosNodeId and not biosFinalizer: | ||
if n.nodeId == 'bios' and not biosFinalizer: | ||
continue | ||
numFins = numFins + 1 | ||
|
||
threshold = int(numFins * 2 / 3 + 1) | ||
if threshold > 2 and threshold == numFins: | ||
# nodes are often stopped, so do not require all node votes | ||
threshold = threshold - 1 | ||
# pnodes does not include biosNode | ||
if Utils.Debug: Utils.Print(f"threshold: {threshold}, numFins: {numFins}, pnodes: {pnodes}") | ||
if Utils.Debug: Utils.Print(f"threshold: {threshold}, numFins: {numFins}") | ||
setFinStr = f'{{"finalizer_policy": {{' | ||
setFinStr += f' "threshold": {threshold}, ' | ||
setFinStr += f' "finalizers": [' | ||
finNum = 1 | ||
for n in launcher.network.nodes.values(): | ||
if n.index == Node.biosNodeId and not biosFinalizer: | ||
for n in (self.nodes + [self.biosNode]): | ||
if not n or not n.keys or not n.keys[0].blspubkey: | ||
continue | ||
if not n.keys or not n.keys[0].blspubkey: | ||
if not n.isProducer: | ||
continue | ||
if not n.producers: | ||
if n.nodeId == 'bios' and not biosFinalizer: | ||
continue | ||
setFinStr += f' {{"description": "finalizer #{finNum}", ' | ||
setFinStr += f' "weight":1, ' | ||
|
@@ -1044,6 +1045,7 @@ def activateInstantFinality(self, launcher, biosFinalizer, pnodes): | |
if not self.biosNode.waitForTransFinalization(transId, timeout=21*12*3): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So is the host function sent to the biosNode first even if biosFinalizer is false? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Doesn't matter which node you look for the transaction on. Maybe we should rename this python method now. It has been there for a long time. It means is the transaction in a LIB block. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should change the name now as a part of the big changes for IF. |
||
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 | ||
|
||
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. | ||
|
@@ -1109,7 +1111,9 @@ def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, | |
return None | ||
|
||
if activateIF: | ||
self.activateInstantFinality(launcher, biosFinalizer, self.productionNodesCount) | ||
if not self.activateInstantFinality(biosFinalizer=biosFinalizer): | ||
Utils.Print("ERROR: Activate instant finality failed") | ||
return None | ||
|
||
Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) | ||
producerKeys.pop(eosioName) | ||
|
@@ -1208,7 +1212,7 @@ def createSystemAccount(accountName): | |
# | ||
# Could activate instant finality here, but have to wait for finality which with all the producers takes a long time | ||
# if activateIF: | ||
# self.activateInstantFinality(launcher) | ||
# self.activateInstantFinality() | ||
|
||
eosioTokenAccount = copy.deepcopy(eosioAccount) | ||
eosioTokenAccount.name = 'eosio.token' | ||
|
@@ -1456,6 +1460,31 @@ def cleanup(self): | |
for f in self.filesToCleanup: | ||
os.remove(f) | ||
|
||
def setProds(self, producers): | ||
"""Call setprods with list of producers""" | ||
setProdsStr = '{"schedule": [' | ||
firstTime = True | ||
for name in producers: | ||
if firstTime: | ||
firstTime = False | ||
else: | ||
setProdsStr += ',' | ||
if not self.defProducerAccounts[name]: | ||
Utils.Print(f"ERROR: no account key for {name}") | ||
return None | ||
key = self.defProducerAccounts[name].activePublicKey | ||
setProdsStr += '{"producer_name":' + name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + key + ', "weight":1}]}]}' | ||
|
||
setProdsStr += ' ] }' | ||
Utils.Print("setprods: %s" % (setProdsStr)) | ||
opts = "--permission eosio@active" | ||
# pylint: disable=redefined-variable-type | ||
trans = self.biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) | ||
if trans is None or not trans[0]: | ||
Utils.Print("ERROR: Failed to set producer with cmd %s" % (setProdsStr)) | ||
return None | ||
return True | ||
|
||
# Create accounts, if account does not already exist, and validates that the last transaction is received on root node | ||
def createAccounts(self, creator, waitForTransBlock=True, stakedDeposit=1000, validationNodeIndex=-1): | ||
if self.accounts is None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#!/usr/bin/env python3 | ||
|
||
from TestHarness import Cluster, TestHelper, Utils, WalletMgr | ||
from TestHarness.TestHelper import AppArgs | ||
|
||
############################################################### | ||
# transition_to_if | ||
# | ||
# Transition to instant-finality with multiple producers (at least 4). | ||
# | ||
############################################################### | ||
|
||
|
||
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=4 | ||
delay=args.d | ||
topo=args.s | ||
debug=args.v | ||
prod_count = 1 # per node prod count | ||
total_nodes=pnodes | ||
dumpErrorDetails=args.dump_error_details | ||
|
||
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 ""}') | ||
|
||
Print("Stand up cluster") | ||
# For now do not load system contract as it does not support setfinalizer | ||
if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, prodCount=prod_count, topo=topo, delay=delay, loadSystemContract=False, | ||
activateIF=False) 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" | ||
|
||
assert cluster.activateInstantFinality(biosFinalizer=False), "Activate instant finality failed" | ||
|
||
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) # into next producer | ||
assert cluster.biosNode.waitForLibToAdvance(), "Lib stopped advancing" | ||
|
||
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" | ||
|
||
# launch setup node_00 (defproducera - defproducerf), node_01 (defproducerg - defproducerk), | ||
# node_02 (defproducerl - defproducerp), node_03 (defproducerq - defproduceru) | ||
# with setprods of (defproducera, defproducerg, defproducerl, defproducerq) | ||
assert cluster.biosNode.waitForProducer("defproducerq"), "defproducerq did not produce" | ||
|
||
# should take effect in first block of defproducerg slot (so defproducerh) | ||
assert cluster.setProds(["defproducerb", "defproducerh", "defproducerm", "defproducerr"]), "setprods failed" | ||
setProdsBlockNum = cluster.biosNode.getBlockNum() | ||
cluster.biosNode.waitForBlock(setProdsBlockNum+12+12+1) | ||
assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] == "defproducerh", "setprods should have taken effect" | ||
|
||
testSuccessful=True | ||
finally: | ||
TestHelper.shutdown(cluster, walletMgr, testSuccessful=testSuccessful, dumpErrorDetails=dumpErrorDetails) | ||
|
||
exitCode = 0 if testSuccessful else 1 | ||
exit(exitCode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this for loop be combined with the one above? They are in similar shape.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, need to have
threshold
calculated first.