From 7df49a4d9c9ec4c3d72cfe66a5f3ae34b8d44029 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:50:11 -0600 Subject: [PATCH 01/15] GH-2100 Support ring, line, star; not just mesh --- tests/TestHarness/TestHelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestHarness/TestHelper.py b/tests/TestHarness/TestHelper.py index 07790d301a..47a62d1471 100644 --- a/tests/TestHarness/TestHelper.py +++ b/tests/TestHarness/TestHelper.py @@ -57,7 +57,7 @@ def createArgumentParser(includeArgs, applicationSpecificArgs=AppArgs(), suppres if "--nodes-file" in includeArgs: thGrp.add_argument("--nodes-file", type=str, help=argparse.SUPPRESS if suppressHelp else "File containing nodes info in JSON format.") if "-s" in includeArgs: - thGrp.add_argument("-s", type=str, help=argparse.SUPPRESS if suppressHelp else "topology", choices=["mesh"], default="mesh") + thGrp.add_argument("-s", type=str, help=argparse.SUPPRESS if suppressHelp else "topology", choices=['star', 'mesh', 'ring', 'line'], default="mesh") if "-c" in includeArgs: thGrp.add_argument("-c", type=str, help=argparse.SUPPRESS if suppressHelp else "chain strategy", choices=[Utils.SyncResyncTag, Utils.SyncReplayTag, Utils.SyncNoneTag, Utils.SyncHardReplayTag], From 7cbb3926a0ed8ef986be234b37f2010e409aad3a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:50:48 -0600 Subject: [PATCH 02/15] GH-2100 Add a lib advance test based on nodeos_run_test.py --- tests/nodeos_lib_test.py | 596 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100755 tests/nodeos_lib_test.py diff --git a/tests/nodeos_lib_test.py b/tests/nodeos_lib_test.py new file mode 100755 index 0000000000..f2f7c289aa --- /dev/null +++ b/tests/nodeos_lib_test.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python3 + +from TestHarness import Account, Cluster, Node, ReturnType, TestHelper, Utils, WalletMgr, CORE_SYMBOL, createAccountKeys + +import decimal +import re +import json +import os +import sys + +############################################################### +# nodeos_lib_test +# +# General test that tests a lib advances after a number of actions +# +############################################################### + +Print=Utils.Print +errorExit=Utils.errorExit +cmdError=Utils.cmdError + +args = TestHelper.parse_args({"-p","-n","-s","--host","--port","--prod-count","--defproducera_prvt_key","--defproducerb_prvt_key" + ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios" + ,"--activate-if","--sanity-test","--wallet-port", "--error-log-path", "--unshared"}) +server=args.host +port=args.port +debug=args.v +defproduceraPrvtKey=args.defproducera_prvt_key +defproducerbPrvtKey=args.defproducerb_prvt_key +dumpErrorDetails=args.dump_error_details +dontLaunch=args.dont_launch +prodCount=args.prod_count +pnodes=args.p +topo=args.s +total_nodes = pnodes + 1 if args.n <= pnodes else args.n +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 +cluster=Cluster(host=server, port=port, defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey,unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +errFileName=f"{cluster.nodeosLogPath}/node_00/stderr.txt" +if args.error_log_path: + errFileName=args.error_log_path +walletMgr=WalletMgr(True, port=walletPort, keepRunning=args.leave_running, keepLogs=args.keep_logs) +testSuccessful=False +dontBootstrap=sanityTest # intent is to limit the scope of the sanity test to just verifying that nodes can be started + +WalletdName=Utils.EosWalletName +ClientName="cleos" +timeout = .5 * 12 * 2 + 60 # time for finalization with 1 producer + 60 seconds padding +Utils.setIrreversibleTimeout(timeout) + +try: + TestHelper.printSystemInfo("BEGIN") + cluster.setWalletMgr(walletMgr) + Print("SERVER: %s" % (server)) + Print("PORT: %d" % (port)) + + if localTest and not dontLaunch: + Print("Stand up cluster") + + abs_path = os.path.abspath(os.getcwd() + '/unittests/contracts/eosio.token/eosio.token.abi') + 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=total_nodes, pnodes=pnodes, topo=topo, prodCount=prodCount, activateIF=activateIF, onlyBios=onlyBios, dontBootstrap=dontBootstrap, extraNodeosArgs=extraNodeosArgs, specificNodeosInstances=specificNodeosInstances) is False: + cmdError("launcher") + errorExit("Failed to stand up eos cluster.") + else: + Print("Collecting cluster info.") + cluster.initializeNodes(defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey) + Print("Stand up %s" % (WalletdName)) + if walletMgr.launch() is False: + cmdError("%s" % (WalletdName)) + errorExit("Failed to stand up eos walletd.") + + if sanityTest: + testSuccessful=True + exit(0) + + Print("Validating system accounts after bootstrap") + cluster.validateAccounts(None) + + accounts=createAccountKeys(4) + if accounts is None: + errorExit("FAILURE - create keys") + testeraAccount=accounts[0] + testeraAccount.name="testera11111" + currencyAccount=accounts[1] + currencyAccount.name="currency1111" + exchangeAccount=accounts[2] + exchangeAccount.name="exchange1111" + # account to test newaccount with authority + testerbAccount=accounts[3] + testerbAccount.name="testerb11111" + testerbOwner = testerbAccount.ownerPublicKey + testerbAccount.ownerPublicKey = '{"threshold":1, "accounts":[{"permission":{"actor": "' + testeraAccount.name + '", "permission":"owner"}, "weight": 1}],"keys":[{"key": "' +testerbOwner + '", "weight": 1}],"waits":[]}' + + PRV_KEY1=testeraAccount.ownerPrivateKey + PUB_KEY1=testeraAccount.ownerPublicKey + PRV_KEY2=currencyAccount.ownerPrivateKey + PUB_KEY2=currencyAccount.ownerPublicKey + PRV_KEY3=exchangeAccount.activePrivateKey + PUB_KEY3=exchangeAccount.activePublicKey + + testeraAccount.activePrivateKey=currencyAccount.activePrivateKey=PRV_KEY3 + testeraAccount.activePublicKey=currencyAccount.activePublicKey=PUB_KEY3 + + exchangeAccount.ownerPrivateKey=PRV_KEY2 + exchangeAccount.ownerPublicKey=PUB_KEY2 + + testWalletName="test" + Print("Creating wallet \"%s\"." % (testWalletName)) + walletAccounts=[cluster.defproduceraAccount,cluster.defproducerbAccount] + if not dontLaunch: + walletAccounts.append(cluster.eosioAccount) + testWallet=walletMgr.create(testWalletName, walletAccounts) + + Print("Wallet \"%s\" password=%s." % (testWalletName, testWallet.password.encode("utf-8"))) + + for account in accounts: + Print("Importing keys for account %s into wallet %s." % (account.name, testWallet.name)) + if not walletMgr.importKey(account, testWallet): + cmdError("%s wallet import" % (ClientName)) + errorExit("Failed to import key for account %s" % (account.name)) + + defproduceraWalletName="defproducera" + Print("Creating wallet \"%s\"." % (defproduceraWalletName)) + defproduceraWallet=walletMgr.create(defproduceraWalletName) + + Print("Wallet \"%s\" password=%s." % (defproduceraWalletName, defproduceraWallet.password.encode("utf-8"))) + + defproduceraAccount=cluster.defproduceraAccount + defproducerbAccount=cluster.defproducerbAccount + + Print("Importing keys for account %s into wallet %s." % (defproduceraAccount.name, defproduceraWallet.name)) + if not walletMgr.importKey(defproduceraAccount, defproduceraWallet): + cmdError("%s wallet import" % (ClientName)) + errorExit("Failed to import key for account %s" % (defproduceraAccount.name)) + + Print("Locking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.lockWallet(testWallet): + cmdError("%s wallet lock" % (ClientName)) + errorExit("Failed to lock wallet %s" % (testWallet.name)) + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + Print("Locking all wallets.") + if not walletMgr.lockAllWallets(): + cmdError("%s wallet lock_all" % (ClientName)) + errorExit("Failed to lock all wallets") + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + Print("Getting open wallet list.") + wallets=walletMgr.getOpenWallets() + if len(wallets) == 0 or wallets[0] != testWallet.name or len(wallets) > 1: + Print("FAILURE - wallet list did not include %s" % (testWallet.name)) + errorExit("Unexpected wallet list: %s" % (wallets)) + + Print("Getting wallet keys.") + actualKeys=walletMgr.getKeys(testWallet) + expectedkeys=[] + for account in accounts: + expectedkeys.append(account.ownerPrivateKey) + expectedkeys.append(account.activePrivateKey) + noMatch=list(set(expectedkeys) - set(actualKeys)) + if len(noMatch) > 0: + errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) + + Print("Locking all wallets.") + if not walletMgr.lockAllWallets(): + cmdError("%s wallet lock_all" % (ClientName)) + errorExit("Failed to lock all wallets") + + Print("Unlocking wallet \"%s\"." % (defproduceraWallet.name)) + if not walletMgr.unlockWallet(defproduceraWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (defproduceraWallet.name)) + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + Print("Getting wallet keys.") + actualKeys=walletMgr.getKeys(defproduceraWallet) + expectedkeys=[defproduceraAccount.ownerPrivateKey] + noMatch=list(set(expectedkeys) - set(actualKeys)) + if len(noMatch) > 0: + errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) + + node=cluster.getNode(total_nodes-1) + + Print("Validating accounts before user accounts creation") + cluster.validateAccounts(None) + + Print("Create new account %s via %s" % (testeraAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(testeraAccount, cluster.defproduceraAccount, stakedDeposit=0, waitForTransBlock=True, exitOnError=True) + + Print("Create new account %s via %s" % (testerbAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(testerbAccount, cluster.defproduceraAccount, stakedDeposit=0, waitForTransBlock=False, exitOnError=True) + + Print("Create new account %s via %s" % (currencyAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(currencyAccount, cluster.defproduceraAccount, buyRAM=200000, stakedDeposit=5000, exitOnError=True) + + Print("Create new account %s via %s" % (exchangeAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(exchangeAccount, cluster.defproduceraAccount, buyRAM=200000, waitForTransBlock=True, exitOnError=True) + + Print("Validating accounts after user accounts creation") + accounts=[testeraAccount, currencyAccount, exchangeAccount] + cluster.validateAccounts(accounts) + + Print("Verify account %s" % (testeraAccount)) + if not node.verifyAccount(testeraAccount): + errorExit("FAILURE - account creation failed.", raw=True) + + transferAmount="97.5321 {0}".format(CORE_SYMBOL) + Print("Transfer funds %s from account %s to %s" % (transferAmount, defproduceraAccount.name, testeraAccount.name)) + node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer", waitForTransBlock=True) + + expectedAmount=transferAmount + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(testeraAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + transferAmount="0.0100 {0}".format(CORE_SYMBOL) + Print("Force transfer funds %s from account %s to %s" % ( + transferAmount, defproduceraAccount.name, testeraAccount.name)) + node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer", force=True, waitForTransBlock=True) + + expectedAmount="97.5421 {0}".format(CORE_SYMBOL) + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(testeraAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + Print("Validating accounts after some user transactions") + accounts=[testeraAccount, currencyAccount, exchangeAccount] + cluster.validateAccounts(accounts) + + Print("Locking all wallets.") + if not walletMgr.lockAllWallets(): + cmdError("%s wallet lock_all" % (ClientName)) + errorExit("Failed to lock all wallets") + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + transferAmount="97.5311 {0}".format(CORE_SYMBOL) + Print("Transfer funds %s from account %s to %s" % ( + transferAmount, testeraAccount.name, currencyAccount.name)) + trans=node.transferFunds(testeraAccount, currencyAccount, transferAmount, "test transfer a->b", waitForTransBlock=True) + transId=Node.getTransId(trans) + + expectedAmount="98.0311 {0}".format(CORE_SYMBOL) # 5000 initial deposit + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(currencyAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + node.waitForTransactionInBlock(transId) + + transaction=node.getTransaction(transId, exitOnError=True, delayedRetry=False) + + typeVal=None + amountVal=None + key="" + try: + key = "[actions][0][action]" + typeVal = transaction["actions"][0]["action"] + key = "[actions][0][params][quantity]" + amountVal = transaction["actions"][0]["params"]["quantity"] + amountVal = int(decimal.Decimal(amountVal.split()[0]) * 10000) + except (TypeError, KeyError) as e: + Print("transaction%s not found. Transaction: %s" % (key, transaction)) + raise + + if typeVal != "transfer" or amountVal != 975311: + errorExit("FAILURE - get transaction trans_id failed: %s %s %s" % (transId, typeVal, amountVal), raw=True) + + Print("Currency Contract Tests") + Print("verify no contract in place") + Print("Get code hash for account %s" % (currencyAccount.name)) + codeHash=node.getAccountCodeHash(currencyAccount.name) + if codeHash is None: + cmdError("%s get code currency1111" % (ClientName)) + errorExit("Failed to get code hash for account %s" % (currencyAccount.name)) + hashNum=int(codeHash, 16) + if hashNum != 0: + errorExit("FAILURE - get code currency1111 failed", raw=True) + + contractDir="unittests/contracts/eosio.token" + wasmFile="eosio.token.wasm" + abiFile="eosio.token.abi" + Print("Publish contract") + trans=node.publishContract(currencyAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) + if trans is None: + cmdError("%s set contract currency1111" % (ClientName)) + errorExit("Failed to publish contract.") + + Print("Get code hash for account %s" % (currencyAccount.name)) + codeHash = node.getAccountCodeHash(currencyAccount.name) + if codeHash is None: + cmdError("%s get code currency1111" % (ClientName)) + errorExit("Failed to get code hash for account %s" % (currencyAccount.name)) + hashNum = int(codeHash, 16) + if hashNum == 0: + errorExit("FAILURE - get code currency1111 failed", raw=True) + + Print("push create action to currency1111 contract") + contract="currency1111" + action="create" + data="{\"issuer\":\"currency1111\",\"maximum_supply\":\"100000.0000 CUR\",\"can_freeze\":\"0\",\"can_recall\":\"0\",\"can_whitelist\":\"0\"}" + opts="--permission currency1111@active" + trans=node.pushMessage(contract, action, data, opts) + try: + assert(trans) + assert(trans[0]) + except (AssertionError, KeyError) as _: + Print("ERROR: Failed push create action to currency1111 contract assertion. %s" % (trans)) + raise + transId=Node.getTransId(trans[1]) + node.waitForTransactionInBlock(transId) + + Print("push issue action to currency1111 contract") + action="issue" + data="{\"to\":\"currency1111\",\"quantity\":\"100000.0000 CUR\",\"memo\":\"issue\"}" + opts="--permission currency1111@active" + trans=node.pushMessage(contract, action, data, opts) + try: + assert(trans) + assert(trans[0]) + except (AssertionError, KeyError) as _: + Print("ERROR: Failed push issue action to currency1111 contract assertion. %s" % (trans)) + raise + transId=Node.getTransId(trans[1]) + node.waitForTransactionInBlock(transId) + + Print("Verify currency1111 contract has proper initial balance (via get table)") + contract="currency1111" + table="accounts" + row0=node.getTableRow(contract, currencyAccount.name, table, 0) + try: + assert(row0) + assert(row0["balance"] == "100000.0000 CUR") + except (AssertionError, KeyError) as _: + Print("ERROR: Failed get table row assertion. %s" % (row0)) + raise + + Print("Verify currency1111 contract has proper initial balance (via get currency1111 balance)") + amountStr=node.getTableAccountBalance("currency1111", currencyAccount.name) + + expected="100000.0000 CUR" + actual=amountStr + if actual != expected: + errorExit("FAILURE - currency1111 balance check failed. Expected: %s, Recieved %s" % (expected, actual), raw=True) + + Print("Verify currency1111 contract has proper total supply of CUR (via get currency1111 stats)") + res=node.getCurrencyStats(contract, "CUR", exitOnError=True) + try: + assert(res["CUR"]["supply"] == "100000.0000 CUR") + except (AssertionError, KeyError) as _: + Print("ERROR: Failed get currecy stats assertion. %s" % (res)) + raise + + transferAmt=10 + totalTransfer=0 + contract="currency1111" + action="transfer" + for _ in range(5): + Print("push transfer action to currency1111 contract") + data="{\"from\":\"currency1111\",\"to\":\"defproducera\",\"quantity\":" + data +="\"00.00%s CUR\",\"memo\":\"test\"}" % (transferAmt) + opts="--permission currency1111@active" + trans=node.pushMessage(contract, action, data, opts, force=True) + if trans is None or not trans[0]: + cmdError("%s push message currency1111 transfer" % (ClientName)) + errorExit("Failed to push message to currency1111 contract") + transId=Node.getTransId(trans[1]) + totalTransfer = totalTransfer + transferAmt + + Print("verify transaction exists") + if not node.waitForTransactionInBlock(transId): + cmdError("%s get transaction trans_id" % (ClientName)) + errorExit("Failed to verify push message transaction id.") + + Print("read current contract balance") + amountStr=node.getTableAccountBalance("currency1111", defproduceraAccount.name) + + expectedDefproduceraBalance="0.00%s CUR" % (totalTransfer) + actual=amountStr + if actual != expectedDefproduceraBalance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedDefproduceraBalance, actual), raw=True) + + amountStr=node.getTableAccountBalance("currency1111", currencyAccount.name) + + expExtension=100-totalTransfer + expectedCurrency1111Balance="99999.99%s CUR" % (expExtension) + actual=amountStr + if actual != expectedCurrency1111Balance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedCurrency1111Balance, actual), raw=True) + + amountStr=node.getCurrencyBalance("currency1111", currencyAccount.name, "CUR") + try: + assert(actual) + assert(isinstance(actual, str)) + actual=amountStr.strip() + assert(expectedCurrency1111Balance == actual) + except (AssertionError, KeyError) as _: + Print("ERROR: Failed get currecy balance assertion. (expected=<%s>, actual=<%s>)" % (expectedCurrency1111Balance, actual)) + raise + + Print("Test for block decoded packed transaction (issue 2932)") + blockNum=node.getBlockNumByTransId(transId) + assert(blockNum) + block=node.getBlock(blockNum, exitOnError=True) + + transactions=None + try: + transactions=block["transactions"] + assert(transactions) + except (AssertionError, TypeError, KeyError) as _: + Print("FAILURE - Failed to parse block. %s" % (block)) + raise + + myTrans=None + for trans in transactions: + assert(trans) + try: + myTransId=trans["trx"]["id"] + if transId == myTransId: + myTrans=trans["trx"]["transaction"] + assert(myTrans) + break + except (AssertionError, TypeError, KeyError) as _: + Print("FAILURE - Failed to parse block transactions. %s" % (trans)) + raise + + assert(myTrans) + try: + assert(myTrans["actions"][0]["name"] == "transfer") + assert(myTrans["actions"][0]["account"] == "currency1111") + assert(myTrans["actions"][0]["authorization"][0]["actor"] == "currency1111") + assert(myTrans["actions"][0]["authorization"][0]["permission"] == "active") + assert(myTrans["actions"][0]["data"]["from"] == "currency1111") + assert(myTrans["actions"][0]["data"]["to"] == "defproducera") + assert(myTrans["actions"][0]["data"]["quantity"] == "0.00%s CUR" % (transferAmt)) + assert(myTrans["actions"][0]["data"]["memo"] == "test") + except (AssertionError, TypeError, KeyError) as _: + Print("FAILURE - Failed to parse block transaction. %s" % (myTrans)) + raise + + Print("Unlocking wallet \"%s\"." % (defproduceraWallet.name)) + if not walletMgr.unlockWallet(defproduceraWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (defproduceraWallet.name)) + + Print("push transfer action to currency1111 contract that would go negative") + contract="currency1111" + action="transfer" + data="{\"from\":\"defproducera\",\"to\":\"currency1111\",\"quantity\":" + data +="\"00.0151 CUR\",\"memo\":\"test\"}" + opts="--permission defproducera@active" + trans=node.pushMessage(contract, action, data, opts, True) + if trans is None or trans[0]: + cmdError("%s push message currency1111 transfer should have failed" % (ClientName)) + errorExit("Failed to reject invalid transfer message to currency1111 contract") + + Print("read current contract balance") + amountStr=node.getTableAccountBalance("currency1111", defproduceraAccount.name) + + actual=amountStr + if actual != expectedDefproduceraBalance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedDefproduceraBalance, actual), raw=True) + + amountStr=node.getTableAccountBalance("currency1111", currencyAccount.name) + + actual=amountStr + if actual != expectedCurrency1111Balance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedCurrency1111Balance, actual), raw=True) + + Print("push another transfer action to currency1111 contract") + contract="currency1111" + action="transfer" + data="{\"from\":\"defproducera\",\"to\":\"currency1111\",\"quantity\":" + data +="\"00.00%s CUR\",\"memo\":\"test\"}" % (totalTransfer) + opts="--permission defproducera@active" + trans=node.pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + cmdError("%s push message currency1111 transfer" % (ClientName)) + errorExit("Failed to push message to currency1111 contract") + transId=Node.getTransId(trans[1]) + + simpleDB = Account("simpledb") + contractDir="contracts/simpledb" + wasmFile="simpledb.wasm" + abiFile="simpledb.abi" + Print("Setting simpledb contract without simpledb account was causing core dump in %s." % (ClientName)) + Print("Verify %s generates an error, but does not core dump." % (ClientName)) + retMap=node.publishContract(simpleDB, contractDir, wasmFile, abiFile, shouldFail=True) + if retMap is None: + errorExit("Failed to publish, but should have returned a details map") + if retMap["returncode"] == 0 or retMap["returncode"] == 139: # 139 SIGSEGV + errorExit("FAILURE - set contract simpledb failed", raw=True) + else: + Print("Test successful, %s returned error code: %d" % (ClientName, retMap["returncode"])) + + Print("set permission") + pType="transfer" + requirement="active" + trans=node.setPermission(testeraAccount, currencyAccount, pType, requirement, waitForTransBlock=True, exitOnError=True) + + Print("remove permission") + requirement="NULL" + trans=node.setPermission(testeraAccount, currencyAccount, pType, requirement, waitForTransBlock=True, exitOnError=True) + + Print("Get account defproducera") + account=node.getEosAccount(defproduceraAccount.name, exitOnError=True) + + Print("Verify non-JSON call works") + rawAccount = node.getEosAccount(defproduceraAccount.name, exitOnError=True, returnType=ReturnType.raw) + coreLiquidBalance = account['core_liquid_balance'] + match = re.search(r'\bliquid:\s*%s\s' % (coreLiquidBalance), rawAccount, re.MULTILINE | re.DOTALL) + assert match is not None, "did not find the core liquid balance (\"liquid:\") of %d in \"%s\"" % (coreLiquidBalance, rawAccount) + + Print("Get head block num.") + currentBlockNum=node.getHeadBlockNum() + Print("CurrentBlockNum: %d" % (currentBlockNum)) + Print("Request blocks 1-%d" % (currentBlockNum)) + start=1 + for blockNum in range(start, currentBlockNum+1): + block=node.getBlock(blockNum, silentErrors=False, exitOnError=True) + + Print("Request invalid block numbered %d. This will generate an expected error message." % (currentBlockNum+1000)) + currentBlockNum=node.getHeadBlockNum() # If the tests take too long, we could be far beyond currentBlockNum+1000 and that'll cause a block to be found. + block=node.getBlock(currentBlockNum+1000, silentErrors=True) + if block is not None: + errorExit("ERROR: Received block where not expected") + else: + Print("Success: No such block found") + + if localTest: + p = re.compile('Assert') + assertionsFound=False + with open(errFileName) as errFile: + for line in errFile: + if p.search(line): + assertionsFound=True + + if assertionsFound: + # Too many assertion logs, hard to validate how many are genuine. Make this a warning + # for now, hopefully the logs will get cleaned up in future. + Print(f"WARNING: Asserts in {errFileName}") + + Print("Validating accounts at end of test") + accounts=[testeraAccount, currencyAccount, exchangeAccount] + cluster.validateAccounts(accounts) + + # Verify "set code" and "set abi" work + Print("Verify set code and set abi work") + setCodeAbiAccount = Account("setcodeabi") + setCodeAbiAccount.ownerPublicKey = cluster.eosioAccount.ownerPublicKey + setCodeAbiAccount.activePublicKey = cluster.eosioAccount.ownerPublicKey + cluster.createAccountAndVerify(setCodeAbiAccount, cluster.eosioAccount, buyRAM=100000) + wasmFile="unittests/test-contracts/payloadless/payloadless.wasm" + abiFile="unittests/test-contracts/payloadless/payloadless.abi" + assert(node.setCodeOrAbi(setCodeAbiAccount, "code", wasmFile)) + assert(node.setCodeOrAbi(setCodeAbiAccount, "abi", abiFile)) + + Print("Verify lib advancing on all nodes") + for cur_node in cluster.getNodes(): + passed = cur_node.waitForLibToAdvance(timeout=6*60) + assert passed, Print("Node %d not advanced lib block within timeout") + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful, dumpErrorDetails) + +errorCode = 0 if testSuccessful else 1 +exit(errorCode) From 26c33c4305bc9324bd8e4a6e790fb4dce66bd675 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:51:50 -0600 Subject: [PATCH 03/15] GH-2100 Fix threshold specification --- tests/TestHarness/Cluster.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index 8988f7f133..0639cc0ba4 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -992,11 +992,14 @@ def parseClusterKeys(totalNodes): Utils.Print(f'Found {len(producerKeys)} producer keys') return producerKeys - def activateInstantFinality(self, launcher): + def activateInstantFinality(self, launcher, pnodes): # call setfinalizer - numFins = len(launcher.network.nodes.values()) + numFins = pnodes + threshold = int(numFins * 2 / 3 + 1) + if threshold >= pnodes: + threshold = pnodes - 1 setFinStr = f'{{"finalizer_policy": {{' - setFinStr += f' "threshold": {int(numFins * 2 / 3 + 1)}, ' + setFinStr += f' "threshold": {threshold}, ' setFinStr += f' "finalizers": [' finNum = 1 for n in launcher.network.nodes.values(): @@ -1091,7 +1094,7 @@ def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, return None if activateIF: - self.activateInstantFinality(launcher) + self.activateInstantFinality(launcher, self.productionNodesCount) Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) producerKeys.pop(eosioName) From f47f81e9ac10374ac59360849db89e1bf022e00b Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:52:18 -0600 Subject: [PATCH 04/15] GH-2100 Add nodeos_lib_test.py --- tests/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2ccb07831b..0067d78e4a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_snapshot_forked_test.py ${CMAK configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_forked_chain_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_forked_chain_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_short_fork_take_over_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_short_fork_take_over_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_lib_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_lib_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_under_min_avail_ram.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_under_min_avail_ram.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_voting_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_voting_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_irreversible_mode_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_irreversible_mode_test.py COPYONLY) @@ -88,8 +89,10 @@ add_test(NAME nodeos_sanity_test COMMAND tests/nodeos_run_test.py -v --sanity-te set_property(TEST nodeos_sanity_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_run_test COMMAND tests/nodeos_run_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_run_test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME nodeos_if_test COMMAND tests/nodeos_run_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -set_property(TEST nodeos_if_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_lib_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s mesh -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_lib_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_lib_if_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s mesh -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_lib_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST block_log_util_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) From d1e43ef4be3a16214fec1b6bf6d98c7795a9e40a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:52:52 -0600 Subject: [PATCH 05/15] GH-2100 Fix vote message processing --- plugins/net_plugin/net_plugin.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index f0dff2408b..97992dec5a 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -311,7 +311,7 @@ namespace eosio { bool have_txn( const transaction_id_type& tid ) const; void expire_txns(); - void bcast_msg( const std::optional& exclude_peer, send_buffer_type msg ); + void bcast_vote_msg( const std::optional& exclude_peer, send_buffer_type msg ); void add_unlinkable_block( signed_block_ptr b, const block_id_type& id ) { std::optional rm_blk_id = unlinkable_block_cache.add_unlinkable_block(std::move(b), id); @@ -539,7 +539,7 @@ namespace eosio { void transaction_ack(const std::pair&); void on_irreversible_block( const block_id_type& id, uint32_t block_num ); - void bcast_hs_message( const std::optional& exclude_peer, const chain::hs_message& msg ); + void bcast_vote_message( const std::optional& exclude_peer, const chain::hs_vote_message& msg ); void warn_hs_message( uint32_t sender_peer, const chain::hs_message_warning& code ); void start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection); @@ -1096,7 +1096,7 @@ namespace eosio { void handle_message( const block_id_type& id, signed_block_ptr ptr ); void handle_message( const packed_transaction& msg ) = delete; // packed_transaction_ptr overload used instead void handle_message( packed_transaction_ptr trx ); - void handle_message( const chain::hs_vote_message& msg ); + void handle_message( const hs_vote_message& msg ); // returns calculated number of blocks combined latency uint32_t calc_block_latency(); @@ -2641,13 +2641,15 @@ namespace eosio { } ); } - void dispatch_manager::bcast_msg( const std::optional& exclude_peer, send_buffer_type msg ) { + void dispatch_manager::bcast_vote_msg( const std::optional& exclude_peer, send_buffer_type msg ) { my_impl->connections.for_each_block_connection( [exclude_peer, msg{std::move(msg)}]( auto& cp ) { if( !cp->current() ) return true; if( exclude_peer.has_value() && cp->connection_id == exclude_peer.value() ) return true; cp->strand.post( [cp, msg]() { - if (cp->protocol_version >= proto_instant_finality) + if (cp->protocol_version >= proto_instant_finality) { + peer_dlog(cp, "sending vote msg"); cp->enqueue_buffer( msg, no_reason ); + } }); return true; } ); @@ -3674,13 +3676,11 @@ namespace eosio { } } - void connection::handle_message( const chain::hs_vote_message& msg ) { + void connection::handle_message( const hs_vote_message& msg ) { peer_dlog(this, "received vote: ${msg}", ("msg", msg)); controller& cc = my_impl->chain_plug->chain(); if( cc.process_vote_message(msg) ) { -#warning TDDO remove hs_message - hs_message hs_msg{msg}; - my_impl->bcast_hs_message(connection_id, hs_msg); + my_impl->bcast_vote_message(connection_id, msg); } } @@ -3943,17 +3943,18 @@ namespace eosio { // called from application thread void net_plugin_impl::on_voted_block(const hs_vote_message& vote) { - bcast_hs_message(std::nullopt, chain::hs_message{ vote }); + fc_dlog(logger, "on voted signal, vote msg: ${msg}", ("msg", vote)); + bcast_vote_message(std::nullopt, vote); } - void net_plugin_impl::bcast_hs_message( const std::optional& exclude_peer, const chain::hs_message& msg ) { - fc_dlog(logger, "sending hs msg: ${msg}", ("msg", msg)); + void net_plugin_impl::bcast_vote_message( const std::optional& exclude_peer, const chain::hs_vote_message& msg ) { + fc_dlog(logger, "sending vote msg: ${msg}", ("msg", msg)); buffer_factory buff_factory; auto send_buffer = buff_factory.get_send_buffer( msg ); dispatcher->strand.post( [this, exclude_peer, msg{std::move(send_buffer)}]() mutable { - dispatcher->bcast_msg( exclude_peer, std::move(msg) ); + dispatcher->bcast_vote_msg( exclude_peer, std::move(msg) ); }); } @@ -4310,7 +4311,7 @@ namespace eosio { controller& cc = chain_plug->chain(); cc.register_pacemaker_bcast_function( [my = shared_from_this()](const std::optional& c, const chain::hs_message& s) { - my->bcast_hs_message(c, s); + //my->bcast_hs_message(c, s); } ); cc.register_pacemaker_warn_function( [my = shared_from_this()](uint32_t c, chain::hs_message_warning s) { From 46e399d7a2f721ab95732bdf7e5e4b9d331c3b8c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:53:02 -0600 Subject: [PATCH 06/15] GH-2100 Fix vote message processing --- plugins/net_plugin/include/eosio/net_plugin/protocol.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index 4862b226a3..d429e6e80f 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -143,7 +143,7 @@ namespace eosio { sync_request_message, signed_block, packed_transaction, - hs_message>; + hs_vote_message>; } // namespace eosio From 34dc1dac7e609c985b00c5c9c8bae336b5b59d9b Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:54:16 -0600 Subject: [PATCH 07/15] GH-2100 Add missing serialization of strong --- libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp index 1d1b6e57bb..d349597f7a 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -250,7 +250,7 @@ namespace eosio::chain { FC_REFLECT(eosio::chain::view_number, (bheight)(pcounter)); FC_REFLECT(eosio::chain::quorum_certificate_message, (proposal_id)(strong_votes)(weak_votes)(active_agg_sig)); FC_REFLECT(eosio::chain::extended_schedule, (producer_schedule)(bls_pub_keys)); -FC_REFLECT(eosio::chain::hs_vote_message, (proposal_id)(finalizer_key)(sig)); +FC_REFLECT(eosio::chain::hs_vote_message, (proposal_id)(strong)(finalizer_key)(sig)); FC_REFLECT(eosio::chain::hs_proposal_message, (proposal_id)(block_id)(parent_id)(final_on_qc)(justify)(phase_counter)); FC_REFLECT(eosio::chain::hs_new_view_message, (high_qc)); FC_REFLECT(eosio::chain::finalizer_state, (b_leaf)(b_lock)(b_exec)(b_finality_violation)(block_exec)(pending_proposal_block)(v_height)(high_qc)(current_qc)(schedule)(proposals)); From d7c6458ca76f9431f0728a86f30685f7aab70f22 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 07:58:26 -0600 Subject: [PATCH 08/15] GH-2100 Use core.last_final_block_num for if irreversible_blocknum() --- libraries/chain/include/eosio/chain/block_state.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 844b3b9b35..a95fc61a7a 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -30,7 +30,7 @@ struct block_state : public block_header_state { // block_header_state provi const extensions_type& header_extensions() const { return block_header_state::header.header_extensions; } bool is_valid() const { return validated; } void set_valid(bool b) { validated = b; } - uint32_t irreversible_blocknum() const { return 0; } // [greg todo] equivalent of dpos_irreversible_blocknum + uint32_t irreversible_blocknum() const { return core.last_final_block_num; } std::optional get_best_qc() const; protocol_feature_activation_set_ptr get_activated_protocol_features() const { return block_header_state::activated_protocol_features; } From a4e45ff6d50ce6a9ad23f0d40b489cabd1b9cb70 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 08:01:42 -0600 Subject: [PATCH 09/15] GH-2100 Set lib according to voting. Fix validation of qc_info. Add in needed digests in block_state. Do not send out vote until after block is processed. --- libraries/chain/block_header_state.cpp | 14 ++- libraries/chain/block_state.cpp | 14 ++- libraries/chain/controller.cpp | 104 +++++++++++++----- libraries/chain/hotstuff/hotstuff.cpp | 14 ++- libraries/chain/hotstuff/qc_chain.cpp | 2 +- .../chain/hotstuff/test/hotstuff_tools.cpp | 4 +- .../eosio/chain/block_header_state.hpp | 1 + .../chain/include/eosio/chain/block_state.hpp | 2 +- .../include/eosio/chain/hotstuff/hotstuff.hpp | 10 +- unittests/block_state_tests.cpp | 10 +- 10 files changed, 122 insertions(+), 53 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 8f2255e134..9359b8271c 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -24,7 +24,8 @@ block_header_state_core block_header_state_core::next(qc_info_t incoming) const } EOS_ASSERT(incoming.last_qc_block_num > this->last_qc_block_num, block_validate_exception, - "new last_qc_block_num must be greater than old last_qc_block_num"); + "new last_qc_block_num ${new} must be greater than old last_qc_block_num ${old}", + ("new", incoming.last_qc_block_num)("old", this->last_qc_block_num)); auto old_last_qc_block_num = this->last_qc_block_num; auto old_final_on_strong_qc_block_num = this->final_on_strong_qc_block_num; @@ -117,9 +118,8 @@ block_header_state block_header_state::next(block_header_state_input& input) con result.active_finalizer_policy = active_finalizer_policy; // [greg todo] correct support for new finalizer_policy activation using finalizer_policies map - - if (input.new_finalizer_policy) - ++input.new_finalizer_policy->generation; + // if (input.new_finalizer_policy) + // ++input.new_finalizer_policy->generation; // add IF block header extension @@ -131,7 +131,9 @@ block_header_state block_header_state::next(block_header_state_input& input) con instant_finality_extension new_if_ext {if_ext.qc_info, std::move(input.new_finalizer_policy), std::move(input.new_proposer_policy)}; - if (input.qc_info) + if (input.validating) + new_if_ext.qc_info = input.qc_info; + else if (input.qc_info) new_if_ext.qc_info = *input.qc_info; emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); @@ -196,7 +198,7 @@ block_header_state block_header_state::next(const signed_block_header& h, const block_header_state_input bhs_input{ bb_input, h.transaction_mroot, h.action_mroot, if_ext.new_proposer_policy, if_ext.new_finalizer_policy, - if_ext.qc_info}; + if_ext.qc_info, true}; return next(bhs_input); } diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index a1cfd65670..a93f6ae136 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -9,12 +9,18 @@ block_state::block_state(const block_header_state& prev, signed_block_ptr b, con const validator_t& validator, bool skip_validate_signee) : block_header_state(prev.next(*b, pfs, validator)) , block(std::move(b)) + , strong_digest(compute_finalizer_digest()) + , weak_digest(compute_finalizer_digest()) + , pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold) {} block_state::block_state(const block_header_state& bhs, deque&& trx_metas, deque&& trx_receipts, const std::optional& qc) : block_header_state(bhs) , block(std::make_shared(signed_block_header{bhs.header})) // [greg todo] do we need signatures? + , strong_digest(compute_finalizer_digest()) + , weak_digest(compute_finalizer_digest()) + , pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold) , pub_keys_recovered(true) // probably not needed , cached_trxs(std::move(trx_metas)) { @@ -29,6 +35,7 @@ block_state::block_state(const block_header_state& bhs, deque ext = bsp.block->extract_header_extension(instant_finality_extension::extension_id()); assert(ext); // required by current transition mechanism @@ -58,7 +65,7 @@ void block_state::set_trxs_metas( deque&& trxs_metas, } // Called from net threads -bool block_state::aggregate_vote(const hs_vote_message& vote) { +std::pair> block_state::aggregate_vote(const hs_vote_message& vote) { const auto& finalizers = active_finalizer_policy->finalizers; auto it = std::find_if(finalizers.begin(), finalizers.end(), @@ -67,15 +74,16 @@ bool block_state::aggregate_vote(const hs_vote_message& vote) { if (it != finalizers.end()) { auto index = std::distance(finalizers.begin(), it); const digest_type& digest = vote.strong ? strong_digest : weak_digest; - return pending_qc.add_vote(vote.strong, + auto [valid, strong] = pending_qc.add_vote(vote.strong, #warning TODO change to use std::span if possible std::vector{digest.data(), digest.data() + digest.data_size()}, index, vote.finalizer_key, vote.sig); + return {valid, strong ? core.final_on_strong_qc_block_num : std::optional{}}; } else { wlog( "finalizer_key (${k}) in vote is not in finalizer policy", ("k", vote.finalizer_key) ); - return false; + return {false, {}}; } } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index edc929fa40..9401331a82 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -302,16 +302,19 @@ struct block_data_t { } // called from net thread - bool aggregate_vote(const hs_vote_message& vote) { + std::pair> aggregate_vote(const hs_vote_message& vote) { return std::visit( - overloaded{[](const block_data_legacy_t&) { + overloaded{[](const block_data_legacy_t&) -> std::pair> { // We can be late in switching to Instant Finality // and receive votes from those already having switched. - return false; }, - [&](const block_data_new_t& bd) { + return {false, {}}; }, + [&](const block_data_new_t& bd) -> std::pair> { auto bsp = bd.fork_db.get_block(vote.proposal_id); - return bsp && bsp->aggregate_vote(vote); } - }, + if (bsp) { + return bsp->aggregate_vote(vote); + } + return {false, {}}; + }}, v); } @@ -857,7 +860,9 @@ struct building_block { assembled_block assemble_block(boost::asio::io_context& ioc, const protocol_feature_set& pfs, - const block_data_t& block_data) { + const block_data_t& block_data, + bool validating, + std::optional validating_qc_info) { digests_t& action_receipts = action_receipt_digests(); return std::visit( overloaded{ @@ -913,13 +918,17 @@ struct building_block { // find most recent ancestor block that has a QC by traversing fork db // branch from parent std::optional qc_data; - auto branch = fork_db.fetch_branch(parent_id()); - for( auto it = branch.begin(); it != branch.end(); ++it ) { - auto qc = (*it)->get_best_qc(); - if( qc ) { - EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception, "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", ("a", qc->block_height)("p", block_header::num_from_id(parent_id())) ); - qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }}; - break; + if (!validating) { + auto branch = fork_db.fetch_branch(parent_id()); + for( auto it = branch.begin(); it != branch.end(); ++it ) { + auto qc = (*it)->get_best_qc(); + if( qc ) { + EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception, + "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", + ("a", qc->block_height)("p", block_header::num_from_id(parent_id())) ); + qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }}; + break; + } } } @@ -930,9 +939,17 @@ struct building_block { .new_protocol_feature_activations = new_protocol_feature_activations() }; + std::optional qc_info; + if (validating) { + qc_info = validating_qc_info; + } else if (qc_data) { + qc_info = qc_data->qc_info; + } block_header_state_input bhs_input{ - bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy), - qc_data ? qc_data->qc_info : std::optional{} }; + bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), + std::move(bb.new_finalizer_policy), + qc_info, validating + }; assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input), std::move(bb.pending_trx_metas), std::move(bb.pending_trx_receipts), @@ -2682,7 +2699,7 @@ struct controller_impl { guard_pending.cancel(); } /// start_block - void finish_block() + void finish_block(bool validating = false, std::optional validating_qc_info = {}) { EOS_ASSERT( pending, block_validate_exception, "it is not valid to finalize when there is no pending block"); EOS_ASSERT( std::holds_alternative(pending->_block_stage), block_validate_exception, "already called finish_block"); @@ -2705,8 +2722,8 @@ struct controller_impl { ); resource_limits.process_block_usage(bb.block_num()); - auto assembled_block = - bb.assemble_block(thread_pool.get_executor(), protocol_features.get_protocol_feature_set(), block_data); + auto assembled_block = bb.assemble_block(thread_pool.get_executor(), + protocol_features.get_protocol_feature_set(), block_data, validating, validating_qc_info); // Update TaPoS table: create_block_summary( assembled_block.id() ); @@ -2875,6 +2892,23 @@ struct controller_impl { EOS_REPORT( "new_producers", b.new_producers, ab.new_producers ) EOS_REPORT( "header_extensions", b.header_extensions, ab.header_extensions ) + if (b.header_extensions != ab.header_extensions) { + { + flat_multimap bheader_exts = b.validate_and_extract_header_extensions(); + if (bheader_exts.count(instant_finality_extension::extension_id())) { + const auto& if_extension = + std::get(bheader_exts.lower_bound(instant_finality_extension::extension_id())->second); + elog("b if: ${i}", ("i", if_extension)); + } + } + flat_multimap abheader_exts = ab.validate_and_extract_header_extensions(); + if (abheader_exts.count(instant_finality_extension::extension_id())) { + const auto& if_extension = + std::get(abheader_exts.lower_bound(instant_finality_extension::extension_id())->second); + elog("ab if: ${i}", ("i", if_extension)); + } + } + #undef EOS_REPORT } @@ -2968,7 +3002,13 @@ struct controller_impl { ("lhs", r)("rhs", static_cast(receipt))); } - finish_block(); + std::optional qc_info; + auto exts = b->validate_and_extract_header_extensions(); + if (auto if_entry = exts.lower_bound(instant_finality_extension::extension_id()); if_entry != exts.end()) { + auto& if_ext = std::get(if_entry->second); + qc_info = if_ext.qc_info; + } + finish_block(true, qc_info); auto& ab = std::get(pending->_block_stage); @@ -3016,22 +3056,31 @@ struct controller_impl { // A vote is created and signed by each finalizer configured on the node that // in active finalizer policy + bool found = false; + // TODO: remove dlog statements + dlog( "active finalizers ${n}, threshold ${t}", + ("n", bsp->active_finalizer_policy->finalizers.size())("t", bsp->active_finalizer_policy->threshold)); for (const auto& f: bsp->active_finalizer_policy->finalizers) { auto it = node_finalizer_keys.find( f.public_key ); if( it != node_finalizer_keys.end() ) { + found = true; + dlog("finalizer used: ${f}", ("f", f.public_key.to_string())); const auto& private_key = it->second; const auto& digest = bsp->compute_finalizer_digest(); auto sig = private_key.sign(std::vector(digest.data(), digest.data() + digest.data_size())); // construct the vote message - hs_vote_message vote{ bsp->id(), strong, private_key.get_public_key(), sig }; + hs_vote_message vote{ bsp->id(), strong, f.public_key, sig }; // net plugin subscribed this signal. it will broadcast the vote message // on receiving the signal emit( self.voted_block, vote ); } } + if (!found) { + dlog("No finalizer found on node for key, we have ${n} finalizers configured", ("n", node_finalizer_keys.size())); + } } // thread safe, expected to be called from thread other than the main thread @@ -3088,9 +3137,7 @@ struct controller_impl { ); EOS_ASSERT( id == bsp->id(), block_validate_exception, - "provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) ); - - create_and_send_vote_msg(bsp); + "provided id ${id} does not match calculated block id ${bid}", ("id", id)("bid", bsp->id()) ); // integrate the received QC into the claimed block integrate_received_qc_to_block(id, b); @@ -3179,6 +3226,9 @@ struct controller_impl { block_data.apply(do_push); + if constexpr (std::is_same_v>) + create_and_send_vote_msg(bsp); + } FC_LOG_AND_RETHROW( ) } @@ -4266,7 +4316,11 @@ void controller::get_finalizer_state( finalizer_state& fs ) const { // called from net threads bool controller::process_vote_message( const hs_vote_message& vote ) { - return my->block_data.aggregate_vote(vote); + auto [valid, new_lib] = my->block_data.aggregate_vote(vote); + if (new_lib) { + my->if_irreversible_block_num = *new_lib; + } + return valid; }; const producer_authority_schedule& controller::active_producers()const { diff --git a/libraries/chain/hotstuff/hotstuff.cpp b/libraries/chain/hotstuff/hotstuff.cpp index 200331842e..c22b046620 100644 --- a/libraries/chain/hotstuff/hotstuff.cpp +++ b/libraries/chain/hotstuff/hotstuff.cpp @@ -87,6 +87,9 @@ bool pending_quorum_certificate::add_strong_vote(const std::vector& pro size_t weak = num_weak(); size_t strong = num_strong(); + // TODO: remove dlog statement + dlog( "strong ${n}, q ${q}", ("n", strong)("q", _quorum)); + switch (_state) { case state_t::unrestricted: case state_t::restricted: @@ -146,12 +149,13 @@ bool pending_quorum_certificate::add_weak_vote(const std::vector& propo return true; } -// thread safe -bool pending_quorum_certificate::add_vote(bool strong, const std::vector& proposal_digest, size_t index, - const bls_public_key& pubkey, const bls_signature& sig) { +// thread safe, +std::pair pending_quorum_certificate::add_vote(bool strong, const std::vector& proposal_digest, size_t index, + const bls_public_key& pubkey, const bls_signature& sig) { std::lock_guard g(*_mtx); - return strong ? add_strong_vote(proposal_digest, index, pubkey, sig) - : add_weak_vote(proposal_digest, index, pubkey, sig); + bool valid = strong ? add_strong_vote(proposal_digest, index, pubkey, sig) + : add_weak_vote(proposal_digest, index, pubkey, sig); + return {valid, _state == state_t::strong}; } // thread safe diff --git a/libraries/chain/hotstuff/qc_chain.cpp b/libraries/chain/hotstuff/qc_chain.cpp index 8315901810..e15af7729c 100644 --- a/libraries/chain/hotstuff/qc_chain.cpp +++ b/libraries/chain/hotstuff/qc_chain.cpp @@ -360,7 +360,7 @@ namespace eosio::chain { for (size_t i=0; i(digest.data(), digest.data() + 32), - i, vote.finalizer_key, vote.sig)) { + i, vote.finalizer_key, vote.sig).first) { // fc_tlog(_logger, " === update bitset ${value} ${finalizer_key}", // ("value", _current_qc.get_active_finalizers_string())("finalizer_key", vote.finalizer_key)); if (_current_qc.is_quorum_met()) { diff --git a/libraries/chain/hotstuff/test/hotstuff_tools.cpp b/libraries/chain/hotstuff/test/hotstuff_tools.cpp index 1ecb2bbec4..a5b62ffda0 100644 --- a/libraries/chain/hotstuff/test/hotstuff_tools.cpp +++ b/libraries/chain/hotstuff/test/hotstuff_tools.cpp @@ -100,11 +100,11 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try { pubkey.push_back(k.get_public_key()); auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index) { - return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest)); + return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest)).first; }; auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index) { - return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest)); + return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest)).first; }; { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 138a847f3a..48253149bc 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -33,6 +33,7 @@ struct block_header_state_input : public building_block_input { std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy std::optional qc_info; // Comes from traversing branch from parent and calling get_best_qc() // assert(qc->block_num <= num_from_id(previous)); + bool validating = false; }; struct block_header_state_core { diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index a95fc61a7a..8c0b633107 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -38,7 +38,7 @@ struct block_state : public block_header_state { // block_header_state provi deque extract_trxs_metas(); void set_trxs_metas(deque&& trxs_metas, bool keys_recovered); const deque& trxs_metas() const { return cached_trxs; } - bool aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc + std::pair> aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc using bhs_t = block_header_state; using bhsp_t = block_header_state_ptr; diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp index d349597f7a..70efe9ffc1 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -199,11 +199,11 @@ namespace eosio::chain { void reset(const fc::sha256& proposal_id, const digest_type& proposal_digest, size_t num_finalizers, size_t quorum); // thread safe - bool add_vote(bool strong, - const std::vector& proposal_digest, - size_t index, - const bls_public_key& pubkey, - const bls_signature& sig); + std::pair add_vote(bool strong, + const std::vector&proposal_digest, + size_t index, + const bls_public_key&pubkey, + const bls_signature&sig); state_t state() const { std::lock_guard g(*_mtx); return _state; }; valid_quorum_certificate to_valid_quorum_certificate() const; diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index 28ad30258c..e28df4ae41 100644 --- a/unittests/block_state_tests.cpp +++ b/unittests/block_state_tests.cpp @@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bool strong = (i % 2 == 0); // alternate strong and weak auto sig = strong ? private_key[i].sign(strong_digest_data) : private_key[i].sign(weak_digest_data); hs_vote_message vote{ block_id, strong, public_key[i], sig }; - BOOST_REQUIRE(bsp->aggregate_vote(vote)); + BOOST_REQUIRE(bsp->aggregate_vote(vote).first); } } @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 }; hs_vote_message vote {block_id, true, public_key[0], private_key[1].sign(strong_digest_data) }; - BOOST_REQUIRE(!bsp->aggregate_vote(vote)); + BOOST_REQUIRE(!bsp->aggregate_vote(vote).first); } { // duplicate votes @@ -69,8 +69,8 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 }; hs_vote_message vote {block_id, true, public_key[0], private_key[0].sign(strong_digest_data) }; - BOOST_REQUIRE(bsp->aggregate_vote(vote)); - BOOST_REQUIRE(!bsp->aggregate_vote(vote)); + BOOST_REQUIRE(bsp->aggregate_vote(vote).first); + BOOST_REQUIRE(!bsp->aggregate_vote(vote).first); } { // public key does not exit in finalizer set @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bls_public_key new_public_key{ new_private_key.get_public_key() }; hs_vote_message vote {block_id, true, new_public_key, private_key[0].sign(strong_digest_data) }; - BOOST_REQUIRE(!bsp->aggregate_vote(vote)); + BOOST_REQUIRE(!bsp->aggregate_vote(vote).first); } } FC_LOG_AND_RETHROW(); From 76d422dd86a4921fa6d2b9a1e55bade49d8f7f3c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 09:49:35 -0600 Subject: [PATCH 10/15] GH-2100 Vote when we produce a block --- libraries/chain/controller.cpp | 10 +++++++--- tests/CMakeLists.txt | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 9401331a82..b69102918a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2797,6 +2797,13 @@ struct controller_impl { block_data.transition_fork_db_to_if(cb.bsp); } + auto vote = [&](auto& fork_db, auto& head) { + const auto& bsp = std::get>(cb.bsp); + if constexpr (std::is_same_v>) + create_and_send_vote_msg(bsp); + }; + block_data.apply(vote); + } catch (...) { // dont bother resetting pending, instead abort the block reset_pending_on_exit.cancel(); @@ -3226,9 +3233,6 @@ struct controller_impl { block_data.apply(do_push); - if constexpr (std::is_same_v>) - create_and_send_vote_msg(bsp); - } FC_LOG_AND_RETHROW( ) } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0067d78e4a..5178c83812 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -89,9 +89,9 @@ add_test(NAME nodeos_sanity_test COMMAND tests/nodeos_run_test.py -v --sanity-te set_property(TEST nodeos_sanity_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_run_test COMMAND tests/nodeos_run_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_run_test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME nodeos_lib_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s mesh -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME nodeos_lib_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_lib_test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME nodeos_lib_if_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s mesh -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME nodeos_lib_if_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s ring -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_lib_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST block_log_util_test PROPERTY LABELS nonparallelizable_tests) From f3b8c7cec1569e4a6c443ac2f879adbcb6fe581c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 11:06:55 -0600 Subject: [PATCH 11/15] GH-2100 Fix variant access --- libraries/chain/controller.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index b69102918a..72355374a4 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2777,6 +2777,13 @@ struct controller_impl { log_irreversible(); } + auto vote = [&](auto& fork_db, auto& head) { + if constexpr (std::is_same_v>) { + create_and_send_vote_msg(head); + } + }; + block_data.apply(vote); + // TODO: temp transition to instant-finality, happens immediately after block with new_finalizer_policy auto transition = [&](auto& fork_db, auto& head) -> bool { const auto& bsp = std::get>(cb.bsp); @@ -2797,13 +2804,6 @@ struct controller_impl { block_data.transition_fork_db_to_if(cb.bsp); } - auto vote = [&](auto& fork_db, auto& head) { - const auto& bsp = std::get>(cb.bsp); - if constexpr (std::is_same_v>) - create_and_send_vote_msg(bsp); - }; - block_data.apply(vote); - } catch (...) { // dont bother resetting pending, instead abort the block reset_pending_on_exit.cancel(); From 5286fcfe9cf00fdeea92fe1747240ba6df060a98 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 12:10:26 -0600 Subject: [PATCH 12/15] GH-2100 Process our own vote. --- libraries/chain/controller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 72355374a4..63707e30d0 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3083,6 +3083,8 @@ struct controller_impl { // net plugin subscribed this signal. it will broadcast the vote message // on receiving the signal emit( self.voted_block, vote ); + + self.process_vote_message(vote); } } if (!found) { From 4060e6fdcc34c3fa6ea5e0c2d41dced604d06ccc Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Thu, 18 Jan 2024 13:37:44 -0500 Subject: [PATCH 13/15] Small cleanups --- libraries/chain/controller.cpp | 64 +++++++++++++++------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 72355374a4..c21dc8ca2f 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -301,23 +301,6 @@ struct block_data_t { }, v); } - // called from net thread - std::pair> aggregate_vote(const hs_vote_message& vote) { - return std::visit( - overloaded{[](const block_data_legacy_t&) -> std::pair> { - // We can be late in switching to Instant Finality - // and receive votes from those already having switched. - return {false, {}}; }, - [&](const block_data_new_t& bd) -> std::pair> { - auto bsp = bd.fork_db.get_block(vote.proposal_id); - if (bsp) { - return bsp->aggregate_vote(vote); - } - return {false, {}}; - }}, - v); - } - signed_block_ptr fork_db_fetch_block_by_num(uint32_t block_num) const { return std::visit([&](const auto& bd) -> signed_block_ptr { auto bsp = bd.fork_db.search_on_branch(bd.fork_db.head()->id(), block_num); @@ -344,7 +327,7 @@ struct block_data_t { } template - R apply(F& f) { + R apply(const F& f) { if constexpr (std::is_same_v) std::visit([&](auto& bd) { bd.template apply(f); }, v); else @@ -352,13 +335,24 @@ struct block_data_t { } template - R apply_dpos(F& f) { + R apply_dpos(const F& f) { if constexpr (std::is_same_v) std::visit(overloaded{[&](block_data_legacy_t& bd) { bd.template apply(f); }, [&](block_data_new_t& bd) {}}, v); else return std::visit(overloaded{[&](block_data_legacy_t& bd) -> R { return bd.template apply(f); }, - [&](block_data_new_t& bd) -> R { return {}; }}, + [&](block_data_new_t& bd) -> R { return {}; }}, + v); + } + + template + R apply_if(const F& f) { + if constexpr (std::is_same_v) + std::visit(overloaded{[&](block_data_legacy_t& bd) {}, [&](block_data_new_t& bd) { bd.template apply(f); }}, + v); + else + return std::visit(overloaded{[&](block_data_legacy_t& bd) -> R { return {}; }, + [&](block_data_new_t& bd) -> R { return bd.template apply(f); }}, v); } }; @@ -2761,28 +2755,22 @@ struct controller_impl { head = bsp; emit( self.accepted_block, std::tie(bsp->block, bsp->id()) ); - - if constexpr (std::is_same_v>) { -#warning todo: support deep_mind_logger even when in IF mode - // at block level, no transaction specific logging is possible - if (auto* dm_logger = get_deep_mind_logger(false)) { - dm_logger->on_accepted_block(bsp); - } - } }; block_data.apply(add_completed_block); + block_data.apply_dpos([this](auto& fork_db, auto& head) { +#warning todo: support deep_mind_logger even when in IF mode (use apply instead of apply_dpos) + // at block level, no transaction specific logging is possible + if (auto* dm_logger = get_deep_mind_logger(false)) { + dm_logger->on_accepted_block(head); + }}); + if( s == controller::block_status::incomplete ) { log_irreversible(); } - auto vote = [&](auto& fork_db, auto& head) { - if constexpr (std::is_same_v>) { - create_and_send_vote_msg(head); - } - }; - block_data.apply(vote); + block_data.apply_if([&](auto& fork_db, auto& head) { create_and_send_vote_msg(head); }); // TODO: temp transition to instant-finality, happens immediately after block with new_finalizer_policy auto transition = [&](auto& fork_db, auto& head) -> bool { @@ -4320,7 +4308,13 @@ void controller::get_finalizer_state( finalizer_state& fs ) const { // called from net threads bool controller::process_vote_message( const hs_vote_message& vote ) { - auto [valid, new_lib] = my->block_data.aggregate_vote(vote); + auto do_vote = [&vote](auto& fork_db, auto& head) -> std::pair> { + auto bsp = fork_db.get_block(vote.proposal_id); + if (bsp) + return bsp->aggregate_vote(vote); + return {false, {}}; + }; + auto [valid, new_lib] = my->block_data.apply_if>>(do_vote); if (new_lib) { my->if_irreversible_block_num = *new_lib; } From 92b33fd097e337ddbe8646587d626a69836f4c49 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 13:55:45 -0600 Subject: [PATCH 14/15] GH-2100 Process vote off main thread --- libraries/chain/controller.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index dec47bf495..17cc0f8259 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3072,7 +3072,9 @@ struct controller_impl { // on receiving the signal emit( self.voted_block, vote ); - self.process_vote_message(vote); + boost::asio::post(thread_pool.get_executor(), [control=this, vote]() { + control->self.process_vote_message(vote); + }); } } if (!found) { From fd5b17582cf7f69bfbc627b7113beea54eba280a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 18 Jan 2024 14:09:01 -0600 Subject: [PATCH 15/15] GH-2100 Remove debug logging --- libraries/chain/controller.cpp | 9 --------- libraries/chain/hotstuff/hotstuff.cpp | 3 --- 2 files changed, 12 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 17cc0f8259..283a4ff66f 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3051,15 +3051,9 @@ struct controller_impl { // A vote is created and signed by each finalizer configured on the node that // in active finalizer policy - bool found = false; - // TODO: remove dlog statements - dlog( "active finalizers ${n}, threshold ${t}", - ("n", bsp->active_finalizer_policy->finalizers.size())("t", bsp->active_finalizer_policy->threshold)); for (const auto& f: bsp->active_finalizer_policy->finalizers) { auto it = node_finalizer_keys.find( f.public_key ); if( it != node_finalizer_keys.end() ) { - found = true; - dlog("finalizer used: ${f}", ("f", f.public_key.to_string())); const auto& private_key = it->second; const auto& digest = bsp->compute_finalizer_digest(); @@ -3077,9 +3071,6 @@ struct controller_impl { }); } } - if (!found) { - dlog("No finalizer found on node for key, we have ${n} finalizers configured", ("n", node_finalizer_keys.size())); - } } // thread safe, expected to be called from thread other than the main thread diff --git a/libraries/chain/hotstuff/hotstuff.cpp b/libraries/chain/hotstuff/hotstuff.cpp index c22b046620..531f2a33a8 100644 --- a/libraries/chain/hotstuff/hotstuff.cpp +++ b/libraries/chain/hotstuff/hotstuff.cpp @@ -87,9 +87,6 @@ bool pending_quorum_certificate::add_strong_vote(const std::vector& pro size_t weak = num_weak(); size_t strong = num_strong(); - // TODO: remove dlog statement - dlog( "strong ${n}, q ${q}", ("n", strong)("q", _quorum)); - switch (_state) { case state_t::unrestricted: case state_t::restricted: