diff --git a/libraries/chain/hotstuff/chain_pacemaker.cpp b/libraries/chain/hotstuff/chain_pacemaker.cpp index 79c6638102..f8697d752d 100644 --- a/libraries/chain/hotstuff/chain_pacemaker.cpp +++ b/libraries/chain/hotstuff/chain_pacemaker.cpp @@ -169,6 +169,7 @@ namespace eosio::chain { if (ext) { std::scoped_lock g( _chain_state_mutex ); if (_active_finalizer_policy.generation == 0) { + ilog("Switching to instant finality at block ${b}", ("b", blk->block_num)); // switching from dpos to hotstuff, all nodes will switch at same block height // block header extension is set in finalize_block to value set by host function set_finalizers _chain->set_hs_irreversible_block_num(blk->block_num); // can be any value <= dpos lib diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index cc954ba36a..ef846ff27b 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -167,7 +167,8 @@ def setAlternateVersionLabels(self, file): # pylint: disable=too-many-statements def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="mesh", delay=2, onlyBios=False, dontBootstrap=False, totalProducers=None, sharedProducers=0, extraNodeosArgs="", specificExtraNodeosArgs=None, specificNodeosInstances=None, onlySetProds=False, - pfSetupPolicy=PFSetupPolicy.FULL, alternateVersionLabelsFile=None, associatedNodeLabels=None, loadSystemContract=True, nodeosLogPath=Path(Utils.TestLogRoot) / Path(f'{Path(sys.argv[0]).stem}{os.getpid()}'), genesisPath=None, + pfSetupPolicy=PFSetupPolicy.FULL, alternateVersionLabelsFile=None, associatedNodeLabels=None, loadSystemContract=True, activateIF=False, + nodeosLogPath=Path(Utils.TestLogRoot) / Path(f'{Path(sys.argv[0]).stem}{os.getpid()}'), genesisPath=None, maximumP2pPerHost=0, maximumClients=25, prodsEnableTraceApi=True): """Launch cluster. pnodes: producer nodes count @@ -519,7 +520,7 @@ def connectGroup(group, producerNodes, bridgeNodes) : return True Utils.Print("Bootstrap cluster.") - if not self.bootstrap(self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract): + if not self.bootstrap(launcher, self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract, activateIF): Utils.Print("ERROR: Bootstrap failed.") return False @@ -991,7 +992,42 @@ def parseClusterKeys(totalNodes): Utils.Print(f'Found {len(producerKeys)} producer keys') return producerKeys - def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True): + def activateInstantFinality(self, launcher): + # call setfinalizer + numFins = len(launcher.network.nodes.values()) + setFinStr = f'{{"finalizer_policy": {{' + setFinStr += f' "threshold": {int(numFins * 2 / 3 + 1)}, ' + setFinStr += f' "finalizers": [' + finNum = 1 + for n in launcher.network.nodes.values(): + if n.keys[0].blspubkey is None: + continue + if len(n.producers) == 0: + continue + setFinStr += f' {{"description": "finalizer #{finNum}", ' + setFinStr += f' "weight":1, ' + setFinStr += f' "public_key": "{n.keys[0].blspubkey}", ' + setFinStr += f' "pop": "{n.keys[0].blspop}"' + setFinStr += f' }}' + if finNum != numFins: + setFinStr += f', ' + finNum = finNum + 1 + setFinStr += f' ]' + setFinStr += f'}}}}' + if Utils.Debug: Utils.Print("setfinalizers: %s" % (setFinStr)) + Utils.Print("Setting finalizers") + opts = "--permission eosio@active" + 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 + 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 + + def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True, activateIF=False): """Create 'prodCount' init accounts and deposits 10000000000 SYS in each. If prodCount is -1 will initialize all possible producers. Ensure nodes are inter-connected prior to this call. One way to validate this will be to check if every node has block 1.""" @@ -1027,12 +1063,11 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli Utils.Print("ERROR: Failed to import %s account keys into ignition wallet." % (eosioName)) return None - contract="eosio.bios" - contractDir= str(self.libTestingContractsPath / contract) - if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): - contractDir=str(self.libTestingContractsPath / "old_versions" / "v1.7.0-develop-preactivate_feature" / contract) - else: - contractDir=str(self.libTestingContractsPath / "old_versions" / "v1.6.0-rc3" / contract) + if not PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): + return True + + contract="eosio.boot" + contractDir= str(self.unittestsContractsPath / contract) wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) @@ -1043,9 +1078,21 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli if pfSetupPolicy == PFSetupPolicy.FULL: biosNode.preactivateAllBuiltinProtocolFeature() - Node.validateTransaction(trans) + contract="eosio.bios" + contractDir= str(self.libTestingContractsPath / contract) + wasmFile="%s.wasm" % (contract) + abiFile="%s.abi" % (contract) + Utils.Print("Publish %s contract" % (contract)) + trans=biosNode.publishContract(eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) + if trans is None: + Utils.Print("ERROR: Failed to publish contract %s." % (contract)) + return None + + if activateIF: + self.activateInstantFinality(launcher) + Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) producerKeys.pop(eosioName) accounts=[] @@ -1092,7 +1139,7 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli if counts[keys["node"]] >= prodCount: Utils.Print(f'Count for this node exceeded: {counts[keys["node"]]}') continue - prodStanzas.append({ 'producer_name': keys['name'], 'block_signing_key': keys['public'] }) + prodStanzas.append({ 'producer_name': keys['name'], 'authority': ["block_signing_authority_v0", { 'threshold': 1, 'keys': [{ 'key': keys['public'], 'weight': 1 }]}]}) prodNames.append(keys["name"]) counts[keys["node"]] += 1 setProdsStr += json.dumps(prodStanzas) diff --git a/tests/TestHarness/TestHelper.py b/tests/TestHarness/TestHelper.py index 6f3a1244ae..07790d301a 100644 --- a/tests/TestHarness/TestHelper.py +++ b/tests/TestHarness/TestHelper.py @@ -97,6 +97,9 @@ def createArgumentParser(includeArgs, applicationSpecificArgs=AppArgs(), suppres if "--dont-launch" in includeArgs: thGrp.add_argument("--dont-launch", help=argparse.SUPPRESS if suppressHelp else "Don't launch own node. Assume node is already running.", action='store_true') + if "--activate-if" in includeArgs: + thGrp.add_argument("--activate-if", help=argparse.SUPPRESS if suppressHelp else "Activate instant finality during bios boot.", + action='store_true') if "--keep-logs" in includeArgs: thGrp.add_argument("--keep-logs", help=argparse.SUPPRESS if suppressHelp else "Don't delete /node_* folders, or other test specific log directories, upon test completion", action='store_true') diff --git a/tests/TestHarness/accounts.py b/tests/TestHarness/accounts.py index e81932e40e..e29d68ea9d 100644 --- a/tests/TestHarness/accounts.py +++ b/tests/TestHarness/accounts.py @@ -49,6 +49,9 @@ def __init__(self, name): self.ownerPublicKey=None self.activePrivateKey=None self.activePublicKey=None + self.blsFinalizerPrivateKey=None + self.blsFinalizerPublicKey=None + self.blsFinalizerPOP=None def __str__(self): @@ -84,12 +87,28 @@ def createAccountKeys(count: int) -> List[Account]: activePrivate=m.group(1) activePublic=m.group(2) + # Private key: PVT_BLS_kRhJJ2MsM+/CddO... + # Public key: PUB_BLS_lbUE8922wUfX0Iy5... + # Proof of Possession: SIG_BLS_3jwkVUUYahHgsnmnEA... + rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False) + pattern = r'(\w+[^:]*): ([^\n]+)' + matched = re.findall(pattern, rslts) + results = {} + for k, v in matched: + results[k.strip()] = v.strip() + assert "PVT_BLS_" in results["Private key"] + assert "PUB_BLS_" in results["Public key"] + assert "SIG_BLS_" in results["Proof of Possession"] + name=''.join(random.choice(string.ascii_lowercase) for _ in range(12)) account=Account(name) account.ownerPrivateKey=ownerPrivate account.ownerPublicKey=ownerPublic account.activePrivateKey=activePrivate account.activePublicKey=activePublic + account.blsFinalizerPrivateKey=results["Private key"] + account.blsFinalizerPublicKey=results["Public key"] + account.blsFinalizerPOP=results["Proof of Possession"] accounts.append(account) if Utils.Debug: Utils.Print("name: %s, key(owner): ['%s', '%s], key(active): ['%s', '%s']" % (name, ownerPublic, ownerPrivate, activePublic, activePrivate)) diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index 9fca7c85c4..1301a5385a 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -30,6 +30,9 @@ def default(self, o): class KeyStrings(object): pubkey: str privkey: str + blspubkey: str = None + blsprivkey: str = None + blspop: str = None @dataclass class nodeDefinition: @@ -291,7 +294,7 @@ def bind_nodes(self): '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')) node.producers.append('eosio') else: - node.keys.append(KeyStrings(account.ownerPublicKey, account.ownerPrivateKey)) + node.keys.append(KeyStrings(account.ownerPublicKey, account.ownerPrivateKey, account.blsFinalizerPublicKey, account.blsFinalizerPrivateKey, account.blsFinalizerPOP)) if i < non_bios: count = per_node if extra: @@ -479,7 +482,10 @@ def make_custom(self): node = topo['nodes'][nodeName] self.network.nodes[nodeName].dont_start = node['dont_start'] for keyObj in node['keys']: - self.network.nodes[nodeName].keys.append(KeyStrings(keyObj['pubkey'], keyObj['privkey'])) + if 'blspubkey' in keyObj: + self.network.nodes[nodeName].keys.append(KeyStrings(keyObj['pubkey'], keyObj['privkey'], keyObj['blspubkey'], keyObj['blsprivkey'], keyObj['blspop'])) + else: + self.network.nodes[nodeName].keys.append(KeyStrings(keyObj['pubkey'], keyObj['privkey'])) for peer in node['peers']: self.network.nodes[nodeName].peers.append(peer) for producer in node['producers']: @@ -508,6 +514,8 @@ def construct_command_line(self, instance: nodeDefinition): a(a(eosdcmd, '--plugin'), 'eosio::producer_plugin') producer_keys = list(sum([('--signature-provider', f'{key.pubkey}=KEY:{key.privkey}') for key in instance.keys], ())) eosdcmd.extend(producer_keys) + finalizer_keys = list(sum([('--signature-provider', f'{key.blspubkey}=KEY:{key.blsprivkey}') for key in instance.keys], ())) + eosdcmd.extend(finalizer_keys) producer_names = list(sum([('--producer-name', p) for p in instance.producers], ())) eosdcmd.extend(producer_names) else: diff --git a/tests/TestHarness/logging-template.json b/tests/TestHarness/logging-template.json index fff1143346..79250422fa 100644 --- a/tests/TestHarness/logging-template.json +++ b/tests/TestHarness/logging-template.json @@ -130,13 +130,21 @@ "stderr" ] },{ - "name": "state_history", - "level": "info", - "enabled": true, - "additivity": false, - "appenders": [ - "stderr" - ] + "name": "state_history", + "level": "info", + "enabled": true, + "additivity": false, + "appenders": [ + "stderr" + ] + },{ + "name": "hotstuff", + "level": "all", + "enabled": true, + "additivity": false, + "appenders": [ + "stderr" + ] },{ "name": "transaction", "level": "info", diff --git a/tests/distributed-transactions-test.py b/tests/distributed-transactions-test.py index 985ad83168..9cda9456f8 100755 --- a/tests/distributed-transactions-test.py +++ b/tests/distributed-transactions-test.py @@ -23,7 +23,7 @@ appArgs = AppArgs() extraArgs = appArgs.add_bool(flag="--speculative", help="Run nodes in read-mode=speculative") -args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed", "--speculative" +args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed", "--speculative", "--activate-if" ,"--dump-error-details","-v","--leave-running","--keep-logs","--unshared"}, applicationSpecificArgs=appArgs) pnodes=args.p @@ -36,12 +36,13 @@ seed=args.seed dumpErrorDetails=args.dump_error_details speculative=args.speculative +activateIF=args.activate_if Utils.Debug=debug testSuccessful=False random.seed(seed) # Use a fixed seed for repeatability. -cluster=Cluster(unshared=args.unshared, keepRunning=True if nodesFile is not None else args.leave_running, keepLogs=args.keep_logs) +cluster=Cluster(unshared=args.unshared, keepRunning=True if nodesFile is not None else args.leave_running, keepLogs=args.keep_logs, loggingLevel="all") walletMgr=WalletMgr(True) try: @@ -67,7 +68,7 @@ if speculative: extraNodeosArgs = " --read-mode speculative " - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, extraNodeosArgs=extraNodeosArgs) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, extraNodeosArgs=extraNodeosArgs, activateIF=activateIF) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/nodeos_chainbase_allocation_test.py b/tests/nodeos_chainbase_allocation_test.py index 4c2ec8ee21..3285413b0a 100755 --- a/tests/nodeos_chainbase_allocation_test.py +++ b/tests/nodeos_chainbase_allocation_test.py @@ -61,7 +61,7 @@ nonProdNode.createAccount(newProducerAcc, cluster.eosioAccount, waitForTransBlock=True) setProdsStr = '{"schedule": [' - setProdsStr += '{"producer_name":' + newProducerAcc.name + ',"block_signing_key":' + newProducerAcc.activePublicKey + '}' + setProdsStr += '{"producer_name":' + newProducerAcc.name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + newProducerAcc.activePublicKey + ', "weight":1}]}]}' setProdsStr += ']}' cmd="push action -j eosio setprods '{}' -p eosio".format(setProdsStr) trans = producerNode.processCleosCmd(cmd, cmd, silentErrors=False) diff --git a/tests/nodeos_extra_packed_data_test.py b/tests/nodeos_extra_packed_data_test.py index 3d38aa0691..3ac0b71007 100755 --- a/tests/nodeos_extra_packed_data_test.py +++ b/tests/nodeos_extra_packed_data_test.py @@ -69,7 +69,6 @@ if cluster.launch(totalNodes=totalNodes, pnodes=pnodes, dontBootstrap=dontBootstrap, - pfSetupPolicy=PFSetupPolicy.PREACTIVATE_FEATURE_ONLY, specificExtraNodeosArgs=specificExtraNodeosArgs, associatedNodeLabels=associatedNodeLabels) is False: cmdError("launcher") diff --git a/tests/nodeos_producer_watermark_test.py b/tests/nodeos_producer_watermark_test.py index 178891b9a2..b39aa4925d 100755 --- a/tests/nodeos_producer_watermark_test.py +++ b/tests/nodeos_producer_watermark_test.py @@ -40,7 +40,7 @@ def setProds(sharedProdKey): key = cluster.defProducerAccounts[name].activePublicKey if name == "shrproducera": key = sharedProdKey - setProdsStr += ' { "producer_name": "%s", "block_signing_key": "%s" }' % (name, key) + setProdsStr += '{"producer_name":' + name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + key + ', "weight":1}]}]}' setProdsStr += ' ] }' Utils.Print("setprods: %s" % (setProdsStr)) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index d131b993c9..e366dbcebf 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from TestHarness import Account, Cluster, Node, ReturnType, TestHelper, Utils, WalletMgr, CORE_SYMBOL, createAccountKeys -from pathlib import Path import decimal import re @@ -22,7 +21,7 @@ args = TestHelper.parse_args({"--host","--port","--prod-count","--defproducera_prvt_key","--defproducerb_prvt_key" ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios" - ,"--sanity-test","--wallet-port", "--error-log-path", "--unshared"}) + ,"--activate-if","--sanity-test","--wallet-port", "--error-log-path", "--unshared"}) server=args.host port=args.port debug=args.v @@ -34,6 +33,7 @@ onlyBios=args.only_bios sanityTest=args.sanity_test walletPort=args.wallet_port +activateIF=args.activate_if Utils.Debug=debug localTest=True if server == TestHelper.LOCAL_HOST else False @@ -63,7 +63,7 @@ traceNodeosArgs=" --http-max-response-time-ms 990000 --trace-rpc-abi eosio.token=" + abs_path extraNodeosArgs=traceNodeosArgs + " --plugin eosio::prometheus_plugin --database-map-mode mapped_private " specificNodeosInstances={0: "bin/nodeos"} - if cluster.launch(totalNodes=2, prodCount=prodCount, onlyBios=onlyBios, dontBootstrap=dontBootstrap, extraNodeosArgs=extraNodeosArgs, specificNodeosInstances=specificNodeosInstances) is False: + if cluster.launch(totalNodes=2, prodCount=prodCount, activateIF=activateIF, onlyBios=onlyBios, dontBootstrap=dontBootstrap, extraNodeosArgs=extraNodeosArgs, specificNodeosInstances=specificNodeosInstances) is False: cmdError("launcher") errorExit("Failed to stand up eos cluster.") else: diff --git a/tests/prod_preactivation_test.py b/tests/prod_preactivation_test.py index 46b4376b49..4b032ea32f 100755 --- a/tests/prod_preactivation_test.py +++ b/tests/prod_preactivation_test.py @@ -3,9 +3,11 @@ import decimal import re import time +import json from TestHarness import Cluster, Node, ReturnType, TestHelper, Utils, WalletMgr from TestHarness.Cluster import PFSetupPolicy +from TestHarness.accounts import Account ############################################################### # prod_preactivation_test @@ -18,8 +20,8 @@ cmdError=Utils.cmdError args = TestHelper.parse_args({"--host","--port","--defproducera_prvt_key","--defproducerb_prvt_key" - ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios" - ,"--sanity-test","--wallet-port","--unshared"}) + ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running" + ,"--wallet-port","--unshared"}) server=args.host port=args.port debug=args.v @@ -28,8 +30,6 @@ dumpErrorDetails=args.dump_error_details dontLaunch=args.dont_launch prodCount=2 -onlyBios=args.only_bios -sanityTest=args.sanity_test walletPort=args.wallet_port Utils.Debug=debug @@ -37,7 +37,6 @@ cluster=Cluster(host=server, port=port, defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey, unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) walletMgr=WalletMgr(True, port=walletPort) testSuccessful=False -dontBootstrap=sanityTest WalletdName=Utils.EosWalletName ClientName="cleos" @@ -50,14 +49,65 @@ if localTest and not dontLaunch: Print("Stand up cluster") - if cluster.launch(pnodes=prodCount, totalNodes=prodCount, prodCount=1, onlyBios=onlyBios, - dontBootstrap=dontBootstrap, - pfSetupPolicy=PFSetupPolicy.NONE, extraNodeosArgs=" --plugin eosio::producer_api_plugin --http-max-response-time-ms 990000 ") is False: + if cluster.launch(pnodes=prodCount, totalNodes=prodCount, prodCount=1, + pfSetupPolicy=PFSetupPolicy.NONE, extraNodeosArgs=" --plugin eosio::producer_api_plugin --http-max-response-time-ms 990000 ") is False: cmdError("launcher") errorExit("Failed to stand up eos cluster.") - Print("Validating system accounts after bootstrap") - cluster.validateAccounts(None) + # setup producers using bios contract that does not need preactivate_feature + contract="eosio.bios" + contractDir="libraries/testing/contracts/old_versions/v1.6.0-rc3/%s" % (contract) + wasmFile="%s.wasm" % (contract) + abiFile="%s.abi" % (contract) + retMap = cluster.biosNode.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) + if retMap is None: + errorExit("publish of contract failed") + + producerKeys=cluster.parseClusterKeys(prodCount) + + eosioName="eosio" + eosioKeys=producerKeys[eosioName] + eosioAccount=Account(eosioName) + eosioAccount.ownerPrivateKey=eosioKeys["private"] + eosioAccount.ownerPublicKey=eosioKeys["public"] + eosioAccount.activePrivateKey=eosioKeys["private"] + eosioAccount.activePublicKey=eosioKeys["public"] + + Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) + producerKeys.pop(eosioName) + accounts=[] + for name, keys in producerKeys.items(): + initx = Account(name) + initx.ownerPrivateKey=keys["private"] + initx.ownerPublicKey=keys["public"] + initx.activePrivateKey=keys["private"] + initx.activePublicKey=keys["public"] + trans=cluster.biosNode.createAccount(initx, eosioAccount, 0) + if trans is None: + errorExit("ERROR: Failed to create account %s" % (name)) + Node.validateTransaction(trans) + accounts.append(initx) + + counts = dict.fromkeys(range(prodCount), 0) # initialize node prods count to 0 + setProdsStr = '{"schedule": ' + prodStanzas = [] + prodNames = [] + for name, keys in list(producerKeys.items())[:21]: + if counts[keys["node"]] >= prodCount: + Utils.Print(f'Count for this node exceeded: {counts[keys["node"]]}') + continue + prodStanzas.append({'producer_name': keys['name'], 'block_signing_key': keys['public']}) + prodNames.append(keys["name"]) + counts[keys["node"]] += 1 + setProdsStr += json.dumps(prodStanzas) + setProdsStr += ' }' + if Utils.Debug: Utils.Print("setprods: %s" % (setProdsStr)) + Utils.Print("Setting producers: %s." % (", ".join(prodNames))) + opts = "--permission eosio@active" + # pylint: disable=redefined-variable-type + trans = cluster.biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) + if trans is None or not trans[0]: + errorExit("ERROR: Failed to set producer %s." % (keys["name"])) node = cluster.getNode(0) resource = "producer" @@ -97,7 +147,7 @@ abiFile="%s.abi" % (contract) Print("publish a new bios contract %s should fails because env.is_feature_activated unresolveable" % (contractDir)) - retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True, shouldFail=True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True, shouldFail=True) outPut = retMap["output"].decode("utf-8") if outPut.find("unresolveable") < 0: @@ -125,6 +175,7 @@ if secwait <= 0: errorExit("No producer of node 0") + resource = "producer" command = "schedule_protocol_feature_activations" payload = {"protocol_features_to_activate":[digest]} @@ -139,7 +190,7 @@ time.sleep(0.6) Print("publish a new bios contract %s should fails because node1 is not producing block yet" % (contractDir)) - retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True, shouldFail=True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True, shouldFail=True) if retMap["output"].decode("utf-8").find("unresolveable") < 0: errorExit("bios contract not result in expected unresolveable error") @@ -156,7 +207,9 @@ errorExit("No blocks produced by node 1") time.sleep(0.6) - retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True, shouldFail=False) + if retMap is None: + errorExit("publish of new contract failed") Print("sucessfully set new contract with new intrinsic!!!") testSuccessful=True diff --git a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py index e730247ff6..6bcc3bca55 100755 --- a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py +++ b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py @@ -356,6 +356,8 @@ def stepSetSystemContract(): # DISABLE_DEFERRED_TRXS_STAGE_2 - PREVENT PREVIOUSLY SCHEDULED DEFERRED TRANSACTIONS FROM REACHING OTHER NODE # THIS DEPENDS ON DISABLE_DEFERRED_TRXS_STAGE_1 retry(args.cleos + 'push action eosio activate \'["09e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16"]\' -p eosio@active') + # INSTANT_FINALITY + retry(args.cleos + 'push action eosio activate \'["8cb6dd1e5607208331eb5983141e159c75a597413887e80e8a9a4b715a507eb7"]\' -p eosio@active') sleep(1) # install eosio.system latest version diff --git a/unittests/contracts/CMakeLists.txt b/unittests/contracts/CMakeLists.txt index 289450ccf1..9293c7cc13 100644 --- a/unittests/contracts/CMakeLists.txt +++ b/unittests/contracts/CMakeLists.txt @@ -21,6 +21,7 @@ elseif( USE_EOSIO_CDT_1_8_X ) add_definitions(-DUSE_EOSIO_CDT_1_8_X=true) endif() +add_subdirectory(eosio.boot) add_subdirectory(eosio.msig) add_subdirectory(eosio.system) add_subdirectory(eosio.token) diff --git a/unittests/contracts/eosio.boot/CMakeLists.txt b/unittests/contracts/eosio.boot/CMakeLists.txt new file mode 100644 index 0000000000..3b37a86c76 --- /dev/null +++ b/unittests/contracts/eosio.boot/CMakeLists.txt @@ -0,0 +1,6 @@ +if( EOSIO_COMPILE_TEST_CONTRACTS ) + add_contract( eosio.boot eosio.boot eosio.boot.cpp ) +else() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/eosio.boot.wasm ${CMAKE_CURRENT_BINARY_DIR}/eosio.boot.wasm COPYONLY ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/eosio.boot.abi ${CMAKE_CURRENT_BINARY_DIR}/eosio.boot.abi COPYONLY ) +endif() diff --git a/unittests/contracts/eosio.boot/eosio.boot.abi b/unittests/contracts/eosio.boot/eosio.boot.abi new file mode 100644 index 0000000000..fa76e88486 --- /dev/null +++ b/unittests/contracts/eosio.boot/eosio.boot.abi @@ -0,0 +1,328 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "activate", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "authority", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + }, + { + "name": "accounts", + "type": "permission_level_weight[]" + }, + { + "name": "waits", + "type": "wait_weight[]" + } + ] + }, + { + "name": "canceldelay", + "base": "", + "fields": [ + { + "name": "canceling_auth", + "type": "permission_level" + }, + { + "name": "trx_id", + "type": "checksum256" + } + ] + }, + { + "name": "deleteauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "key_weight", + "base": "", + "fields": [ + { + "name": "key", + "type": "public_key" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "linkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + }, + { + "name": "requirement", + "type": "name" + } + ] + }, + { + "name": "newaccount", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "name", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + } + ] + }, + { + "name": "onerror", + "base": "", + "fields": [ + { + "name": "sender_id", + "type": "uint128" + }, + { + "name": "sent_trx", + "type": "bytes" + } + ] + }, + { + "name": "permission_level", + "base": "", + "fields": [ + { + "name": "actor", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "permission_level_weight", + "base": "", + "fields": [ + { + "name": "permission", + "type": "permission_level" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "reqactivated", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "setabi", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "abi", + "type": "bytes" + } + ] + }, + { + "name": "setcode", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "vmtype", + "type": "uint8" + }, + { + "name": "vmversion", + "type": "uint8" + }, + { + "name": "code", + "type": "bytes" + } + ] + }, + { + "name": "unlinkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + } + ] + }, + { + "name": "updateauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + }, + { + "name": "parent", + "type": "name" + }, + { + "name": "auth", + "type": "authority" + } + ] + }, + { + "name": "wait_weight", + "base": "", + "fields": [ + { + "name": "wait_sec", + "type": "uint32" + }, + { + "name": "weight", + "type": "uint16" + } + ] + } + ], + "actions": [ + { + "name": "activate", + "type": "activate", + "ricardian_contract": "" + }, + { + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "" + }, + { + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "" + }, + { + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "" + }, + { + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "" + }, + { + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + }, + { + "name": "reqactivated", + "type": "reqactivated", + "ricardian_contract": "" + }, + { + "name": "setabi", + "type": "setabi", + "ricardian_contract": "" + }, + { + "name": "setcode", + "type": "setcode", + "ricardian_contract": "" + }, + { + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "" + }, + { + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} \ No newline at end of file diff --git a/unittests/contracts/eosio.boot/eosio.boot.cpp b/unittests/contracts/eosio.boot/eosio.boot.cpp new file mode 100644 index 0000000000..33c5f91ea2 --- /dev/null +++ b/unittests/contracts/eosio.boot/eosio.boot.cpp @@ -0,0 +1,19 @@ +#include "eosio.boot.hpp" +#include + +namespace eosioboot { + +void boot::onerror( ignore, ignore> ) { + check( false, "the onerror action cannot be called directly" ); +} + +void boot::activate( const eosio::checksum256& feature_digest ) { + require_auth( get_self() ); + eosio::preactivate_feature( feature_digest ); +} + +void boot::reqactivated( const eosio::checksum256& feature_digest ) { + check( eosio::is_feature_activated( feature_digest ), "protocol feature is not activated" ); +} + +} diff --git a/unittests/contracts/eosio.boot/eosio.boot.hpp b/unittests/contracts/eosio.boot/eosio.boot.hpp new file mode 100644 index 0000000000..422c2e2df6 --- /dev/null +++ b/unittests/contracts/eosio.boot/eosio.boot.hpp @@ -0,0 +1,260 @@ +#pragma once + +#include +#include + +namespace eosioboot { + + using eosio::action_wrapper; + using eosio::check; + using eosio::checksum256; + using eosio::ignore; + using eosio::name; + using eosio::permission_level; + using eosio::public_key; + + /** + * A weighted permission. + * + * @details Defines a weighted permission, that is a permission which has a weight associated. + * A permission is defined by an account name plus a permission name. The weight is going to be + * used against a threshold, if the weight is equal or greater than the threshold set then authorization + * will pass. + */ + struct permission_level_weight { + permission_level permission; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( permission_level_weight, (permission)(weight) ) + }; + + /** + * Weighted key. + * + * @details A weighted key is defined by a public key and an associated weight. + */ + struct key_weight { + eosio::public_key key; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( key_weight, (key)(weight) ) + }; + + /** + * Wait weight. + * + * @details A wait weight is defined by a number of seconds to wait for and a weight. + */ + struct wait_weight { + uint32_t wait_sec; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( wait_weight, (wait_sec)(weight) ) + }; + + /** + * Blockchain authority. + * + * @details An authority is defined by: + * - a vector of key_weights (a key_weight is a public key plus a weight), + * - a vector of permission_level_weights, (a permission_level is an account name plus a permission name) + * - a vector of wait_weights (a wait_weight is defined by a number of seconds to wait and a weight) + * - a threshold value + */ + struct authority { + uint32_t threshold = 0; + std::vector keys; + std::vector accounts; + std::vector waits; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( authority, (threshold)(keys)(accounts)(waits) ) + }; + + /** + * @defgroup eosioboot eosio.boot + * @ingroup eosiocontracts + * + * eosio.boot is a extremely minimalistic system contract that only supports the native actions and an + * activate action that allows activating desired protocol features prior to deploying a system contract + * with more features such as eosio.bios or eosio.system. + * + * @{ + */ + class [[eosio::contract("eosio.boot")]] boot : public eosio::contract { + public: + using contract::contract; + /** + * @{ + * These actions map one-on-one with the ones defined in + * [Native Action Handlers](@ref native_action_handlers) section. + * They are present here so they can show up in the abi file and thus user can send them + * to this contract, but they have no specific implementation at this contract level, + * they will execute the implementation at the core level and nothing else. + */ + /** + * New account action + * + * @details Creates a new account. + * + * @param creator - the creator of the account + * @param name - the name of the new account + * @param owner - the authority for the owner permission of the new account + * @param active - the authority for the active permission of the new account + */ + [[eosio::action]] + void newaccount( name creator, + name name, + ignore owner, + ignore active) {} + /** + * Update authorization action. + * + * @details Updates pemission for an account. + * + * @param account - the account for which the permission is updated, + * @param pemission - the permission name which is updated, + * @param parem - the parent of the permission which is updated, + * @param aut - the json describing the permission authorization. + */ + [[eosio::action]] + void updateauth( ignore account, + ignore permission, + ignore parent, + ignore auth ) {} + + /** + * Delete authorization action. + * + * @details Deletes the authorization for an account's permision. + * + * @param account - the account for which the permission authorization is deleted, + * @param permission - the permission name been deleted. + */ + [[eosio::action]] + void deleteauth( ignore account, + ignore permission ) {} + + /** + * Link authorization action. + * + * @details Assigns a specific action from a contract to a permission you have created. Five system + * actions can not be linked `updateauth`, `deleteauth`, `linkauth`, `unlinkauth`, and `canceldelay`. + * This is useful because when doing authorization checks, the EOSIO based blockchain starts with the + * action needed to be authorized (and the contract belonging to), and looks up which permission + * is needed to pass authorization validation. If a link is set, that permission is used for authoraization + * validation otherwise then active is the default, with the exception of `eosio.any`. + * `eosio.any` is an implicit permission which exists on every account; you can link actions to `eosio.any` + * and that will make it so linked actions are accessible to any permissions defined for the account. + * + * @param account - the permission's owner to be linked and the payer of the RAM needed to store this link, + * @param code - the owner of the action to be linked, + * @param type - the action to be linked, + * @param requirement - the permission to be linked. + */ + [[eosio::action]] + void linkauth( ignore account, + ignore code, + ignore type, + ignore requirement ) {} + + /** + * Unlink authorization action. + * + * @details It's doing the reverse of linkauth action, by unlinking the given action. + * + * @param account - the owner of the permission to be unlinked and the receiver of the freed RAM, + * @param code - the owner of the action to be unlinked, + * @param type - the action to be unlinked. + */ + [[eosio::action]] + void unlinkauth( ignore account, + ignore code, + ignore type ) {} + + /** + * Cancel delay action. + * + * @details Cancels a deferred transaction. + * + * @param canceling_auth - the permission that authorizes this action, + * @param trx_id - the deferred transaction id to be cancelled. + */ + [[eosio::action]] + void canceldelay( ignore canceling_auth, ignore trx_id ) {} + + /** + * Set code action. + * + * @details Sets the contract code for an account. + * + * @param account - the account for which to set the contract code. + * @param vmtype - reserved, set it to zero. + * @param vmversion - reserved, set it to zero. + * @param code - the code content to be set, in the form of a blob binary.. + */ + [[eosio::action]] + void setcode( name account, uint8_t vmtype, uint8_t vmversion, const std::vector& code ) {} + + /** + * Set abi for contract. + * + * @details Set the abi for contract identified by `account` name. + * + * @param account - the name of the account to set the abi for + * @param abi - the abi hash represented as a vector of characters + */ + [[eosio::action]] + void setabi( name account, const std::vector& abi ) {} + + /** @}*/ + + /** + * On error action. + * + * @details Notification of this action is delivered to the sender of a deferred transaction + * when an objective error occurs while executing the deferred transaction. + * This action is not meant to be called directly. + * + * @param sender_id - the id for the deferred transaction chosen by the sender, + * @param sent_trx - the deferred transaction that failed. + */ + [[eosio::action]] + void onerror( ignore sender_id, ignore> sent_trx ); + + /** + * Activates a protocol feature. + * + * @details Activates a protocol feature + * + * @param feature_digest - hash of the protocol feature to activate. + */ + [[eosio::action]] + void activate( const eosio::checksum256& feature_digest ); + + /** + * Asserts that a protocol feature has been activated. + * + * @details Asserts that a protocol feature has been activated + * + * @param feature_digest - hash of the protocol feature to check for activation. + */ + [[eosio::action]] + void reqactivated( const eosio::checksum256& feature_digest ); + + using newaccount_action = action_wrapper<"newaccount"_n, &boot::newaccount>; + using updateauth_action = action_wrapper<"updateauth"_n, &boot::updateauth>; + using deleteauth_action = action_wrapper<"deleteauth"_n, &boot::deleteauth>; + using linkauth_action = action_wrapper<"linkauth"_n, &boot::linkauth>; + using unlinkauth_action = action_wrapper<"unlinkauth"_n, &boot::unlinkauth>; + using canceldelay_action = action_wrapper<"canceldelay"_n, &boot::canceldelay>; + using setcode_action = action_wrapper<"setcode"_n, &boot::setcode>; + using setabi_action = action_wrapper<"setabi"_n, &boot::setabi>; + using activate_action = action_wrapper<"activate"_n, &boot::activate>; + using reqactivated_action = action_wrapper<"reqactivated"_n, &boot::reqactivated>; + }; + /** @}*/ // end of @defgroup eosioboot eosio.boot +} /// namespace eosioboot diff --git a/unittests/contracts/eosio.boot/eosio.boot.wasm b/unittests/contracts/eosio.boot/eosio.boot.wasm new file mode 100755 index 0000000000..0da4b8c983 Binary files /dev/null and b/unittests/contracts/eosio.boot/eosio.boot.wasm differ