Expand transaction generator to support new account trx

Update user data transaction specification to support multiple actions and automatically generating an account namer per transaction to substitute into defined actions using the 'ACCT_PER_TRX' key word.

Removed arguments for action data pieces that have been moved into the larger transaction spec in the json files.

Added/Updated tests to exercise the new feature.
This commit is contained in:
Peter Oschwald
2023-02-08 17:35:47 -06:00
parent b35c3d6fba
commit d8d9fa3195
9 changed files with 366 additions and 132 deletions
@@ -36,8 +36,8 @@ class TpsTrxGensConfig:
class TransactionGeneratorsLauncher:
def __init__(self, chainId: int, lastIrreversibleBlockId: int, contractOwnerAccount: str, accts: str, privateKeys: str,
trxGenDurationSec: int, logDir: str, abiFile: Path, actionName: str, actionAuthAcct: str, actionAuthPrivKey: str, actionData,
def __init__(self, chainId: int, lastIrreversibleBlockId: int, contractOwnerAccount: str, accts: str, privateKeys: str, trxGenDurationSec: int, logDir: str,
abiFile: Path, actionsData, actionsAuths,
peerEndpoint: str, port: int, tpsTrxGensConfig: TpsTrxGensConfig):
self.chainId = chainId
self.lastIrreversibleBlockId = lastIrreversibleBlockId
@@ -48,17 +48,15 @@ class TransactionGeneratorsLauncher:
self.tpsTrxGensConfig = tpsTrxGensConfig
self.logDir = logDir
self.abiFile = abiFile
self.actionName = actionName
self.actionAuthAcct = actionAuthAcct
self.actionAuthPrivKey = actionAuthPrivKey
self.actionData = actionData
self.actionsData=actionsData
self.actionsAuths=actionsAuths
self.peerEndpoint = peerEndpoint
self.port = port
def launch(self, waitToComplete=True):
self.subprocess_ret_codes = []
for id, targetTps in enumerate(self.tpsTrxGensConfig.targetTpsPerGenList):
if self.abiFile is not None and self.actionName is not None and self.actionData is not None and self.actionAuthAcct is not None and self.actionAuthPrivKey is not None:
if self.abiFile is not None and self.actionsData is not None and self.actionsAuths is not None:
if Utils.Debug:
Print(
f'Running trx_generator: ./tests/trx_generator/trx_generator '
@@ -71,11 +69,9 @@ class TransactionGeneratorsLauncher:
f'--trx-gen-duration {self.trxGenDurationSec} '
f'--target-tps {targetTps} '
f'--log-dir {self.logDir} '
f'--action-name {self.actionName} '
f'--action-auth-acct {self.actionAuthAcct} '
f'--action-auth-acct-priv-key {self.actionAuthPrivKey} '
f'--action-data {self.actionData} '
f'--abi-file {self.abiFile} '
f'--actions-data {self.actionsData} '
f'--actions-auths {self.actionsAuths} '
f'--peer-endpoint {self.peerEndpoint} '
f'--port {self.port}'
)
@@ -91,11 +87,9 @@ class TransactionGeneratorsLauncher:
'--trx-gen-duration', f'{self.trxGenDurationSec}',
'--target-tps', f'{targetTps}',
'--log-dir', f'{self.logDir}',
'--action-name', f'{self.actionName}',
'--action-auth-acct', f'{self.actionAuthAcct}',
'--action-auth-acct-priv-key', f'{self.actionAuthPrivKey}',
'--action-data', f'{self.actionData}',
'--abi-file', f'{self.abiFile}',
'--actions-data', f'{self.actionsData}',
'--actions-auths', f'{self.actionsAuths}',
'--peer-endpoint', f'{self.peerEndpoint}',
'--port', f'{self.port}'
])
@@ -155,11 +149,9 @@ def parseArgs():
parser.add_argument("target_tps", type=int, help="Goal transactions per second")
parser.add_argument("tps_limit_per_generator", type=int, help="Maximum amount of transactions per second a single generator can have.", default=4000)
parser.add_argument("log_dir", type=str, help="Path to directory where trx logs should be written.")
parser.add_argument("action_name", type=str, help="The action name applied to the provided action data input")
parser.add_argument("action_auth_acct", type=str, help="The authorization account name used for trx action authorization")
parser.add_argument("action_auth_acct_priv_key", type=str, help="The authorization account's private key used for signing trx")
parser.add_argument("action_data", type=str, help="The path to the json action data file or json action data description string to use")
parser.add_argument("abi_file", type=str, help="The path to the contract abi file to use for the supplied transaction action data")
parser.add_argument("actions_data", type=str, help="The json actions data file or json actions data description string to use")
parser.add_argument("actions_auths", type=str, help="The json actions auth file or json actions auths description string to use, containting authAcctName to activePrivateKey pairs.")
parser.add_argument("peer_endpoint", type=str, help="set the peer endpoint to send transactions to", default="127.0.0.1")
parser.add_argument("port", type=int, help="set the peer endpoint port to send transactions to", default=9876)
args = parser.parse_args()
@@ -171,8 +163,7 @@ def main():
trxGenLauncher = TransactionGeneratorsLauncher(chainId=args.chain_id, lastIrreversibleBlockId=args.last_irreversible_block_id,
contractOwnerAccount=args.contract_owner_account, accts=args.accounts,
privateKeys=args.priv_keys, trxGenDurationSec=args.trx_gen_duration, logDir=args.log_dir,
abiFile=args.abi_file, actionName=args.action_name, actionAuthAcct=args.action_auth_acct,
actionAuthPrivKey=args.action_auth_acct_priv_key, actionData=args.action_data,
abiFile=args.abi_file, actionsData=args.actions_data, actionsAuths=args.actions_auths,
peerEndpoint=args.peer_endpoint, port=args.port,
tpsTrxGensConfig=TpsTrxGensConfig(targetTps=args.target_tps, tpsLimitPerGenerator=args.tps_limit_per_generator))
+3 -1
View File
@@ -8,9 +8,11 @@ configure_file(nodeos_log_3_2.txt.gz nodeos_log_3_2.txt.gz COPYONLY)
configure_file(genesis.json genesis.json COPYONLY)
configure_file(validate_nodeos_plugin_args.py validate_nodeos_plugin_args.py COPYONLY)
configure_file(userTrxDataTransfer.json userTrxDataTransfer.json COPYONLY)
configure_file(userTrxDataNewAccount.json userTrxDataNewAccount.json COPYONLY)
add_test(NAME performance_test_basic COMMAND tests/performance_tests/performance_test_basic.py -v -p 1 -n 1 --target-tps 20 --tps-limit-per-generator 10 --clean-run WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME performance_test_basic_ex_trx_spec COMMAND tests/performance_tests/performance_test_basic.py -v -p 1 -n 1 --target-tps 20 --tps-limit-per-generator 10 --clean-run --user-trx-data-file tests/performance_tests/userTrxDataTransfer.json WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME performance_test_basic_ex_transfer_trx_spec COMMAND tests/performance_tests/performance_test_basic.py -v -p 1 -n 1 --target-tps 20 --tps-limit-per-generator 10 --clean-run --user-trx-data-file tests/performance_tests/userTrxDataTransfer.json WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME performance_test_basic_ex_new_acct_trx_spec COMMAND tests/performance_tests/performance_test_basic.py -v -p 1 -n 1 --target-tps 20 --tps-limit-per-generator 10 --clean-run --user-trx-data-file tests/performance_tests/userTrxDataNewAccount.json WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME log_reader_tests COMMAND tests/performance_tests/log_reader_tests.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_test(NAME validate_nodeos_plugin_args COMMAND tests/performance_tests/validate_nodeos_plugin_args.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST performance_test_basic PROPERTY LABELS nonparallelizable_tests)
@@ -70,8 +70,10 @@ class PerformanceTestBasic:
@dataclass
class SpecifiedContract:
accountName: str = "c"
accountName: str = "eosio"
ownerPrivateKey: str = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"
ownerPublicKey: str = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"
activePrivateKey: str = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"
activePublicKey: str = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"
contractDir: str = "unittests/contracts/eosio.system"
wasmFile: str = "eosio.system.wasm"
@@ -288,20 +290,32 @@ class PerformanceTestBasic:
self.userTrxDataDict = json.load(f)
def setupContract(self):
specifiedAccount = Account(self.clusterConfig.specifiedContract.accountName)
specifiedAccount.ownerPublicKey = self.clusterConfig.specifiedContract.ownerPublicKey
specifiedAccount.activePublicKey = self.clusterConfig.specifiedContract.activePublicKey
self.cluster.createAccountAndVerify(specifiedAccount, self.cluster.eosioAccount, validationNodeIndex=self.validationNodeId)
print("Publishing contract")
transaction=self.cluster.biosNode.publishContract(specifiedAccount, self.clusterConfig.specifiedContract.contractDir,
self.clusterConfig.specifiedContract.wasmFile,
self.clusterConfig.specifiedContract.abiFile, waitForTransBlock=True)
if transaction is None:
print("ERROR: Failed to publish contract.")
return None
if (self.clusterConfig.specifiedContract.accountName != self.cluster.eosioAccount.name):
specifiedAccount = Account(self.clusterConfig.specifiedContract.accountName)
specifiedAccount.ownerPublicKey = self.clusterConfig.specifiedContract.ownerPublicKey
specifiedAccount.ownerPrivateKey = self.clusterConfig.specifiedContract.ownerPrivateKey
specifiedAccount.activePublicKey = self.clusterConfig.specifiedContract.activePublicKey
specifiedAccount.activePrivateKey = self.clusterConfig.specifiedContract.activePrivateKey
self.cluster.createAccountAndVerify(specifiedAccount, self.cluster.eosioAccount, validationNodeIndex=self.validationNodeId)
print("Publishing contract")
transaction=self.cluster.biosNode.publishContract(specifiedAccount, self.clusterConfig.specifiedContract.contractDir,
self.clusterConfig.specifiedContract.wasmFile,
self.clusterConfig.specifiedContract.abiFile, waitForTransBlock=True)
if transaction is None:
print("ERROR: Failed to publish contract.")
return None
else:
self.clusterConfig.specifiedContract.activePrivateKey = self.cluster.eosioAccount.activePrivateKey
self.clusterConfig.specifiedContract.activePublicKey = self.cluster.eosioAccount.activePublicKey
self.clusterConfig.specifiedContract.ownerPrivateKey = self.cluster.eosioAccount.ownerPrivateKey
self.clusterConfig.specifiedContract.ownerPublicKey = self.cluster.eosioAccount.ownerPublicKey
print(f"setupContract: default {self.clusterConfig.specifiedContract.accountName} \
activePrivateKey: {self.clusterConfig.specifiedContract.activePrivateKey} \
activePublicKey: {self.clusterConfig.specifiedContract.activePublicKey} \
ownerPrivateKey: {self.clusterConfig.specifiedContract.ownerPrivateKey} \
ownerPublicKey: {self.clusterConfig.specifiedContract.ownerPublicKey}")
def runTpsTest(self) -> PtbTpsTestResult:
completedRun = False
self.producerNode = self.cluster.getNode(self.producerNodeId)
self.producerP2pPort = self.cluster.getNodeP2pPort(self.producerNodeId)
@@ -313,25 +327,34 @@ class PerformanceTestBasic:
self.data = log_reader.chainData()
abiFile=None
actionName=None
actionAuthAcct=None
actionAuthPrivKey=None
actionData=None
actionsDataJson=None
actionsAuthsJson=None
self.accountNames=[]
self.accountPrivKeys=[]
if (self.ptbConfig.userTrxDataFile is not None):
self.readUserTrxDataFromFile(self.ptbConfig.userTrxDataFile)
self.setupWalletAndAccounts(accountCnt=len(self.userTrxDataDict['accounts']), accountNames=self.userTrxDataDict['accounts'])
if self.userTrxDataDict['initAccounts']:
print(f"Creating accounts specified in userTrxData: {self.userTrxDataDict['initAccounts']}")
self.setupWalletAndAccounts(accountCnt=len(self.userTrxDataDict['initAccounts']), accountNames=self.userTrxDataDict['initAccounts'])
abiFile = self.userTrxDataDict['abiFile']
actionName = self.userTrxDataDict['actionName']
actionAuthAcct = self.userTrxDataDict['actionAuthAcct']
actionData = json.dumps(self.userTrxDataDict['actionData'])
if actionAuthAcct == self.cluster.eosioAccount.name:
actionAuthPrivKey = self.cluster.eosioAccount.activePrivateKey
else:
for account in self.cluster.accounts:
if actionAuthAcct == account.name:
actionAuthPrivKey = account.activePrivateKey
break
actionsDataJson = json.dumps(self.userTrxDataDict['actions'])
authorizations={}
for act in self.userTrxDataDict['actions']:
actionAuthAcct=act["actionAuthAcct"]
actionAuthPrivKey=None
if actionAuthAcct == self.cluster.eosioAccount.name:
actionAuthPrivKey = self.cluster.eosioAccount.activePrivateKey
else:
for account in self.cluster.accounts:
if actionAuthAcct == account.name:
actionAuthPrivKey = account.activePrivateKey
break
if actionAuthPrivKey is not None:
authorizations[actionAuthAcct]=actionAuthPrivKey
actionsAuthsJson = json.dumps(authorizations)
else:
self.setupWalletAndAccounts()
@@ -340,10 +363,10 @@ class PerformanceTestBasic:
self.data.startBlock = self.waitForEmptyBlocks(self.validationNode, self.emptyBlockGoal)
tpsTrxGensConfig = TpsTrxGensConfig(targetTps=self.ptbConfig.targetTps, tpsLimitPerGenerator=self.ptbConfig.tpsLimitPerGenerator)
trxGenLauncher = TransactionGeneratorsLauncher(chainId=chainId, lastIrreversibleBlockId=lib_id,
contractOwnerAccount=self.clusterConfig.specifiedContract.accountName, accts=','.join(map(str, self.accountNames)),
privateKeys=','.join(map(str, self.accountPrivKeys)), trxGenDurationSec=self.ptbConfig.testTrxGenDurationSec, logDir=self.trxGenLogDirPath,
abiFile=abiFile, actionName=actionName, actionAuthAcct=actionAuthAcct, actionAuthPrivKey=actionAuthPrivKey, actionData=actionData,
trxGenLauncher = TransactionGeneratorsLauncher(chainId=chainId, lastIrreversibleBlockId=lib_id, contractOwnerAccount=self.clusterConfig.specifiedContract.accountName,
accts=','.join(map(str, self.accountNames)), privateKeys=','.join(map(str, self.accountPrivKeys)),
trxGenDurationSec=self.ptbConfig.testTrxGenDurationSec, logDir=self.trxGenLogDirPath,
abiFile=abiFile, actionsData=actionsDataJson, actionsAuths=actionsAuthsJson,
peerEndpoint=self.producerNode.host, port=self.producerP2pPort, tpsTrxGensConfig=tpsTrxGensConfig)
trxGenExitCodes = trxGenLauncher.launch()
@@ -520,7 +543,7 @@ class PtbArgumentsHandler(object):
ptbBaseParserGroup.add_argument("--quiet", help="Whether to quiet printing intermediate results and reports to stdout", action='store_true')
ptbBaseParserGroup.add_argument("--prods-enable-trace-api", help="Determines whether producer nodes should have eosio::trace_api_plugin enabled", action='store_true')
ptbBaseParserGroup.add_argument("--print-missing-transactions", type=bool, help="Toggles if missing transactions are be printed upon test completion.", default=False)
ptbBaseParserGroup.add_argument("--account-name", type=str, help="Name of the account to create and assign a contract to", default="c")
ptbBaseParserGroup.add_argument("--account-name", type=str, help="Name of the account to create and assign a contract to", default="eosio")
ptbBaseParserGroup.add_argument("--owner-public-key", type=str, help="Owner public key to use with specified account name", default="EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV")
ptbBaseParserGroup.add_argument("--active-public-key", type=str, help="Active public key to use with specified account name", default="EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV")
ptbBaseParserGroup.add_argument("--contract-dir", type=str, help="Path to contract dir", default="unittests/contracts/eosio.system")
@@ -0,0 +1,53 @@
{
"initAccounts": [],
"abiFile": "unittests/contracts/eosio.system/eosio.system.abi",
"actions": [
{
"actionAuthAcct": "eosio",
"actionName": "newaccount",
"authorization": {
"actor": "eosio",
"permission": "active"
},
"actionData": {
"creator": "eosio",
"name": "ACCT_PER_TRX",
"owner": {
"threshold": 1,
"keys": [
{
"key": "EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"active": {
"threshold": 1,
"keys": [
{
"key": "EOS65rXebLhtk2aTTzP4e9x1AQZs7c5NNXJp89W8R3HyaA6Zyd4im",
"weight": 1
}
],
"accounts": [],
"waits": []
}
}
},
{
"actionAuthAcct": "eosio",
"actionName": "buyrambytes",
"authorization": {
"actor": "eosio",
"permission": "active"
},
"actionData": {
"payer": "eosio",
"receiver": "ACCT_PER_TRX",
"bytes": "3000"
}
}
]
}
@@ -1,13 +1,23 @@
{
"accounts": ["testacct1", "testacct2"],
"initAccounts": [
"testacct1",
"testacct2"
],
"abiFile": "unittests/contracts/eosio.token/eosio.token.abi",
"actionName": "transfer",
"actionAuthAcct": "testacct1",
"actionData":
"actions": [
{
"from":"testacct1",
"to":"testacct2",
"quantity":"0.0001 CUR",
"memo":"transaction specified"
"actionAuthAcct": "testacct1",
"actionName": "transfer",
"authorization": {
"actor": "testacct1",
"permission": "active"
},
"actionData": {
"from": "testacct1",
"to": "testacct2",
"quantity": "0.0001 CUR",
"memo": "transaction specified"
}
}
]
}
+12 -29
View File
@@ -46,11 +46,9 @@ int main(int argc, char** argv) {
unsigned short port;
bool transaction_specified = false;
std::string action_name_in;
std::string action_auth_acct_in;
std::string action_auth_acct_priv_key_in;
std::string action_data_file_or_str;
std::string abi_file_path_in;
std::string actions_data_json_file_or_str;
std::string actions_auths_json_file_or_str;
vector<string> account_str_vector;
vector<string> private_keys_str_vector;
@@ -70,12 +68,9 @@ int main(int argc, char** argv) {
("monitor-max-lag-percent", bpo::value<uint32_t>(&max_lag_per)->default_value(5), "Max percentage off from expected transactions sent before being in violation. Defaults to 5.")
("monitor-max-lag-duration-us", bpo::value<int64_t>(&max_lag_duration_us)->default_value(1000000), "Max microseconds that transaction generation can be in violation before quitting. Defaults to 1000000 (1s).")
("log-dir", bpo::value<string>(&log_dir_in), "set the logs directory")
("action-name", bpo::value<string>(&action_name_in), "The action name applied to the provided action data input")
("action-auth-acct", bpo::value<string>(&action_auth_acct_in), "The action authorization account")
("action-auth-acct-priv-key", bpo::value<string>(&action_auth_acct_priv_key_in), "The action authorization account priv key for signing trxs")
("action-data", bpo::value<string>(&action_data_file_or_str), "The path to the json action data file or json action data description string to use")
("abi-file", bpo::value<string>(&abi_file_path_in), "The path to the contract abi file to use for the supplied transaction action data")
("stop-on-trx-failed", bpo::value<bool>(&stop_on_trx_failed)->default_value(true), "stop transaction generation if sending fails.")
("actions-data", bpo::value<string>(&actions_data_json_file_or_str), "The json actions data file or json actions data description string to use")
("actions-auths", bpo::value<string>(&actions_auths_json_file_or_str), "The json actions auth file or json actions auths description string to use, containting authAcctName to activePrivateKey pairs.")
("peer-endpoint", bpo::value<string>(&peer_endpoint)->default_value("127.0.0.1"), "set the peer endpoint to send transactions to")
("port", bpo::value<uint16_t>(&port)->default_value(9876), "set the peer endpoint port to send transactions to")
("help,h", "print this list")
@@ -90,15 +85,15 @@ int main(int argc, char** argv) {
return SUCCESS;
}
if((vmap.count("action-name") || vmap.count("action-auth-acct") || vmap.count("action-auth-acct-priv-key") || vmap.count("action-data") || vmap.count("abi-file")) &&
!(vmap.count("action-name") && vmap.count("action-auth-acct") && vmap.count("action-auth-acct-priv-key") && vmap.count("action-data") && vmap.count("abi-file"))) {
ilog("Initialization error: If using action-name, action-auth-acct, action-auth-acct-priv-key, action-data, or abi-file to specify a transaction type to generate, must provide all inputs.");
if((vmap.count("abi-file") || vmap.count("actions-data") || vmap.count("actions-auths")) &&
!(vmap.count("abi-file") && vmap.count("actions-data") && vmap.count("actions-auths"))) {
ilog("Initialization error: If using abi-file, actions-data, and actions-auths to specify a transaction type to generate, must provide all inputs.");
cli.print(std::cerr);
return INITIALIZE_FAIL;
}
if(vmap.count("action-name") && vmap.count("action-auth-acct") && vmap.count("action-auth-acct-priv-key") && vmap.count("action-data") && vmap.count("abi-file")) {
ilog("Specifying transaction to generate directly using action-name, action-auth-acct, action-auth-acct-priv-key, action-data, and abi-file.");
if(vmap.count("abi-file") && vmap.count("actions-data") && vmap.count("actions-auths")) {
ilog("Specifying transaction to generate directly using abi-file, actions-data, and actions-auths.");
transaction_specified = true;
}
@@ -134,11 +129,6 @@ int main(int argc, char** argv) {
cli.print(std::cerr);
return INITIALIZE_FAIL;
}
if (transaction_specified && account_str_vector.size() < 1) {
ilog("Initialization error: Specifying transaction to generate requires at minimum 1 account.");
cli.print(std::cerr);
return INITIALIZE_FAIL;
}
} else {
ilog("Initialization error: did not specify transfer accounts. Auto transfer transaction generation requires at minimum 2 transfer accounts, while providing transaction action data requires at least one.");
cli.print(std::cerr);
@@ -152,11 +142,6 @@ int main(int argc, char** argv) {
cli.print(std::cerr);
return INITIALIZE_FAIL;
}
if (transaction_specified && private_keys_str_vector.size() < 1) {
ilog("Initialization error: Specifying transaction to generate requires at minimum 1 private key");
cli.print(std::cerr);
return INITIALIZE_FAIL;
}
} else {
ilog("Initialization error: did not specify accounts' private keys. Auto transfer transaction generation requires at minimum 2 private keys, while providing transaction action data requires at least one.");
cli.print(std::cerr);
@@ -221,11 +206,9 @@ int main(int argc, char** argv) {
ilog("Peer Endpoint ${peer-endpoint}:${peer-port}", ("peer-endpoint", peer_endpoint)("peer-port", port));
if (transaction_specified) {
ilog("User Transaction Specified: Action Name ${act}", ("act", action_name_in));
ilog("User Transaction Specified: Action Auth Acct Name ${acct}", ("acct", action_auth_acct_in));
ilog("User Transaction Specified: Action Auth Acct Priv Key ${key}", ("key", action_auth_acct_priv_key_in));
ilog("User Transaction Specified: Action Data File or Str ${data}", ("data", action_data_file_or_str));
ilog("User Transaction Specified: Abi File ${abi}", ("abi", abi_file_path_in));
ilog("User Transaction Specified: Actions Data ${acts}", ("acts", actions_data_json_file_or_str));
ilog("User Transaction Specified: Actions Auths ${auths}", ("auths", actions_auths_json_file_or_str));
}
fc::microseconds trx_expr_ms = fc::seconds(trx_expr);
@@ -233,7 +216,7 @@ int main(int argc, char** argv) {
std::shared_ptr<tps_performance_monitor> monitor;
if (transaction_specified) {
auto generator = std::make_shared<trx_generator>(gen_id, chain_id_in, abi_file_path_in, contract_owner_acct,
action_name_in, action_auth_acct_in, action_auth_acct_priv_key_in, action_data_file_or_str,
actions_data_json_file_or_str, actions_auths_json_file_or_str,
trx_expr_ms, lib_id_str, log_dir_in, stop_on_trx_failed, peer_endpoint, port);
monitor = std::make_shared<tps_performance_monitor>(spinup_time_us, max_lag_per, max_lag_duration_us);
trx_tps_tester<trx_generator, tps_performance_monitor> tester{generator, monitor, gen_duration, target_tps};
+136 -24
View File
@@ -26,10 +26,12 @@ namespace eosio::testing {
trx.delay_sec = delay_sec;
}
signed_transaction_w_signer trx_generator_base::create_trx_w_action_and_signer(const action& act, const fc::crypto::private_key& priv_key, uint64_t& nonce_prefix, uint64_t& nonce, const fc::microseconds& trx_expiration, const chain_id_type& chain_id, const block_id_type& last_irr_block_id) {
signed_transaction_w_signer trx_generator_base::create_trx_w_actions_and_signer(std::vector<action> acts, const fc::crypto::private_key& priv_key, uint64_t& nonce_prefix, uint64_t& nonce, const fc::microseconds& trx_expiration, const chain_id_type& chain_id, const block_id_type& last_irr_block_id) {
signed_transaction trx;
set_transaction_headers(trx, last_irr_block_id, trx_expiration);
trx.actions.push_back(act);
for (auto act:acts) {
trx.actions.push_back(act);
}
trx.context_free_actions.emplace_back(action({}, config::null_account_name, name("nonce"),
fc::raw::pack(std::to_string(nonce_prefix) + ":" + std::to_string(++nonce) + ":" +
fc::time_point::now().time_since_epoch().count())));
@@ -42,9 +44,14 @@ namespace eosio::testing {
std::vector<signed_transaction_w_signer> trxs;
trxs.reserve(2 * action_pairs_vector.size());
std::vector<action> act_vec;
for(action_pair_w_keys ap: action_pairs_vector) {
trxs.emplace_back(create_trx_w_action_and_signer(ap._first_act, ap._first_act_priv_key, nonce_prefix, nonce, trx_expiration, chain_id, last_irr_block_id));
trxs.emplace_back(create_trx_w_action_and_signer(ap._second_act, ap._second_act_priv_key, nonce_prefix, nonce, trx_expiration, chain_id, last_irr_block_id));
act_vec.push_back(ap._first_act);
trxs.emplace_back(create_trx_w_actions_and_signer(act_vec, ap._first_act_priv_key, nonce_prefix, nonce, trx_expiration, chain_id, last_irr_block_id));
act_vec.clear();
act_vec.push_back(ap._second_act);
trxs.emplace_back(create_trx_w_actions_and_signer(act_vec, ap._second_act_priv_key, nonce_prefix, nonce, trx_expiration, chain_id, last_irr_block_id));
act_vec.clear();
}
return trxs;
@@ -161,13 +168,102 @@ namespace eosio::testing {
}
}
void trx_generator::locate_key_words_in_action_mvo(std::vector<std::string>& acct_gen_fields_out, fc::mutable_variant_object& action_mvo, const std::string& key_word) {
for(const mutable_variant_object::entry& e: action_mvo) {
if(e.value().get_type() == fc::variant::string_type && e.value() == key_word) {
acct_gen_fields_out.push_back(e.key());
} else if(e.value().get_type() == fc::variant::object_type) {
auto inner_mvo = fc::mutable_variant_object(e.value());
locate_key_words_in_action_mvo(acct_gen_fields_out, inner_mvo, key_word);
}
}
}
void trx_generator::locate_key_words_in_action_array(std::map<int, std::vector<std::string>>& acct_gen_fields_out, fc::variants& action_array, const std::string& key_word) {
for(size_t i = 0; i < action_array.size(); ++i) {
auto action_mvo = fc::mutable_variant_object(action_array[i]);
locate_key_words_in_action_mvo(acct_gen_fields_out[i], action_mvo, key_word);
}
}
void trx_generator::update_key_word_fields_in_sub_action(std::string key, fc::mutable_variant_object& action_mvo, std::string action_inner_key, const std::string key_word) {
auto mvo = action_mvo.find(action_inner_key);
if(mvo != action_mvo.end()) {
fc::mutable_variant_object inner_mvo = fc::mutable_variant_object(action_mvo[action_inner_key].get_object());
if (inner_mvo.find(key) != inner_mvo.end()) {
inner_mvo.set(key, key_word);
action_mvo.set(action_inner_key, inner_mvo);
}
}
}
void trx_generator::update_key_word_fields_in_action(std::vector<std::string>& acct_gen_fields, fc::mutable_variant_object& action_mvo, const std::string key_word) {
for(auto key: acct_gen_fields) {
auto mvo = action_mvo.find(key);
if(mvo != action_mvo.end()) {
action_mvo.set(key, key_word);
} else {
for(auto e: action_mvo) {
if(e.value().get_type() == fc::variant::object_type) {
update_key_word_fields_in_sub_action(key, action_mvo, e.key(), key_word);
}
}
}
}
}
void trx_generator::update_resign_transaction(signed_transaction& trx, fc::crypto::private_key priv_key, uint64_t& nonce_prefix, uint64_t& nonce, const fc::microseconds& trx_expiration, const chain_id_type& chain_id, const block_id_type& last_irr_block_id) {
trx.actions.clear();
update_actions();
for(auto act: _actions) {
trx.actions.push_back(act);
}
trx_generator_base::update_resign_transaction(trx, priv_key, nonce_prefix, nonce, trx_expiration, chain_id, last_irr_block_id);
}
trx_generator::trx_generator(uint16_t id, std::string chain_id_in, const std::string& abi_data_file, std::string contract_owner_account,
std::string action_name, std::string action_auth_account, const std::string& private_key_str, const std::string& action_data_file_or_str,
const std::string& actions_data_json_file_or_str, const std::string& actions_auths_json_file_or_str,
fc::microseconds trx_expr, std::string lib_id_str, std::string log_dir, bool stop_on_trx_failed,
const std::string& peer_endpoint, unsigned short port)
: trx_generator_base(id, chain_id_in, contract_owner_account, trx_expr, lib_id_str, log_dir, stop_on_trx_failed, peer_endpoint, port),
_abi_data_file_path(abi_data_file), _action(action_name), _action_auth_account(action_auth_account),
_action_auth_priv_key(fc::crypto::private_key(private_key_str)), _action_data_file_or_str(action_data_file_or_str) {}
_abi_data_file_path(abi_data_file),
_actions_data_json_file_or_str(actions_data_json_file_or_str), _actions_auths_json_file_or_str(actions_auths_json_file_or_str),
_acct_name_generator() {}
void trx_generator::update_actions() {
_actions.clear();
if (!_acct_gen_fields.empty()) {
std::string generated_account_name = _acct_name_generator.calcName();
_acct_name_generator.increment();
for (auto const& [key, val] : _acct_gen_fields) {
update_key_word_fields_in_action(_acct_gen_fields.at(key), _unpacked_actions.at(key), generated_account_name);
}
}
for (auto action_mvo : _unpacked_actions) {
chain::name action_name = chain::name(action_mvo["actionName"].as_string());
chain::name action_auth_acct = chain::name(action_mvo["actionAuthAcct"].as_string());
bytes packed_action_data;
try {
auto action_type = _abi.get_action_type( action_name );
FC_ASSERT( !action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action_name)( "contract", action_auth_acct ));
packed_action_data = _abi.variant_to_binary( action_type, action_mvo["actionData"], abi_serializer::create_yield_function( abi_serializer_max_time ) );
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse unpacked action data JSON")
eosio::chain::action act;
act.account = _contract_owner_account;
act.name = action_name;
chain::name auth_actor = chain::name(action_mvo["authorization"].get_object()["actor"].as_string());
chain::name auth_perm = chain::name(action_mvo["authorization"].get_object()["permission"].as_string());
act.authorization = vector<permission_level>{{auth_actor, auth_perm}};
act.data = std::move(packed_action_data);
_actions.push_back(act);
}
}
bool trx_generator::setup() {
_nonce_prefix = 0;
@@ -177,31 +273,47 @@ namespace eosio::testing {
stop_generation();
ilog("Create Initial Transaction with action data.");
abi_serializer abi = abi_serializer(fc::json::from_file(_abi_data_file_path).as<abi_def>(), abi_serializer::create_yield_function( abi_serializer_max_time ));
fc::variant unpacked_action_data_json = json_from_file_or_string(_action_data_file_or_str);
ilog("action data variant: ${data}", ("data", fc::json::to_pretty_string(unpacked_action_data_json)));
_abi = abi_serializer(fc::json::from_file(_abi_data_file_path).as<abi_def>(), abi_serializer::create_yield_function( abi_serializer_max_time ));
fc::variant unpacked_actions_data_json = json_from_file_or_string(_actions_data_json_file_or_str);
fc::variant unpacked_actions_auths_data_json = json_from_file_or_string(_actions_auths_json_file_or_str);
ilog("Loaded actions data: ${data}", ("data", fc::json::to_pretty_string(unpacked_actions_data_json)));
ilog("Loaded actions auths data: ${auths}", ("auths", fc::json::to_pretty_string(unpacked_actions_auths_data_json)));
bytes packed_action_data;
try {
auto action_type = abi.get_action_type( _action );
FC_ASSERT( !action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", _action)( "contract", _action_auth_account ));
packed_action_data = abi.variant_to_binary( action_type, unpacked_action_data_json, abi_serializer::create_yield_function( abi_serializer_max_time ) );
const std::string gen_acct_name_per_trx("ACCT_PER_TRX");
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse unpacked action data JSON")
auto action_array = unpacked_actions_data_json.get_array();
for (size_t i =0; i < action_array.size(); ++i ) {
_unpacked_actions.push_back(fc::mutable_variant_object(action_array[i]));
}
locate_key_words_in_action_array(_acct_gen_fields, action_array, gen_acct_name_per_trx);
ilog("${packed_data}", ("packed_data", fc::to_hex(packed_action_data.data(), packed_action_data.size())));
if(!_acct_gen_fields.empty()) {
ilog("Located the following account names that need to be generated and populted in each transaction:");
for(auto e: _acct_gen_fields) {
ilog("acct_gen_fields entry: ${value}", ("value", e));
}
ilog("Priming name generator for trx generator prefix.");
_acct_name_generator.setPrefix(_id);
}
eosio::chain::action act;
act.account = _contract_owner_account;
act.name = _action;
act.authorization = vector<permission_level>{{_action_auth_account, config::active_name}};
act.data = std::move(packed_action_data);
ilog("Setting up transaction signer.");
fc::crypto::private_key signer_key;
signer_key = fc::crypto::private_key(unpacked_actions_auths_data_json.get_object()[_unpacked_actions.at(0)["actionAuthAcct"].as_string()].as_string());
_trxs.emplace_back(create_trx_w_action_and_signer(act, _action_auth_priv_key, ++_nonce_prefix, _nonce, _trx_expiration, _chain_id, _last_irr_block_id));
ilog("Setting up initial transaction actions.");
update_actions();
ilog("Initial actions (${count}):", ("count", _unpacked_actions.size()));
for (size_t i = 0; i < _unpacked_actions.size(); ++i) {
ilog("Initial action ${index}: ${act}", ("index", i)("act", fc::json::to_pretty_string(_unpacked_actions.at(i))));
ilog("Initial action packed data ${index}: ${packed_data}", ("packed_data", fc::to_hex(_actions.at(i).data.data(), _actions.at(i).data.size())));
}
ilog("Populate initial transaction.");
_trxs.emplace_back(create_trx_w_actions_and_signer(_actions, signer_key, ++_nonce_prefix, _nonce, _trx_expiration, _chain_id, _last_irr_block_id));
ilog("Setup p2p transaction provider");
ilog("Update each trx to qualify as unique and fresh timestamps, re-sign trx, and send each updated transactions via p2p transaction provider");
ilog("Update each trx to qualify as unique and fresh timestamps and update each action with unique generated account name if necessary, re-sign trx, and send each updated transactions via p2p transaction provider");
_provider.setup();
return true;
+70 -7
View File
@@ -6,6 +6,7 @@
#include <boost/program_options.hpp>
#include <eosio/chain/transaction.hpp>
#include <eosio/chain/asset.hpp>
#include <eosio/chain/abi_serializer.hpp>
#include <fc/io/json.hpp>
namespace eosio::testing {
@@ -27,6 +28,53 @@ namespace eosio::testing {
fc::crypto::private_key _second_act_priv_key;
};
struct account_name_generator {
account_name_generator() : _name_index_vec(ACCT_NAME_LEN, 0) {}
const char* CHARMAP = "12345abcdefghijklmnopqrstuvwxyz";
const int ACCT_NAME_CHAR_CNT = 31;
const int ACCT_NAME_LEN = 12;
const int MAX_PREFEX = 960;
std::vector<int> _name_index_vec;
void increment(int index) {
_name_index_vec[index]++;
if(_name_index_vec[index] >= ACCT_NAME_CHAR_CNT) {
_name_index_vec[index] = 0;
increment(index - 1);
}
}
void increment() {
increment(_name_index_vec.size() - 1);
}
void incrementPrefix() {
increment(1);
}
void setPrefix(int id) {
if (id > MAX_PREFEX) {
elog("Account Name Generator Prefix above allowable ${max}", ("max", MAX_PREFEX));
return;
}
_name_index_vec[0] = 0;
_name_index_vec[1] = 0;
for(int i = 0; i < id; i++) {
incrementPrefix();
}
};
std::string calcName() {
std::string name;
name.reserve(12);
for(auto i: _name_index_vec) {
name += CHARMAP[i];
}
return name;
}
};
struct trx_generator_base {
p2p_trx_provider _provider;
uint16_t _id;
@@ -49,14 +97,16 @@ namespace eosio::testing {
trx_generator_base(uint16_t id, std::string chain_id_in, std::string contract_owner_account, fc::microseconds trx_expr, std::string lib_id_str, std::string log_dir, bool stop_on_trx_failed,
const std::string& peer_endpoint="127.0.0.1", unsigned short port=9876);
void update_resign_transaction(eosio::chain::signed_transaction& trx, fc::crypto::private_key priv_key, uint64_t& nonce_prefix, uint64_t& nonce,
virtual void update_resign_transaction(eosio::chain::signed_transaction& trx, fc::crypto::private_key priv_key, uint64_t& nonce_prefix, uint64_t& nonce,
const fc::microseconds& trx_expiration, const eosio::chain::chain_id_type& chain_id, const eosio::chain::block_id_type& last_irr_block_id);
void push_transaction(p2p_trx_provider& provider, signed_transaction_w_signer& trx, uint64_t& nonce_prefix,
uint64_t& nonce, const fc::microseconds& trx_expiration, const eosio::chain::chain_id_type& chain_id,
const eosio::chain::block_id_type& last_irr_block_id);
void set_transaction_headers(eosio::chain::transaction& trx, const eosio::chain::block_id_type& last_irr_block_id, const fc::microseconds expiration, uint32_t delay_sec = 0);
signed_transaction_w_signer create_trx_w_action_and_signer(const eosio::chain::action& act, const fc::crypto::private_key& priv_key, uint64_t& nonce_prefix, uint64_t& nonce,
signed_transaction_w_signer create_trx_w_actions_and_signer(std::vector<eosio::chain::action> act, const fc::crypto::private_key& priv_key, uint64_t& nonce_prefix, uint64_t& nonce,
const fc::microseconds& trx_expiration, const eosio::chain::chain_id_type& chain_id, const eosio::chain::block_id_type& last_irr_block_id);
void log_first_trx(const std::string& log_dir, const eosio::chain::signed_transaction& trx);
@@ -89,18 +139,31 @@ namespace eosio::testing {
struct trx_generator : public trx_generator_base{
std::string _abi_data_file_path;
eosio::chain::name _action;
eosio::chain::name _action_auth_account;
fc::crypto::private_key _action_auth_priv_key;
std::string _action_data_file_or_str;
std::string _actions_data_json_file_or_str;
std::string _actions_auths_json_file_or_str;
account_name_generator _acct_name_generator;
eosio::chain::abi_serializer _abi;
std::vector<fc::mutable_variant_object> _unpacked_actions;
std::map<int, std::vector<std::string>> _acct_gen_fields;
std::vector<eosio::chain::action> _actions;
const fc::microseconds abi_serializer_max_time = fc::seconds(10); // No risk to client side serialization taking a long time
trx_generator(uint16_t id, std::string chain_id_in, const std::string& abi_data_file, std::string contract_owner_account,
std::string action_name, std::string action_auth_account, const std::string& action_auth_priv_key_str, const std::string& action_data_file_or_str,
const std::string& actions_data_json_file_or_str, const std::string& actions_auths_json_file_or_str,
fc::microseconds trx_expr, std::string lib_id_str, std::string log_dir, bool stop_on_trx_failed,
const std::string& peer_endpoint="127.0.0.1", unsigned short port=9876);
void locate_key_words_in_action_mvo(std::vector<std::string>& acctGenFieldsOut, fc::mutable_variant_object& action_mvo, const std::string& keyword);
void locate_key_words_in_action_array(std::map<int, std::vector<std::string>>& acctGenFieldsOut, fc::variants& action_array, const std::string& keyword);
void update_key_word_fields_in_sub_action(std::string key, fc::mutable_variant_object& action_mvo, std::string action_inner_key, const std::string keyWord);
void update_key_word_fields_in_action(std::vector<std::string>& acctGenFields, fc::mutable_variant_object& action_mvo, const std::string keyWord);
void update_actions();
virtual void update_resign_transaction(eosio::chain::signed_transaction& trx, fc::crypto::private_key priv_key, uint64_t& nonce_prefix, uint64_t& nonce,
const fc::microseconds& trx_expiration, const eosio::chain::chain_id_type& chain_id, const eosio::chain::block_id_type& last_irr_block_id);
fc::variant json_from_file_or_string(const std::string& file_or_str, fc::json::parse_type ptype = fc::json::parse_type::legacy_parser);
bool setup();
+6 -9
View File
@@ -327,23 +327,20 @@ BOOST_AUTO_TEST_CASE(trx_generator_constructor)
{
uint16_t id = 1;
std::string chain_id = "999";
std::string contract_owner_account = "eosio";
std::string acct = "aaa";
std::string action_name = "transfer";
std::string action_auth_acct = "aaa";
std::string action_auth_priv_key_str = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3";
const std::string action_data = "{\"from\":\"aaa\",\"to\":\"bbb\",\"quantity\":\"10.0000 SYS\",\"memo\":\"hello\"}";
const std::string abi_file = "../../unittests/contracts/eosio.token/eosio.token.abi";
std::string contract_owner_account = "eosio";
std::string actions_data = "[{\"actionAuthAcct\": \"testacct1\",\"actionName\": \"transfer\",\"authorization\": {\"actor\": \"testacct1\",\"permission\": \"active\"},\"actionData\": {\"from\": \"testacct1\",\"to\": \"testacct2\",\"quantity\": \"0.0001 CUR\",\"memo\": \"transaction specified\"}}]";
std::string action_auths = "{\"testacct1\":\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\",\"testacct2\":\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\",\"eosio\":\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"}";
fc::microseconds trx_expr = fc::seconds(3600);
std::string log_dir = ".";
std::string lib_id_str = "00000062989f69fd251df3e0b274c3364ffc2f4fce73de3f1c7b5e11a4c92f21";
bool stop_on_trx_failed = true;
std::string peer_endpoint = "127.0.0.1";
unsigned short port = 9876;
bool stop_on_trx_failed = true;
auto generator = trx_generator(id, chain_id, abi_file, contract_owner_account,
action_name, action_auth_acct, action_auth_priv_key_str, action_data, trx_expr,
lib_id_str, log_dir, stop_on_trx_failed, peer_endpoint, port);
actions_data, action_auths,
trx_expr, lib_id_str, log_dir, stop_on_trx_failed, peer_endpoint, port);
}
BOOST_AUTO_TEST_SUITE_END()