Repository: IBAX-io/go-ibax Branch: main Commit: 66f135846a10 Files: 473 Total size: 2.0 MB Directory structure: gitextract_ogj7kazk/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── build.sh ├── cmd/ │ ├── config.go │ ├── generateFirstBlock.go │ ├── generateKeys.go │ ├── initDatabase.go │ ├── rollback.go │ ├── root.go │ ├── start.go │ ├── stopNetwork.go │ └── version.go ├── go.mod ├── go.sum ├── main.go ├── packages/ │ ├── api/ │ │ ├── api.go │ │ ├── api_test.go │ │ ├── app_content.go │ │ ├── app_content_test.go │ │ ├── appparam.go │ │ ├── appparams.go │ │ ├── auth.go │ │ ├── balance.go │ │ ├── balance_test.go │ │ ├── block.go │ │ ├── block_test.go │ │ ├── common_forms.go │ │ ├── config.go │ │ ├── content.go │ │ ├── content_test.go │ │ ├── context.go │ │ ├── contract_test.go │ │ ├── contracts.go │ │ ├── data.go │ │ ├── ecosystem.go │ │ ├── ecosystem_params.go │ │ ├── ecosystem_test.go │ │ ├── errors.go │ │ ├── getcontract.go │ │ ├── getuid.go │ │ ├── getuid_test.go │ │ ├── history.go │ │ ├── history_test.go │ │ ├── import_test.go │ │ ├── interface.go │ │ ├── interface_test.go │ │ ├── lang_test.go │ │ ├── limit_test.go │ │ ├── list.go │ │ ├── list_test.go │ │ ├── login.go │ │ ├── member.go │ │ ├── metrics.go │ │ ├── middlewares.go │ │ ├── network.go │ │ ├── node.go │ │ ├── platform_params.go │ │ ├── read_test.go │ │ ├── request.go │ │ ├── route.go │ │ ├── row.go │ │ ├── sections.go │ │ ├── send_tx.go │ │ ├── smart_test.go │ │ ├── table.go │ │ ├── tables.go │ │ ├── tables_test.go │ │ ├── template_test.go │ │ ├── trash.go │ │ ├── tx_record.go │ │ ├── txinfo.go │ │ ├── txstatus.go │ │ └── version.go │ ├── block/ │ │ ├── block.go │ │ ├── check.go │ │ ├── db.go │ │ ├── play.go │ │ └── serialization.go │ ├── chain/ │ │ ├── daemonsctl/ │ │ │ └── daemonsctl.go │ │ ├── notandroid.go │ │ ├── start.go │ │ └── system/ │ │ ├── pid.go │ │ ├── unix.go │ │ └── windows.go │ ├── clbmanager/ │ │ ├── config.go │ │ └── manager.go │ ├── common/ │ │ ├── crypto/ │ │ │ ├── asymalgo/ │ │ │ │ ├── asymalgo.go │ │ │ │ ├── error.go │ │ │ │ ├── p256.go │ │ │ │ ├── secp256k1.go │ │ │ │ └── sm2.go │ │ │ ├── base58/ │ │ │ │ └── base58.go │ │ │ ├── checksum.go │ │ │ ├── converter.go │ │ │ ├── crypto.go │ │ │ ├── crypto.pb.go │ │ │ ├── crypto_test.go │ │ │ ├── ecies/ │ │ │ │ ├── ecccrypt.go │ │ │ │ └── ecies.go │ │ │ ├── hashalgo/ │ │ │ │ ├── hashalgo.go │ │ │ │ ├── keccak256.go │ │ │ │ ├── sha256.go │ │ │ │ ├── sha3_256.go │ │ │ │ └── sm3.go │ │ │ ├── provider.go │ │ │ ├── random.go │ │ │ ├── symalgo/ │ │ │ │ └── aes/ │ │ │ │ ├── aes.go │ │ │ │ └── aes_test.go │ │ │ └── x509/ │ │ │ └── cert.go │ │ ├── log/ │ │ │ ├── filename_hook.go │ │ │ ├── hex_hook.go │ │ │ ├── syslog_hook.go │ │ │ └── syslog_hook_windows.go │ │ ├── random/ │ │ │ ├── rand.go │ │ │ └── rand_test.go │ │ └── size.go │ ├── conf/ │ │ ├── conf.go │ │ ├── runmode.go │ │ ├── syspar/ │ │ │ ├── honornode.go │ │ │ ├── honornode_test.go │ │ │ └── syspar.go │ │ └── types.go │ ├── consts/ │ │ ├── conf.go │ │ ├── consts.go │ │ ├── log_types.go │ │ └── used_stop_certs.go │ ├── converter/ │ │ ├── address.go │ │ └── converter.go │ ├── daemons/ │ │ ├── block_generator.go │ │ ├── block_generator_candidate.go │ │ ├── block_generator_tx.go │ │ ├── blocks_collection.go │ │ ├── candidate_node_votings.go │ │ ├── common.go │ │ ├── confirmations.go │ │ ├── disseminator.go │ │ ├── external_network.go │ │ ├── genesis.go │ │ ├── locking.go │ │ ├── mode.go │ │ ├── monitoring.go │ │ ├── queue_parser_blocks.go │ │ ├── queue_parser_tx.go │ │ ├── scheduler.go │ │ ├── stopdaemons.go │ │ ├── upd_full_nodes.go │ │ └── wait_for_signals.go │ ├── language/ │ │ └── language.go │ ├── migration/ │ │ ├── clb/ │ │ │ ├── applications_data.go │ │ │ ├── clb_data_contracts.go │ │ │ ├── data.go │ │ │ ├── keys_data.go │ │ │ ├── menu_data.go │ │ │ ├── pages_data.go │ │ │ ├── parameters_data.go │ │ │ ├── platform_parameters_data.go │ │ │ ├── roles_data.go │ │ │ ├── scheme.go │ │ │ ├── sections_data.go │ │ │ ├── snippets_data.go │ │ │ └── tables_data.go │ │ ├── contracts/ │ │ │ ├── clb/ │ │ │ │ ├── EditCron.sim │ │ │ │ ├── ListCLB.sim │ │ │ │ ├── MainCondition.sim │ │ │ │ ├── NewCLB.sim │ │ │ │ ├── NewCron.sim │ │ │ │ ├── RemoveCLB.sim │ │ │ │ ├── RunCLB.sim │ │ │ │ └── StopCLB.sim │ │ │ ├── ecosystem/ │ │ │ │ ├── DeveloperCondition.sim │ │ │ │ └── MainCondition.sim │ │ │ └── first_ecosystem/ │ │ │ ├── AccessControlMode.sim │ │ │ ├── AccessVoteTempRun.sim │ │ │ ├── BindWallet.sim │ │ │ ├── CallDelayedContract.sim │ │ │ ├── CheckNodesBan.sim │ │ │ ├── EditAppParam.sim │ │ │ ├── EditApplication.sim │ │ │ ├── EditColumn.sim │ │ │ ├── EditContract.sim │ │ │ ├── EditLang.sim │ │ │ ├── EditMenu.sim │ │ │ ├── EditPage.sim │ │ │ ├── EditParameter.sim │ │ │ ├── EditSnippet.sim │ │ │ ├── EditTable.sim │ │ │ ├── HonorNodeCondition.sim │ │ │ ├── Import.sim │ │ │ ├── ImportUpload.sim │ │ │ ├── NewAppParam.sim │ │ │ ├── NewApplication.sim │ │ │ ├── NewBadBlock.sim │ │ │ ├── NewContract.sim │ │ │ ├── NewEcosystem.sim │ │ │ ├── NewLang.sim │ │ │ ├── NewMenu.sim │ │ │ ├── NewPage.sim │ │ │ ├── NewParameter.sim │ │ │ ├── NewSnippet.sim │ │ │ ├── NewTable.sim │ │ │ ├── NewUser.sim │ │ │ ├── NodeOwnerCondition.sim │ │ │ ├── UnbindWallet.sim │ │ │ ├── UpdatePlatformParam.sim │ │ │ ├── UploadBinary.sim │ │ │ └── UploadFile.sim │ │ ├── contracts_data.go │ │ ├── data.go │ │ ├── ecosystem.go │ │ ├── ecosystem_test.go │ │ ├── first_delayed_contracts.go │ │ ├── first_ecosys_contracts_data.go │ │ ├── first_ecosys_pages_data.go │ │ ├── first_ecosys_snippets_data.go │ │ ├── first_ecosystem_schema.go │ │ ├── first_ecosystems_data.go │ │ ├── first_tables_data.go │ │ ├── gen/ │ │ │ ├── contracts.go │ │ │ └── contracts_test.go │ │ ├── keys_data.go │ │ ├── members_data.go │ │ ├── menu_data.go │ │ ├── migration.go │ │ ├── migration_test.go │ │ ├── pages_data.go │ │ ├── parameters_data.go │ │ ├── platform_parameters_data.go │ │ ├── sections_data.go │ │ ├── tables_data.go │ │ ├── timezones.go │ │ └── updates/ │ │ └── migration_update_exec.go │ ├── modes/ │ │ ├── api.go │ │ ├── client_tx.go │ │ ├── daemons.go │ │ ├── ecosystem_getter.go │ │ ├── mode_fabrics.go │ │ └── rpc.go │ ├── network/ │ │ ├── httpserver/ │ │ │ └── max_body.go │ │ ├── machineState.go │ │ ├── protocol.go │ │ ├── protocol_test.go │ │ ├── tcpclient/ │ │ │ ├── blocks_collection.go │ │ │ ├── candidate_node_voting.go │ │ │ ├── client.go │ │ │ ├── client_test.go │ │ │ ├── confirmation.go │ │ │ ├── disseminator.go │ │ │ ├── max_block.go │ │ │ ├── pools.go │ │ │ ├── pools_test.go │ │ │ └── stop_network.go │ │ └── tcpserver/ │ │ ├── blocks_collection.go │ │ ├── candidate_node_voting.go │ │ ├── confirmation.go │ │ ├── disseminate_tx.go │ │ ├── disseminator.go │ │ ├── max_block.go │ │ ├── stop_network.go │ │ └── tcpserver.go │ ├── notificator/ │ │ ├── notificator.go │ │ ├── queue.go │ │ └── token_movements.go │ ├── pb/ │ │ ├── Makefile │ │ ├── block.proto │ │ ├── crypto.proto │ │ ├── gas.proto │ │ ├── play.proto │ │ ├── tx.proto │ │ └── vm.proto │ ├── pbgo/ │ │ └── tx.pb.go │ ├── protocols/ │ │ ├── block_counter.go │ │ ├── block_counter_mock.go │ │ ├── block_time_calculator.go │ │ ├── block_time_calculator_test.go │ │ ├── block_time_counter_test.go │ │ └── generation-queue.go │ ├── publisher/ │ │ └── publisher.go │ ├── rollback/ │ │ ├── block.go │ │ ├── rollback.go │ │ └── transaction.go │ ├── scheduler/ │ │ ├── contract/ │ │ │ ├── request.go │ │ │ └── task.go │ │ ├── scheduler.go │ │ ├── scheduler_test.go │ │ └── task.go │ ├── script/ │ │ ├── cmds_list.go │ │ ├── code_block.go │ │ ├── compile.go │ │ ├── compile_test.go │ │ ├── errors.go │ │ ├── eval.go │ │ ├── eval_test.go │ │ ├── extend.go │ │ ├── func.go │ │ ├── handle.go │ │ ├── lex.go │ │ ├── lex_table.go │ │ ├── lex_test.go │ │ ├── lextable/ │ │ │ └── lextable.go │ │ ├── runtime.go │ │ ├── runtime_test.go │ │ ├── stack.go │ │ ├── state.go │ │ ├── vm.go │ │ ├── vm.pb.go │ │ └── vminit.go │ ├── service/ │ │ ├── gateway/ │ │ │ └── gateway.go │ │ ├── jsonrpc/ │ │ │ ├── accounts.go │ │ │ ├── api.go │ │ │ ├── auth.go │ │ │ ├── block.go │ │ │ ├── callback.go │ │ │ ├── common.go │ │ │ ├── common_forms.go │ │ │ ├── context.go │ │ │ ├── data.go │ │ │ ├── debug.go │ │ │ ├── ecosystem_params.go │ │ │ ├── errors.go │ │ │ ├── handlers.go │ │ │ ├── history.go │ │ │ ├── http.go │ │ │ ├── id.go │ │ │ ├── jwt.go │ │ │ ├── middlewares.go │ │ │ ├── namespace.go │ │ │ ├── namespace_debug.go │ │ │ ├── namespace_ibax.go │ │ │ ├── namespace_net.go │ │ │ ├── params.go │ │ │ ├── raw_response.go │ │ │ ├── request.go │ │ │ ├── response.go │ │ │ ├── server.go │ │ │ ├── service.go │ │ │ ├── transaction.go │ │ │ └── txinfo.go │ │ ├── node/ │ │ │ ├── node_actualization.go │ │ │ ├── node_ban.go │ │ │ ├── node_paused.go │ │ │ └── node_relevance.go │ │ └── protos/ │ │ ├── build.sh │ │ └── googleapis/ │ │ └── google/ │ │ └── api/ │ │ ├── annotations.proto │ │ └── http.proto │ ├── smart/ │ │ ├── builtin_excel.go │ │ ├── contract.go │ │ ├── datetime.go │ │ ├── errors.go │ │ ├── funcs.go │ │ ├── gas.go │ │ ├── gas.pb.go │ │ ├── math.go │ │ ├── selective.go │ │ ├── smart.go │ │ ├── smart_p.go │ │ ├── smart_test.go │ │ ├── sysrollback.go │ │ └── utils.go │ ├── statsd/ │ │ └── statsd.go │ ├── storage/ │ │ ├── kvdb/ │ │ │ ├── leveldb/ │ │ │ │ └── leveldb.go │ │ │ └── redis/ │ │ │ ├── goredis.go │ │ │ └── maxblockid.go │ │ ├── sqldb/ │ │ │ ├── app_param.go │ │ │ ├── bad_blocks.go │ │ │ ├── binary.go │ │ │ ├── blockchain.go │ │ │ ├── candidate_node.go │ │ │ ├── confirmations.go │ │ │ ├── contract.go │ │ │ ├── cron.go │ │ │ ├── database.go │ │ │ ├── db.go │ │ │ ├── delayed_contract.go │ │ │ ├── ecosystem.go │ │ │ ├── ecosystem_parameter.go │ │ │ ├── external_blockchain.go │ │ │ ├── history.go │ │ │ ├── info_block.go │ │ │ ├── install.go │ │ │ ├── keys.go │ │ │ ├── language.go │ │ │ ├── log_transaction.go │ │ │ ├── members.go │ │ │ ├── menu.go │ │ │ ├── metric.go │ │ │ ├── migration_history.go │ │ │ ├── node_ban_logs.go │ │ │ ├── notification.go │ │ │ ├── notification_test.go │ │ │ ├── ordering.go │ │ │ ├── pages.go │ │ │ ├── platform_parameter.go │ │ │ ├── queryBuilder/ │ │ │ │ ├── expression.go │ │ │ │ ├── query_builder.go │ │ │ │ ├── query_builder_test.go │ │ │ │ └── where.go │ │ │ ├── querycost/ │ │ │ │ ├── explain.go │ │ │ │ ├── formula.go │ │ │ │ ├── formula_test.go │ │ │ │ └── querycost.go │ │ │ ├── queue_block.go │ │ │ ├── queue_tx.go │ │ │ ├── responecode.go │ │ │ ├── result.go │ │ │ ├── role.go │ │ │ ├── roles_participants.go │ │ │ ├── rollback_tx.go │ │ │ ├── schema.go │ │ │ ├── send_tx.go │ │ │ ├── signatures.go │ │ │ ├── snippet.go │ │ │ ├── spent_info.go │ │ │ ├── stop_daemons.go │ │ │ ├── tables.go │ │ │ ├── transaction.go │ │ │ ├── transaction_status.go │ │ │ ├── transactions_attempts.go │ │ │ ├── tx_record.go │ │ │ ├── upd_full_nodes.go │ │ │ └── utxo_token.go │ │ └── storage.go │ ├── system/ │ │ ├── system.go │ │ ├── system_notwindows.go │ │ └── system_windows.go │ ├── template/ │ │ ├── calculate.go │ │ ├── dbfind.go │ │ ├── funcs.go │ │ └── template.go │ ├── transaction/ │ │ ├── ban.go │ │ ├── builder.go │ │ ├── cache.go │ │ ├── contract.go │ │ ├── db.go │ │ ├── deliver.go │ │ ├── first_block.go │ │ ├── limits.go │ │ ├── play.go │ │ ├── process.go │ │ ├── raw.go │ │ ├── smart_contract.go │ │ ├── stop_network.go │ │ └── transaction.go │ ├── types/ │ │ ├── api.go │ │ ├── block.pb.go │ │ ├── block_data.go │ │ ├── compress.go │ │ ├── custom_tx.go │ │ ├── file.go │ │ ├── map.go │ │ ├── mode_interfaces.go │ │ ├── notifications.go │ │ └── play.pb.go │ └── utils/ │ ├── ban_error.go │ ├── ban_error_test.go │ ├── clock.go │ ├── clock_mock.go │ ├── metric/ │ │ ├── collector.go │ │ ├── collector_test.go │ │ └── metrics.go │ ├── ntp.go │ ├── utils.go │ └── utils_test.go └── tools/ └── desync_monitor/ ├── config/ │ └── config.go ├── config.toml.temp ├── main.go └── query/ ├── query.go └── utils.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: "\U0001F41E Bug report" description: Report an issue with IBAX labels: [pending triage] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: bug-description attributes: label: Describe the bug description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! placeholder: Bug description validations: required: true - type: textarea id: reproduction attributes: label: Reproduction description: Please provide a link to a repo that can reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided after 3 days, it will be auto-closed. placeholder: Reproduction validations: required: true - type: textarea id: system-info attributes: label: System Info description: render: shell placeholder: System, Binaries, Browsers validations: required: true - type: textarea id: logs attributes: label: Logs description: | Optional if provided reproduction. Please try not to insert an image but copy paste the log text. 1. Run `go-ibax` or `go-ibax start` with the `--test` flag. 2. Provide the error log here. render: shell - type: checkboxes id: checkboxes attributes: label: Validations description: Before submitting the issue, please make sure you do the following options: - label: Read the [docs](https://docs.ibax.io). required: true - label: Check that there isn't [already an issue](https://github.com/IBAX-io/go-ibax/issues) that reports the same bug to avoid creating a duplicate. required: true - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/IBAX-io/go-ibax/discussions) or join our [Discord Chat Server](https://discord.com/invite/zRX6Mwafya). required: true - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: IBAX Official English Group url: https://t.me/IBAXNetwork about: A Decentralized Commercial Cross-Chain infrastructure Network. - name: Discord Chat url: https://discord.com/invite/zRX6Mwafya about: Ask questions and discuss with other IBAX users in real time. - name: Questions & Discussions url: https://github.com/IBAX-io/go-ibax/discussions about: Use GitHub discussions for message-board style questions and discussions. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: "\U0001F680 New feature proposal" description: Propose a new feature to be added to IBAX labels: ["enhancement: pending triage"] body: - type: markdown attributes: value: | Thanks for your interest in the project and taking the time to fill out this feature report! - type: textarea id: feature-description attributes: label: Clear and concise description of the problem description: "As a developer using IBAX I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!" validations: required: true - type: textarea id: suggested-solution attributes: label: Suggested solution description: "In module [xy] we could provide following implementation..." validations: required: true - type: textarea id: alternative attributes: label: Alternative description: Clear and concise description of any alternative solutions or features you've considered. - type: textarea id: additional-context attributes: label: Additional context description: Any other context or screenshots about the feature request here. - type: checkboxes id: checkboxes attributes: label: Validations description: Before submitting the issue, please make sure you do the following options: - label: Read the [docs](https://docs.ibax.io). required: true - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. required: true ================================================ FILE: .gitignore ================================================ .directory ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio *.iml ## Directory-based project format: .idea/ # if you remove the above rule, at least ignore the following: # User-specific stuff: # .idea/workspace.xml # .idea/tasks.xml # .idea/dictionaries # Sensitive or high-churn files: # .idea/dataSources.ids # .idea/dataSources.xml # .idea/sqlDataSources.xml # .idea/dynamic.xml # .idea/uiDesigner.xml # Gradle: # .idea/gradle.xml # .idea/libraries # Mongo Explorer plugin: # .idea/mongoSettings.xml ## File-based project format: *.ipr *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties ### Go template # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.ffs_db *.exe *.test *.prof ### OSX template .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk packages/api/key go-ibax 1block NodePrivateKey PrivateKey NodePublicKey PublicKey KeyID chain.pid config.toml packages/static/ public/ .vscode/ packages/api/helper_test.go /tempdir/* #/tools/ .scannerwork/ sonar-project.properties tempdir/ ibax-log/ packages/api/key* *.lock *.pid initDatabase.txt .env docker-compose.yml Dockerfile Dockerfile_src test.log ================================================ FILE: .travis.yml ================================================ language: go env: GO111MODULE=on go: - 1.17.x - master go_import_path: github.com/IBAX-io/go-ibax install: true script: go build github.com/IBAX-io/go-ibax ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 IBAX. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ export GOPROXY=https://goproxy.io export GO111MODULE=on HOMEDIR := $(shell pwd) all: mod build mod: go mod tidy -v build: bash $(HOMEDIR)/build.sh try: go build go-ibax generateFirstBlock --test=true go-ibax initDatabase go-ibax start init: go-ibax initDatabase # avoid filename conflict and speed up build .PHONY: all ================================================ FILE: README.md ================================================ # IBAX Blockchain System Platform [![Go Reference](https://pkg.go.dev/badge/github.com/IBAX-io/go-ibax.svg)](https://pkg.go.dev/github.com/IBAX-io/go-ibax) [![Go Report Card](https://goreportcard.com/badge/github.com/IBAX-io/go-ibax)](https://goreportcard.com/report/github.com/IBAX-io/go-ibax) ## The Most Powerful Infrastructure for Applications on Decentralized/Centralized Ecosystems A powerful blockchain system platform with a new system framework and a simplified programming language, it is including smart contract, database table and interface. ### Build from Source #### Install Go The build process for go-ibax requires Go 1.17 or higher. If you don't have it: [Download Go 1.17+](https://go.dev). You'll need to add Go's bin directories to your `$PATH` environment variable e.g., by adding these lines to your `/etc/profile` (for a system-wide installation) or `$HOME/.profile`: ``` export PATH=$PATH:/usr/local/go/bin export PATH=$PATH:$GOPATH/bin ``` (If you run into trouble, see the [Go install instructions](https://go.dev/dl/)). #### Compile ``` $ export GOPROXY=https://athens.azurefd.net $ GO111MODULE=on go mod tidy -v $ go build ``` ### Run 1. Create the node configuration file: ```bash $ go-ibax config ``` 2. Generate node keys: ```bash $ go-ibax generateKeys ``` 3. Generate the first block. If you are creating your own blockchain network. You must use the `--test=true` option. Otherwise you will not be able to create new accounts. ```bash $ go-ibax generateFirstBlock --test=true ``` 4. Initialize the database. ```bash $ go-ibax initDatabase ``` 5.Starting go-ibax. ```bash $ go-ibax start ``` ================================================ FILE: build.sh ================================================ #!/bin/bash set -e -x HOMEDIR=$(pwd) function buildpkg() { buildBin=$1 buildModule=$2 buildFile=$3 buildBranch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown) buildDate=$(date -u "+%Y-%m-%d-%H:%M:%S(UTC)") commitHash=$(git rev-parse --short HEAD 2>/dev/null || echo unknown) go build -o "$buildBin" -ldflags "-s -w -X $buildModule/cmd.buildBranch=$buildBranch -X $buildModule/cmd.buildDate=$buildDate -X $buildModule/cmd.commitHash=$commitHash" "$buildFile" } buildpkg go-ibax "github.com/IBAX-io/go-ibax" "$HOMEDIR/main.go" ================================================ FILE: cmd/config.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "fmt" "path/filepath" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) // configCmd represents the config command var configCmd = &cobra.Command{ Use: "config", Short: "Initial config generation", Run: func(cmd *cobra.Command, args []string) { // Error omitted because we have default flag value configPath, _ := cmd.Flags().GetString("path") err := conf.FillRuntimePaths() if err != nil { log.WithError(err).Fatal("Filling config") } if configPath == "" { configPath = filepath.Join(conf.Config.DirPathConf.DataDir, consts.DefaultConfigFile) } err = viper.Unmarshal(&conf.Config) if err != nil { log.WithError(err).Fatal("Marshalling config to global struct variable") } err = conf.SaveConfig(configPath) if err != nil { log.WithError(err).Fatal("Saving config") } log.Infof("Config is saved to %s", configPath) }, } func init() { cmdFlags := configCmd.Flags() // Command flags cmdFlags.String("path", "", "Generate config to (default dataDir/config.toml)") // Etc cmdFlags.StringVar(&conf.Config.DirPathConf.PidFilePath, "pid", "", fmt.Sprintf("ibax pid file name (default dataDir/%s)", consts.DefaultPidFilename), ) cmdFlags.StringVar(&conf.Config.DirPathConf.LockFilePath, "lock", "", fmt.Sprintf("ibax lock file name (default dataDir/%s)", consts.DefaultLockFilename), ) cmdFlags.StringVar(&conf.Config.DirPathConf.KeysDir, "keysDir", "", "Keys directory (default dataDir)") cmdFlags.StringVar(&conf.Config.DirPathConf.DataDir, "dataDir", "", "Data directory (default cwd/data)") cmdFlags.StringVar(&conf.Config.DirPathConf.TempDir, "tempDir", "", "Temporary directory (default temporary directory of OS)") cmdFlags.StringVar(&conf.Config.DirPathConf.FirstBlockPath, "firstBlock", "", "First block path (default dataDir/1block)") // tls cmdFlags.BoolVar(&conf.Config.TLSConf.Enabled, "tlsEnable", false, "Enable https") cmdFlags.StringVar(&conf.Config.TLSConf.TLSCert, "tlsCert", "", "Filepath to the fullchain of certificates") cmdFlags.StringVar(&conf.Config.TLSConf.TLSKey, "tlsKey", "", "Filepath to the private key") //Bootstrap cmdFlags.StringSliceVar(&conf.Config.BootNodes.NodesAddr, "bootNodes", []string{}, "List of addresses for downloading blockchain") //LocalConf cmdFlags.Int64Var(&conf.Config.LocalConf.MaxPageGenerationTime, "mpgt", 3000, "Max page generation time in ms") cmdFlags.Int64Var(&conf.Config.LocalConf.HTTPServerMaxBodySize, "mbs", 1<<20, "Max server body size in byte") cmdFlags.Int64Var(&conf.Config.LocalConf.NetworkID, "networkID", 1, "Network ID") cmdFlags.StringVar(&conf.Config.LocalConf.RunNodeMode, "runMode", consts.NoneCLB, "running node mode, example NONE|CLB|CLBMaster|SubNode") // TCP Server cmdFlags.StringVar(&conf.Config.TCPServer.Host, "tcpHost", "127.0.0.1", "Node TCP host") cmdFlags.IntVar(&conf.Config.TCPServer.Port, "tcpPort", 7078, "Node TCP port") // HTTP Server cmdFlags.StringVar(&conf.Config.HTTP.Host, "httpHost", "127.0.0.1", "Node HTTP host") cmdFlags.IntVar(&conf.Config.HTTP.Port, "httpPort", 7079, "Node HTTP port") // JSON-RPC Server cmdFlags.BoolVar(&conf.Config.JsonRPC.Enabled, "jsonRPCEnabled", false, "Node Json-RPC Enabled") cmdFlags.StringVar(&conf.Config.JsonRPC.Namespace, "jsonRPCNamespace", "ibax,net", "Node Json-RPC Namespace") // DB cmdFlags.StringVar(&conf.Config.DB.Host, "dbHost", "127.0.0.1", "DB host") cmdFlags.IntVar(&conf.Config.DB.Port, "dbPort", 5432, "DB port") cmdFlags.StringVar(&conf.Config.DB.Name, "dbName", "ibax", "DB name") cmdFlags.StringVar(&conf.Config.DB.User, "dbUser", "postgres", "DB username") cmdFlags.StringVar(&conf.Config.DB.Password, "dbPassword", "123456", "DB password") cmdFlags.IntVar(&conf.Config.DB.LockTimeout, "dbLockTimeout", 5000, "DB lock timeout") cmdFlags.IntVar(&conf.Config.DB.IdleInTxTimeout, "dbIdleInTxTimeout", 5000, "DB idle tx timeout") cmdFlags.IntVar(&conf.Config.DB.MaxIdleConns, "dbMaxIdleConns", 5, "DB sets the maximum number of connections in the idle connection pool") cmdFlags.IntVar(&conf.Config.DB.MaxOpenConns, "dbMaxOpenConns", 100, "sets the maximum number of open connections to the database") //Redis cmdFlags.BoolVar(&conf.Config.Redis.Enable, "redisEnable", false, "enable redis") cmdFlags.StringVar(&conf.Config.Redis.Host, "redisHost", "localhost", "redis host") cmdFlags.IntVar(&conf.Config.Redis.Port, "redisPort", 6379, "redis port") cmdFlags.IntVar(&conf.Config.Redis.DbName, "redisDb", 0, "redis db") cmdFlags.StringVar(&conf.Config.Redis.Password, "redisPassword", "123456", "redis password") // StatsD cmdFlags.StringVar(&conf.Config.StatsD.Host, "statsdHost", "127.0.0.1", "StatsD host") cmdFlags.IntVar(&conf.Config.StatsD.Port, "statsdPort", 8125, "StatsD port") cmdFlags.StringVar(&conf.Config.StatsD.Name, "statsdName", "chain", "StatsD name") // Centrifugo cmdFlags.StringVar(&conf.Config.Centrifugo.Secret, "centSecret", "127.0.0.1", "Centrifugo secret") cmdFlags.StringVar(&conf.Config.Centrifugo.URL, "centUrl", "127.0.0.1", "Centrifugo URL") cmdFlags.StringVar(&conf.Config.Centrifugo.Key, "centKey", "127.0.0.1", "Centrifugo API key") // Log cmdFlags.StringVar(&conf.Config.Log.LogTo, "logTo", "stdout", "Send logs to stdout|(filename)|syslog") cmdFlags.StringVar(&conf.Config.Log.LogLevel, "logLevel", "ERROR", "Log verbosity (DEBUG | INFO | WARN | ERROR)") cmdFlags.StringVar(&conf.Config.Log.LogFormat, "logFormat", "text", "log format, could be text|json") cmdFlags.StringVar(&conf.Config.Log.Syslog.Facility, "syslogFacility", "kern", "syslog facility") cmdFlags.StringVar(&conf.Config.Log.Syslog.Tag, "syslogTag", "go-ibax", "syslog program tag") // TokenMovement cmdFlags.StringVar(&conf.Config.TokenMovement.Host, "tmovHost", "", "Token movement host") cmdFlags.IntVar(&conf.Config.TokenMovement.Port, "tmovPort", 0, "Token movement port") cmdFlags.StringVar(&conf.Config.TokenMovement.Username, "tmovUser", "", "Token movement username") cmdFlags.StringVar(&conf.Config.TokenMovement.Password, "tmovPw", "", "Token movement password") cmdFlags.StringVar(&conf.Config.TokenMovement.To, "tmovTo", "", "Token movement to field") cmdFlags.StringVar(&conf.Config.TokenMovement.From, "tmovFrom", "", "Token movement from field") cmdFlags.StringVar(&conf.Config.TokenMovement.Subject, "tmovSubj", "", "Token movement subject") cmdFlags.IntVar(&conf.Config.BanKey.BadTime, "badTime", 5, "Period for bad tx (minutes)") cmdFlags.IntVar(&conf.Config.BanKey.BanTime, "banTime", 15, "Ban time in minutes") cmdFlags.IntVar(&conf.Config.BanKey.BadTx, "badTx", 5, "Maximum bad tx during badTime minutes") // CryptoSettings cmdFlags.StringVar(&conf.Config.CryptoSettings.Hasher, "hasher", crypto.HashAlgo_KECCAK256.String(), fmt.Sprintf("Hash Algorithm (%s | %s | %s | %s)", crypto.HashAlgo_SHA256, crypto.HashAlgo_KECCAK256, crypto.HashAlgo_SHA3_256, crypto.HashAlgo_SM3)) cmdFlags.StringVar(&conf.Config.CryptoSettings.Cryptoer, "cryptoer", crypto.AsymAlgo_ECC_Secp256k1.String(), fmt.Sprintf("Key and Sign Algorithm (%s | %s | %s | %s)", crypto.AsymAlgo_ECC_P256, crypto.AsymAlgo_ECC_Secp256k1, crypto.AsymAlgo_ECC_P512, crypto.AsymAlgo_SM2)) // BlockSyncMethod cmdFlags.StringVar(&conf.Config.BlockSyncMethod.Method, "sync", types.BlockSyncMethod_CONTRACTVM.String(), fmt.Sprintf("Block sync method (%s | %s)", types.BlockSyncMethod_CONTRACTVM, types.BlockSyncMethod_SQLDML)) viper.BindPFlags(configCmd.PersistentFlags()) } ================================================ FILE: cmd/generateFirstBlock.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "os" "path/filepath" "time" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var stopNetworkBundleFilepath string var testBlockchain bool var privateBlockchain bool // generateFirstBlockCmd represents the generateFirstBlock command var generateFirstBlockCmd = &cobra.Command{ Use: "generateFirstBlock", Short: "First generation", PreRun: loadConfigWKey, Run: func(cmd *cobra.Command, args []string) { block, err := genesisBlock() if err != nil { log.WithFields(log.Fields{"type": consts.MarshallingError, "error": err}).Fatal("first block marshalling") } os.WriteFile(conf.Config.DirPathConf.FirstBlockPath, block, 0644) log.Info("first block generated") }, } func init() { generateFirstBlockCmd.Flags().StringVar(&stopNetworkBundleFilepath, "stopNetworkCert", "", "Filepath to the fullchain of certificates for network stopping") generateFirstBlockCmd.Flags().BoolVar(&testBlockchain, "test", false, "if true - test blockchain") generateFirstBlockCmd.Flags().BoolVar(&privateBlockchain, "private", false, "if true - all transactions will be free") } func genesisBlock() ([]byte, error) { now := time.Now().Unix() header := &types.BlockHeader{ BlockId: 1, Timestamp: now, EcosystemId: 0, KeyId: conf.Config.KeyID, NetworkId: conf.Config.LocalConf.NetworkID, NodePosition: 0, Version: consts.BlockVersion, RollbacksHash: crypto.Hash([]byte(`0`)), ConsensusMode: consts.HonorNodeMode, } decodeKeyFile := func(kName string) []byte { filepath := filepath.Join(conf.Config.DirPathConf.KeysDir, kName) data, err := os.ReadFile(filepath) if err != nil { log.WithError(err).WithFields(log.Fields{"key": kName, "filepath": filepath}).Fatal("Reading key data") } decodedKey, err := crypto.HexToPub(string(data)) if err != nil { log.WithError(err).Fatalf("converting %s from hex", kName) } return decodedKey } var stopNetworkCert []byte if len(stopNetworkBundleFilepath) > 0 { var err error fp := filepath.Join(conf.Config.DirPathConf.KeysDir, stopNetworkBundleFilepath) if stopNetworkCert, err = os.ReadFile(fp); err != nil { log.WithError(err).WithFields(log.Fields{"filepath": fp}).Fatal("Reading cert data") } } if len(stopNetworkCert) == 0 { log.Warn("the fullchain of certificates for a network stopping is not specified") } var test int64 var pb uint64 if testBlockchain == true { test = 1 } if privateBlockchain == true { pb = 1 } fbp := new(transaction.FirstBlockParser) tx, err := fbp.BinMarshal(&types.FirstBlock{ KeyID: conf.Config.KeyID, Time: now, PublicKey: decodeKeyFile(consts.PublicKeyFilename), NodePublicKey: decodeKeyFile(consts.NodePublicKeyFilename), StopNetworkCertBundle: stopNetworkCert, Test: test, PrivateBlockchain: pb, }) if err != nil { log.WithFields(log.Fields{"type": consts.MarshallingError, "error": err}).Fatal("first block body bin marshalling") } return block.MarshallBlock(types.WithCurHeader(header), types.WithPrevHeader(&types.BlockHeader{ BlockHash: crypto.DoubleHash([]byte(`0`)), RollbacksHash: crypto.Hash([]byte(`0`)), }), types.WithTxFullData([][]byte{tx})) } ================================================ FILE: cmd/generateKeys.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "encoding/hex" "os" "path/filepath" "strconv" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) const fileMode = 0600 // generateKeysCmd represents the generateKeys command var generateKeysCmd = &cobra.Command{ Use: "generateKeys", Short: "Keys generation", PreRun: loadConfig, Run: func(cmd *cobra.Command, args []string) { _, publicKey, err := createKeyPair( filepath.Join(conf.Config.DirPathConf.KeysDir, consts.PrivateKeyFilename), filepath.Join(conf.Config.DirPathConf.KeysDir, consts.PublicKeyFilename), ) if err != nil { log.WithError(err).Fatal("generating user keys") return } _, _, err = createKeyPair( filepath.Join(conf.Config.DirPathConf.KeysDir, consts.NodePrivateKeyFilename), filepath.Join(conf.Config.DirPathConf.KeysDir, consts.NodePublicKeyFilename), ) if err != nil { log.WithError(err).Fatal("generating node keys") return } address := crypto.Address(publicKey) keyIDPath := filepath.Join(conf.Config.DirPathConf.KeysDir, consts.KeyIDFilename) err = createFile(keyIDPath, []byte(strconv.FormatInt(address, 10))) if err != nil { log.WithFields(log.Fields{"error": err, "path": keyIDPath}).Fatal("generating node keys") return } log.Info("keys generated") }, } func createFile(filename string, data []byte) error { dir := filepath.Dir(filename) if _, err := os.Stat(dir); os.IsNotExist(err) { err := os.Mkdir(dir, 0775) if err != nil { return errors.Wrapf(err, "creating dir %s", dir) } } return os.WriteFile(filename, data, fileMode) } func createKeyPair(privFilename, pubFilename string) (priv, pub []byte, err error) { priv, pub, err = crypto.GenKeyPair() if err != nil { log.WithError(err).Error("generate keys") return } err = createFile(privFilename, []byte(hex.EncodeToString(priv))) if err != nil { log.WithFields(log.Fields{"error": err, "path": privFilename}).Error("creating private key") return } err = createFile(pubFilename, []byte(crypto.PubToHex(pub))) if err != nil { log.WithFields(log.Fields{"error": err, "path": pubFilename}).Error("creating public key") return } return } ================================================ FILE: cmd/initDatabase.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) // initDatabaseCmd represents the initDatabase command var initDatabaseCmd = &cobra.Command{ Use: "initDatabase", Short: "Initializing database", PreRun: loadConfigWKey, Run: func(cmd *cobra.Command, args []string) { if err := sqldb.InitDB(conf.Config.DB); err != nil { log.WithError(err).Fatal("init db") } log.Info("initDatabase completed") }, } ================================================ FILE: cmd/rollback.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/rollback" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var blockID int64 // rollbackCmd represents the rollback command var rollbackCmd = &cobra.Command{ Use: "rollback", Short: "Rollback blockchain to blockID", PreRun: loadConfigWKey, Run: func(cmd *cobra.Command, args []string) { f := utils.LockOrDie(conf.Config.DirPathConf.LockFilePath) defer f.Unlock() if err := sqldb.GormInit(conf.Config.DB); err != nil { log.WithError(err).Fatal("init db") return } if err := syspar.SysUpdate(nil); err != nil { log.WithError(err).Error("can't read platform parameters") } if err := syspar.SysTableColType(nil); err != nil { log.WithError(err).Error("updating sys table col type") } smart.InitVM() if err := smart.LoadContracts(); err != nil { log.WithError(err).Fatal("loading contracts") return } err := rollback.ToBlockID(blockID, nil, log.WithFields(log.Fields{})) if err != nil { log.WithError(err).Fatal("rollback to block id") return } // block id = 1, is a special case for full rollback if blockID != 1 { log.Info("Not full rollback, finishing work without checking") return } }, } func init() { rollbackCmd.Flags().Int64Var(&blockID, "blockId", 1, "blockID to rollback") rollbackCmd.MarkFlagRequired("blockId") } ================================================ FILE: cmd/root.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "fmt" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "path/filepath" "github.com/IBAX-io/go-ibax/packages/conf" ) var ( buildBranch = "" buildDate = "" commitHash = "" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "go-ibax", Short: "ibax application", } func init() { rootCmd.AddCommand( generateFirstBlockCmd, generateKeysCmd, initDatabaseCmd, rollbackCmd, startCmd, configCmd, stopNetworkCmd, versionCmd, ) consts.BuildInfo = func() string { if buildBranch == "" { return fmt.Sprintf("branch.%s commit.%s time.%s", "unknown", "unknown", "unknown") } return fmt.Sprintf("branch.%s commit.%s time.%s", buildBranch, commitHash, buildDate) }() // This flags are visible for all child commands rootCmd.PersistentFlags().StringVar(&conf.Config.ConfigPath, "config", defautConfigPath(), "filepath to config.toml") } // Execute executes rootCmd command. // This is called by main.main(). It only needs to happen once to the rootCmd func Execute() { if err := rootCmd.Execute(); err != nil { log.WithError(err).Fatal("Executing root command") } } func defautConfigPath() string { //p, err := os.Getwd() //if err != nil { // log.WithError(err).Fatal("getting cur wd") //} // //return filepath.Join(p, "data", "config.toml") return filepath.Join("data", "config.toml") } // Load the configuration from file func loadConfig(cmd *cobra.Command, args []string) { err := conf.LoadConfig(conf.Config.ConfigPath) if err != nil { log.WithError(err).Fatal("Loading config") } } func loadConfigWKey(cmd *cobra.Command, args []string) { loadConfig(cmd, args) err := conf.FillRuntimeKey() if err != nil { log.WithError(err).Fatal("Filling keys") } } ================================================ FILE: cmd/start.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "time" "github.com/IBAX-io/go-ibax/packages/chain" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/spf13/cobra" ) // startCmd is starting node var startCmd = &cobra.Command{ Use: "start", Short: "Starting node", PreRun: loadConfigWKey, Run: func(cmd *cobra.Command, args []string) { chain.Start() }, } func init() { time.Local = time.UTC startCmd.Flags().BoolVar(&conf.Config.TestRollBack, "testRollBack", false, "Starts special set of daemons") startCmd.Flags().BoolVar(&conf.Config.FuncBench, "funcBench", false, "Disable access checking in some built-in functions for benchmarks") } ================================================ FILE: cmd/stopNetwork.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "os" "path/filepath" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( addrsForStopping []string stopNetworkCertFilepath string ) // stopNetworkCmd represents the stopNetworkCmd command var stopNetworkCmd = &cobra.Command{ Use: "stopNetwork", Short: "Sending a special transaction to stop the network", PreRun: loadConfigWKey, Run: func(cmd *cobra.Command, args []string) { fp := filepath.Join(conf.Config.DirPathConf.KeysDir, stopNetworkCertFilepath) stopNetworkCert, err := os.ReadFile(fp) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "filepath": fp}).Fatal("Reading cert data") } req := &network.StopNetworkRequest{ Data: stopNetworkCert, } errCount := 0 for _, addr := range addrsForStopping { if err := tcpclient.SendStopNetwork(addr, req); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.NetworkError, "addr": addr}).Errorf("Sending request") errCount++ continue } log.WithFields(log.Fields{"addr": addr}).Info("Sending request") } log.WithFields(log.Fields{ "successful": len(addrsForStopping) - errCount, "failed": errCount, }).Info("Complete") }, } func init() { stopNetworkCmd.Flags().StringVar(&stopNetworkCertFilepath, "stopNetworkCert", "", "Filepath to certificate for network stopping") stopNetworkCmd.Flags().StringArrayVar(&addrsForStopping, "addr", []string{}, "Node address") stopNetworkCmd.MarkFlagRequired("stopNetworkCert") stopNetworkCmd.MarkFlagRequired("addr") } ================================================ FILE: cmd/version.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package cmd import ( "fmt" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/spf13/cobra" ) // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", Short: "Show version", Run: func(cmd *cobra.Command, args []string) { fmt.Println(consts.Version()) }, } ================================================ FILE: go.mod ================================================ module github.com/IBAX-io/go-ibax go 1.20 require ( github.com/360EntSecGroup-Skylar/excelize v1.4.1 github.com/BurntSushi/toml v1.3.2 github.com/btcsuite/btcd v0.24.2 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.6 github.com/cactus/go-statsd-client/v5 v5.1.0 github.com/centrifugal/gocent v2.2.0+incompatible github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/didip/tollbooth v4.0.2+incompatible github.com/go-redis/redis v6.15.9+incompatible github.com/gobuffalo/fizz v1.14.4 github.com/gogo/protobuf v1.3.2 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.0 github.com/ochinchina/go-ini v1.0.1 github.com/ochinchina/supervisord/config v0.0.0-20230719054037-813956ff6a67 github.com/ochinchina/supervisord/process v0.0.0-20230719054037-813956ff6a67 github.com/pkg/errors v0.9.1 github.com/robfig/cron/v3 v3.0.1 github.com/shopspring/decimal v1.3.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/theckman/go-flock v0.8.1 github.com/tjfoc/gmsm v1.4.1 github.com/vmihailenco/msgpack/v5 v5.3.5 golang.org/x/crypto v0.23.0 gorm.io/driver/postgres v1.5.2 gorm.io/gorm v1.25.2 ) require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/gobuffalo/github_flavored_markdown v1.1.4 // indirect github.com/gobuffalo/helpers v0.6.7 // indirect github.com/gobuffalo/plush/v4 v4.1.19 // indirect github.com/gobuffalo/tags/v3 v3.1.4 // indirect github.com/gobuffalo/validate/v3 v3.3.3 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/rpc v1.2.0 // indirect github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.4.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/ochinchina/filechangemonitor v0.3.1 // indirect github.com/ochinchina/gorilla-xmlrpc v0.0.0-20171012055324-ecf2fe693a2c // indirect github.com/ochinchina/supervisord/events v0.0.0-20230719054037-813956ff6a67 // indirect github.com/ochinchina/supervisord/faults v0.0.0-20230719054037-813956ff6a67 // indirect github.com/ochinchina/supervisord/logger v0.0.0-20230719054037-813956ff6a67 // indirect github.com/ochinchina/supervisord/signals v0.0.0-20230719054037-813956ff6a67 // indirect github.com/ochinchina/supervisord/util v0.0.0-20230719054037-813956ff6a67 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4= github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrifugal/gocent v2.2.0+incompatible h1:49oQLm1CDojd8vgz2w5RrECgW3Ew+Z5muIQGIggI2Vk= github.com/centrifugal/gocent v2.2.0+incompatible/go.mod h1:gtbj3+fMApCIcaGmGvk2BinwEauUtGeu8YZPLcedOvQ= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/fizz v1.14.4 h1:8uume7joF6niTNWN582IQ2jhGTUoa9g1fiV/tIoGdBs= github.com/gobuffalo/fizz v1.14.4/go.mod h1:9/2fGNXNeIFOXEEgTPJwiK63e44RjG+Nc4hfMm1ArGM= github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= github.com/gobuffalo/github_flavored_markdown v1.1.4 h1:WacrEGPXUDX+BpU1GM/Y0ADgMzESKNWls9hOTG1MHVs= github.com/gobuffalo/github_flavored_markdown v1.1.4/go.mod h1:Vl9686qrVVQou4GrHRK/KOG3jCZOKLUqV8MMOAYtlso= github.com/gobuffalo/helpers v0.6.7 h1:C9CedoRSfgWg2ZoIkVXgjI5kgmSpL34Z3qdnzpfNVd8= github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= github.com/gobuffalo/plush/v4 v4.1.16/go.mod h1:6t7swVsarJ8qSLw1qyAH/KbrcSTwdun2ASEQkOznakg= github.com/gobuffalo/plush/v4 v4.1.19 h1:o0E5gEJw+ozkAwQoCeiaWC6VOU2lEmX+GhtGkwpqZ8o= github.com/gobuffalo/plush/v4 v4.1.19/go.mod h1:WiKHJx3qBvfaDVlrv8zT7NCd3dEMaVR/fVxW4wqV17M= github.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM= github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4= github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg= github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/ochinchina/filechangemonitor v0.3.1 h1:Fyt8iE44kFwmI3ncNWAi21GZnmRBrAUSlMunpcDlMjQ= github.com/ochinchina/filechangemonitor v0.3.1/go.mod h1:OLRTJMpgb3yP1zBKA2g5GMYsKzJUoLq01lNOsReEzbQ= github.com/ochinchina/go-ini v1.0.1 h1:qrKGrgxJjY+4H8aV7B2HPohShzHGrymW+/X1Gx933zU= github.com/ochinchina/go-ini v1.0.1/go.mod h1:Tqs5+JmccLSNMX1KXbbyG/B3ro4J9uXVYC5U5VOeRE8= github.com/ochinchina/gorilla-xmlrpc v0.0.0-20171012055324-ecf2fe693a2c h1:6xgMUqscagnZicBedm1h4T3q6IQHbrrZp7bker+toOI= github.com/ochinchina/gorilla-xmlrpc v0.0.0-20171012055324-ecf2fe693a2c/go.mod h1:/gFmJ8Das0jFgYxzt/RkvAO62T/ZPcyTaZlOkEBu/jw= github.com/ochinchina/supervisord/config v0.0.0-20220721095143-c2527852d28f/go.mod h1:jMN/SL0T6GCWWG/dD7Les9iPqjQ2OjEVyBWx8c9RJqI= github.com/ochinchina/supervisord/config v0.0.0-20230719054037-813956ff6a67 h1:u5/DJWJrK2k7uRPo2Oex1xam15ZKlIaRfmfYugeECS4= github.com/ochinchina/supervisord/config v0.0.0-20230719054037-813956ff6a67/go.mod h1:jMN/SL0T6GCWWG/dD7Les9iPqjQ2OjEVyBWx8c9RJqI= github.com/ochinchina/supervisord/events v0.0.0-20220721095143-c2527852d28f/go.mod h1:I+vx/d8jVSVe0nmhaJUPuouUnhpfUUiUbDciSDViL5I= github.com/ochinchina/supervisord/events v0.0.0-20230719054037-813956ff6a67 h1:hAS+XSoEc3szEJKdlbP1BFWIivUOyAnBfm5Ee9tL+cI= github.com/ochinchina/supervisord/events v0.0.0-20230719054037-813956ff6a67/go.mod h1:I+vx/d8jVSVe0nmhaJUPuouUnhpfUUiUbDciSDViL5I= github.com/ochinchina/supervisord/faults v0.0.0-20220721095143-c2527852d28f/go.mod h1:kReR3fnUfV2OHFESJ9IDriCTg/VU1D0cFfNPNOnMQCs= github.com/ochinchina/supervisord/faults v0.0.0-20230719054037-813956ff6a67 h1:dqmuzSF/uRe55HEcgHN5PtuKJGNVqZ54K7ucbiGQePw= github.com/ochinchina/supervisord/faults v0.0.0-20230719054037-813956ff6a67/go.mod h1:kReR3fnUfV2OHFESJ9IDriCTg/VU1D0cFfNPNOnMQCs= github.com/ochinchina/supervisord/logger v0.0.0-20220721095143-c2527852d28f/go.mod h1:DPIKvK0KqJdneJKrAqfLNgONWZ3m4cRAi96PG0A7deg= github.com/ochinchina/supervisord/logger v0.0.0-20230719054037-813956ff6a67 h1:Oi6D5XysnDTRo0Di3D+1MLfhNAjHmv/+eflPSOUFFOU= github.com/ochinchina/supervisord/logger v0.0.0-20230719054037-813956ff6a67/go.mod h1:DPIKvK0KqJdneJKrAqfLNgONWZ3m4cRAi96PG0A7deg= github.com/ochinchina/supervisord/process v0.0.0-20230719054037-813956ff6a67 h1:SHEZn3Qhc5SdhSAASQ9OCJuQGIT04HAmml1gIIEhVhU= github.com/ochinchina/supervisord/process v0.0.0-20230719054037-813956ff6a67/go.mod h1:QUKHvTSxsiZN56GSHhWN59LNropTaL5cwHtu97pvsVY= github.com/ochinchina/supervisord/signals v0.0.0-20220721095143-c2527852d28f/go.mod h1:o2x4RZxVWzKvgbSOv7G8z94pITwuweY+ZkITvp/VqGY= github.com/ochinchina/supervisord/signals v0.0.0-20230719054037-813956ff6a67 h1:ULpyG32Ravk2wnbqhMosCXvvmToSm3bTwRCstZ121Ms= github.com/ochinchina/supervisord/signals v0.0.0-20230719054037-813956ff6a67/go.mod h1:o2x4RZxVWzKvgbSOv7G8z94pITwuweY+ZkITvp/VqGY= github.com/ochinchina/supervisord/util v0.0.0-20220721095143-c2527852d28f/go.mod h1:V/yb0hfd2ax3Pzn83yoxBxww4HLJ5AXYH+rQBCieqcU= github.com/ochinchina/supervisord/util v0.0.0-20230719054037-813956ff6a67 h1:TGe+60SSuWJGK+N8PtsEEQ3VzfrdNjK5fColmRHau64= github.com/ochinchina/supervisord/util v0.0.0-20230719054037-813956ff6a67/go.mod h1:V/yb0hfd2ax3Pzn83yoxBxww4HLJ5AXYH+rQBCieqcU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31 h1:DE4LcMKyqAVa6a0CGmVxANbnVb7stzMmPkQiieyNmfQ= github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/theckman/go-flock v0.8.1 h1:kTixuOsFBOtGYSTLRLWK6GOs1hk/8OD11sR1pDd0dl4= github.com/theckman/go-flock v0.8.1/go.mod h1:kjuth3y9VJ2aNlkNEO99G/8lp9fMIKaGyBmh84IBheM= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: main.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package main import ( "runtime" "github.com/IBAX-io/go-ibax/cmd" ) func main() { runtime.LockOSThread() cmd.Execute() } ================================================ FILE: packages/api/api.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "encoding/json" "net/http" "strings" "github.com/gorilla/schema" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) const ( multipartBuf = 100000 // the buffer size for ParseMultipartForm multipartFormData = "multipart/form-data" contentType = "Content-Type" ) type Mode struct { EcosystemGetter types.EcosystemGetter ContractRunner types.SmartContractRunner ClientTxProcessor types.ClientTxPreprocessor } // Client represents data of client type Client struct { KeyID int64 AccountID string EcosystemID int64 EcosystemName string RoleID int64 } func (c *Client) Prefix() string { return converter.Int64ToStr(c.EcosystemID) } func jsonResponse(w http.ResponseWriter, v any) { jsonResult, err := json.Marshal(v) if err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marhsalling http response to json") errorResponse(w, err, http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Write(jsonResult) } func errorResponse(w http.ResponseWriter, err error, code ...int) { et, ok := err.(errType) if !ok { et = errServer et.Message = err.Error() } w.Header().Set("X-Content-Type-Options", "nosniff") if len(code) == 0 { w.WriteHeader(et.Status) } else { w.WriteHeader(code[0]) } jsonResponse(w, et) } type formValidator interface { Validate(r *http.Request) error } type nopeValidator struct{} func (np nopeValidator) Validate(r *http.Request) error { return nil } func parseForm(r *http.Request, f formValidator) (err error) { if isMultipartForm(r) { err = r.ParseMultipartForm(multipartBuf) } else { err = r.ParseForm() } if err != nil { return } decoder := schema.NewDecoder() decoder.IgnoreUnknownKeys(true) if err := decoder.Decode(f, r.Form); err != nil { return err } return f.Validate(r) } func isMultipartForm(r *http.Request) bool { return strings.HasPrefix(r.Header.Get(contentType), multipartFormData) } type hexValue struct { value []byte } func (hv hexValue) Bytes() []byte { return hv.value } func (hv *hexValue) UnmarshalText(v []byte) (err error) { hv.value, err = hex.DecodeString(string(v)) return } ================================================ FILE: packages/api/api_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "bytes" "encoding/hex" "encoding/json" "errors" "fmt" "io" "mime/multipart" "net/http" "net/url" "os" "strconv" "strings" "testing" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/stretchr/testify/assert" ) var apiAddress = "http://localhost:7079" var ( gAuth string gAddress string gPrivate, gPublic string ) // PrivateToPublicHex returns the hex public key for the specified hex private key. func PrivateToPublicHex(hexkey string) (string, error) { key, err := hex.DecodeString(hexkey) if err != nil { return ``, fmt.Errorf("Decode hex error") } pubKey, err := crypto.PrivateToPublic(key) if err != nil { return ``, err } return crypto.PubToHex(pubKey), nil } func sendRawRequest(rtype, url string, form *url.Values) ([]byte, error) { client := &http.Client{} var ioform io.Reader if form != nil { ioform = strings.NewReader(form.Encode()) } req, err := http.NewRequest(rtype, apiAddress+consts.ApiPath+url, ioform) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if len(gAuth) > 0 { req.Header.Set("Authorization", jwtPrefix+gAuth) } resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(`%d %s`, resp.StatusCode, strings.TrimSpace(string(data))) } return data, nil } func sendRequest(rtype, url string, form *url.Values, v any) error { data, err := sendRawRequest(rtype, url, form) if err != nil { return err } return json.Unmarshal(data, v) } func sendGet(url string, form *url.Values, v any) error { return sendRequest("GET", url, form, v) } func sendPost(url string, form *url.Values, v any) error { return sendRequest("POST", url, form, v) } func keyLogin(state int64) (err error) { var ( key, sign []byte ) key, err = os.ReadFile(`key`) if err != nil { return } if len(key) > 64 { key = key[:64] } var ret getUIDResult err = sendGet(`getuid`, nil, &ret) if err != nil { return } gAuth = ret.Token if len(ret.UID) == 0 { return fmt.Errorf(`getuid has returned empty uid`) } var pub string sign, err = crypto.SignString(string(key), `LOGIN`+ret.NetworkID+ret.UID) if err != nil { return } pub, err = PrivateToPublicHex(string(key)) if err != nil { return } form := url.Values{"pubkey": {pub}, "signature": {hex.EncodeToString(sign)}, `ecosystem`: {converter.Int64ToStr(state)}, "role_id": {"0"}} var logret loginResult err = sendPost(`login`, &form, &logret) if err != nil { return } gAddress = logret.Account gPrivate = string(key) gPublic, err = PrivateToPublicHex(gPrivate) gAuth = logret.Token if err != nil { return } return } func keyLoginToken(state int64) (err error) { var ( key, sign []byte ) str, _ := os.Getwd() fmt.Println("dir " + str) key, err = os.ReadFile(`key`) if err != nil { return } if len(key) > 64 { key = key[:64] } var ret getUIDResult err = sendGet(`getuid`, nil, &ret) if err != nil { return } gAuth = ret.Token if len(ret.UID) == 0 { return fmt.Errorf(`getuid has returned empty uid`) } var pub string sign, err = crypto.SignString(string(key), `LOGIN`+ret.NetworkID+ret.UID) if err != nil { return } pub, err = PrivateToPublicHex(string(key)) if err != nil { return } form := url.Values{"pubkey": {pub}, "signature": {hex.EncodeToString(sign)}, `ecosystem`: {converter.Int64ToStr(state)}, "role_id": {"0"}, "expire": {"5"}} var logret loginResult err = sendPost(`login`, &form, &logret) if err != nil { return } gAddress = logret.Account gPrivate = string(key) gPublic, err = PrivateToPublicHex(gPrivate) gAuth = logret.Token if err != nil { return } return } func keyLoginex(state int64, m ...string) (err error) { var ( key, sign []byte ) key, err = os.ReadFile(`key` + m[0]) if err != nil { return } if len(key) > 64 { key = key[:64] } var ret getUIDResult err = sendGet(`getuid`, nil, &ret) if err != nil { return } gAuth = ret.Token if len(ret.UID) == 0 { return fmt.Errorf(`getuid has returned empty uid`) } var pub string sign, err = crypto.SignString(string(key), `LOGIN`+ret.NetworkID+ret.UID) if err != nil { return } pub, err = PrivateToPublicHex(string(key)) if err != nil { return } form := url.Values{"pubkey": {pub}, "signature": {hex.EncodeToString(sign)}, `ecosystem`: {converter.Int64ToStr(state)}, "role_id": {"0"}} var logret loginResult err = sendPost(`login`, &form, &logret) if err != nil { return } gAddress = logret.Account gPrivate = string(key) gPublic, err = PrivateToPublicHex(gPrivate) gAuth = logret.Token if err != nil { return } return } func waitTx(hash string) (blockid int64, penalty int64, err error) { data, err := json.Marshal(&txstatusRequest{ Hashes: []string{hash}, }) if err != nil { return } for i := 0; i < 100; i++ { var multiRet multiTxStatusResult err = sendPost(`txstatus`, &url.Values{ "data": {string(data)}, }, &multiRet) if err != nil { return } ret := multiRet.Results[hash] var errtext []byte if len(ret.BlockID) > 0 { blockid = converter.StrToInt64(ret.BlockID) penalty = ret.Penalty if ret.Penalty == 1 { errtext, err = json.Marshal(ret.Message) if err != nil { return } err = errors.New(string(errtext)) return } else { err = fmt.Errorf(ret.Result) return } } if ret.Message != nil { errtext, err = json.Marshal(ret.Message) if err != nil { return } err = errors.New(string(errtext)) return } time.Sleep(time.Second) } return 0, 0, fmt.Errorf(`TxStatus timeout`) } func randName(prefix string) string { return fmt.Sprintf(`%s%d`, prefix, time.Now().Unix()) } type getter interface { Get(string) string } type contractParams map[string]any func (cp *contractParams) Get(key string) string { if _, ok := (*cp)[key]; !ok { return "" } return fmt.Sprintf("%v", (*cp)[key]) } func (cp *contractParams) GetRaw(key string) any { return (*cp)[key] } func postTxResult(name string, form getter) (id int64, msg string, err error) { var contract getContractResult if err = sendGet("contract/"+name, nil, &contract); err != nil { return } params := make(map[string]any) for _, field := range contract.Fields { name := field.Name value := form.Get(name) if len(value) == 0 { continue } switch field.Type { case "bool": params[name], err = strconv.ParseBool(value) case "int", "address": params[name], err = strconv.ParseInt(value, 10, 64) case "float": params[name], err = strconv.ParseFloat(value, 64) case "array": var v any err = json.Unmarshal([]byte(value), &v) params[name] = v case "map": var v map[string]any err = json.Unmarshal([]byte(value), &v) params[name] = v case "string", "money": params[name] = value case "file", "bytes": if cp, ok := form.(*contractParams); !ok { err = fmt.Errorf("Form is not *contractParams type") } else { params[name] = cp.GetRaw(name) } } if err != nil { err = fmt.Errorf("Parse param '%s': %s", name, err) return } } var privateKey, publicKey []byte if privateKey, err = hex.DecodeString(gPrivate); err != nil { return } if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { return } data, hash, err := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(contract.ID), EcosystemID: 1, Time: time.Now().Unix(), KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, Params: params, Lang: "en", }, privateKey) if err != nil { return 0, "", err } ret := &sendTxResult{} err = sendMultipart("sendTx", map[string][]byte{ hex.EncodeToString(hash): data, }, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { return } id, penalty, err := waitTx(ret.Hashes[hex.EncodeToString(hash)]) if id != 0 && err != nil { if penalty == 1 { return } msg = err.Error() err = nil } return } func postTxResultMultipart(name string, form getter) (id int64, msg string, err error) { var contract getContractResult if err = sendGet("contract/"+name, nil, &contract); err != nil { return } params := make(map[string]any) for _, field := range contract.Fields { name := field.Name value := form.Get(name) if len(value) == 0 { continue } switch field.Type { case "bool": params[name], err = strconv.ParseBool(value) case "int", "address": params[name], err = strconv.ParseInt(value, 10, 64) case "float": params[name], err = strconv.ParseFloat(value, 64) case "array": var v any err = json.Unmarshal([]byte(value), &v) params[name] = v case "map": var v map[string]any err = json.Unmarshal([]byte(value), &v) params[name] = v case "string", "money": params[name] = value case "file", "bytes": if cp, ok := form.(*contractParams); !ok { err = fmt.Errorf("Form is not *contractParams type") } else { params[name] = cp.GetRaw(name) } } if err != nil { err = fmt.Errorf("Parse param '%s': %s", name, err) return } } var privateKey, publicKey []byte if privateKey, err = hex.DecodeString(gPrivate); err != nil { return } if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { return } arrData := make(map[string][]byte) for i := 0; i < 1; i++ { conname := crypto.RandSeq(10) params["ApplicationId"] = int64(1) params["Conditions"] = "1" //params["TokenEcosystem"] = int64(2) params["Value"] = fmt.Sprintf(`contract rnd%v%d { action { }}`, conname, i) expedite := strconv.Itoa(1) data, txhash, _ := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(contract.ID), Time: time.Now().Unix(), EcosystemID: 1, KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, Params: params, Expedite: expedite, }, privateKey) arrData[fmt.Sprintf("%x", txhash)] = data fmt.Println(fmt.Sprintf("%x", txhash)) } ret := &sendTxResult{} err = sendMultipart("sendTx", arrData, &ret) //err = sendMultipart("sendTx", map[string][]byte{ // "data": data, //}, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { return } //var ids, ps []int64 // //for s := range arrData { // id, penalty, err := waitTx(ret.Hashes[s]) // ids = append(ids, id) // ps = append(ps, penalty) // if id != 0 && err != nil { // if penalty == 1 { // //return // } // msg = err.Error() // err = nil // } //} //fmt.Println(ids, ps) return } func postSignTxResult(name string, form getter) (id int64, msg string, err error) { var contract getContractResult if err = sendGet("contract/"+name, nil, &contract); err != nil { return } params := make(map[string]any) for _, field := range contract.Fields { name := field.Name value := form.Get(name) if len(value) == 0 { continue } switch field.Type { case "bool": params[name], err = strconv.ParseBool(value) case "int", "address": params[name], err = strconv.ParseInt(value, 10, 64) case "float": params[name], err = strconv.ParseFloat(value, 64) case "array": var v any err = json.Unmarshal([]byte(value), &v) params[name] = v case "map": var v map[string]any err = json.Unmarshal([]byte(value), &v) params[name] = v case "string", "money": params[name] = value case "file", "bytes": if cp, ok := form.(*contractParams); !ok { err = fmt.Errorf("Form is not *contractParams type") } else { params[name] = cp.GetRaw(name) } } if err != nil { err = fmt.Errorf("Parse param '%s': %s", name, err) return } } var privateKey, publicKey []byte if privateKey, err = hex.DecodeString(gPrivate); err != nil { return } if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { return } data, _, err := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(contract.ID), EcosystemID: 1, Time: time.Now().Unix(), KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, Params: params, }, privateKey) if err != nil { return 0, "", err } ret := &sendTxResult{} err = sendMultipart("sendSignTx", map[string][]byte{ "data": data, }, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { return } id, penalty, err := waitTx(ret.Hashes["data"]) if id != 0 && err != nil { if penalty == 1 { return } msg = err.Error() err = nil } return } func postTxResult2(name string, form getter) (id int64, msg string, err error) { var contract getContractResult if err = sendGet("contract/"+name, nil, &contract); err != nil { return } params := make(map[string]any) for _, field := range contract.Fields { name := field.Name value := form.Get(name) if len(value) == 0 { continue } switch field.Type { case "bool": params[name], err = strconv.ParseBool(value) case "int", "address": params[name], err = strconv.ParseInt(value, 10, 64) case "float": params[name], err = strconv.ParseFloat(value, 64) case "array": var v any err = json.Unmarshal([]byte(value), &v) params[name] = v case "map": var v map[string]any err = json.Unmarshal([]byte(value), &v) params[name] = v case "string", "money": params[name] = value case "file", "bytes": if cp, ok := form.(*contractParams); !ok { err = fmt.Errorf("Form is not *contractParams type") } else { params[name] = cp.GetRaw(name) } } if err != nil { err = fmt.Errorf("Parse param '%s': %s", name, err) return } } var privateKey, publicKey []byte if privateKey, err = hex.DecodeString(gPrivate); err != nil { return } if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { return } data, _, err := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(contract.ID), EcosystemID: 2, Time: time.Now().Unix(), KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, Params: params, }, privateKey) if err != nil { return 0, "", err } ret := &sendTxResult{} err = sendMultipart("sendTx", map[string][]byte{ "data": data, }, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { return } id, penalty, err := waitTx(ret.Hashes["data"]) if id != 0 && err != nil { if penalty == 1 { return } msg = err.Error() err = nil } return } func RawToString(input json.RawMessage) string { out := strings.Trim(string(input), `"`) return strings.Replace(out, `\"`, `"`, -1) } func postTx(txname string, form *url.Values) error { _, _, err := postTxResult(txname, form) return err } func postTxMultipart(txname string, form *url.Values) error { _, _, err := postTxResultMultipart(txname, form) return err } func postTransferSelfTxMultipart(form *url.Values) error { _, _, err := postTransferSelfTxResult(form) return err } func postUTXOTxMultipart(form *url.Values) error { _, _, err := postUTXOTxResult(form) return err } func postTransferSelfTxResult(form getter) (id int64, msg string, err error) { var privateKey, publicKey []byte if privateKey, err = hex.DecodeString(gPrivate); err != nil { return } if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { return } data, _, err := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(1), EcosystemID: 1, Time: time.Now().Unix(), KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, TransferSelf: &types.TransferSelf{ Value: "1000000000000000000", //Asset: "IBAX", Source: "UTXO", Target: "Account", }, }, privateKey) if err != nil { return 0, "", err } ret := &sendTxResult{} err = sendMultipart("sendTx", map[string][]byte{ "data": data, }, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { return } id, penalty, err := waitTx(ret.Hashes["data"]) if id != 0 && err != nil { if penalty == 1 { return } msg = err.Error() err = nil } return } func postUTXOTxResult(form getter) (id int64, msg string, err error) { var privateKey, publicKey []byte if privateKey, err = hex.DecodeString(gPrivate); err != nil { return } if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { return } data, _, err := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(1), EcosystemID: 1, Time: time.Now().Unix(), KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, UTXO: &types.UTXO{ Value: "1000000000000000", ToID: -8055926748644556208, }, }, privateKey) if err != nil { return 0, "", err } ret := &sendTxResult{} err = sendMultipart("sendTx", map[string][]byte{ "data": data, }, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { return } id, penalty, err := waitTx(ret.Hashes["data"]) if id != 0 && err != nil { if penalty == 1 { return } msg = err.Error() err = nil } return } func postSignTx(txname string, form *url.Values) error { _, _, err := postSignTxResult(txname, form) return err } func cutErr(err error) string { out := err.Error() if off := strings.IndexByte(out, '('); off != -1 { out = out[:off] } return strings.TrimSpace(out) } func TestGetAvatar(t *testing.T) { err := keyLogin(1) assert.NoError(t, err) url := `http://localhost:7079` + consts.ApiPath + "avatar/-1744264011260937456" req, err := http.NewRequest(http.MethodGet, url, nil) assert.NoError(t, err) if len(gAuth) > 0 { req.Header.Set("Authorization", jwtPrefix+gAuth) } cli := http.DefaultClient resp, err := cli.Do(req) assert.NoError(t, err) defer resp.Body.Close() mime := resp.Header.Get("Content-Type") expectedMime := "image/png" assert.Equal(t, expectedMime, mime, "content type must be a '%s' but returns '%s'", expectedMime, mime) } func sendMultipart(url string, files map[string][]byte, v any) error { body := new(bytes.Buffer) writer := multipart.NewWriter(body) for key, data := range files { part, err := writer.CreateFormFile(key, key) if err != nil { return err } if _, err := part.Write(data); err != nil { return err } } if err := writer.Close(); err != nil { return err } req, err := http.NewRequest("POST", apiAddress+consts.ApiPath+url, body) if err != nil { return err } req.Header.Set("Content-Type", writer.FormDataContentType()) if len(gAuth) > 0 { req.Header.Set("Authorization", jwtPrefix+gAuth) } client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf(`%d %s`, resp.StatusCode, strings.TrimSpace(string(data))) } return json.Unmarshal(data, &v) } ================================================ FILE: packages/api/app_content.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/gorilla/mux" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type appContentResult struct { Snippets []sqldb.Snippet `json:"snippets"` Pages []sqldb.Page `json:"pages"` Contracts []sqldb.Contract `json:"contracts"` } func (m Mode) getAppContentHandler(w http.ResponseWriter, r *http.Request) { form := &appParamsForm{ ecosystemForm: ecosystemForm{ Validator: m.EcosystemGetter, }, } if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } logger := getLogger(r) params := mux.Vars(r) sni := &sqldb.Snippet{} p := &sqldb.Page{} c := &sqldb.Contract{} appID := converter.StrToInt64(params["appID"]) ecosystemID := converter.StrToInt64(form.EcosystemPrefix) snippets, err := sni.GetByApp(appID, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting block interfaces by appID") errorResponse(w, err) return } pages, err := p.GetByApp(appID, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting pages by appID") errorResponse(w, err) return } contracts, err := c.GetByApp(appID, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting pages by appID") errorResponse(w, err) return } jsonResponse(w, &appContentResult{ Snippets: snippets, Pages: pages, Contracts: contracts, }) } ================================================ FILE: packages/api/app_content_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "testing" "github.com/stretchr/testify/assert" ) func TestAppContent(t *testing.T) { assert.NoError(t, keyLogin(1)) var ret appContentResult err := sendGet(`appcontent/1`, nil, &ret) if err != nil { t.Error(err) return } if len(ret.Snippets) == 0 { t.Error("incorrect snippets count") } if len(ret.Contracts) == 0 { t.Error("incorrect contracts count") } if len(ret.Pages) == 0 { t.Error("incorrent pages count") } } ================================================ FILE: packages/api/appparam.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) func (m Mode) GetAppParamHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) form := &ecosystemForm{ Validator: m.EcosystemGetter, } if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) ap := &sqldb.AppParam{} ap.SetTablePrefix(form.EcosystemPrefix) name := params["name"] found, err := ap.Get(nil, converter.StrToInt64(params["appID"]), name) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting app parameter by name") errorResponse(w, err) return } if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "key": name}).Debug("app parameter not found") errorResponse(w, errParamNotFound.Errorf(name)) return } jsonResponse(w, ¶mResult{ ID: converter.Int64ToStr(ap.ID), Name: ap.Name, Value: ap.Value, Conditions: ap.Conditions, }) } ================================================ FILE: packages/api/appparams.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type appParamsResult struct { App string `json:"app_id"` List []paramResult `json:"list"` } type appParamsForm struct { ecosystemForm paramsForm } func (f *appParamsForm) Validate(r *http.Request) error { return f.ecosystemForm.Validate(r) } func (m Mode) getAppParamsHandler(w http.ResponseWriter, r *http.Request) { form := &appParamsForm{ ecosystemForm: ecosystemForm{ Validator: m.EcosystemGetter, }, } if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) logger := getLogger(r) ap := &sqldb.AppParam{} ap.SetTablePrefix(form.EcosystemPrefix) list, err := ap.GetAllAppParameters(converter.StrToInt64(params["appID"]), nil, nil, nil) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting all app parameters") } result := &appParamsResult{ App: params["appID"], List: make([]paramResult, 0), } acceptNames := form.AcceptNames() for _, item := range list { if len(acceptNames) > 0 && !acceptNames[item.Name] { continue } result.List = append(result.List, paramResult{ ID: converter.Int64ToStr(item.ID), Name: item.Name, Value: item.Value, Conditions: item.Conditions, }) } jsonResponse(w, result) } ================================================ FILE: packages/api/auth.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "errors" "fmt" "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" "github.com/golang-jwt/jwt/v4" ) var ( jwtSecret []byte jwtPrefix = "Bearer " jwtExpire = 28800 // By default, seconds jwtrefeshExpire = 600 // By default, seconds //jwtrefeshExpire = 10 // By default, seconds test errJWTAuthValue = errors.New("wrong authorization value") errEcosystemNotFound = errors.New("ecosystem not found") ) // JWTClaims is storing jwt claims type JWTClaims struct { UID string `json:"uid,omitempty"` EcosystemID string `json:"ecosystem_id,omitempty"` KeyID string `json:"key_id,omitempty"` AccountID string `json:"account_id,omitempty"` RoleID string `json:"role_id,omitempty"` jwt.RegisteredClaims } func generateJWTToken(claims JWTClaims) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(jwtSecret) } func parseJWTToken(header string) (*jwt.Token, error) { if len(header) == 0 { return nil, nil } if strings.HasPrefix(header, jwtPrefix) { header = header[len(jwtPrefix):] } else { return nil, errJWTAuthValue } return jwt.ParseWithClaims(header, &JWTClaims{}, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(jwtSecret), nil }) } func getClientFromToken(token *jwt.Token, ecosysNameService types.EcosystemGetter) (*Client, error) { claims, ok := token.Claims.(*JWTClaims) if !ok { return nil, nil } if len(claims.KeyID) == 0 { return nil, nil } client := &Client{ EcosystemID: converter.StrToInt64(claims.EcosystemID), KeyID: converter.StrToInt64(claims.KeyID), AccountID: claims.AccountID, RoleID: converter.StrToInt64(claims.RoleID), } sID := converter.StrToInt64(claims.EcosystemID) name, err := ecosysNameService.GetEcosystemName(sID) if err != nil { return nil, err } client.EcosystemName = name return client, nil } type authStatusResponse struct { IsActive bool `json:"active"` ExpiresAt int64 `json:"exp,omitempty"` } func getAuthStatus(w http.ResponseWriter, r *http.Request) { result := new(authStatusResponse) defer jsonResponse(w, result) token := getToken(r) if token == nil { return } claims, ok := token.Claims.(*JWTClaims) if !ok { return } result.IsActive = true result.ExpiresAt = claims.ExpiresAt.Unix() } func InitJwtSecret(secret []byte) { if secret == nil { panic("jwt secret invalid") } jwtSecret = secret } ================================================ FILE: packages/api/balance.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) type balanceResult struct { Amount string `json:"amount"` Digits int64 `json:"digits"` Total string `json:"total"` Utxo string `json:"utxo"` TokenSymbol string `json:"token_symbol"` TokenName string `json:"token_name"` } func (m Mode) getBalanceHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) form := &ecosystemForm{ Validator: m.EcosystemGetter, } if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) keyID := converter.StringToAddress(params["wallet"]) if keyID == 0 { logger.WithFields(log.Fields{"type": consts.ConversionError, "value": params["wallet"]}).Error("converting wallet to address") errorResponse(w, errInvalidWallet.Errorf(params["wallet"])) return } key := &sqldb.Key{} key.SetTablePrefix(form.EcosystemID) _, err := key.Get(nil, keyID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting Key for wallet") errorResponse(w, err) return } accountAmount, _ := decimal.NewFromString(key.Amount) sp := &sqldb.SpentInfo{} utxoAmount, err := sp.GetBalance(nil, keyID, form.EcosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting UTXO Key for wallet") errorResponse(w, err) return } total := utxoAmount.Add(accountAmount) eco := sqldb.Ecosystem{} _, err = eco.Get(nil, form.EcosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting key balance token symbol") errorResponse(w, err) return } jsonResponse(w, &balanceResult{ Amount: key.Amount, Digits: eco.Digits, Total: total.String(), Utxo: utxoAmount.String(), TokenSymbol: eco.TokenSymbol, TokenName: eco.TokenName, }) } ================================================ FILE: packages/api/balance_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "testing" ) func TestBalance(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret balanceResult err := sendGet(`balance/`+gAddress, nil, &ret) if err != nil { t.Error(err) return } if len(ret.Amount) < 10 { t.Error(`too low balance`, ret) } err = sendGet(`balance/3434341`, nil, &ret) if err != nil { t.Error(err) return } if len(ret.Amount) > 0 { t.Error(fmt.Errorf(`wrong balance %s`, ret.Amount)) return } } func TestMoneyMoreSend(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } //for i := 0; i < 100; i++ { // form := url.Values{`Amount`: {`1`}, `Recipient`: {`1088-3972-0775-1704-9008`}, `Comment`: {`Test`}} // if err := postSignTx(`TokensSend`, &form); err != nil { // t.Error(err) // return // } // time.Sleep(2 * time.Second) //} //for i := 0; i < 2; i++ { // form := url.Values{`Amount`: {`-1`}, `Recipient`: {`1088-3972-0775-1704-9008`}, `Comment`: {`Test`}} // if err := postTx(`TokensSend`, &form); err != nil { // t.Error(err) // return // } // time.Sleep(2 * time.Second) //} form := url.Values{`Amount`: {`-1`}, `Account`: {`0323-3625-0280-2110-5478`}, `Type`: {`1`}} if err := postTx(`AddAssignMember`, &form); err != nil { t.Error(err) return } } ================================================ FILE: packages/api/block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "bytes" "errors" "net/http" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/common" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type maxBlockResult struct { MaxBlockID int64 `json:"max_block_id"` } func getMaxBlockHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) block := &sqldb.BlockChain{} found, err := block.GetMaxBlock() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting max block") errorResponse(w, err) return } if !found { logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("last block not found") errorResponse(w, errNotFound) return } jsonResponse(w, &maxBlockResult{block.ID}) } type blockInfoResult struct { Hash []byte `json:"hash"` EcosystemID int64 `json:"ecosystem_id"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Tx int32 `json:"tx_count"` RollbacksHash []byte `json:"rollbacks_hash"` NodePosition int64 `json:"node_position"` ConsensusMode int32 `json:"consensus_mode"` } func getBlockInfoHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) params := mux.Vars(r) blockID := converter.StrToInt64(params["id"]) block := sqldb.BlockChain{} found, err := block.Get(blockID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting block") errorResponse(w, err) return } if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "id": blockID}).Debug("block with id not found") errorResponse(w, errNotFound) return } jsonResponse(w, &blockInfoResult{ Hash: block.Hash, EcosystemID: block.EcosystemID, KeyID: block.KeyID, Time: block.Time, Tx: block.Tx, RollbacksHash: block.RollbacksHash, NodePosition: block.NodePosition, ConsensusMode: block.ConsensusMode, }) } type TxInfo struct { Hash []byte `json:"hash"` ContractName string `json:"contract_name"` Params map[string]any `json:"params"` KeyID int64 `json:"key_id"` } type blocksTxInfoForm struct { BlockID int64 `schema:"block_id"` Count int64 `schema:"count"` } func (f *blocksTxInfoForm) Validate(r *http.Request) error { if f.BlockID > 0 { f.BlockID-- } if f.Count <= 0 { f.Count = defaultPaginatorLimit } if f.Count > maxPaginatorLimit { f.Count = maxPaginatorLimit } return nil } func getBlocksTxInfoHandler(w http.ResponseWriter, r *http.Request) { form := &blocksTxInfoForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } if form.BlockID < 0 || form.Count < 0 { err := errors.New("parameter is invalid") errorResponse(w, err, http.StatusBadRequest) return } logger := getLogger(r) blocks, err := sqldb.GetBlockchain(form.BlockID, form.BlockID+form.Count, sqldb.OrderASC) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting blocks range") errorResponse(w, err) return } if len(blocks) == 0 { errorResponse(w, errNotFound) return } result := map[int64][]TxInfo{} for _, blockModel := range blocks { blck, err := block.UnmarshallBlock(bytes.NewBuffer(blockModel.Data), false) if err != nil { logger.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err, "bolck_id": blockModel.ID}).Error("on unmarshalling block") errorResponse(w, err) return } txInfoCollection := make([]TxInfo, 0, len(blck.Transactions)) for _, tx := range blck.Transactions { txInfo := TxInfo{ Hash: tx.Hash(), } if tx.IsSmartContract() { if tx.SmartContract().TxContract != nil { txInfo.ContractName = tx.SmartContract().TxContract.Name } txInfo.Params = tx.SmartContract().TxData } if blck.IsGenesis() { txInfo.KeyID = blck.Header.KeyId } else { txInfo.KeyID = tx.KeyID() } txInfoCollection = append(txInfoCollection, txInfo) logger.WithFields(log.Fields{"block_id": blockModel.ID, "tx hash": txInfo.Hash, "contract_name": txInfo.ContractName, "key_id": txInfo.KeyID, "params": txInfoCollection}).Debug("BlockChain Transactions Information") } result[blockModel.ID] = txInfoCollection } jsonResponse(w, &result) } type TxDetailedInfo struct { Hash []byte `json:"hash"` ContractName string `json:"contract_name"` Params map[string]any `json:"params"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Type byte `json:"type"` Size string `json:"size"` } type BlockHeaderInfo struct { BlockID int64 `json:"block_id"` Time int64 `json:"time"` EcosystemID int64 `json:"-"` KeyID int64 `json:"key_id"` NodePosition int64 `json:"node_position"` Sign []byte `json:"-"` Hash []byte `json:"-"` Version int `json:"version"` } type BlockDetailedInfo struct { Header BlockHeaderInfo `json:"header"` Hash []byte `json:"hash"` EcosystemID int64 `json:"-"` NodePosition int64 `json:"node_position"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Tx int32 `json:"tx_count"` Size string `json:"size"` RollbacksHash []byte `json:"rollbacks_hash"` MerkleRoot []byte `json:"merkle_root"` BinData []byte `json:"bin_data"` SysUpdate bool `json:"-"` GenBlock bool `json:"-"` StopCount int `json:"stop_count"` Transactions []TxDetailedInfo `json:"transactions"` } func getBlocksDetailedInfoHandler(w http.ResponseWriter, r *http.Request) { form := &blocksTxInfoForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } if form.BlockID < 0 || form.Count < 0 { err := errors.New("parameter is invalid") errorResponse(w, err, http.StatusBadRequest) return } logger := getLogger(r) blocks, err := sqldb.GetBlockchain(form.BlockID, form.BlockID+form.Count, sqldb.OrderASC) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting blocks range") errorResponse(w, err) return } if len(blocks) == 0 { errorResponse(w, errNotFound) return } result := map[int64]BlockDetailedInfo{} for _, blockModel := range blocks { blck, err := block.UnmarshallBlock(bytes.NewBuffer(blockModel.Data), false) if err != nil { logger.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err, "block_id": blockModel.ID}).Error("on unmarshalling block") errorResponse(w, err) return } txDetailedInfoCollection := make([]TxDetailedInfo, 0, len(blck.Transactions)) for _, tx := range blck.Transactions { txDetailedInfo := TxDetailedInfo{ Hash: tx.Hash(), KeyID: tx.KeyID(), Time: tx.Timestamp(), Type: tx.Type(), Size: common.StorageSize(len(tx.Payload())).TerminalString(), } if tx.IsSmartContract() { if tx.SmartContract().TxContract != nil { txDetailedInfo.ContractName = tx.SmartContract().TxContract.Name } txDetailedInfo.Params = tx.SmartContract().TxData if tx.Type() == types.TransferSelfTxType { txDetailedInfo.Params = make(map[string]any) txDetailedInfo.Params["TransferSelf"] = tx.SmartContract().TxSmart.TransferSelf } if tx.Type() == types.UtxoTxType { txDetailedInfo.Params = make(map[string]any) txDetailedInfo.Params["UTXO"] = tx.SmartContract().TxSmart.UTXO } } txDetailedInfoCollection = append(txDetailedInfoCollection, txDetailedInfo) logger.WithFields(log.Fields{"block_id": blockModel.ID, "tx hash": txDetailedInfo.Hash, "contract_name": txDetailedInfo.ContractName, "key_id": txDetailedInfo.KeyID, "time": txDetailedInfo.Time, "type": txDetailedInfo.Type, "params": txDetailedInfoCollection}).Debug("BlockChain Transactions Information") } header := BlockHeaderInfo{ BlockID: blck.Header.BlockId, Time: blck.Header.Timestamp, EcosystemID: blck.Header.EcosystemId, KeyID: blck.Header.KeyId, NodePosition: blck.Header.NodePosition, Sign: blck.Header.Sign, Hash: blck.Header.BlockHash, Version: int(blck.Header.Version), } bdi := BlockDetailedInfo{ Header: header, Hash: blockModel.Hash, EcosystemID: blockModel.EcosystemID, NodePosition: blockModel.NodePosition, KeyID: blockModel.KeyID, Time: blockModel.Time, Tx: blockModel.Tx, RollbacksHash: blockModel.RollbacksHash, MerkleRoot: blck.MerkleRoot, BinData: blck.BinData, Size: common.StorageSize(len(blockModel.Data)).TerminalString(), SysUpdate: blck.SysUpdate, GenBlock: blck.GenBlock, Transactions: txDetailedInfoCollection, } result[blockModel.ID] = bdi } jsonResponse(w, &result) } ================================================ FILE: packages/api/block_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "testing" "github.com/stretchr/testify/assert" ) func TestGetMaxBlockID(t *testing.T) { var ret maxBlockResult err := sendGet(`maxblockid`, nil, &ret) assert.NoError(t, err) } func TestGetBlockInfo(t *testing.T) { var ret blockInfoResult err := sendGet(`block/1`, nil, &ret) assert.NoError(t, err) } ================================================ FILE: packages/api/common_forms.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/converter" ) const ( defaultPaginatorLimit = 25 maxPaginatorLimit = 1000 ) type paginatorForm struct { defaultLimit int Limit int `schema:"limit"` Offset int `schema:"offset"` } func (f *paginatorForm) Validate(r *http.Request) error { if f.Limit <= 0 { f.Limit = f.defaultLimit if f.Limit == 0 { f.Limit = defaultPaginatorLimit } } if f.Limit > maxPaginatorLimit { f.Limit = maxPaginatorLimit } return nil } type paramsForm struct { nopeValidator Names string `schema:"names"` } func (f *paramsForm) AcceptNames() map[string]bool { names := make(map[string]bool) for _, item := range strings.Split(f.Names, ",") { if len(item) == 0 { continue } names[item] = true } return names } type ecosystemForm struct { EcosystemID int64 `schema:"ecosystem"` EcosystemPrefix string `schema:"-"` Validator types.EcosystemGetter } func (f *ecosystemForm) Validate(r *http.Request) error { if f.Validator == nil { panic("ecosystemForm.Validator should not be empty") } client := getClient(r) logger := getLogger(r) ecosysID, err := f.Validator.ValidateId(f.EcosystemID, client.EcosystemID, logger) if err != nil { if err == ErrEcosystemNotFound { err = errEcosystem.Errorf(f.EcosystemID) } return err } f.EcosystemID = ecosysID f.EcosystemPrefix = converter.Int64ToStr(f.EcosystemID) return nil } ================================================ FILE: packages/api/config.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/publisher" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type configOptionHandler func(w http.ResponseWriter, option string) error func getConfigOptionHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) if len(params["option"]) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject, "error": "option not specified"}).Error("on getting option in config handler") errorResponse(w, errNotFound) return } switch params["option"] { case "centrifugo": centrifugoAddressHandler(w, r) return } errorResponse(w, errNotFound) } func replaceHttpSchemeToWs(centrifugoURL string) string { if strings.HasPrefix(centrifugoURL, "http:") { return strings.Replace(centrifugoURL, "http:", "ws:", -1) } else if strings.HasPrefix(centrifugoURL, "https:") { return strings.Replace(centrifugoURL, "https:", "wss:", -1) } return centrifugoURL } func centrifugoAddressHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) if _, err := publisher.GetStats(); err != nil { logger.WithFields(log.Fields{"type": consts.CentrifugoError, "error": err}).Warn("on getting centrifugo stats") errorResponse(w, err) return } jsonResponse(w, replaceHttpSchemeToWs(conf.Config.Centrifugo.URL)) } ================================================ FILE: packages/api/content.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "encoding/json" "errors" "fmt" "net/http" "strconv" "strings" "sync" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/template" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type contentResult struct { Menu string `json:"menu,omitempty"` MenuTree json.RawMessage `json:"menutree,omitempty"` Title string `json:"title,omitempty"` Tree json.RawMessage `json:"tree"` NodesCount int64 `json:"nodesCount,omitempty"` } type hashResult struct { Hash string `json:"hash"` } const ( strTrue = `true` strOne = `1` ) var errEmptyTemplate = errors.New("Empty template") func initVars(r *http.Request) *map[string]string { client := getClient(r) r.ParseMultipartForm(multipartBuf) vars := make(map[string]string) for name := range r.Form { vars[name] = r.FormValue(name) } vars["_full"] = "0" vars["current_time"] = fmt.Sprintf("%d", time.Now().Unix()) vars["guest_key"] = consts.GuestKey vars["guest_account"] = consts.GuestAddress vars["black_hole_key"] = strconv.FormatInt(converter.HoleAddrMap[converter.BlackHoleAddr].K, 10) vars["black_hole_account"] = converter.HoleAddrMap[converter.BlackHoleAddr].S vars["white_hole_key"] = strconv.FormatInt(converter.HoleAddrMap[converter.WhiteHoleAddr].K, 10) vars["white_hole_account"] = converter.HoleAddrMap[converter.WhiteHoleAddr].S if client.KeyID != 0 { vars["ecosystem_id"] = converter.Int64ToStr(client.EcosystemID) vars["key_id"] = converter.Int64ToStr(client.KeyID) vars["account_id"] = client.AccountID vars["role_id"] = converter.Int64ToStr(client.RoleID) vars["ecosystem_name"] = client.EcosystemName } else { vars["ecosystem_id"] = vars["ecosystem"] delete(vars, "ecosystem") if len(vars["keyID"]) > 0 { vars["key_id"] = vars["keyID"] vars["account_id"] = converter.AddressToString(converter.StrToInt64(vars["keyID"])) } else { vars["key_id"] = "0" vars["account_id"] = "" } if len(vars["roleID"]) > 0 { vars["role_id"] = vars["roleID"] } else { vars["role_id"] = "0" } if len(vars["ecosystem_id"]) != 0 { ecosystems := sqldb.Ecosystem{} if found, _ := ecosystems.Get(nil, converter.StrToInt64(vars["ecosystem_id"])); found { vars["ecosystem_name"] = ecosystems.Name } } } if _, ok := vars["lang"]; !ok { vars["lang"] = r.Header.Get("Accept-Language") } return &vars } func parseEcosystem(in string) (string, string) { ecosystem, name := converter.ParseName(in) if ecosystem == 0 { return ``, name } return converter.Int64ToStr(ecosystem), name } func pageValue(r *http.Request) (*sqldb.Page, string, error) { params := mux.Vars(r) logger := getLogger(r) client := getClient(r) var ecosystem string page := &sqldb.Page{} name := params["name"] if strings.HasPrefix(name, `@`) { ecosystem, name = parseEcosystem(name) if len(name) == 0 { logger.WithFields(log.Fields{ "type": consts.NotFound, "value": params["name"], }).Debug("page not found") return nil, ``, errNotFound } } else { ecosystem = client.Prefix() } page.SetTablePrefix(ecosystem) found, err := page.Get(name) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting page") return nil, ``, err } if !found { logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("page not found") return nil, ``, errNotFound } return page, ecosystem, nil } func getPage(r *http.Request) (result *contentResult, err error) { page, _, err := pageValue(r) if err != nil { return nil, err } logger := getLogger(r) client := getClient(r) menu := &sqldb.Menu{} menu.SetTablePrefix(client.Prefix()) _, err = menu.Get(page.Menu) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting page menu") return nil, errServer } var wg sync.WaitGroup var timeout bool wg.Add(2) success := make(chan bool, 1) go func() { defer wg.Done() vars := initVars(r) (*vars)["app_id"] = converter.Int64ToStr(page.AppID) ret := template.Template2JSON(page.Value, &timeout, vars) if timeout { return } retmenu := template.Template2JSON(menu.Value, &timeout, vars) if timeout { return } result = &contentResult{ Tree: ret, Menu: page.Menu, MenuTree: retmenu, NodesCount: page.ValidateCount, } success <- true }() go func() { defer wg.Done() if conf.Config.LocalConf.MaxPageGenerationTime == 0 { return } select { case <-time.After(time.Duration(conf.Config.LocalConf.MaxPageGenerationTime) * time.Millisecond): timeout = true case <-success: } }() wg.Wait() close(success) if timeout { logger.WithFields(log.Fields{"type": consts.InvalidObject}).Error(page.Name + " is a heavy page") return nil, errHeavyPage } return result, nil } func getPageHandler(w http.ResponseWriter, r *http.Request) { result, err := getPage(r) if err != nil { errorResponse(w, err) return } jsonResponse(w, result) } func getPageHashHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) params := mux.Vars(r) if ecosystem := r.FormValue("ecosystem"); len(ecosystem) > 0 && !strings.HasPrefix(params["name"], "@") { params["name"] = "@" + ecosystem + params["name"] } result, err := getPage(r) if err != nil { errorResponse(w, err) return } out, err := json.Marshal(result) if err != nil { logger.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("getting string for hash") errorResponse(w, errServer) return } jsonResponse(w, &hashResult{Hash: hex.EncodeToString(crypto.Hash(out))}) } func getMenuHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) client := getClient(r) logger := getLogger(r) var ecosystem string menu := &sqldb.Menu{} name := params["name"] if strings.HasPrefix(name, `@`) { ecosystem, name = parseEcosystem(name) if len(name) == 0 { logger.WithFields(log.Fields{ "type": consts.NotFound, "value": params["name"], }).Debug("page not found") errorResponse(w, errNotFound) return } } else { ecosystem = client.Prefix() } menu.SetTablePrefix(ecosystem) found, err := menu.Get(name) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting menu") errorResponse(w, err) return } if !found { logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("menu not found") errorResponse(w, errNotFound) return } var timeout bool ret := template.Template2JSON(menu.Value, &timeout, initVars(r)) jsonResponse(w, &contentResult{Tree: ret, Title: menu.Title}) } type jsonContentForm struct { Template string `schema:"template"` Source bool `schema:"source"` } func (f *jsonContentForm) Validate(r *http.Request) error { if len(f.Template) == 0 { return errEmptyTemplate } return nil } func jsonContentHandler(w http.ResponseWriter, r *http.Request) { form := &jsonContentForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } var timeout bool vars := initVars(r) if form.Source { (*vars)["_full"] = strOne } ret := template.Template2JSON(form.Template, &timeout, vars) jsonResponse(w, &contentResult{Tree: ret}) } func getSourceHandler(w http.ResponseWriter, r *http.Request) { page, _, err := pageValue(r) if err != nil { errorResponse(w, err) return } var timeout bool vars := initVars(r) (*vars)["_full"] = strOne ret := template.Template2JSON(page.Value, &timeout, vars) jsonResponse(w, &contentResult{Tree: ret}) } func getPageValidatorsCountHandler(w http.ResponseWriter, r *http.Request) { page, _, err := pageValue(r) if err != nil { errorResponse(w, err) return } res := map[string]int64{"validate_count": page.ValidateCount} jsonResponse(w, &res) } ================================================ FILE: packages/api/content_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "encoding/json" "net/url" "testing" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/stretchr/testify/assert" ) func TestContentHash(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`page`) assert.NoError(t, postTx(`NewPage`, &url.Values{ "ApplicationId": {`1`}, "Name": {name}, "Value": {`Div(Body: "New value of parameter - test")`}, "Menu": {`default_menu`}, "Conditions": {"true"}, })) urls := "content/page/" + name var ret contentResult assert.NoError(t, sendPost(urls, &url.Values{}, &ret)) out, err := json.Marshal(ret) assert.NoError(t, err) hash := crypto.Hash(out) urls = "content/hash/" + name var hret hashResult assert.NoError(t, sendPost(urls, &url.Values{}, &hret)) if hex.EncodeToString(hash) != hret.Hash { t.Error(`wrong hash`, hex.EncodeToString(hash), hret.Hash) } } func TestContent(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`page`) assert.NoError(t, postTx(`NewPage`, &url.Values{ "ApplicationId": {`1`}, "Name": {name}, "Value": {`If(true){Div(){Span(My text)Address()}}.Else{Div(Body: Hidden text)}`}, "Menu": {`default_menu`}, "Conditions": {"true"}, })) cases := []struct { url string form url.Values expected string }{ { "content/source/" + name, url.Values{}, `[{"tag":"if","attr":{"condition":"true"},"children":[{"tag":"div","children":[{"tag":"span","children":[{"tag":"text","text":"My text"}]},{"tag":"address"}]}],"tail":[{"tag":"else","children":[{"tag":"div","children":[{"tag":"text","text":"Hidden text"}]}]}]}]`, }, { "content", url.Values{ "template": {"input Div(myclass, #mytest# Div(mypar) the Div)"}, "mytest": {"test value"}, }, `[{"tag":"text","text":"input "},{"tag":"div","attr":{"class":"myclass"},"children":[{"tag":"text","text":"test value "},{"tag":"div","attr":{"class":"mypar"}},{"tag":"text","text":" the Div"}]}]`, }, { "content", url.Values{ "template": {"#test_page# input Div(myclass, #test_page# ok) #test_page#"}, "test_page": {"7"}, }, `[{"tag":"text","text":"7 input "},{"tag":"div","attr":{"class":"myclass"},"children":[{"tag":"text","text":"7 ok"}]},{"tag":"text","text":" 7"}]`, }, { "content", url.Values{ "template": {"SetVar(mytest, myvar)Div(myclass, Span(#mytest#) Div(mypar){Span(test)}#mytest#)"}, "source": {"true"}, }, `[{"tag":"setvar","attr":{"name":"mytest","value":"myvar"}},{"tag":"div","attr":{"class":"myclass"},"children":[{"tag":"span","children":[{"tag":"text","text":"#mytest#"}]},{"tag":"div","attr":{"class":"mypar"},"children":[{"tag":"span","children":[{"tag":"text","text":"test"}]}]},{"tag":"text","text":"#mytest#"}]}]`, }, { "content", url.Values{ "template": {`DBFind(Name: pages, Source: src).Custom(custom_col){ Span(Body: "test") }`}, "lang": {"ru"}, "source": {"true"}, }, `[{"tag":"dbfind","attr":{"name":"pages","source":"src"},"tail":[{"tag":"custom","attr":{"column":"custom_col"},"children":[{"tag":"span","children":[{"tag":"text","text":"test"}]}]}]}]`, }, { "content", url.Values{ "template": {`Data(Source: src).Custom(custom_col){ Span(Body: "test") }`}, "lang": {"ru"}, "source": {"true"}, }, `[{"tag":"data","attr":{"source":"src"},"tail":[{"tag":"custom","attr":{"column":"custom_col"},"children":[{"tag":"span","children":[{"tag":"text","text":"test"}]}]}]}]`, }, { "content", url.Values{ "template": {`Data(myforlist,"id,name", "1",Test message 1 2,"Test message 2" 3,"Test message 3" )`}, "source": {"true"}, }, `[{"tag":"data","attr":{"columns":"id,name","data":"1,Test message 1\n\t\t\t\t\t2,\"Test message 2\"\n\t\t\t\t\t3,\"Test message 3\"","source":"myforlist"}}]`, }, { "content", url.Values{ "template": {` Data(src_test,"type"){ text } ForList(src_test){ If(#type#==text){ Span(:#type#) } }`}, }, `[{"tag":"data","attr":{"columns":["type"],"data":[["text"]],"source":"src_test","types":["text"]}},{"tag":"forlist","attr":{"source":"src_test"},"children":[{"tag":"span","attr":{"":"text"}}]}]`, }, } var ret contentResult for _, v := range cases { assert.NoError(t, sendPost(v.url, &v.form, &ret)) assert.Equal(t, v.expected, string(ret.Tree)) } } ================================================ FILE: packages/api/context.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "context" "net/http" "github.com/golang-jwt/jwt/v4" log "github.com/sirupsen/logrus" ) type contextKey int const ( contextKeyLogger contextKey = iota contextKeyToken contextKeyClient ) func setContext(r *http.Request, key, value any) *http.Request { return r.WithContext(context.WithValue(r.Context(), key, value)) } func getContext(r *http.Request, key any) any { return r.Context().Value(key) } func setLogger(r *http.Request, log *log.Entry) *http.Request { return setContext(r, contextKeyLogger, log) } func getLogger(r *http.Request) *log.Entry { if v := getContext(r, contextKeyLogger); v != nil { return v.(*log.Entry) } return nil } func setToken(r *http.Request, token *jwt.Token) *http.Request { return setContext(r, contextKeyToken, token) } func getToken(r *http.Request) *jwt.Token { if v := getContext(r, contextKeyToken); v != nil { return v.(*jwt.Token) } return nil } func setClient(r *http.Request, client *Client) *http.Request { return setContext(r, contextKeyClient, client) } func getClient(r *http.Request) *Client { if v := getContext(r, contextKeyClient); v != nil { return v.(*Client) } return nil } ================================================ FILE: packages/api/contract_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "strconv" "strings" "testing" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func init() { crypto.InitAsymAlgo("ECC_Secp256k1") crypto.InitHashAlgo("KECCAK256") } func TestBin(t *testing.T) { assert.NoError(t, keyLogin(1)) form := url.Values{`nowait`: {`nowait`}} assert.NoError(t, postTxMultipart(`@1NewContract`, &form)) //_, _, err := postTxResult("rndPtlziReAis01638415503", &url.Values{}) //assert.NoError(t, err) } func TestTransferSelf(t *testing.T) { assert.NoError(t, keyLogin(1)) form := url.Values{`nowait`: {`nowait`}} assert.NoError(t, postTransferSelfTxMultipart(&form)) } func TestUTXO(t *testing.T) { assert.NoError(t, keyLogin(1)) form := url.Values{`nowait`: {`nowait`}} assert.NoError(t, postUTXOTxMultipart(&form)) } func TestMath(t *testing.T) { assert.NoError(t, keyLogin(1)) users := []string{ `04d750da3e19c5f7721a1dafd8663f2739dba23d81e01f0667730d217472a3bc9f93c6fbaade9c6b6387ece296c478d25559cb87ca3e58aa7ce627dd47ec902aea`, `0471f24f9dee904277a081df37377c4dbd7cc14671fec2f0c1a580a5d972c2f98d528ea7f460de8900ce70d6de08a118060f1a261b272722a460d87de93014f960`, `04df303174dfbc79b1e3baa196103c284b6ca97c21848511af18a401fdd1b0fb29bfd8724f43b44062ff5f67a969409ab037ae674a1010602b1a7d25245b49738c`, `044d92cd4f7f6bd3cb0c9600782d32797ebbc8a277a308b128f33453ed84f470e2bb88ceb5de87cd8288b8ade9e437c90de3610ddcfcfec41d6e70d302690bc3ec`, `04cbe75c40a2ef1256483c5bb910745171258b341f981598a01876fc3711c243f8fab181d72ea7abb838ad3f27e8a03b54032e8ab34656d9503539a38b05151cfa`, `04f220d6620b8a0ede8ac23e2724e467888138d654721fefb1a910bb717aaff279fed5560462ed7af1ac1208852822e73336033ce6289694a0d7dc974754cf61ae`, } for _, u := range users { form := url.Values{"NewPubkey": {u}} assert.NoError(t, postTx(`@1NewUser`, &form)) } //form := url.Values{`Value`: {`[{"api_address":"http://127.0.0.1:7079","public_key":"0498b18e551493a269b6f419d7784d26c8e3555638e80897c69997ef9f211e21d5d0b8adeeaab0e0e750e720ddf3048ec55d613ba5dee3fdfd4e7c17d346731e9b","tcp_address":"127.0.0.1:7078"},{"tcp_address":"127.0.0.1:2078","api_address":"http://127.0.0.1:2079","public_key":"04d750da3e19c5f7721a1dafd8663f2739dba23d81e01f0667730d217472a3bc9f93c6fbaade9c6b6387ece296c478d25559cb87ca3e58aa7ce627dd47ec902aea"},{"tcp_address":"127.0.0.1:3078","api_address":"http://127.0.0.1:3079","public_key":"04df303174dfbc79b1e3baa196103c284b6ca97c21848511af18a401fdd1b0fb29bfd8724f43b44062ff5f67a969409ab037ae674a1010602b1a7d25245b49738c"},{"tcp_address":"127.0.0.1:4078","api_address":"http://127.0.0.1:4079","public_key":"04cbe75c40a2ef1256483c5bb910745171258b341f981598a01876fc3711c243f8fab181d72ea7abb838ad3f27e8a03b54032e8ab34656d9503539a38b05151cfa"}]`}, "Name": {"honor_nodes"}, `Conditions`: {`true`}} form := url.Values{`Value`: {`[{"api_address":"http://127.0.0.1:7079","public_key":"0498b18e551493a269b6f419d7784d26c8e3555638e80897c69997ef9f211e21d5d0b8adeeaab0e0e750e720ddf3048ec55d613ba5dee3fdfd4e7c17d346731e9b","tcp_address":"127.0.0.1:7078"},{"tcp_address":"127.0.0.1:2078","api_address":"http://127.0.0.1:2079","public_key":"04d750da3e19c5f7721a1dafd8663f2739dba23d81e01f0667730d217472a3bc9f93c6fbaade9c6b6387ece296c478d25559cb87ca3e58aa7ce627dd47ec902aea"}]`}, "Name": {"honor_nodes"}, `Conditions`: {`true`}} //form := url.Values{`Value`: {`[{"api_address":"http://127.0.0.1:7079","public_key":"0498b18e551493a269b6f419d7784d26c8e3555638e80897c69997ef9f211e21d5d0b8adeeaab0e0e750e720ddf3048ec55d613ba5dee3fdfd4e7c17d346731e9b","tcp_address":"127.0.0.1:7078"},{"tcp_address":"127.0.0.1:2078","api_address":"http://127.0.0.1:2079","public_key":"04d750da3e19c5f7721a1dafd8663f2739dba23d81e01f0667730d217472a3bc9f93c6fbaade9c6b6387ece296c478d25559cb87ca3e58aa7ce627dd47ec902aea"},{"tcp_address":"127.0.0.1:3078","api_address":"http://127.0.0.1:3079","public_key":"04df303174dfbc79b1e3baa196103c284b6ca97c21848511af18a401fdd1b0fb29bfd8724f43b44062ff5f67a969409ab037ae674a1010602b1a7d25245b49738c"}]`}, "Name": {"honor_nodes"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`@1UpdatePlatformParam`, &form)) } func TestArray(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `@1rnd0Q3a3mIzpg01627469951` form := url.Values{`Info`: {crypto.RandSeq(14)}} assert.NoError(t, postTx(rnd, &form)) } func TestCheckCondition(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `cnt` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + ` { action { $result = Sprintf("%v %v %v %v %v", CheckCondition("1"), CheckCondition("0"), CheckCondition("ContractConditions(\"MainCondition\")"), CheckCondition("true"), CheckCondition("false")) } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(rnd, &url.Values{}) assert.NoError(t, err) if msg != `true false true true false` { t.Error(fmt.Errorf(`wrong msg %s`, msg)) } } func TestDBFindContract(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `db` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + `1 { data { } action { var fm array fm = DBFind("@1contracts").Where({"ecosystem": $ecosystem_id, "app_id": 1, ,"id": {"$gt": 2}}) $result = Len(fm) }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"unexpected lexeme; expecting string key [CreateContract @1NewContract:32]"}`) form = url.Values{`Value`: {`contract ` + rnd + `2 { data { } action { var fm array fm = DBFind("@1contracts").Where({"ecosystem": $ecosystem_id, "app_id": 1,"id": {"$gt": 2},}) }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"unexpected lexeme; expecting string key [CreateContract @1NewContract:32]"}`) form = url.Values{`Value`: {`contract ` + rnd + `3 { data { } action { var fm array fm = [1, 2, 3,] }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"unexpected lexeme; expecting string, int value or variable [CreateContract @1NewContract:32]"}`) form = url.Values{`Value`: {`contract ` + rnd + ` { data { } action { var ret i j k m array var inr inc map var rids array rids = JSONDecode("[]")//role["roles_access"]) inr = DBFind("@1roles_participants").Where({"ecosystem": $ecosystem_id, "role->id": {"$in": rids}, "member->member_id": $key_id, "deleted": 0}).Row() inc = DBFind("contracts").Where({"ecosystem": $ecosystem_id, "id": {"$in": rids}}).Row() ret = DBFind("contracts").Where({value: {"$ibegin": "CONTRACT"}}).Limit(100) i = DBFind("contracts").Where({value: {$ilike: "rEmove"}}).Limit(100) j = DBFind("contracts").Where({id: {$lt: 10}}) k = DBFind("contracts").Where({id: {$lt: 11}, $or: [{id: 5}, {id: 7}], $and: [{id: {$neq: 25}}, id: {$neq: 26} ]}) m = DBFind("contracts").Where({id: 10, name: "EditColumn", $or: [id: 10, id: {$neq: 20}]}) $result = Sprintf("%d %d %d %d %d", Len(ret), Len(i), Len(j), Len(k), Len(m)) }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(rnd, &url.Values{}) assert.NoError(t, err) if msg != `25 25 9 2 1` { t.Error(fmt.Errorf(`wrong msg %s`, msg)) } } func TestErrorContract(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `err` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + ` { data { } action { error("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tincidunt vestibulum eros. Curabitur fermentum pulvinar nibh, in maximus dolor tempor quis. Donec non nulla id ex lacinia bibendum eu a sapien. Nam eu mi feugiat, gravida erat ac, tincidunt dolor. Curabitur sed erat et felis turpis duis.") }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, _, err := postTxResult(rnd, &url.Values{}) if len(err.Error()) > 250 { t.Error(`Too long error`) } rnd += `1` form = url.Values{`Value`: {`contract ` + rnd + ` { data { } action { Throw("This is a problem", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tincidunt vestibulum eros. Curabitur fermentum pulvinar nibh, in maximus dolor tempor quis. Donec non nulla id ex lacinia bibendum eu a sapien. Nam eu mi feugiat, gravida erat ac, tincidunt dolor. Curabitur sed erat et felis turpis duis.") }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, _, err = postTxResult(rnd, &url.Values{}) if len(err.Error()) > 250 { t.Error(`Too long error`) } } func TestUpdate_HonorNodes(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } err := postTx("UpdatePlatformParam", &url.Values{ "Name": {"honor_nodes"}, "Value": {"[]"}, }) if err != nil { t.Error(err) return } } func TestCrashContract(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `crash` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + ` { data {} conditions { $Recipient = Append([], "1") $Recipient = Append($Recipient, "7") } action { var i int var steps map var list myarr q b array while i < Len($Recipient) { steps["recipient_role"] = JSONDecode($Recipient[i]) list[i] = Append(list, steps) myarr = Split(list[i], ",") i = i + 1 } } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(rnd, &url.Values{}), `{"type":"panic","error":"self assignment"}`) } func TestHardContract(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `hard` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + ` { data { } action { var i int while i < 500 { DBFind("pages").Where({id:5}) DBUpdate("pages", 5, {"value":"P(text)"}) DBInsert("pages", {"name": Sprintf("` + rnd + `%d", i), "value":"P(text)", "conditions": "true"}) DBFind("pages").Where({id:6}) DBFind("pages").Where({id:7}) DBUpdate("pages", 6, {"value": "P(text)"}) i = i + 1 } }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(rnd, &url.Values{}), `{"type":"txError","error":"Time limit exceeded"}`) } func TestExistContract(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `cnt` + crypto.RandSeq(4) form := url.Values{"Name": {rnd}, "Value": {`contract ` + rnd + ` { data { Name string } action { Throw($Name, "Text of the error") }}`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(rnd, &url.Values{"Name": {"1"}}), `{"type":"exception","error":"Text of the error","id":"1"}`) form = url.Values{"Name": {`EditPage`}, "Value": {`contract EditPage {action {}}`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"Contract EditPage already exists"}`) } func TestDataContract(t *testing.T) { assert.NoError(t, keyLogin(1)) name := `cnt` + crypto.RandSeq(4) form := url.Values{"Name": {name}, "Value": {`contract ` + name + `1 { data {Name int string qwerty} action {} }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"expecting name of the data field [Ln:3 Col:5]"}`) form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { data {MyApp qwerty} action {} }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"expecting type of the data field [Ln:2 Col:16]"}`) form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { data {MyApp int Qwert} action {} }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.EqualError(t, postTx(`NewContract`, &form), `{"type":"panic","error":"expecting type of the data field [Ln:3 Col:13]"}`) } func TestTypesContract(t *testing.T) { assert.NoError(t, keyLogin(1)) name := `cnt` + crypto.RandSeq(4) form := url.Values{"Name": {name}, "Value": {`contract ` + name + ` { data { Float float Addr address Arr array Map map } action { $result = Sprintf("%v=%v=%v=%v", $Float, $Addr, $Arr, $Map) } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(name, &url.Values{"Float": {"1.23"}, "Addr": {"-1334343423"}, "Arr": {`[23,"tt"]`}, "Map": {`{"k" : "v"}`}}) assert.NoError(t, err) if msg != `1.23=-1334343423=[23 tt]=map[k:v]` { t.Error(`Wrong msg`, msg) } } func TestNewContracts(t *testing.T) { //wanted := func(name, want string) bool { // var ret getTestResult // return assert.NoError(t, sendPost(`test/`+name, nil, &ret)) && assert.Equal(t, want, ret.Value) //} assert.NoError(t, keyLogin(1)) rnd := crypto.RandSeq(4) for i, item := range contracts { var ret getContractResult if i > 100 { break } name := strings.Replace(item.Name, `#rnd#`, rnd, -1) err := sendGet(`contract/`+name, nil, &ret) if err != nil { if strings.Contains(err.Error(), errContract.Errorf(name).Error()) { form := url.Values{"Name": {name}, "Value": {strings.Replace(item.Value, `#rnd#`, rnd, -1)}, "ApplicationId": {`1`}, "Conditions": {`true`}} if err := postTx(`NewContract`, &form); err != nil { assert.EqualError(t, err, item.Params[0].Results[`error`]) continue } } else { t.Error(err) return } } if strings.HasSuffix(name, `testUpd`) { continue } for _, par := range item.Params { form := url.Values{} for key, value := range par.Params { form[key] = []string{value} } if err := postTx(name, &form); err != nil { assert.EqualError(t, err, par.Results[`error`]) continue } //for key, value := range par.Results { // if !wanted(key, value) { // return // } //} } } var row rowResult assert.NoError(t, sendGet(`row/menu/1`, nil, &row)) assert.NotEqual(t, `update`, row.Value[`value`]) } var contracts = []smartContract{ {`Empty`, `contract Empty { action { var a1 array var a2 map $a1 = [] $a2 = {} Test("result", "ok") } }`, []smartParams{ {nil, map[string]string{`result`: `ok`}}, }}, {`FmtMoney`, `contract FmtMoney { action { Test("result", FormatMoney("123456789", 0)) $num2 = "5500000" $num1 = "12345672372" Test("t1", FormatMoney($num1, -1)) //123456723720 Test("t2", FormatMoney($num1, 0)) //12345672372 Test("t3", FormatMoney($num1, 1)) //1234567237,2 } }`, []smartParams{ {nil, map[string]string{`result`: `123456789`, `t1`: `123456723720`, `t2`: `12345672372`, `t3`: `1234567237.2`}}, }}, {`StrNil`, `contract StrNil { action { Test("result", Sprintf("empty: %s", Str(nil))) } }`, []smartParams{ {nil, map[string]string{`result`: `empty: `}}, }}, {`TestJSON`, `contract TestJSON { data {} conditions { } action { var a map a["ok"] = 10 a["arr"] = ["first", ""] Test("json", JSONEncode(a)) Test("ok", JSONEncodeIndent(a, "\t")) } }`, []smartParams{ {nil, map[string]string{`ok`: "{\n\t\"ok\": 10,\n\t\"arr\": [\n\t\t\"first\",\n\t\t\"\"\n\t]\n}", `json`: "{\"ok\":10,\"arr\":[\"first\",\"\"]}"}}, }}, {`GuestKey`, `contract GuestKey { action { Test("result", $guest_key) } }`, []smartParams{ {nil, map[string]string{`result`: `4544233900443112470`}}, }}, {`TestCyr`, `contract TestCyr { data {} conditions { } action { //test var a map a["test"] = "test" Test("ok", a["test"]) } }`, []smartParams{ {nil, map[string]string{`ok`: `test`}}, }}, {`DBFindLike`, `contract DBFindLike { action { var list array list = DBFind("pages").Where({"name":{"$like": "ort_"}}) Test("size", Len(list)) list = DBFind("pages").Where({"name":{"$end": "page"}}) Test("end", Len(list)) } }`, []smartParams{ {nil, map[string]string{`size`: `4`, `end`: `2`}}, }}, {`TestDBFindOK`, ` contract TestDBFindOK { action { var ret array var vals map ret = DBFind("contracts").Columns("id,value").Where({"$and":[{"id":{"$gte": 3}}, {"id":{"$lte":5}}]}).Order("id") if Len(ret) { Test("0", "1") } else { Test("0", "0") } ret = DBFind("contracts").Limit(3) if Len(ret) == 3 { Test("1", "1") } else { Test("1", "0") } ret = DBFind("contracts").Order("id").Offset(1).Limit(1) if Len(ret) != 1 { Test("2", "0") } else { vals = ret[0] Test("2", vals["id"]) } ret = DBFind("contracts").Columns("id").Order(["id"]).Offset(1).Limit(1) if Len(ret) != 1 { Test("3", "0") } else { vals = ret[0] Test("3", vals["id"]) } ret = DBFind("contracts").Columns("id").Where({"$or":[{"id": "1"}]}) if Len(ret) != 1 { Test("4", "0") } else { vals = ret[0] Test("4", vals["id"]) } ret = DBFind("contracts").Columns("id").Where({"id": 1}) if Len(ret) != 1 { Test("4", "0") } else { vals = ret[0] Test("4", vals["id"]) } ret = DBFind("contracts").Columns("id,value").Where({"id":[{"$gt":3},{"$lt":8}]}).Order([{"id": 1}, {"name": "-1"}]) if Len(ret) != 4 { Test("5", "0") } else { vals = ret[0] Test("5", vals["id"]) } ret = DBFind("contracts").WhereId(7) if Len(ret) != 1 { Test("6", "0") } else { vals = ret[0] Test("6", vals["id"]) } var one string one = DBFind("contracts").WhereId(5).One("id") Test("7", one) var row map row = DBFind("contracts").WhereId(3).Row() Test("8", row["id"]) Test("255", "255") } }`, []smartParams{ {nil, map[string]string{`0`: `1`, `1`: `1`, `2`: `2`, `3`: `2`, `4`: `1`, `5`: `4`, `6`: `7`, `7`: `5`, `8`: `3`, `255`: `255`}}, }}, {`DBFindCol`, `contract DBFindCol { action { var ret string ret = DBFind("keys").Columns(["amount", "id"]).One("amount") Test("size", Size(ret)>0) } }`, []smartParams{ {nil, map[string]string{`size`: `true`}}, }}, {`DBFindColumnNow`, `contract DBFindColumnNow { action { var list array list = DBFind("keys").Columns("now()") } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"pq: current transaction is aborted, commands ignored until end of transaction block"}`}}, }}, {`DBFindCURRENT`, `contract DBFindCURRENT { action { var list array list = DBFind("mytable").Where({"date": {"$lt": "CURRENT_DATE"}}) } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"It is prohibited to use NOW() or current time functions"}`}}, }}, {`RowType`, `contract RowType { action { var app map var result string result = GetType(app) app = DBFind("applications").Where({"id":"1"}).Row() result = result + GetType(app) app["app_id"] = 2 Test("result", Sprintf("%s %s %d", result, app["name"], app["app_id"])) } }`, []smartParams{ {nil, map[string]string{`result`: `*types.Map*types.Map System 2`}}, }}, {`StackType`, `contract StackType { action { var lenStack int lenStack = Len($stack) var par string par = $stack[0] Test("result", Sprintf("len=%d %v %s", lenStack, $stack, par)) } }`, []smartParams{ {nil, map[string]string{`result`: `len=1 [@1StackType] @1StackType`}}, }}, {`DBFindNow`, `contract DBFindNow { action { var list array list = DBFind("mytable").Where({"date": {"$lt": "now ( )"}}) } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"It is prohibited to use NOW() or current time functions"}`}}, }}, {`BlockTimeCheck`, `contract BlockTimeCheck { action { if Size(BlockTime()) == Size("2006-01-02 15:04:05") { Test("ok", "1") } else { Test("ok", "0") } } }`, []smartParams{ {nil, map[string]string{`ok`: `1`}}, }}, {`RecCall`, `contract RecCall { data { } conditions { } action { var par map CallContract("RecCall", par) } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"There is loop in @1RecCall contract"}`}}, }}, {`Recursion`, `contract Recursion { data { } conditions { } action { Recursion() } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"The contract can't call itself recursively"}`}}, }}, {`MyTable#rnd#`, `contract MyTable#rnd# { action { NewTable("Name,Columns,ApplicationId,Permissions", "#rnd#1", "[{\"name\":\"MyName\",\"type\":\"varchar\", \"index\": \"0\", \"conditions\":{\"update\":\"true\", \"read\":\"true\"}}]", 100, "{\"insert\": \"true\", \"update\" : \"true\", \"new_column\": \"true\"}") var cols array cols[0] = "{\"conditions\":\"true\",\"name\":\"column1\",\"type\":\"text\"}" cols[1] = "{\"conditions\":\"true\",\"name\":\"column2\",\"type\":\"text\"}" NewTable("Name,Columns,ApplicationId,Permissions", "#rnd#2", JSONEncode(cols), 100, "{\"insert\": \"true\", \"update\" : \"true\", \"new_column\": \"true\"}") Test("ok", "1") } }`, []smartParams{ {nil, map[string]string{`ok`: `1`}}, }}, {`IntOver`, `contract IntOver { action { info Int("123456789101112131415161718192021222324252627282930") } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"123456789101112131415161718192021222324252627282930 is not a valid integer : value out of range"}`}}, }}, {`Double`, `contract Double { data { } conditions { } action { $$$$$$$$result = "hello" } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"unknown lexeme $ [Ln:5 Col:6]"}`}}, }}, {`Price`, `contract Price { action { Test("int", Int("")+Int(nil)+2) Test("price", 1) } func price() money { return Money(100) } }`, []smartParams{ {nil, map[string]string{`price`: `1`, `int`: `2`}}, }}, {`CheckFloat`, `contract CheckFloat { action { var fl float fl = -3.67 Test("float2", Sprintf("%d %s", Int(1.2), Str(fl))) Test("float3", Sprintf("%.2f %.2f", 10.7/7, 10/7.0)) Test("float4", Sprintf("%.2f %.2f %.2f", 10+7.0, 10-3.1, 5*2.5)) Test("float5", Sprintf("%t %t %t %t %t", 10 <= 7.0, 4.5 <= 5, 3>5.7, 6 == 6.0, 7 != 7.1)) }}`, []smartParams{ {nil, map[string]string{`float2`: `1 -3.670000`, `float3`: `1.53 1.43`, `float4`: `17.00 6.90 12.50`, `float5`: `false true false true true`}}, }}, {`Crash`, `contract Crash { data {} conditions {} action { $result=DBUpdate("menu", 1, {"value": "updated"}) } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"Access denied"}`}}, }}, {`TestOneInput`, `contract TestOneInput { data { list array } action { var coltype string coltype = GetColumnType("keys", "amount" ) Test("oneinput", $list[0]+coltype) } }`, []smartParams{ {map[string]string{`list`: `Input value`}, map[string]string{`oneinput`: `Input valuemoney`}}, }}, {`DBProblem`, `contract DBProblem { action{ DBFind("members1").Where({"member_name": "name"}) } }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"pq: current transaction is aborted, commands ignored until end of transaction block"}`}}, }}, {`TestMultiForm`, `contract TestMultiForm { data { list array } action { Test("multiform", $list[0]+$list[1]) } }`, []smartParams{ {map[string]string{`list[]`: `2`, `list[0]`: `start`, `list[1]`: `finish`}, map[string]string{`multiform`: `startfinish`}}, }}, {`errTestMessage`, `contract errTestMessage { conditions { } action { qvar ivar int} }`, []smartParams{ {nil, map[string]string{`error`: `{"type":"panic","error":"unknown variable qvar"}`}}, }}, {`EditProfile9`, `contract EditProfile9 { data { } conditions { } action { var ar array ar = Split("point 1,point 2", ",") Test("split", Str(ar[1])) $ret = DBFind("contracts").Columns("id,value").Where({"id":[{"$gte": 3}, {"$lte":5}]}).Order("id") Test("edit", "edit value 0") } }`, []smartParams{ {nil, map[string]string{`edit`: `edit value 0`, `split`: `point 2`}}, }}, {`testEmpty`, `contract testEmpty { action { Test("empty", "empty value")}}`, []smartParams{ {nil, map[string]string{`empty`: `empty value`}}, }}, {`testUpd`, `contract testUpd { action { Test("date", "-2006.01.02-")}}`, []smartParams{ {nil, map[string]string{`date`: `-` + time.Now().Format(`2006.01.02`) + `-`}}, }}, {`testLong`, `contract testLong { action { Test("long", "long result") $result = DBFind("contracts").WhereId(2).One("value") + DBFind("contracts").WhereId(4).One("value") Println("Result", $result) Test("long", "long result") }}`, []smartParams{ {nil, map[string]string{`long`: `long result`}}, }}, {`testSimple`, `contract testSimple { data { Amount int Name string } conditions { Test("scond", $Amount, $Name) } action { Test("sact", $Name, $Amount)}}`, []smartParams{ {map[string]string{`Name`: `Simple name`, `Amount`: `-56781`}, map[string]string{`scond`: `-56781Simple name`, `sact`: `Simple name-56781`}}, }}, {`errTestVar`, `contract errTestVar { conditions { } action { var ivar int} }`, nil}, {`testGetContract`, `contract testGetContract { action { Test("ByName", GetContractByName(""), GetContractByName("ActivateContract")) Test("ById", GetContractById(10000000), GetContractById(16))}}`, []smartParams{ {nil, map[string]string{`ByName`: `0 4`, `ById`: `EditTable`}}, }}, { `testDateTime`, `contract testDateTime { data { Date string Unix int } action { Test("DateTime", DateTime($Unix)) Test("UnixDateTime", UnixDateTime($Date)) } }`, []smartParams{ {map[string]string{ "Unix": "1257894000", "Date": "2009-11-11 04:00:00", }, map[string]string{ "DateTime": "2009-11-11 04:00:00", "UnixDateTime": timeMustParse("2009-11-11 04:00:00"), }}, }, }, } func timeMustParse(value string) string { t, _ := time.Parse("2006-01-02 15:04:05", value) return converter.Int64ToStr(t.Unix()) } func TestEditContracts(t *testing.T) { //wanted := func(name, want string) bool { // var ret getTestResult // err := sendPost(`test/`+name, nil, &ret) // if err != nil { // t.Error(err) // return false // } // if ret.Value != want { // t.Error(fmt.Errorf(`%s != %s`, ret.Value, want)) // return false // } // return true //} if err := keyLogin(1); err != nil { t.Error(err) return } var cntlist contractsResult err := sendGet(`contracts`, nil, &cntlist) if err != nil { t.Error(err) return } var ret getContractResult err = sendGet(`contract/testUpd`, nil, &ret) if err != nil { t.Error(err) return } sid := ret.TableID var row rowResult err = sendGet(`row/contracts/`+sid, nil, &row) if err != nil { t.Error(err) return } code := row.Value[`value`] off := strings.IndexByte(code, '-') newCode := code[:off+1] + time.Now().Format(`2006.01.02`) + code[off+11:] form := url.Values{`Id`: {sid}, `Value`: {newCode}, `Conditions`: {row.Value[`conditions`]}, `WalletId`: {"01231234123412341230"}} if err := postTx(`EditContract`, &form); err != nil { t.Error(err) return } for _, item := range contracts { if !strings.HasSuffix(item.Name, `testUpd`) { continue } for _, par := range item.Params { form := url.Values{} for key, value := range par.Params { form[key] = []string{value} } if err := postTx(item.Name, &form); err != nil { t.Error(err) return } //for key, value := range par.Results { // if !wanted(key, value) { // return // } //} } } } func TestNewTableWithEmptyName(t *testing.T) { require.NoError(t, keyLogin(1)) sql1 := `new_column varchar(10); update block_chain set key_id='1234' where id='1' --` sql2 := `new_column varchar(10); update block_chain set key_id='12' where id='1' --` name := randName(`tbl`) form := url.Values{ "Name": {name}, "Columns": {"[{\"name\":\"" + sql1 + "\",\"type\":\"varchar\", \"index\": \"0\", \"conditions\":{\"update\":\"true\", \"read\":\"true\"}}]"}, "ApplicationId": {"1"}, "Permissions": {"{\"insert\": \"true\", \"update\" : \"true\", \"new_column\": \"true\"}"}, } require.NoError(t, postTx("NewTable", &form)) form = url.Values{"TableName": {name}, "Name": {sql2}, "Type": {"varchar"}, "Index": {"0"}, "Permissions": {"true"}} assert.NoError(t, postTx(`NewColumn`, &form)) form = url.Values{ "Name": {""}, "Columns": {"[{\"name\":\"MyName\",\"type\":\"varchar\", \"index\": \"0\", \"conditions\":{\"update\":\"true\", \"read\":\"true\"}}]"}, "ApplicationId": {"1"}, "Permissions": {"{\"insert\": \"true\", \"update\" : \"true\", \"new_column\": \"true\"}"}, } if err := postTx("NewTable", &form); err == nil || err.Error() != `400 {"error": "E_SERVER", "msg": "Name is empty" }` { t.Error(`wrong error`, err) } form = url.Values{ "Name": {"Digit" + name}, "Columns": {"[{\"name\":\"1\",\"type\":\"varchar\", \"index\": \"0\", \"conditions\":{\"update\":\"true\", \"read\":\"true\"}}]"}, "ApplicationId": {"1"}, "Permissions": {"{\"insert\": \"true\", \"update\" : \"true\", \"new_column\": \"true\"}"}, } assert.EqualError(t, postTx("NewTable", &form), `{"type":"panic","error":"Column name cannot begin with digit"}`) } func TestContracts(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret contractsResult err := sendGet(`contracts`, nil, &ret) if err != nil { t.Error(err) return } } func TestSignature(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(6) form := url.Values{`Value`: {`contract ` + rnd + `Transfer { data { Recipient int Amount money Signature string "optional hidden" } action { $result = "OK " + Str($Amount) }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} if err := postTx(`NewContract`, &form); err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract ` + rnd + `Test { data { Recipient int "hidden" Amount money Signature string "signature:` + rnd + `Transfer" } func action { ` + rnd + `Transfer("Recipient,Amount,Signature",$Recipient,$Amount,$Signature ) $result = "OOOPS " + Str($Amount) } } `}, `Conditions`: {`true`}, "ApplicationId": {"1"}} if err := postTx(`NewContract`, &form); err != nil { t.Error(err) return } form = url.Values{`Name`: {rnd + `Transfer`}, `Value`: {`{"title": "Would you like to sign", "params":[ {"name": "Receipient", "text": "Wallet"}, {"name": "Amount", "text": "Amount(money)"} ]}`}, `Conditions`: {`true`}} if err := postTx(`NewSign`, &form); err != nil { t.Error(err) return } err := postTx(rnd+`Test`, &url.Values{`Amount`: {`12345`}, `Recipient`: {`98765`}}) if err != nil { t.Error(err) return } } var ( imp = `{ "menus": [ { "Name": "test_%s", "Conditions": "ContractAccess(\"@1EditMenu\")", "Value": "MenuItem(main, Default Ecosystem Menu)" } ], "contracts": [ { "Name": "testContract%[1]s", "Value": "contract testContract%[1]s {\n data {}\n conditions {}\n action {\n var res array\n res = DBFind(\"pages\").Columns(\"name\").Where({id: 1}).Order(\"id\")\n $result = res\n }\n }", "Conditions": "ContractConditions(` + "`MainCondition`" + `)" } ], "pages": [ { "Name": "test_%[1]s", "Conditions": "ContractAccess(\"@1EditPage\")", "Menu": "default_menu", "Value": "P(class, Default Ecosystem Page)\nImage().Style(width:100px;)" } ], "blocks": [ { "Name": "test_%[1]s", "Conditions": "true", "Value": "block content" }, { "Name": "test_a%[1]s", "Conditions": "true", "Value": "block content" }, { "Name": "test_b%[1]s", "Conditions": "true", "Value": "block content" } ], "tables": [ { "Name": "members%[1]s", "Columns": "[{\"name\":\"name\",\"type\":\"varchar\",\"conditions\":\"true\"},{\"name\":\"birthday\",\"type\":\"datetime\",\"conditions\":\"true\"},{\"name\":\"member_id\",\"type\":\"number\",\"conditions\":\"true\"},{\"name\":\"val\",\"type\":\"text\",\"conditions\":\"true\"},{\"name\":\"name_first\",\"type\":\"text\",\"conditions\":\"true\"},{\"name\":\"name_middle\",\"type\":\"text\",\"conditions\":\"true\"}]", "Permissions": "{\"insert\":\"true\",\"update\":\"true\",\"new_column\":\"true\"}" } ], "parameters": [ { "Name": "host%[1]s", "Value": "", "Conditions": "ContractConditions(` + "`MainCondition`" + `)" }, { "Name": "host0%[1]s", "Value": "test", "Conditions": "ContractConditions(` + "`MainCondition`" + `)" } ], "data": [ { "Table": "members%[1]s", "Columns": ["name","val"], "Data": [ ["Bob","Richard mark"], ["Mike Winter","Alan summer"] ] } ] }` ) func TestImport(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } name := crypto.RandSeq(4) form := url.Values{"Data": {fmt.Sprintf(imp, name)}} err := postTx(`@1Import`, &form) if err != nil { t.Error(err) return } } func TestEditContracts_ChangeWallet(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(6) code := `contract ` + rnd + ` { data { Par string "optional" } action { $result = $par}}` form := url.Values{`Value`: {code}, `Conditions`: {`true`}} if err := postTx(`NewContract`, &form); err != nil { t.Error(err) return } var ret getContractResult err := sendGet(`contract/`+rnd, nil, &ret) if err != nil { t.Error(err) return } keyID := ret.WalletID sid := ret.TableID var row rowResult err = sendGet(`row/contracts/`+sid, nil, &row) if err != nil { t.Error(err) return } if err := postTx(`ActivateContract`, &url.Values{`Id`: {sid}}); err != nil { t.Error(err) return } code = row.Value[`value`] form = url.Values{`Id`: {sid}, `Value`: {code}, `Conditions`: {row.Value[`conditions`]}, `WalletId`: {"1248-5499-7861-4204-5166"}} err = postTx(`EditContract`, &form) if err == nil { t.Error("Expected `Contract activated` error") return } err = sendGet(`contract/`+rnd, nil, &ret) if err != nil { t.Error(err) return } if ret.WalletID != keyID { t.Error(`wrong walletID`, ret.WalletID, keyID) return } if err := postTx(`DeactivateContract`, &url.Values{`Id`: {sid}}); err != nil { t.Error(err) return } if err := postTx(`EditContract`, &form); err != nil { t.Error(err) return } err = sendGet(`contract/`+rnd, nil, &ret) if err != nil { t.Error(err) return } if ret.Address != "1248-5499-7861-4204-5166" { t.Error(`wrong address`, ret.Address, "!= 1248-5499-7861-4204-5166") return } } func TestUpdateFunc(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `rnd` + crypto.RandSeq(6) form := url.Values{`Value`: {`contract f` + rnd + ` { data { par string } func action { $result = Sprintf("X=%s %s %s", $par, $original_contract, $this_contract) }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} _, id, err := postTxResult(`NewContract`, &form) assert.NoError(t, err) form = url.Values{`Value`: {` contract one` + rnd + ` { action { var ret map ret = DBFind("contracts").Columns("id,value").WhereId(10).Row() $result = ret["id"] }}`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{`Value`: {`contract row` + rnd + ` { action { var ret string ret = DBFind("contracts").Columns("id,value").WhereId(11).One("id") $result = ret }} `}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(`one`+rnd, &url.Values{}) assert.NoError(t, err) assert.Equal(t, "10", msg) _, msg, err = postTxResult(`row`+rnd, &url.Values{}) assert.NoError(t, err) assert.Equal(t, "11", msg) form = url.Values{`Value`: {` contract ` + rnd + ` { data { Par string } action { $result = f` + rnd + `("par",$Par) + " " + $this_contract }} `}, "ApplicationId": {"1"}, `Conditions`: {`true`}} _, idcnt, err := postTxResult(`NewContract`, &form) if err != nil { t.Error(err) return } _, msg, err = postTxResult(rnd, &url.Values{`Par`: {`my param`}}) assert.NoError(t, err) assert.Equal(t, fmt.Sprintf(`X=my param %s f%[1]s %[1]s`, rnd), msg) form = url.Values{`Id`: {id}, `Value`: {` func MyTest2(input string) string { return "Y="+input }`}, `Conditions`: {`true`}} err = postTx(`EditContract`, &form) assert.EqualError(t, postTx(`EditContract`, &form), `{"type":"panic","error":"Contracts or functions names cannot be changed"}`) form = url.Values{`Id`: {id}, `Value`: {`contract f` + rnd + `{ data { par string } action { $result = "Y="+$par }}`}, `Conditions`: {`true`}} assert.NoError(t, postTx(`EditContract`, &form)) _, msg, err = postTxResult(rnd, &url.Values{`Par`: {`new param`}}) assert.NoError(t, err) assert.Equal(t, `Y=new param `+rnd, msg) form = url.Values{`Id`: {idcnt}, `Value`: {` contract ` + rnd + ` { data { Par string } action { $result = f` + rnd + `("par",$Par) + f` + rnd + `("par","OK") }} `}, `Conditions`: {`true`}} _, idcnt, err = postTxResult(`EditContract`, &form) assert.NoError(t, err) _, msg, err = postTxResult(rnd, &url.Values{`Par`: {`finish`}}) assert.NoError(t, err) assert.Equal(t, `Y=finishY=OK`, msg) } func TestGlobalVars(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(4) form := url.Values{`Value`: {` contract ` + rnd + ` { data { Par string } action { $Par = $Par + "end" $key_id = 1234 $result = Str($key_id) + $Par }} `}, "ApplicationId": {"1"}, `Conditions`: {`true`}} err := postTx(`NewContract`, &form) if err == nil { t.Errorf(`must be error`) return } else if err.Error() != `{"type":"panic","error":"system variable $key_id cannot be changed"}` { t.Error(err) return } form = url.Values{`Value`: {`contract c_` + rnd + ` { data { Test string } action { $result = $Test + Str($ecosystem_id) } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {` contract a_` + rnd + ` { data { Par string} conditions {} action { var params map params["Test"] = "TEST" $aaa = 123 if $Par == "b" { $result = CallContract("b_` + rnd + `", params) } else { $result = CallContract("c_` + rnd + `", params) + c_` + rnd + `("Test","OK") } } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract b_` + rnd + ` { data { Test string } action { $result = $Test + $aaa } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } err = postTx(`a_`+rnd, &url.Values{"Par": {"b"}}) if err == nil { t.Errorf(`must be error aaa`) return } else if err.Error() != `{"type":"panic","error":"unknown extend identifier aaa"}` { t.Error(err) return } _, msg, err := postTxResult(`a_`+rnd, &url.Values{"Par": {"c"}}) if err != nil { t.Error(err) return } if msg != `TEST1OK1` { t.Errorf(`wrong result %s`, msg) return } } func TestContractChain(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(4) form := url.Values{"Name": {rnd}, "ApplicationId": {"1"}, "Columns": {`[{"name":"value","type":"varchar", "index": "0", "conditions":"true"}, {"name":"amount", "type":"number","index": "0", "conditions":"true"}, {"name":"dt","type":"datetime", "index": "0", "conditions":"true"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} err := postTx(`NewTable`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract sub` + rnd + ` { data { Id int } action { $row = DBFind("` + rnd + `").Columns("value").WhereId($Id) if Len($row) != 1 { error "sub contract getting error" } $record = $row[0] $new = $record["value"] var val string val = $new+"="+$new DBUpdate("` + rnd + `", $Id, {"value": val }) } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract ` + rnd + ` { data { Initial string } action { $id = DBInsert("` + rnd + `", {value: $Initial, amount:"0"}) sub` + rnd + `($id) $row = DBFind("` + rnd + `").Columns("value").WhereId($id) if Len($row) != 1 { error "contract getting error" } $record = $row[0] $result = $record["value"] } } `}, "ApplicationId": {"1"}, `Conditions`: {`true`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } _, msg, err := postTxResult(rnd, &url.Values{`Initial`: {rnd}}) if err != nil { t.Error(err) return } if msg != rnd+`=`+rnd { t.Error(fmt.Errorf(`wrong result %s`, msg)) } form = url.Values{`Value`: {`contract ` + rnd + `1 { action { DBInsert("` + rnd + `", {amount: 0,dt: "timestamp NOW()"}) } } `}, "ApplicationId": {"1"}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(rnd+`1`, &url.Values{}), `{"type":"panic","error":"It is prohibited to use Now() function"}`) } func TestLoopCond(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + `1 { conditions { } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} err := postTx(`NewContract`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract ` + rnd + `2 { conditions { ContractConditions("` + rnd + `1") } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } var ret getContractResult err = sendGet(`contract/`+rnd+`1`, nil, &ret) if err != nil { t.Error(err) return } sid := ret.TableID form = url.Values{`Value`: {`contract ` + rnd + `1 { conditions { ContractConditions("` + rnd + `2") } }`}, `Id`: {sid}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} err = postTx(`EditContract`, &form) if err != nil { t.Error(err) return } assert.EqualError(t, postTx(rnd+`2`, &url.Values{}), `{"type":"panic","error":"There is loop in `+rnd+`1 contract"}`) form = url.Values{"Name": {`ecosystems`}, "InsertPerm": {`ContractConditions("MainCondition")`}, "UpdatePerm": {`EditEcosysName(1, "HANG")`}, "NewColumnPerm": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`EditTable`, &form)) assert.EqualError(t, postTx(`EditEcosystemName`, &url.Values{"EcosystemID": {`1`}, "NewName": {`Hang`}}), `{"type":"panic","error":"There is loop in EditEcosysName contract"}`) form = url.Values{`Value`: {`contract ` + rnd + `shutdown { action { DBInsert("` + rnd + `table", {"test": "SHUTDOWN"}) } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{ "Name": {rnd + `table`}, "Columns": {`[{"name":"test","type":"varchar", "index": "0", "conditions":"true"}]`}, "ApplicationId": {"1"}, "Permissions": {`{"insert": "` + rnd + `shutdown()", "update" : "true", "new_column": "true"}`}, } require.NoError(t, postTx("NewTable", &form)) assert.EqualError(t, postTx(rnd+`shutdown`, &url.Values{}), `{"type":"panic","error":"There is loop in @1`+rnd+`shutdown contract"}`) } func TestRand(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + ` { action { var result i int i = 3 while i < 15 { var rnd int rnd = Random(0, 3*i) result = result + rnd i=i+1 } $result = result } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) _, val1, err := postTxResult(rnd, &url.Values{}) assert.NoError(t, err) _, val2, err := postTxResult(rnd, &url.Values{}) assert.NoError(t, err) // val1 == val2 for seed = blockId % 1 if val1 != val2 { t.Errorf(`%s!=%s`, val1, val2) } } func TestKillNode(t *testing.T) { require.NoError(t, keyLogin(1)) form := url.Values{"Name": {`MyTestContract1`}, "Value": {`contract MyTestContract1 {action {}}`}, "ApplicationId": {`1`}, "Conditions": {`true`}, "nowait": {`true`}} require.NoError(t, postTx(`NewContract`, &form)) require.NoError(t, postTx("Kill", &url.Values{"nowait": {`true`}})) } func TestLoopCondExt(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := `rnd` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + `1 { conditions { } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} err := postTx(`NewContract`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract ` + rnd + `2 { conditions { ContractConditions("` + rnd + `1") } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} err = postTx(`NewContract`, &form) if err != nil { t.Error(err) return } var ret getContractResult err = sendGet(`contract/`+rnd+`1`, nil, &ret) if err != nil { t.Error(err) return } sid := ret.TableID form = url.Values{`Value`: {`contract ` + rnd + `1 { conditions { ContractConditions("` + rnd + `2") } }`}, `Id`: {sid}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} err = postTx(`EditContract`, &form) if err != nil { t.Error(err) return } err = postTx(rnd+`2`, &url.Values{}) if err != nil { t.Error(err) return } } func TestBlockTransactions(t *testing.T) { require.NoError(t, keyLogin(1)) rnd := `rnd` + crypto.RandSeq(4) form := url.Values{`Value`: {`contract ` + rnd + `1 { conditions { } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} require.NoError(t, postTx(`NewContract`, &form)) var ret getContractResult require.NoError(t, sendGet(`contract/`+rnd+`1`, nil, &ret)) var result map[int64][]TxInfo require.NoError(t, sendGet(`blocks?block_id=1&count=10`, nil, &result)) fmt.Printf("%+v", result) } func TestCost(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`cnt`) form := url.Values{`Value`: {`contract ` + name + `1 { func my() { var i int while i < 1000 { i = i + 1 } } conditions { var i int while i < 1000 { i = i + 1 } } action { var i int while i < 10000 { i = i + 1 } my() $result = "OK" } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} require.NoError(t, postTx(`NewContract`, &form)) form = url.Values{`Value`: {`contract ` + name + `2 { conditions { var i int while i < 1000 { i = i + 1 } } action { var i int while i < 10000 { i = i + 1 } ` + name + `1() $result = "OK" } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} require.NoError(t, postTx(`NewContract`, &form)) require.NoError(t, postTx(name+`1`, &url.Values{})) require.NoError(t, postTx(name+`2`, &url.Values{})) t.Error(`OK`) } func TestHard(t *testing.T) { require.NoError(t, keyLogin(1)) name := randName(`h`) form := url.Values{"Name": {name}, "Value": {`contract ` + name + ` { data { Par int } action {}}`}, "ApplicationId": {`1`}, "Conditions": {`true`}} _, msg, err := postTxResult(`NewContract`, &form) require.NoError(t, err) fmt.Println(`MSg=`, msg, name) for i := 0; i < 1000; i++ { form = url.Values{"Id": {msg}, "Value": {fmt.Sprintf(`contract %s {action { Println("OK %d") }}`, name, i)}, "Conditions": {`true`}, "nowait": {`true`}, "Par": {fmt.Sprintf("%d", i)}} if err = postTx( /*name */ `EditContract`, &form); err != nil { t.Error(err) } } t.Error(`OK`) } func TestInsert(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`cnt`) form := url.Values{`Value`: {`contract ` + name + `1 { conditions { } action { NewTable("Name,Columns,ApplicationId,Permissions", "` + name + `2", "[{\"name\":\"MyName\",\"type\":\"varchar\", \"index\": \"0\", \"conditions\":{\"update\":\"true\", \"read\":\"true\"}}]", 100, "{\"insert\": \"true\", \"update\" : \"true\", \"new_column\": \"true\"}") } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} require.NoError(t, postTx(`NewContract`, &form)) form = url.Values{`Value`: {`contract ` + name + `2 { action { DBInsert("` + name + `2", {MyName: "insert"}) } }`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} require.NoError(t, postTx(`NewContract`, &form)) require.NoError(t, postTx(name+`1`, &url.Values{})) require.NoError(t, postTx(name+`2`, &url.Values{})) t.Error(`OK`) } func TestErrors(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`cnt`) form := url.Values{`Value`: {`contract ` + name + `1 { action { // comment DBFind("qq") }}`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(name+`1`, &url.Values{}), `{"type":"panic","error":"pq: relation \"1_qq\" does not exist [DBSelect @1`+name+`1:4]"}`) form = url.Values{`Value`: {`contract ` + name + `2 { action { // comment var i int i = 1/0 }}`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(name+`2`, &url.Values{}), `{"type":"panic","error":"divided by zero [@1`+name+`2:5]"}`) form = url.Values{`Value`: {`contract ` + name + `5 { action { // comment Throw("Problem", "throw message") }}`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(name+`5`, &url.Values{}), `{"type":"panic","error":"throw message [Throw @1`+name+`5:4]"}`) form = url.Values{`Value`: {`contract ` + name + `4 { action { // comment error("error message") }}`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(name+`4`, &url.Values{}), `{"type":"error","error":"error message"}`) form = url.Values{`Value`: {`contract ` + name + `3 { data { Par int } action { if $Par == 1 { ` + name + `1() } if $Par == 2 { ` + name + `2() } }}`}, `Conditions`: {`true`}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.EqualError(t, postTx(name+`3`, &url.Values{`Par`: {`1`}}), `{"type":"panic","error":"pq: relation \"1_qq\" does not exist [DBSelect @1`+name+`1:4 @1`+name+`3:6]"}`) assert.EqualError(t, postTx(name+`3`, &url.Values{`Par`: {`2`}}), `{"type":"panic","error":"divided by zero [@1`+name+`2:5 @1`+name+`3:9]"}`) t.Error(`OK`) } func TestExternalNetwork(t *testing.T) { assert.NoError(t, keyLogin(1)) var form url.Values name := `cnt` + crypto.RandSeq(4) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `Hashes { data { hash string block int UID string } action { Println("SUCCESS", $UID, $hash, $block ) if $UID == "123456" { $result = "ok" } } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `Result { data { UID string Status int Block int Msg string "optional" } action { Println("Result Contract", $UID, $Status, $Block, $Msg ) } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `Errors { data { hash string block int UID string "optional" } action { if $UID == "stop" { error("Error message") } $result = 1/0 } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `2 { action { var params map params["hash"] = PubToHex($txhash) params["block"] = $block SendExternalTransaction( "123456", "http://localhost:7079", "@1` + name + `Hashes", params, "@1` + name + `Result") SendExternalTransaction( "654321", "http://localhost:7079", "@1` + name + `Hashes", params, "@1` + name + `Result") SendExternalTransaction( "stop", "http://localhost:7079", "@1` + name + `Errors", params, "@1` + name + `Result") SendExternalTransaction( "zero", "http://localhost:7079", "@1` + name + `Errors", params, "@1` + name + `Result") } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.NoError(t, postTx(name+`2`, &url.Values{})) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `3 { action { var params map params["hash"] = PubToHex($txhash) params["block"] = $block SendExternalTransaction( "77", "http://localhost:7079", "@1` + name + `Hashes", params, "@1None") } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.NoError(t, postTx(name+`3`, &url.Values{})) } func TestApos(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`cnt`) form := url.Values{"Name": {name}, "Value": {`contract ` + name + ` { data { Address string } action { var m map var id int m["member_name"] = "test" m["member_info->country"] = $Address m["member_info->ooops"] = "seses' seseses " id = DBInsert("members", m) m["member_info->new"] = "ok'; ok" m["memb'er_info->ne'wq"] = "stop'" DBUpdate("members", id, m) }}`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.NoError(t, postTx(name, &url.Values{`Address`: {"Name d'Company"}})) } func TestCondition(t *testing.T) { assert.NoError(t, keyLogin(1)) var form url.Values name := `cnt` + crypto.RandSeq(4) form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { action { Println("COND" ) } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{"Name": {name + `2`}, "Value": {`contract ` + name + `2 { conditions { } action { Println("COND 2" ) } }`}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) assert.NoError(t, postTx(`NewPage`, &url.Values{ "ApplicationId": {`1`}, "Name": {name}, "Value": {`Div(Body: "Condition 2 - test")`}, "Menu": {`default_menu`}, "Conditions": {`ContractConditions("` + name + `2")`}, })) var ret listResult err := sendGet(`list/pages`, nil, &ret) if err != nil { t.Error(err) return } id := strconv.FormatInt(ret.Count, 10) assert.NoError(t, postTx(`EditPage`, &url.Values{ "Id": {id}, "Value": {`Div(Body: "Condition 1 - test")`}, "Conditions": {`ContractConditions("` + name + `")`}, })) assert.NoError(t, postTx(`EditPage`, &url.Values{ "Id": {id}, "Value": {`Div(Body: "Condition - test")`}, "Conditions": {`true`}, })) } func TestCurrentKeyFromAccount(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(t.Name()) form := url.Values{ "Name": {name}, "Value": {`contract ` + name + ` { data { Account string } action { info CurrentKeyFromAccount($Account) } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, } assert.NoError(t, postTx("NewContract", &form)) expected := fmt.Sprintf(`{"type":"info","error":"%d"}`, converter.StringToAddress(gAddress)) assert.Error(t, postTx(name, &url.Values{`Account`: {gAddress}}), expected) } ================================================ FILE: packages/api/contracts.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type contractsResult struct { Count string `json:"count"` List []map[string]string `json:"list"` } func getContractsHandler(w http.ResponseWriter, r *http.Request) { form := &paginatorForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } client := getClient(r) logger := getLogger(r) contract := &sqldb.Contract{} contract.EcosystemID = client.EcosystemID count, err := contract.CountByEcosystem() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting table records count") errorResponse(w, err) return } contracts, err := contract.GetListByEcosystem(form.Offset, form.Limit) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all") errorResponse(w, err) return } list := make([]map[string]string, len(contracts)) for i, c := range contracts { list[i] = c.ToMap() list[i]["address"] = converter.AddressToString(c.WalletID) } if len(list) == 0 { list = nil } jsonResponse(w, &listResult{ Count: count, List: list, }) } ================================================ FILE: packages/api/data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "crypto/md5" "encoding/hex" "fmt" "net/http" "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) const binaryColumn = "data" func compareHash(data []byte, urlHash string) bool { urlHash = strings.ToLower(urlHash) var hash []byte switch len(urlHash) { case 32: h := md5.Sum(data) hash = h[:] case 64: hash = crypto.Hash(data) } return hex.EncodeToString(hash) == urlHash } func getDataHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) table, column := params["table"], params["column"] id, err := strconv.ParseInt(params["id"], 10, 64) if err != nil { errorResponse(w, errParamNotFound.Errorf("id")) return } data, err := sqldb.GetColumnByID(table, column, id) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting data from table") errorResponse(w, errNotFound) return } if !compareHash([]byte(data), params["hash"]) { errorResponse(w, errHashWrong) return } w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment") w.Header().Set("Access-Control-Allow-Origin", "*") w.Write([]byte(data)) return } func getBinaryHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) bin := sqldb.Binary{} found, err := bin.GetByID(converter.StrToInt64(params["id"])) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Errorf("getting binary by id") errorResponse(w, err) return } if !found { errorResponse(w, errNotFound) return } if !compareHash(bin.Data, params["hash"]) { errorResponse(w, errHashWrong) return } w.Header().Set("Content-Type", bin.MimeType) w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, bin.Name)) w.Header().Set("Access-Control-Allow-Origin", "*") w.Write(bin.Data) return } ================================================ FILE: packages/api/ecosystem.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) func getEcosystemNameHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) ecosystemID := converter.StrToInt64(r.FormValue("id")) ecosystems := sqldb.Ecosystem{} found, err := ecosystems.Get(nil, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting ecosystem name") errorResponse(w, err) return } if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "ecosystem_id": ecosystemID}).Debug("ecosystem by id not found") errorResponse(w, errParamNotFound.Errorf("name")) return } jsonResponse(w, &struct { EcosystemName string `json:"ecosystem_name"` }{ EcosystemName: ecosystems.Name, }) } ================================================ FILE: packages/api/ecosystem_params.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/gorilla/mux" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type paramResult struct { ID string `json:"id"` Name string `json:"name"` Value string `json:"value"` Conditions string `json:"conditions"` } type paramsResult struct { List []paramResult `json:"list"` } func (m Mode) getEcosystemParamsHandler(w http.ResponseWriter, r *http.Request) { form := &appParamsForm{ ecosystemForm: ecosystemForm{ Validator: m.EcosystemGetter, }, } if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } logger := getLogger(r) sp := &sqldb.StateParameter{} sp.SetTablePrefix(form.EcosystemPrefix) names := strings.Split(form.Names, ",") list, err := sp.GetAllStateParameters(nil, nil, names) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting all state parameters") } result := ¶msResult{ List: make([]paramResult, 0), } for _, item := range list { result.List = append(result.List, paramResult{ ID: converter.Int64ToStr(item.ID), Name: item.Name, Value: item.Value, Conditions: item.Conditions, }) } jsonResponse(w, result) } func (m Mode) getEcosystemParamHandler(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) form := &ecosystemForm{ Validator: m.EcosystemGetter, } if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) sp := &sqldb.StateParameter{} sp.SetTablePrefix(form.EcosystemPrefix) name := params["name"] if found, err := sp.Get(nil, name); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting state parameter by name") errorResponse(w, err) return } else if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "key": name}).Debug("state parameter not found") errorResponse(w, errParamNotFound.Errorf(name)) return } jsonResponse(w, ¶mResult{ ID: converter.Int64ToStr(sp.ID), Name: sp.Name, Value: sp.Value, Conditions: sp.Conditions, }) } ================================================ FILE: packages/api/ecosystem_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "testing" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewEcosystem(t *testing.T) { var ( err error ) if err = keyLogin(1); err != nil { t.Error(err) return } form := url.Values{`Name`: {`test`}} if _, _, err = postTxResult(`NewEcosystem`, &form); err != nil { t.Error(err) return } form = url.Values{`Name`: {crypto.RandSeq(13)}} if err := postTx(`NewEcosystem`, &form); err != nil { t.Error(err) return } } func TestEditEcosystem(t *testing.T) { var ( err error ) if err = keyLogin(2); err != nil { t.Error(err) return } menu := `government` value := `P(test,test paragraph)` name := randName(`page`) form := url.Values{"Name": {name}, "Value": {value}, "Menu": {menu}, "Conditions": {"ContractConditions(`MainCondition`)"}} err = postTx(`@1NewPage`, &form) if err != nil { t.Error(err) return } err = postTx(`@1NewPage`, &form) if cutErr(err) != fmt.Sprintf(`{"type":"warning","error":"Page %s already exists"}`, name) { t.Error(err) return } form = url.Values{"Name": {name}, "Value": {`MenuItem(default_page)`}, "ApplicationId": {`1`}, "Conditions": {"ContractConditions(`MainCondition`)"}} assert.NoError(t, postTx(`@1NewMenu`, &form)) form = url.Values{"Id": {`1`}, "Value": {value}, "Menu": {menu}, "Conditions": {"ContractConditions(`MainCondition`)"}} err = postTx(`@1EditPage`, &form) if err != nil { t.Error(err) return } nameCont := randName(`test`) form = url.Values{"Value": {`contract ` + nameCont + ` { action { Test("empty", "empty value")}}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} _, id, err := postTxResult(`@1NewContract`, &form) if err != nil { t.Error(err) return } form = url.Values{"Id": {id}, "Value": {`contract ` + nameCont + ` { action { Test("empty3", "empty value")}}`}, "Conditions": {`ContractConditions("MainCondition")`}} if err := postTx(`@1EditContract`, &form); err != nil { t.Error(err) return } gAuth = `` if err = keyLogin(1); err != nil { t.Error(err) return } var ret contentResult assert.NoError(t, sendPost(`content/page/@2`+name, &url.Values{}, &ret)) if RawToString(ret.Tree) != `[{"tag":"p","attr":{"class":"test paragraph"},"children":[{"tag":"text","text":"test"}]}]` { t.Errorf(`%s != %s`, RawToString(ret.Tree), `[{"tag":"p","attr":{"class":"test paragraph"},"children":[{"tag":"text","text":"test"}]}]`) } assert.NoError(t, sendPost(`content/menu/@2`+name, &url.Values{}, &ret)) if RawToString(ret.Tree) != `[{"tag":"menuitem","attr":{"title":"default_page"}}]` { t.Errorf(`%s != %s`, RawToString(ret.Tree), `[{"tag":"menuitem","attr":{"title":"default_page"}}]`) } } func TestPlatformParams(t *testing.T) { require.NoError(t, keyLogin(1)) var ret paramsResult require.NoError(t, sendGet(`ecosystemparams`, nil, &ret)) if len(ret.List) < 5 { t.Error(fmt.Errorf(`wrong count of parameters %d`, len(ret.List))) } require.NoError(t, sendGet(`ecosystemparams?names=ecosystem_name,new_table&ecosystem=1`, nil, &ret)) require.Equalf(t, 1, len(ret.List), `wrong count of parameters %d`, len(ret.List)) } func TestSystemParams(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret paramsResult err := sendGet(`systemparams`, nil, &ret) if err != nil { t.Error(err) return } assert.Equal(t, 66, len(ret.List), `wrong count of parameters %d`, len(ret.List)) } func TestSomeSystemParam(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret paramsResult param := "gap_between_blocks" err := sendGet(`systemparams/?names=`+param, nil, &ret) if err != nil { t.Error(err) return } assert.Equal(t, 1, len(ret.List), "parameter %s not found", param) } func TestEcosystemParam(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret, ret1 paramResult err := sendGet(`ecosystemparam/changing_menu`, nil, &ret) if err != nil { t.Error(err) return } if ret.Value != `ContractConditions("MainCondition")` { t.Error(err) return } err = sendGet(`ecosystemparam/myval`, nil, &ret1) if err != nil && err.Error() != `400 {"error": "E_PARAMNOTFOUND", "msg": "Parameter myval has not been found" , "params": ["myval"]}` { t.Error(err) return } if len(ret1.Value) != 0 { t.Error(err) return } } func TestAppParams(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := `rnd` + crypto.RandSeq(3) form := url.Values{`ApplicationId`: {`1`}, `Name`: {rnd + `1`}, `Value`: {`simple string,index`}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewAppParam`, &form)) form[`Name`] = []string{rnd + `2`} form[`Value`] = []string{`another string`} assert.NoError(t, postTx(`NewAppParam`, &form)) var ret appParamsResult assert.NoError(t, sendGet(`appparams/1`, nil, &ret)) if len(ret.List) < 2 { t.Error(fmt.Errorf(`wrong count of parameters %d`, len(ret.List))) return } assert.NoError(t, sendGet(fmt.Sprintf(`appparams/1?names=%s1,%[1]s2&ecosystem=1`, rnd), nil, &ret)) assert.Len(t, ret.List, 2) var ret1, ret2 paramResult assert.NoError(t, sendGet(`appparam/1/`+rnd+`2`, nil, &ret1)) assert.Equal(t, `another string`, ret1.Value) form[`Id`] = []string{ret1.ID} form[`Name`] = []string{rnd + `2`} form[`Value`] = []string{`{"par1":"value 1", "par2":"value 2"}`} assert.NoError(t, postTx(`EditAppParam`, &form)) form = url.Values{"Value": {`contract ` + rnd + `Par { data {} conditions {} action { var row map row=JSONDecode(AppParam(1, "` + rnd + `2", 1)) $result = row["par1"] } }`}, "Conditions": {"true"}, `ApplicationId`: {`1`}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(rnd+`Par`, &form) assert.NoError(t, err) assert.Equal(t, "value 1", msg) forTest := tplList{{`AppParam(` + rnd + `1, 1, Source: myname)`, `[{"tag":"data","attr":{"columns":["id","name"],"data":[["1","simple string"],["2","index"]],"source":"myname","types":["text","text"]}}]`}, {`SetVar(myapp, 1)AppParam(` + rnd + `2, App: #myapp#)`, `[{"tag":"text","text":"{"par1":"value 1", "par2":"value 2"}"}]`}} for _, item := range forTest { var ret contentResult assert.NoError(t, sendPost(`content`, &url.Values{`template`: {item.input}}, &ret)) assert.Equal(t, item.want, RawToString(ret.Tree)) } assert.EqualError(t, sendGet(`appparam/1/myval`, nil, &ret2), `404 {"error":"E_PARAMNOTFOUND","msg":"Parameter myval has not been found"}`) assert.Len(t, ret2.Value, 0) } ================================================ FILE: packages/api/errors.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "errors" "fmt" "net/http" ) var ( defaultStatus = http.StatusBadRequest ErrEcosystemNotFound = errors.New("Ecosystem not found") errContract = errType{"E_CONTRACT", "There is not %s contract", http.StatusNotFound} errDBNil = errType{"E_DBNIL", "DB is nil", defaultStatus} errDeletedKey = errType{"E_DELETEDKEY", "The key is deleted", http.StatusForbidden} errEcosystem = errType{"E_ECOSYSTEM", "Ecosystem %d doesn't exist", defaultStatus} errEmptyPublic = errType{"E_EMPTYPUBLIC", "Public key is undefined", http.StatusBadRequest} errKeyNotFound = errType{"E_KEYNOTFOUND", "Key has not been found", http.StatusNotFound} errEmptySign = errType{"E_EMPTYSIGN", "Signature is undefined", defaultStatus} errHashWrong = errType{"E_HASHWRONG", "Hash is incorrect", http.StatusBadRequest} errHashNotFound = errType{"E_HASHNOTFOUND", "Hash %s has not been found", defaultStatus} errHeavyPage = errType{"E_HEAVYPAGE", "This page is heavy", defaultStatus} errInstalled = errType{"E_INSTALLED", "Chain is already installed", defaultStatus} errInvalidWallet = errType{"E_INVALIDWALLET", "Wallet %s is not valid", http.StatusBadRequest} errLimitForsign = errType{"E_LIMITFORSIGN", "Length of forsign is too big (%d)", defaultStatus} errLimitTxSize = errType{"E_LIMITTXSIZE", "The size of tx is too big (%d)", defaultStatus} errNotFound = errType{"E_NOTFOUND", "Page not found", http.StatusNotFound} errNotFoundRecord = errType{"E_NOTFOUND", "Record not found", http.StatusNotFound} errParamNotFound = errType{"E_PARAMNOTFOUND", "Parameter %s has not been found", http.StatusNotFound} errPermission = errType{"E_PERMISSION", "Permission denied", http.StatusUnauthorized} errQuery = errType{"E_QUERY", "DB query is wrong", http.StatusInternalServerError} errRecovered = errType{"E_RECOVERED", "API recovered", http.StatusInternalServerError} errServer = errType{"E_SERVER", "Server error", defaultStatus} errSignature = errType{"E_SIGNATURE", "Signature is incorrect", http.StatusBadRequest} errUnknownSign = errType{"E_UNKNOWNSIGN", "Unknown signature", defaultStatus} errStateLogin = errType{"E_STATELOGIN", "%d is not a membership of ecosystem %d", http.StatusForbidden} errTableNotFound = errType{"E_TABLENOTFOUND", "Table %s has not been found", http.StatusNotFound} errToken = errType{"E_TOKEN", "Token is not valid", defaultStatus} errTokenExpired = errType{"E_TOKENEXPIRED", "Token is expired by %s", http.StatusUnauthorized} errUnauthorized = errType{"E_UNAUTHORIZED", "Unauthorized", http.StatusUnauthorized} errUndefineval = errType{"E_UNDEFINEVAL", "Value %s is undefined", defaultStatus} errUnknownUID = errType{"E_UNKNOWNUID", "Unknown uid", defaultStatus} errCLB = errType{"E_CLB", "Virtual Dedicated Ecosystem %d doesn't exist", defaultStatus} errCLBCreated = errType{"E_CLBCREATED", "Virtual Dedicated Ecosystem is already created", http.StatusBadRequest} errRequestNotFound = errType{"E_REQUESTNOTFOUND", "Request %s doesn't exist", defaultStatus} errUpdating = errType{"E_UPDATING", "Node is updating blockchain, block height %d", http.StatusServiceUnavailable} errStopping = errType{"E_STOPPING", "Network is stopping", http.StatusServiceUnavailable} errNotImplemented = errType{"E_NOTIMPLEMENTED", "Not implemented", http.StatusNotImplemented} errParamMoneyDigit = errType{"E_PARAMMONEYDIGIT", "The number of decimal places cannot be exceeded ( %s )", http.StatusBadRequest} errDiffKey = errType{"E_DIFKEY", "Sender's key is different from tx key", defaultStatus} errBanned = errType{"E_BANNED", "The key %d is banned till %s", http.StatusForbidden} errCheckRole = errType{"E_CHECKROLE", "Access denied", http.StatusForbidden} errNewUser = errType{"E_NEWUSER", "The block packing in progress, please wait", http.StatusUnauthorized} errEcoNotOpen = errType{"E_ECONOTOPEN", "The ecosystem (%d) is not open and cannot be registered address", http.StatusUnauthorized} ) type errType struct { Err string `json:"error"` Message string `json:"msg"` Status int `json:"-"` } func (et errType) Error() string { return et.Err } func (et errType) Errorf(v ...any) errType { et.Message = fmt.Sprintf(et.Message, v...) return et } ================================================ FILE: packages/api/getcontract.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/script" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type contractField struct { Name string `json:"name"` Type string `json:"type"` Optional bool `json:"optional"` } type getContractResult struct { ID uint32 `json:"id"` StateID uint32 `json:"state"` TableID string `json:"tableid"` WalletID string `json:"walletid"` TokenID string `json:"tokenid"` Address string `json:"address"` Fields []contractField `json:"fields"` Name string `json:"name"` AppId uint32 `json:"app_id"` Ecosystem uint32 `json:"ecosystem"` Conditions string `json:"conditions"` } func getContractInfoHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) contract := getContract(r, params["name"]) if contract == nil { logger.WithFields(log.Fields{"type": consts.ContractError, "contract_name": params["name"]}).Debug("contract name") errorResponse(w, errContract.Errorf(params["name"])) return } var result getContractResult info := getContractInfo(contract) con := &sqldb.Contract{} exits, err := con.Get(info.Owner.TableID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "contract_id": info.Owner.TableID}).Error("get contract") errorResponse(w, errQuery) return } if !exits { logger.WithFields(log.Fields{"type": consts.ContractError, "contract id": info.Owner.TableID}).Debug("get contract") errorResponse(w, errContract.Errorf(params["name"])) return } fields := make([]contractField, 0) result = getContractResult{ ID: uint32(info.Owner.TableID + consts.ShiftContractID), TableID: converter.Int64ToStr(info.Owner.TableID), Name: info.Name, StateID: info.Owner.StateID, WalletID: converter.Int64ToStr(info.Owner.WalletID), TokenID: converter.Int64ToStr(info.Owner.TokenID), Address: converter.AddressToString(info.Owner.WalletID), Ecosystem: uint32(con.EcosystemID), AppId: uint32(con.AppID), Conditions: con.Conditions, } if info.Tx != nil { for _, fitem := range *info.Tx { fields = append(fields, contractField{ Name: fitem.Name, Type: script.OriginalToString(fitem.Original), Optional: fitem.ContainsTag(script.TagOptional), }) } } result.Fields = fields jsonResponse(w, result) } ================================================ FILE: packages/api/getuid.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "math/rand" "net/http" "time" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/golang-jwt/jwt/v4" log "github.com/sirupsen/logrus" ) const jwtUIDExpire = time.Second * 5 type getUIDResult struct { UID string `json:"uid,omitempty"` Token string `json:"token,omitempty"` Expire string `json:"expire,omitempty"` EcosystemID string `json:"ecosystem_id,omitempty"` KeyID string `json:"key_id,omitempty"` Address string `json:"address,omitempty"` NetworkID string `json:"network_id,omitempty"` Cryptoer string `json:"cryptoer"` Hasher string `json:"hasher"` } func getUIDHandler(w http.ResponseWriter, r *http.Request) { result := new(getUIDResult) result.NetworkID = converter.Int64ToStr(conf.Config.LocalConf.NetworkID) token := getToken(r) result.Cryptoer, result.Hasher = conf.Config.CryptoSettings.Cryptoer, conf.Config.CryptoSettings.Hasher if token != nil { if claims, ok := token.Claims.(*JWTClaims); ok && len(claims.KeyID) > 0 { result.EcosystemID = claims.EcosystemID result.Expire = claims.ExpiresAt.Sub(time.Now()).String() result.KeyID = claims.KeyID result.Address = converter.AddressToString(converter.StrToInt64(claims.KeyID)) jsonResponse(w, result) return } } result.UID = converter.Int64ToStr(rand.New(rand.NewSource(time.Now().Unix())).Int63()) claims := JWTClaims{ UID: result.UID, EcosystemID: "1", RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(jwtUIDExpire)}, }, } var err error if result.Token, err = generateJWTToken(claims); err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.JWTError, "error": err}).Error("generating jwt token") errorResponse(w, err) return } jsonResponse(w, result) } ================================================ FILE: packages/api/getuid_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "encoding/json" "net/url" "testing" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/stretchr/testify/assert" ) func TestGetUID(t *testing.T) { var ret getUIDResult err := sendGet(`getuid`, nil, &ret) if err != nil { var v map[string]string json.Unmarshal([]byte(err.Error()[4:]), &v) t.Error(err) return } gAuth = ret.Token priv, pub, err := crypto.GenHexKeys() if err != nil { t.Error(err) return } sign, err := crypto.SignString(priv, `LOGIN`+ret.NetworkID+ret.UID) if err != nil { t.Error(err) return } form := url.Values{"pubkey": {pub}, "signature": {hex.EncodeToString(sign)}} var lret loginResult err = sendPost(`login`, &form, &lret) if err != nil { t.Error(err) return } gAuth = lret.Token } func TestNetwork(t *testing.T) { var ret NetworkResult assert.NoError(t, sendGet(`network`, nil, &ret)) if len(ret.NetworkID) == 0 || len(ret.CentrifugoURL) == 0 || len(ret.HonorNodes) == 0 { t.Error(`Wrong value`, ret) } } ================================================ FILE: packages/api/history.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/json" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) const rollbackHistoryLimit = 100 type historyResult struct { List []map[string]string `json:"list"` } func getHistoryHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) client := getClient(r) table := client.Prefix() + "_" + params["name"] rollbackTx := &sqldb.RollbackTx{} txs, err := rollbackTx.GetRollbackTxsByTableIDAndTableName(params["id"], table, rollbackHistoryLimit) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("rollback history") errorResponse(w, err) return } rollbackList := []map[string]string{} for _, tx := range *txs { if tx.Data == "" { continue } rollback := map[string]string{} if err := json.Unmarshal([]byte(tx.Data), &rollback); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling rollbackTx.Data from JSON") errorResponse(w, err) return } rollbackList = append(rollbackList, rollback) } jsonResponse(w, &historyResult{rollbackList}) } ================================================ FILE: packages/api/history_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( stdErrors "errors" "testing" ) func TestHistory(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret historyResult err := sendGet("history/pages/1", nil, &ret) if err != nil { t.Error(err) return } if len(ret.List) == 0 { t.Error(stdErrors.New("History should not be empty")) } err = sendGet("history/pages/1000", nil, &ret) if err != nil { t.Error(err) return } if len(ret.List) != 0 { t.Error(stdErrors.New("History should be empty")) } } ================================================ FILE: packages/api/import_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "errors" "fmt" "net/url" "os" "testing" "github.com/stretchr/testify/assert" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/types" ) func ImportApps(path, appname string) error { apps, err := os.ReadFile(path + "/" + appname + ".json") if err != nil { return err } var val = make(map[any]any) val["Body"] = apps val["MimeType"] = "application/json" val["Name"] = appname + ".json" params := contractParams{ "Data": val, } _, _, err = postTxResult("ImportUpload", ¶ms) if err != nil { return err } fmt.Println("successful upload ", val["Name"], "---------------") damap, err := smart.JSONDecode(string(apps)) if err != nil { return err } if _, o := damap.(*types.Map).Get("data"); o { vals := converter.MarshalJson(damap.(*types.Map).Values()[1]) params2 := url.Values{`Data`: {vals}} _, _, err = postTxResult("Import", ¶ms2) if err != nil { return err } fmt.Println("successful import ", val["Name"], "---------------") } else { return errors.New("nil data") } return nil } func TestImportApps(t *testing.T) { assert.NoError(t, keyLogin(1)) path, err := os.Getwd() assert.NoError(t, err) assert.NoError(t, ImportApps(path, "system")) assert.NoError(t, ImportApps(path, "conditions")) assert.NoError(t, ImportApps(path, "basic")) assert.NoError(t, ImportApps(path, "lang_res")) assert.NoError(t, ImportApps(path, "platform_apps/ecosystems_catalog")) assert.NoError(t, ImportApps(path, "platform_apps/token_emission")) form := url.Values{} assert.NoError(t, postTx(`@1RolesInstall`, &form)) fmt.Println("successful RolesInstall ") //form = url.Values{"SetDefault": {"yes"}} //assert.NoError(t, postTx(`@1VotingTemplatesInstall`, &form)) //fmt.Println("successful VotingTemplatesInstall ") //nodePub := `0498b18e551493a269b6f419d7784d26c8e3555638e80897c69997ef9f211e21d5d0b8adeeaab0e0e750e720ddf3048ec55d613ba5dee3fdfd4e7c17d346731e9b` //tcpHost := `127.0.0.1:7078` //firstNode := fmt.Sprintf(`{"api_address":"%v","public_key":"%v","tcp_address":"%v"}`, apiAddress, nodePub, tcpHost) //firstNodeID := `18` //form = url.Values{"Conditions": {`ContractConditions("@1DeveloperCondition")`}, // "Id": {firstNodeID}, // "Value": {firstNode}, //} //assert.NoError(t, postTx(`@1EditAppParam`, &form)) //fmt.Println("successful EditAppParam to first_node ") //users := []string{ // `04794cbbfa0ff0d1a3dc3e08e5332ff44131be265d9d67ad60996fd5e3f04d50610b8de6b99bb068991a29806e16832290c0bc890373ae592037317fa213227e39`, // `045b9c7555a9218f67a94c54c740e33bac6658d6f19be3d527932ccf067cecea17d9d104c315f603458c4ff022af6234e6ee2c8772d334c6a4f478c25fd5ac9a81`, // `0432ed8601fbe0e452f647147e26bfbfc93532e019f9dad80d183c3dafe7d432f9d84cff6595d9c023fc40119f0b31fa3b2e05d6511bb83f1ba38eb487df8cafe1`, // `046ea9 // //// 254d1d7a530794ef7f5798dcd7842829628ab3901f72b8ae3944d77403bdb9ac4cd286c664d7f247f83b88844bf4c1d3cc8990cd3944db49cf494cb277b3`, //} //for _, u := range users { // form = url.Values{"NewPubkey": {u}} // assert.NoError(t, postTx(`@1NewUser`, &form)) //} //fmt.Println("successful 4 NewUser ") //form = url.Values{ // "TcpAddress": {`127.0.0.1:8078`}, // "ApiAddress": {`http://127.0.0.1:8079`}, // "PubKey": {`04794cbbfa0ff0d1a3dc3e08e5332ff44131be265d9d67ad60996fd5e3f04d50610b8de6b99bb068991a29806e16832290c0bc890373ae592037317fa213227e39`}, // "Description": {`node2`}} //assert.NoError(t, postTx(`@1CNConnectionRequest`, &form)) //fmt.Println("successful node2 CNConnectionRequest ") //form = url.Values{ // "TcpAddress": {`127.0.0.1:9078`}, // "ApiAddress": {`http://127.0.0.1:9079`}, // "PubKey": {`0432ed8601fbe0e452f647147e26bfbfc93532e019f9dad80d183c3dafe7d432f9d84cff6595d9c023fc40119f0b31fa3b2e05d6511bb83f1ba38eb487df8cafe1`}, // "Description": {`node3`}} //assert.NoError(t, postTx(`@1CNConnectionRequest`, &form)) //fmt.Println("successful node3 CNConnectionRequest ") } ================================================ FILE: packages/api/interface.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type componentModel interface { SetTablePrefix(prefix string) Get(name string) (bool, error) } func getPageRowHandler(w http.ResponseWriter, r *http.Request) { getInterfaceRow(w, r, &sqldb.Page{}) } func getMenuRowHandler(w http.ResponseWriter, r *http.Request) { getInterfaceRow(w, r, &sqldb.Menu{}) } func getSnippetRowHandler(w http.ResponseWriter, r *http.Request) { getInterfaceRow(w, r, &sqldb.Snippet{}) } func getInterfaceRow(w http.ResponseWriter, r *http.Request, c componentModel) { params := mux.Vars(r) logger := getLogger(r) client := getClient(r) c.SetTablePrefix(client.Prefix()) if ok, err := c.Get(params["name"]); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting one row") errorResponse(w, errQuery) return } else if !ok { errorResponse(w, errNotFound) return } jsonResponse(w, c) } ================================================ FILE: packages/api/interface_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetInterfaceRow(t *testing.T) { cases := []struct { url string contract string equalAttrs []string }{ {"interface/page/", "NewPage", []string{"Name", "Value", "Menu", "Conditions"}}, {"interface/menu/", "NewMenu", []string{"Name", "Value", "Title", "Conditions"}}, {"interface/snippet/", "NewSnippet", []string{"Name", "Value", "Conditions"}}, } checkEqualAttrs := func(form url.Values, result map[string]any, equalKeys []string) { for _, key := range equalKeys { v := result[strings.ToLower(key)] assert.EqualValues(t, form.Get(key), v) } } errUnauthorized := `401 {"error": "E_UNAUTHORIZED", "msg": "Unauthorized" }` for _, c := range cases { assert.EqualError(t, sendGet(c.url+"-", &url.Values{}, nil), errUnauthorized) } assert.NoError(t, keyLogin(1)) for _, c := range cases { name := randName("component") form := url.Values{ "Name": {name}, "Value": {"value"}, "Menu": {"default_menu"}, "Title": {"title"}, "Conditions": {"true"}, } assert.NoError(t, postTx(c.contract, &form)) result := map[string]any{} assert.NoError(t, sendGet(c.url+name, &url.Values{}, &result)) checkEqualAttrs(form, result, c.equalAttrs) } } func TestNewMenuNoError(t *testing.T) { require.NoError(t, keyLogin(1)) menuname := "myTestMenu" form := url.Values{"Name": {menuname}, "Value": {`first second third`}, "Title": {`My Test Menu`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewMenu`, &form)) err := postTx(`NewMenu`, &form) assert.Equal(t, fmt.Sprintf(`{"type":"warning","error":"Menu %s already exists"}`, menuname), cutErr(err)) } func TestEditMenuNoError(t *testing.T) { require.NoError(t, keyLogin(1)) form := url.Values{ "Id": {"1"}, "Value": {`first second third andmore`}, "Title": {`My edited Test Menu`}, } assert.NoError(t, postTx(`EditMenu`, &form)) } func TestAppendMenuNoError(t *testing.T) { require.NoError(t, keyLogin(1)) form := url.Values{ "Id": {"3"}, "Value": {"appended item"}, } assert.NoError(t, postTx("AppendMenu", &form)) } ================================================ FILE: packages/api/lang_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" ) func TestLang(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName("lng") utfName := randName("lngutf") err := postTx("NewLang", &url.Values{ "Name": {name}, "Trans": {`{"en": "My test", "fr": "French string", "en-US": "US locale"}`}, "ApplicationId": {"1"}, }) assert.NoError(t, err) var list listResult err = sendGet(`list/languages`, nil, &list) if err != nil { return } id := strconv.FormatInt(list.Count, 10) cases := []struct { url string form url.Values expect string }{ { "NewLang", url.Values{ "Name": {utfName}, "Trans": {`{"en": "test"}`}, "ApplicationId": {"1"}, }, "", }, { "NewPage", url.Values{ "Name": {name}, "Value": {fmt.Sprintf("Span($@1%s$)", name)}, "Menu": {"default_menu"}, "Conditions": {`ContractConditions("MainCondition")`}, "ApplicationId": {"1"}, }, "", }, { "content/page/" + name, url.Values{"lang": {"fr"}}, `[{"tag":"span","children":[{"tag":"text","text":"French string"}]}]`, }, { "content/page/" + name, url.Values{"lang": {"en-GB"}}, `[{"tag":"span","children":[{"tag":"text","text":"My test"}]}]`, }, { "content/page/" + name, url.Values{"lang": {"en-US"}}, `[{"tag":"span","children":[{"tag":"text","text":"US locale"}]}]`, }, { "content", url.Values{ "template": { fmt.Sprintf(`Div(){ Button(Body: $%[1]s$ $, Page:test).Alert(Text: $%[1]s$, ConfirmButton: $confirm$, CancelButton: $cancel$) Button(Body: LangRes(@1%[1]s) LangRes, PageParams: "test", ).Alert(Text: $%[1]s$, CancelButton: $cancel$) }`, utfName), }, "app_id": {"1"}, }, `[{"tag":"div","children":[{"tag":"button","attr":{"alert":{"cancelbutton":"$cancel$","confirmbutton":"$confirm$","text":"test"},"page":"test"},"children":[{"tag":"text","text":"test $"}]},{"tag":"button","attr":{"alert":{"cancelbutton":"$cancel$","text":"test"},"pageparams":{"test":{"text":"test","type":"text"}}},"children":[{"tag":"text","text":"test"},{"tag":"text","text":" LangRes"}]}]}]`, }, { "content", url.Values{ `template`: {fmt.Sprintf(`Span(Text LangRes(%s)+LangRes(%[1]s,fr))`, name)}, `app_id`: {`1`}, }, `[{"tag":"span","children":[{"tag":"text","text":"Text My test"},{"tag":"text","text":"+French string"}]}]`, }, { "content", url.Values{ "template": {fmt.Sprintf(`Span(Text LangRes(%s)+LangRes(%[1]s,fr))`, name)}, "lang": {"fr"}, "app_id": {"1"}, }, `[{"tag":"span","children":[{"tag":"text","text":"Text French string"},{"tag":"text","text":"+French string"}]}]`, }, { "EditLang", url.Values{ "Id": {id}, "Trans": {`{"en": "My test", "fr": "French string", "es": "Spanish text"}`}, }, "", }, { "content", url.Values{ "template": {fmt.Sprintf(`Table(mysrc,"$%[1]s$=name")Span(Text LangRes(%[1]s,es) $%[1]s$) Input(Class: form-control, Placeholder: $%[1]s$, Type: text, Name: Name)`, name)}, "app_id": {"1"}, }, `[{"tag":"table","attr":{"columns":[{"Name":"name","Title":"My test"}],"source":"mysrc"}},{"tag":"span","children":[{"tag":"text","text":"Text Spanish text"},{"tag":"text","text":" My test"}]},{"tag":"input","attr":{"class":"form-control","name":"Name","placeholder":"My test","type":"text"}}]`, }, { "content", url.Values{ "template": {fmt.Sprintf(`MenuGroup($%s$){MenuItem(Ooops, ooops)}MenuGroup(nolang){MenuItem(no, no)}`, name)}, "app_id": {"1"}, }, fmt.Sprintf(`[{"tag":"menugroup","attr":{"name":"$%s$","title":"My test"},"children":[{"tag":"menuitem","attr":{"page":"ooops","title":"Ooops"}}]},{"tag":"menugroup","attr":{"name":"nolang","title":"nolang"},"children":[{"tag":"menuitem","attr":{"page":"no","title":"no"}}]}]`, name), }, } for _, v := range cases { var ret contentResult if len(v.expect) == 0 { assert.NoError(t, postTx(v.url, &v.form)) continue } assert.NoError(t, sendPost(v.url, &v.form, &ret)) assert.Equal(t, v.expect, RawToString(ret.Tree)) } } ================================================ FILE: packages/api/limit_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "strconv" "testing" "time" "github.com/stretchr/testify/assert" "github.com/IBAX-io/go-ibax/packages/converter" ) func TestLimit(t *testing.T) { assert.NoError(t, keyLogin(1)) rnd := randName(``) form := url.Values{"Name": {"tbl" + rnd}, "Columns": {`[{"name":"name","type":"number", "conditions":"true"}, {"name":"block", "type":"varchar","conditions":"true"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) form = url.Values{`Value`: {`contract Limit` + rnd + ` { data { Num int } conditions { } action { DBInsert("tbl` + rnd + `", {name: $Num, block: $block}) } }`}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{`Value`: {`contract Upd` + rnd + ` { data { Name string Value string } conditions { } action { DBUpdatePlatformParam($Name, $Value, "") } }`}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) all := 10 sendList := func() { for i := 0; i < all; i++ { assert.NoError(t, postTx(`Limit`+rnd, &url.Values{ `Num`: {converter.IntToStr(i)}, `nowait`: {`true`}, })) } time.Sleep(10 * time.Second) } checkList := func(count, wantBlocks int) (err error) { var list listResult err = sendGet(`list/tbl`+rnd, nil, &list) if err != nil { return } if converter.StrToInt(strconv.FormatInt(list.Count, 10)) != count { return fmt.Errorf(`wrong list items %d != %d`, list.Count, count) } blocks := make(map[string]int) for _, item := range list.List { if v, ok := blocks[item["block"]]; ok { blocks[item["block"]] = v + 1 } else { blocks[item["block"]] = 1 } } if wantBlocks > 0 && len(blocks) != wantBlocks { return fmt.Errorf(`wrong number of blocks %d != %d`, len(blocks), wantBlocks) } return nil } sendList() assert.NoError(t, checkList(10, 1)) var syspar paramsResult assert.NoError(t, sendGet(`systemparams?names=max_tx_block,max_tx_block_per_user`, nil, &syspar)) var maxusers, maxtx string if syspar.List[0].Name == "max_tx_block" { maxusers = syspar.List[1].Value maxtx = syspar.List[0].Value } else { maxusers = syspar.List[0].Value maxtx = syspar.List[1].Value } restoreMax := func() { assert.NoError(t, postTx(`Upd`+rnd, &url.Values{`Name`: {`max_tx_block`}, `Value`: {maxtx}})) assert.NoError(t, postTx(`Upd`+rnd, &url.Values{`Name`: {`max_tx_block_per_user`}, `Value`: {maxusers}})) } defer restoreMax() assert.NoError(t, postTx(`Upd`+rnd, &url.Values{`Name`: {`max_tx_block`}, `Value`: {`7`}})) sendList() assert.NoError(t, checkList(20, 3)) assert.NoError(t, postTx(`Upd`+rnd, &url.Values{`Name`: {`max_tx_block_per_user`}, `Value`: {`3`}})) sendList() assert.NoError(t, checkList(30, 7)) restoreMax() assert.NoError(t, sendGet(`systemparams?names=max_block_generation_time`, nil, &syspar)) var maxtime string maxtime = syspar.List[0].Value defer func() { assert.NoError(t, postTx(`Upd`+rnd, &url.Values{ `Name`: {`max_block_generation_time`}, `Value`: {maxtime}, })) }() assert.NoError(t, postTx(`Upd`+rnd, &url.Values{`Name`: {`max_block_generation_time`}, `Value`: {`100`}})) sendList() assert.NoError(t, checkList(40, 0)) } ================================================ FILE: packages/api/list.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/json" "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" "github.com/IBAX-io/go-ibax/packages/template" "github.com/IBAX-io/go-ibax/packages/types" //"io/ioutil" "net/http" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type listResult struct { Count int64 `json:"count"` List []map[string]string `json:"list"` } type sumResult struct { Sum string `json:"sum"` } type listForm struct { paginatorForm rowForm } type listWhereForm struct { listForm Order string `schema:"order"` InWhere string `schema:"where"` } type SumWhereForm struct { Column string `schema:"column"` Where string `schema:"where"` } func (f *listForm) Validate(r *http.Request) error { if err := f.paginatorForm.Validate(r); err != nil { return err } return f.rowForm.Validate(r) } func (f *SumWhereForm) Validate(r *http.Request) error { if len(f.Column) > 0 { f.Column = converter.Sanitize(f.Column, ``) } return nil } func checkAccess(tableName, columns string, client *Client) (table string, cols string, err error) { sc := smart.SmartContract{ CLB: conf.Config.IsSupportingCLB(), VM: script.GetVM(), TxSmart: &types.SmartTransaction{ Header: &types.Header{ EcosystemID: client.EcosystemID, KeyID: client.KeyID, NetworkID: conf.Config.LocalConf.NetworkID, }, }, } table, _, cols, err = sc.CheckAccess(tableName, columns, client.EcosystemID) return } func getListHandler(w http.ResponseWriter, r *http.Request) { form := &listForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) client := getClient(r) logger := getLogger(r) var ( err error table string ) table, form.Columns, err = checkAccess(params["name"], form.Columns, client) if err != nil { errorResponse(w, err) return } q := sqldb.GetTableQuery(params["name"], client.EcosystemID) if len(form.Columns) > 0 { q = q.Select("id," + form.Columns) } result := new(listResult) err = q.Count(&result.Count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting table records count") errorResponse(w, errTableNotFound.Errorf(table)) return } rows, err := q.Order("id ASC").Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") errorResponse(w, err) return } result.List, err = sqldb.GetResult(rows) if err != nil { errorResponse(w, err) return } jsonResponse(w, result) } func getListWhereHandler(w http.ResponseWriter, r *http.Request) { form := &listWhereForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) client := getClient(r) logger := getLogger(r) var ( err error table, where, order string ) table, form.Columns, err = checkAccess(params["name"], form.Columns, client) if err != nil { errorResponse(w, err) return } if form.Order != "" { var orderParam any err = json.Unmarshal([]byte(form.Order), &orderParam) if err != nil { errorResponse(w, fmt.Errorf("order unamrshal:%v", err)) return } order, err = qb.GetOrder(table, orderParam, true) if err != nil { errorResponse(w, err) return } } //q := sqldb.GetTableQuery(params["name"], client.EcosystemID) q := sqldb.GetTableListQuery(params["name"], client.EcosystemID) if len(form.Columns) > 0 { q = q.Select("id," + smart.PrepareColumns([]string{form.Columns})) } if len(form.InWhere) > 0 { inWhere, _, err := template.ParseObject([]rune(form.InWhere)) if err != nil { errorResponse(w, err) return } switch v := inWhere.(type) { case string: if len(v) == 0 { where = `true` } else { errorResponse(w, errors.New(`Where has wrong format`)) return } case map[string]any: where, err = qb.GetWhere(types.LoadMap(v)) if err != nil { errorResponse(w, err) return } case *types.Map: where, err = qb.GetWhere(v) if err != nil { errorResponse(w, err) return } default: errorResponse(w, errors.New(`Where has wrong format`)) return } q = q.Where(where) } result := new(listResult) err = q.Count(&result.Count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}). Errorf("selecting rows from table %s select %s where %s", table, smart.PrepareColumns([]string{form.Columns}), where) errorResponse(w, errTableNotFound.Errorf(table)) return } if len(order) > 0 { rows, err := q.Order(order).Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") errorResponse(w, err) return } result.List, err = sqldb.GetResult(rows) if err != nil { errorResponse(w, err) return } } else { rows, err := q.Order("id ASC").Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") errorResponse(w, err) return } result.List, err = sqldb.GetResult(rows) if err != nil { errorResponse(w, err) return } } jsonResponse(w, result) } func getsumWhereHandler(w http.ResponseWriter, r *http.Request) { var ( err error table, where string ) form := &SumWhereForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) client := getClient(r) logger := getLogger(r) table, form.Column, err = checkAccess(params["name"], form.Column, client) if err != nil { errorResponse(w, err) return } //q := sqldb.GetTableQuery(params["name"], client.EcosystemID) // //if len(form.Columns) > 0 { // q = q.Select("id," + smart.PrepareColumns([]string{form.Columns})) //} if len(form.Where) > 0 { inWhere, _, err := template.ParseObject([]rune(form.Where)) if err != nil { errorResponse(w, err) return } switch v := inWhere.(type) { case string: if len(v) == 0 { where = `true` } else { errorResponse(w, errors.New(`Where has wrong format`)) return } case map[string]any: where, err = qb.GetWhere(types.LoadMap(v)) if err != nil { errorResponse(w, err) return } case *types.Map: where, err = qb.GetWhere(v) if err != nil { errorResponse(w, err) return } default: errorResponse(w, errors.New(`Where has wrong format`)) return } //q = q.Where(where) } count, err := sqldb.NewDbTransaction(nil).GetSumColumnCount(table, form.Column, where) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Errorf("selecting rows from table %s select %s where %s", table, smart.PrepareColumns([]string{form.Column}), where) errorResponse(w, err) return } result := new(sumResult) if count > 0 { sum, err := sqldb.NewDbTransaction(nil).GetSumColumn(table, form.Column, where) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}). Errorf("selecting rows from table %s select %s where %s", table, smart.PrepareColumns([]string{form.Column}), where) errorResponse(w, errTableNotFound.Errorf(table)) return } result.Sum = sum } jsonResponse(w, result) } ================================================ FILE: packages/api/list_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "strconv" "testing" "github.com/IBAX-io/go-ibax/packages/converter" ) func TestList(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret listResult err := sendGet(`list/contracts`, nil, &ret) if err != nil { t.Error(err) return } if converter.StrToInt64(strconv.FormatInt(ret.Count, 10)) < 7 { t.Error(fmt.Errorf(`The number of records %d < 7`, ret.Count)) return } err = sendGet(`list/qwert`, nil, &ret) if err.Error() != `404 {"error":"E_TABLENOTFOUND","msg":"Table 1_qwert has not been found"}` { t.Error(err) return } var retTable tableResult for _, item := range []string{`app_params`, `parameters`} { err = sendGet(`table/`+item, nil, &retTable) if err != nil { t.Error(err) return } if retTable.Name != item { t.Errorf(`wrong table name %s != %s`, retTable.Name, item) return } } var sec listResult err = sendGet(`sections`, nil, &sec) if err != nil { t.Error(err) return } if sec.Count == 0 { t.Errorf(`section error`) return } } ================================================ FILE: packages/api/login.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "encoding/json" "errors" "fmt" "net/http" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/publisher" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/golang-jwt/jwt/v4" log "github.com/sirupsen/logrus" ) // Special word used by frontend to sign UID generated by /getuid API command, sign is performed for contcatenated word and UID func nonceSalt() string { return fmt.Sprintf("LOGIN%d", conf.Config.LocalConf.NetworkID) } type loginForm struct { EcosystemID int64 `schema:"ecosystem"` Expire int64 `schema:"expire"` PublicKey publicKeyValue `schema:"pubkey"` KeyID string `schema:"key_id"` Signature hexValue `schema:"signature"` RoleID int64 `schema:"role_id"` } type publicKeyValue struct { hexValue } func (pk *publicKeyValue) UnmarshalText(v []byte) (err error) { pk.value, err = hex.DecodeString(string(v)) pk.value = crypto.CutPub(pk.value) return } func (f *loginForm) Validate(r *http.Request) error { if f.Expire == 0 { f.Expire = int64(jwtExpire) } return nil } type loginResult struct { Token string `json:"token,omitempty"` EcosystemID string `json:"ecosystem_id,omitempty"` KeyID string `json:"key_id,omitempty"` Account string `json:"account,omitempty"` NotifyKey string `json:"notify_key,omitempty"` IsNode bool `json:"isnode"` IsOwner bool `json:"isowner"` IsCLB bool `json:"clb"` Timestamp string `json:"timestamp,omitempty"` Roles []rolesResult `json:"roles,omitempty"` } type rolesResult struct { RoleID int64 `json:"role_id"` RoleName string `json:"role_name"` } func (m Mode) loginHandler(w http.ResponseWriter, r *http.Request) { var ( publicKey []byte wallet, founder, fm int64 uid string err error isExistPub bool form = new(loginForm) spfounder, spfm sqldb.StateParameter ) if uid, err = getUID(r); err != nil { errorResponse(w, err, http.StatusBadRequest) return } if err = parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } client := getClient(r) logger := getLogger(r) if form.EcosystemID > 0 { client.EcosystemID = form.EcosystemID } else if client.EcosystemID == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Warning("state is empty, using 1 as a state") client.EcosystemID = 1 } if len(form.KeyID) > 0 { wallet = converter.StringToAddress(form.KeyID) } else if len(form.PublicKey.Bytes()) > 0 { wallet = crypto.Address(form.PublicKey.Bytes()) } account := &sqldb.Key{} account.SetTablePrefix(client.EcosystemID) isAccount, err := account.Get(nil, wallet) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting public key from keys") errorResponse(w, err) return } spfm.SetTablePrefix(converter.Int64ToStr(client.EcosystemID)) if ok, err := spfm.Get(nil, "free_membership"); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting free_membership parameter") errorResponse(w, err) return } else if ok { fm = converter.StrToInt64(spfm.Value) } publicKey = account.PublicKey isExistPub = len(publicKey) == 0 isCan := func(a, e bool) bool { return !a || (a && e) } if isCan(isAccount, isExistPub) { if !(fm == 1 || client.EcosystemID == 1) { errorResponse(w, errEcoNotOpen.Errorf(client.EcosystemID)) return } } if isAccount && !isExistPub { if account.Deleted == 1 { errorResponse(w, errDeletedKey) return } } else { if !allowCreateUser(client) { errorResponse(w, errKeyNotFound) return } if isCan(isAccount, isExistPub) { publicKey = form.PublicKey.Bytes() if len(publicKey) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty") errorResponse(w, errEmptyPublic) return } nodePrivateKey := syspar.GetNodePrivKey() contract := smart.GetContract("NewUser", 1) sc := types.SmartTransaction{ Header: &types.Header{ ID: int(contract.Info().ID), EcosystemID: 1, Time: time.Now().Unix(), KeyID: conf.Config.KeyID, NetworkID: conf.Config.LocalConf.NetworkID, }, Params: map[string]any{ "NewPubkey": hex.EncodeToString(publicKey), "Ecosystem": client.EcosystemID, }, } stp := &transaction.SmartTransactionParser{ SmartContract: &smart.SmartContract{TxSmart: new(types.SmartTransaction)}, } txData, err := stp.BinMarshalWithPrivate(&sc, nodePrivateKey, true) if err != nil { log.WithFields(log.Fields{"type": consts.ContractError, "err": err}).Error("Building transaction") errorResponse(w, err) return } if err := m.ContractRunner.RunContract(txData, stp.Hash, sc.KeyID, stp.Timestamp, logger); err != nil { errorResponse(w, err) return } if !conf.Config.IsSupportingCLB() { gt := 3 * syspar.GetMaxBlockGenerationTime() l := &sqldb.LogTransaction{} for i := 0; i < 2; i++ { found, err := l.GetByHash(nil, stp.Hash) if err != nil { errorResponse(w, err) return } if found { if l.Status != 0 { errorResponse(w, errors.New(`encountered some problems when login account`)) return } else { _, _ = account.Get(nil, wallet) break } } time.Sleep(time.Duration(gt) * time.Millisecond) } if l.Block == 0 { errorResponse(w, errNewUser) return } } } else { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty, and state is not default") errorResponse(w, errStateLogin.Errorf(wallet, client.EcosystemID)) return } } if len(publicKey) == 0 { if client.EcosystemID > 1 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty, and state is not default") errorResponse(w, errStateLogin.Errorf(wallet, client.EcosystemID)) return } if len(form.PublicKey.Bytes()) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty") errorResponse(w, errEmptyPublic) return } } if form.RoleID != 0 && client.RoleID == 0 { checkedRole, err := checkRoleFromParam(form.RoleID, client.EcosystemID, account.AccountID) if err != nil { errorResponse(w, err) return } if checkedRole != form.RoleID { errorResponse(w, errCheckRole) return } client.RoleID = checkedRole } verify, err := crypto.Verify(publicKey, []byte(nonceSalt()+uid), form.Signature.Bytes()) if err != nil { logger.WithFields(log.Fields{"type": consts.CryptoError, "pubkey": publicKey, "uid": uid, "signature": form.Signature.Bytes()}).Info("checking signature") errorResponse(w, err) return } if !verify { logger.WithFields(log.Fields{"type": consts.InvalidObject, "pubkey": publicKey, "uid": uid, "signature": form.Signature.Bytes()}).Error("incorrect signature") errorResponse(w, errSignature) return } spfounder.SetTablePrefix(converter.Int64ToStr(client.EcosystemID)) if ok, err := spfounder.Get(nil, "founder_account"); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting founder_account parameter") errorResponse(w, err) return } else if ok { founder = converter.StrToInt64(spfounder.Value) } result := &loginResult{ Account: account.AccountID, EcosystemID: converter.Int64ToStr(client.EcosystemID), KeyID: converter.Int64ToStr(wallet), IsOwner: founder == wallet, IsNode: conf.Config.KeyID == wallet, IsCLB: conf.Config.IsSupportingCLB(), } claims := JWTClaims{ KeyID: result.KeyID, AccountID: account.AccountID, EcosystemID: result.EcosystemID, RoleID: converter.Int64ToStr(form.RoleID), RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Second * time.Duration(form.Expire))}, }, } result.Token, err = generateJWTToken(claims) if err != nil { logger.WithFields(log.Fields{"type": consts.JWTError, "error": err}).Error("generating jwt token") errorResponse(w, err) return } result.NotifyKey, result.Timestamp, err = publisher.GetJWTCent(wallet, form.Expire) if err != nil { errorResponse(w, err) return } ra := &sqldb.RolesParticipants{} roles, err := ra.SetTablePrefix(client.EcosystemID).GetActiveMemberRoles(account.AccountID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting roles") errorResponse(w, err) return } for _, r := range roles { var res map[string]string if err := json.Unmarshal([]byte(r.Role), &res); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling role") errorResponse(w, err) return } result.Roles = append(result.Roles, rolesResult{ RoleID: converter.StrToInt64(res["id"]), RoleName: res["name"], }) } jsonResponse(w, result) } func getUID(r *http.Request) (string, error) { var uid string token := getToken(r) if token != nil { if claims, ok := token.Claims.(*JWTClaims); ok { uid = claims.UID } } else if len(uid) == 0 { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.EmptyObject}).Warning("UID is empty") return "", errUnknownUID } return uid, nil } func checkRoleFromParam(role, ecosystemID int64, account string) (int64, error) { if role > 0 { ok, err := sqldb.MemberHasRole(nil, role, ecosystemID, account) if err != nil { log.WithFields(log.Fields{ "type": consts.DBError, "account": account, "role": role, "ecosystem": ecosystemID}).Error("check role") return 0, err } if !ok { log.WithFields(log.Fields{ "type": consts.NotFound, "account": account, "role": role, "ecosystem": ecosystemID, }).Error("member hasn't role") return 0, nil } } return role, nil } func allowCreateUser(c *Client) bool { if conf.Config.IsSupportingCLB() { return true } return true } ================================================ FILE: packages/api/member.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strconv" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) func getAvatarHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) account := params["account"] ecosystemID := converter.StrToInt64(params["ecosystem"]) member := &sqldb.Member{} member.SetTablePrefix(converter.Int64ToStr(ecosystemID)) found, err := member.Get(account) if err != nil { logger.WithFields(log.Fields{ "type": consts.DBError, "error": err, "ecosystem": ecosystemID, "account": account, }).Error("getting member") errorResponse(w, err) return } if !found { errorResponse(w, errNotFoundRecord) return } if member.ImageID == nil { errorResponse(w, errNotFoundRecord) return } bin := &sqldb.Binary{} bin.SetTablePrefix(converter.Int64ToStr(ecosystemID)) found, err = bin.GetByID(*member.ImageID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "image_id": *member.ImageID}).Errorf("on getting binary by id") errorResponse(w, err) return } if !found { errorResponse(w, errNotFound) return } if len(bin.Data) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject, "error": err, "image_id": *member.ImageID}).Errorf("on check avatar size") errorResponse(w, errNotFound) return } w.Header().Set("Content-Type", bin.MimeType) w.Header().Set("Content-Length", strconv.Itoa(len(bin.Data))) if _, err := w.Write(bin.Data); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("unable to write image") } } func getMemberHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) account := params["account"] ecosystemID := converter.StrToInt64(params["ecosystem"]) member := &sqldb.Member{} member.SetTablePrefix(converter.Int64ToStr(ecosystemID)) _, err := member.Get(account) if err != nil { logger.WithFields(log.Fields{ "type": consts.DBError, "error": err, "ecosystem": ecosystemID, "account": account, }).Error("getting member") errorResponse(w, err) return } jsonResponse(w, member) } ================================================ FILE: packages/api/metrics.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "runtime" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/gorilla/mux" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type blockMetric struct { Count int64 `json:"count"` } type blockMetricByNode struct { TotalCount int64 `json:"totalcount"` PartialCount int64 `json:"partialcount"` } type txMetric struct { Count int64 `json:"count"` } type ecosysMetric struct { Count int64 `json:"count"` } type keyMetric struct { Count int64 `json:"count"` } type honorNodeMetric struct { Count int64 `json:"count"` } type memMetric struct { Alloc uint64 `json:"alloc"` Sys uint64 `json:"sys"` } type banMetric struct { NodePosition int `json:"node_position"` Status bool `json:"status"` } func blocksCountHandler(w http.ResponseWriter, r *http.Request) { b := &sqldb.BlockChain{} logger := getLogger(r) found, err := b.GetMaxBlock() if err != nil { logger.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("on getting max block") errorResponse(w, err, http.StatusInternalServerError) return } if !found { errorResponse(w, errNotFound) return } bm := blockMetric{Count: b.ID} jsonResponse(w, bm) } func blocksCountByNodeHandler(w http.ResponseWriter, r *http.Request) { b := &sqldb.BlockChain{} logger := getLogger(r) params := mux.Vars(r) nodeId := converter.StrToInt64(params["node"]) mode := converter.StrToInt64(params["mode"]) if mode != consts.CandidateNodeMode && mode != consts.HonorNodeMode { errorResponse(w, errParamNotFound.Errorf("mode")) return } found, err := b.GetMaxBlock() if err != nil { logger.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("on getting max block") errorResponse(w, err, http.StatusInternalServerError) return } if !found { errorResponse(w, errNotFound) return } c, err := sqldb.GetBlockCountByNode(nodeId, int32(mode)) if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting block count by node") errorResponse(w, err, http.StatusInternalServerError) return } bm := blockMetricByNode{TotalCount: b.ID, PartialCount: c} jsonResponse(w, bm) } func txCountHandler(w http.ResponseWriter, r *http.Request) { c, err := sqldb.GetTxCount() if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting tx count") errorResponse(w, err, http.StatusInternalServerError) return } jsonResponse(w, txMetric{Count: c}) } func ecosysCountHandler(w http.ResponseWriter, r *http.Request) { total, err := sqldb.GetAllSystemCount() if err != nil { logger := getLogger(r) logger.WithError(err).Error("on getting ecosystem count") errorResponse(w, err, http.StatusInternalServerError) return } jsonResponse(w, ecosysMetric{Count: total}) } func keysCountHandler(w http.ResponseWriter, r *http.Request) { cnt, err := sqldb.GetKeysCount() if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting keys count") errorResponse(w, err, http.StatusInternalServerError) return } jsonResponse(w, keyMetric{Count: cnt}) } func honorNodesCountHandler(w http.ResponseWriter, _ *http.Request) { fnMetric := honorNodeMetric{ Count: syspar.GetNumberOfNodesFromDB(nil), } jsonResponse(w, fnMetric) } func memStatHandler(w http.ResponseWriter, _ *http.Request) { var m runtime.MemStats runtime.ReadMemStats(&m) jsonResponse(w, memMetric{Alloc: m.Alloc, Sys: m.Sys}) } func banStatHandler(w http.ResponseWriter, _ *http.Request) { nodes := syspar.GetNodes() list := make([]banMetric, 0, len(nodes)) b := node.GetNodesBanService() for i, n := range nodes { list = append(list, banMetric{ NodePosition: i, Status: b.IsBanned(n), }) } jsonResponse(w, list) } ================================================ FILE: packages/api/middlewares.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/http" "runtime/debug" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/statsd" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) func authRequire(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { client := getClient(r) if client != nil && client.KeyID != 0 { next(w, r) return } logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.EmptyObject}).Debug("wallet is empty") errorResponse(w, errUnauthorized) } } func loggerFromRequest(r *http.Request) *log.Entry { return log.WithFields(log.Fields{ "headers": r.Header, "path": r.URL.Path, "protocol": r.Proto, "remote": r.RemoteAddr, }) } func loggerMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger := loggerFromRequest(r) logger.Info("received http request") r = setLogger(r, logger) next.ServeHTTP(w, r) }) } func recoverMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { logger := getLogger(r) logger.WithFields(log.Fields{ "type": consts.PanicRecoveredError, "error": err, "stack": string(debug.Stack()), }).Debug("panic recovered error") fmt.Println("API Recovered", fmt.Sprintf("%s: %s", err, debug.Stack())) errorResponse(w, errRecovered) } }() next.ServeHTTP(w, r) }) } func nodeStateMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var reason errType switch node.NodePauseType() { case node.NoPause: next.ServeHTTP(w, r) return case node.PauseTypeUpdatingBlockchain: var s sqldb.BlockChain s.GetMaxBlock() reason = errUpdating.Errorf(s.ID) break case node.PauseTypeStopingNetwork: reason = errStopping break } errorResponse(w, reason) }) } func tokenMiddleware(next http.Handler) http.Handler { const authHeader = "AUTHORIZATION" return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //token, err := RefreshToken(r.Header.Get(authHeader)) token, err := parseJWTToken(r.Header.Get(authHeader)) if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.JWTError, "error": err}).Warning("starting session") } if token != nil && token.Valid { r = setToken(r, token) } next.ServeHTTP(w, r) }) } func (m Mode) clientMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := getToken(r) var client *Client if token != nil { // get client from token var err error if client, err = getClientFromToken(token, m.EcosystemGetter); err != nil { errorResponse(w, err) return } } if client == nil { // create client with default ecosystem client = &Client{EcosystemID: 1} } r = setClient(r, client) next.ServeHTTP(w, r) }) } func statsdMiddleware(next http.Handler) http.Handler { const v = 1.0 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { route := mux.CurrentRoute(r) counterName := statsd.APIRouteCounterName(r.Method, route.GetName()) statsd.Client.Inc(counterName+statsd.Count, 1, v) startTime := time.Now() defer func() { statsd.Client.TimingDuration(counterName+statsd.Time, time.Since(startTime), v) }() next.ServeHTTP(w, r) }) } ================================================ FILE: packages/api/network.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strconv" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/converter" ) type HonorNodeJSON struct { TCPAddress string `json:"tcp_address"` APIAddress string `json:"api_address"` PublicKey string `json:"public_key"` UnbanTime string `json:"unban_time"` Stopped bool `json:"stopped"` } type NetworkResult struct { NetworkID string `json:"network_id"` CentrifugoURL string `json:"centrifugo_url"` Test bool `json:"test"` Private bool `json:"private"` HonorNodes []HonorNodeJSON `json:"honor_nodes"` } func GetNodesJSON() []HonorNodeJSON { nodes := make([]HonorNodeJSON, 0) for _, node := range syspar.GetNodes() { nodes = append(nodes, HonorNodeJSON{ TCPAddress: node.TCPAddress, APIAddress: node.APIAddress, PublicKey: crypto.PubToHex(node.PublicKey), UnbanTime: strconv.FormatInt(node.UnbanTime.Unix(), 10), }) } return nodes } func getNetworkHandler(w http.ResponseWriter, r *http.Request) { jsonResponse(w, &NetworkResult{ NetworkID: converter.Int64ToStr(conf.Config.LocalConf.NetworkID), CentrifugoURL: conf.Config.Centrifugo.URL, Test: syspar.IsTestMode(), Private: syspar.IsPrivateBlockchain(), HonorNodes: GetNodesJSON(), }) } ================================================ FILE: packages/api/node.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" ) // nodeContract is used when calling a cron contract in CLB mode func nodeContractHandler(w http.ResponseWriter, r *http.Request) { errorResponse(w, errNotImplemented) } ================================================ FILE: packages/api/platform_params.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "github.com/IBAX-io/go-ibax/packages/converter" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) func getPlatformParamsHandler(w http.ResponseWriter, r *http.Request) { form := ¶msForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } logger := getLogger(r) list, err := sqldb.GetAllPlatformParameters(nil, nil, nil, nil) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting all platform parameters") } result := ¶msResult{ List: make([]paramResult, 0), } acceptNames := form.AcceptNames() for _, item := range list { if len(acceptNames) > 0 && !acceptNames[item.Name] { continue } result.List = append(result.List, paramResult{ ID: converter.Int64ToStr(item.ID), Name: item.Name, Value: item.Value, Conditions: item.Conditions, }) } if len(result.List) == 0 { errorResponse(w, errParamNotFound.Errorf(form.Names), http.StatusBadRequest) return } jsonResponse(w, result) } ================================================ FILE: packages/api/read_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestRead(t *testing.T) { var ( retCont contentResult ) assert.NoError(t, keyLogin(1)) name := randName(`tbl`) form := url.Values{"Name": {name}, "ApplicationId": {`1`}, "Columns": {`[{"name":"my","type":"varchar", "index": "1", "conditions":"true"}, {"name":"amount", "type":"number","index": "0", "conditions":"{\"update\":\"true\", \"read\":\"true\"}"}, {"name":"active", "type":"character","index": "0", "conditions":"{\"update\":\"true\", \"read\":\"false\"}"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "read": "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) contList := []string{`contract %s { action { DBInsert("%[1]s", {my: "Alex",amount: 100}) DBInsert("%[1]s", {my: "Alex 2",amount: 13300}) DBInsert("%[1]s", {my: "Mike",amount: 0}) DBInsert("%[1]s", {my: "Mike 2",amount: 25500}) DBInsert("%[1]s", {my: "John Mike", amount: 0}) DBInsert("%[1]s", {my: "Serena Martin",amount:777}) } }`, `contract Get%s { action { var row array row = DBFind("%[1]s").Where({id:[{$gte: 2},{"$lte":5}]}) } }`, `contract GetOK%s { action { var row array row = DBFind("%[1]s").Columns("my,amount").Where({id:[{$gte: 2},{"$lte":5}]}) } }`, `contract GetData%s { action { var row array row = DBFind("%[1]s").Columns("active").Where({id:[{$gte: 2},{"$lte":5}]}) } }`, `func ReadFilter%s bool { var i int var row map while i < Len($data) { row = $data[i] if i == 1 || i == 3 { row["my"] = "No name" $data[i] = row } i = i+ 1 } return true }`, } for _, contract := range contList { form = url.Values{"Value": {fmt.Sprintf(contract, name)}, "ApplicationId": {`1`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) } assert.NoError(t, postTx(name, &url.Values{})) assert.EqualError(t, postTx(`GetData`+name, &url.Values{}), `{"type":"panic","error":"Access denied"}`) assert.NoError(t, sendPost(`content`, &url.Values{`template`: { `DBFind(` + name + `, src).Limit(2)`}}, &retCont)) if strings.Contains(RawToString(retCont.Tree), `active`) { t.Errorf(`wrong tree %s`, RawToString(retCont.Tree)) return } assert.NoError(t, postTx(`GetOK`+name, &url.Values{})) assert.NoError(t, postTx(`EditColumn`, &url.Values{`TableName`: {name}, `Name`: {`active`}, "UpdatePerm": {"true"}, "ReadPerm": {"true" /*"ContractConditions(\"MainCondition\")"*/}, })) var ret listResult assert.NoError(t, sendGet(`list/`+name, nil, &ret)) assert.NoError(t, postTx(`Get`+name, &url.Values{})) assert.NoError(t, sendPost(`content`, &url.Values{`template`: { `DBFind(` + name + `, src).Limit(2)`}}, &retCont)) if !strings.Contains(RawToString(retCont.Tree), `Alex 2`) { t.Errorf(`wrong tree %s`, RawToString(retCont.Tree)) return } form = url.Values{"Name": {name}, "InsertPerm": {`ContractConditions("MainCondition")`}, "UpdatePerm": {"true"}, "ReadPerm": {`false`}, "NewColumnPerm": {`true`}} assert.NoError(t, postTx(`EditTable`, &form)) assert.EqualError(t, postTx(`GetOK`+name, &url.Values{}), `{"type":"panic","error":"Access denied"}`) assert.EqualError(t, sendGet(`list/`+name, nil, &ret), `400 {"error":"E_SERVER","msg":"Access denied"}`) form = url.Values{"Name": {name}, "InsertPerm": {`ContractConditions("MainCondition")`}, "UpdatePerm": {"true"}, "FilterPerm": {`ReadFilter` + name + `()`}, "NewColumnPerm": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`EditTable`, &form)) var tableInfo tableResult assert.NoError(t, sendGet(`table/`+name, nil, &tableInfo)) assert.Equal(t, `ReadFilter`+name+`()`, tableInfo.Filter) assert.NoError(t, sendPost(`content`, &url.Values{`template`: { `DBFind(` + name + `, src).Limit(2)`}}, &retCont)) if !strings.Contains(RawToString(retCont.Tree), `No name`) { t.Errorf(`wrong tree %s`, RawToString(retCont.Tree)) return } } ================================================ FILE: packages/api/request.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "bytes" "encoding/hex" "encoding/json" "errors" "fmt" "io" "mime/multipart" "net/http" "net/url" "strconv" "strings" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" ) type Connect struct { Auth string Root string PrivateKey []byte PublicKey string } type WaitResult struct { BlockID int64 Msg string } func SendRawRequest(rtype, url, auth string, form *url.Values) ([]byte, error) { client := &http.Client{} var ioform io.Reader if form != nil { ioform = strings.NewReader(form.Encode()) } req, err := http.NewRequest(rtype, url, ioform) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if len(auth) > 0 { req.Header.Set("Authorization", jwtPrefix+auth) } resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(`%d %s`, resp.StatusCode, strings.TrimSpace(string(data))) } return data, nil } func SendRequest(rtype, url, auth string, form *url.Values, v any) error { data, err := SendRawRequest(rtype, url, auth, form) if err != nil { return err } return json.Unmarshal(data, v) } func (connect *Connect) SendGet(url string, form *url.Values, v any) error { return SendRequest("GET", connect.Root+url, connect.Auth, form, v) } func (connect *Connect) SendPost(url string, form *url.Values, v any) error { return SendRequest("POST", connect.Root+url, connect.Auth, form, v) } func (connect *Connect) SendMultipart(url string, files map[string][]byte, v any) error { body := new(bytes.Buffer) writer := multipart.NewWriter(body) for key, data := range files { part, err := writer.CreateFormFile(key, key) if err != nil { return err } if _, err := part.Write(data); err != nil { return err } } if err := writer.Close(); err != nil { return err } req, err := http.NewRequest("POST", connect.Root+url, body) if err != nil { return err } req.Header.Set("Content-Type", writer.FormDataContentType()) if len(connect.Auth) > 0 { req.Header.Set("Authorization", jwtPrefix+connect.Auth) } client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf(`%d %s`, resp.StatusCode, strings.TrimSpace(string(data))) } return json.Unmarshal(data, &v) } func (connect *Connect) WaitTx(hash string) (int64, error) { data, err := json.Marshal(&txstatusRequest{ Hashes: []string{hash}, }) if err != nil { return 0, err } for i := 0; i < 15; i++ { var multiRet multiTxStatusResult err := connect.SendPost(`txstatus`, &url.Values{ "data": {string(data)}, }, &multiRet) if err != nil { return 0, err } ret := multiRet.Results[hash] if len(ret.BlockID) > 0 { return converter.StrToInt64(ret.BlockID), fmt.Errorf(ret.Result) } if ret.Message != nil { errtext, err := json.Marshal(ret.Message) if err != nil { return 0, err } return 0, errors.New(string(errtext)) } time.Sleep(time.Second) } return 0, fmt.Errorf(`TxStatus timeout`) } func (connect *Connect) WaitTxList(hashes []string) (map[string]WaitResult, error) { data, err := json.Marshal(&txstatusRequest{ Hashes: hashes, }) if err != nil { return nil, err } var multiRet multiTxStatusResult err = connect.SendPost(`txstatus`, &url.Values{ "data": {string(data)}, }, &multiRet) if err != nil { return nil, err } waitResults := map[string]WaitResult{} for key, ret := range multiRet.Results { if len(ret.BlockID) > 0 { waitResults[key] = WaitResult{ BlockID: converter.StrToInt64(ret.BlockID), Msg: ret.Result, } continue } if ret.Message != nil { var msg string errtext, err := json.Marshal(ret.Message) if err != nil { msg = err.Error() } else { msg = string(errtext) } waitResults[key] = WaitResult{ Msg: msg, } } } return waitResults, nil } func (connect *Connect) PostTxResult(name string, form *url.Values) (id int64, msg string, err error) { var contract getContractResult if err = connect.SendGet("contract/"+name, nil, &contract); err != nil { return } params := make(map[string]any) for _, field := range contract.Fields { name := field.Name value := form.Get(name) if len(value) == 0 { continue } switch field.Type { case "bool": params[name], err = strconv.ParseBool(value) case "int", "address": params[name], err = strconv.ParseInt(value, 10, 64) case "float": params[name], err = strconv.ParseFloat(value, 64) case "array": var v any err = json.Unmarshal([]byte(value), &v) params[name] = v case "map": var v map[string]any err = json.Unmarshal([]byte(value), &v) params[name] = v case "string", "money": params[name] = value } if err != nil { err = fmt.Errorf("Parse param '%s': %s", name, err) return } } var publicKey []byte if publicKey, err = crypto.PrivateToPublic(connect.PrivateKey); err != nil { return } txTime := time.Now().Unix() if newTime := form.Get("txtime"); len(newTime) > 0 { txTime = converter.StrToInt64(newTime) } data, txhash, err := transaction.NewTransactionInProc(types.SmartTransaction{ Header: &types.Header{ ID: int(contract.ID), EcosystemID: 1, Time: txTime, KeyID: crypto.Address(publicKey), NetworkID: conf.Config.LocalConf.NetworkID, }, Params: params, }, connect.PrivateKey) if err != nil { return 0, "", err } ret := &sendTxResult{} err = connect.SendMultipart("sendTx", map[string][]byte{ fmt.Sprintf("%x", txhash): data, }, &ret) if err != nil { return } if len(form.Get("nowait")) > 0 { msg = ret.Hashes["data"] return } id, err = connect.WaitTx(ret.Hashes[fmt.Sprintf("%x", txhash)]) if id != 0 && err != nil { msg = err.Error() err = nil } return } func (connect *Connect) Login() error { var ( sign []byte ret getUIDResult err error ) if err = connect.SendGet(`getuid`, nil, &ret); err != nil { return err } if len(ret.UID) == 0 { return nil } connect.Auth = ret.Token sign, err = crypto.SignString(hex.EncodeToString(connect.PrivateKey), `LOGIN`+ret.NetworkID+ret.UID) if err != nil { return err } form := url.Values{"pubkey": {connect.PublicKey}, "signature": {hex.EncodeToString(sign)}, `ecosystem`: {`1`}, "role_id": {"0"}} var logret loginResult err = connect.SendPost(`login`, &form, &logret) connect.Auth = logret.Token return err } ================================================ FILE: packages/api/route.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/gorilla/handlers" "github.com/gorilla/mux" ) const corsMaxAge = 600 type Router struct { main *mux.Router apiVersions map[string]*mux.Router } func (r Router) GetAPI() *mux.Router { return r.main } func (r Router) GetAPIVersion(preffix string) *mux.Router { return r.apiVersions[preffix] } func (r Router) NewVersion(preffix string) *mux.Router { api := r.main.PathPrefix(preffix).Subrouter() r.apiVersions[preffix] = api return api } // Route sets routing pathes func (m Mode) SetCommonRoutes(r Router) { NoneMiddlewareRoutes(r.NewVersion("/api/v2"), m) api := r.NewVersion("/api/v2") api.Use(nodeStateMiddleware, tokenMiddleware, m.clientMiddleware) SetOtherCommonRoutes(api, m) api.HandleFunc("/data/{id}/data/{hash}", getBinaryHandler).Methods("GET") api.HandleFunc("/data/{table}/{id}/{column}/{hash}", getDataHandler).Methods("GET") api.HandleFunc("/avatar/{ecosystem}/{account}", getAvatarHandler).Methods("GET") api.HandleFunc("/auth/status", getAuthStatus).Methods("GET") api.HandleFunc("/contract/{name}", authRequire(getContractInfoHandler)).Methods("GET") api.HandleFunc("/contracts", authRequire(getContractsHandler)).Methods("GET") api.HandleFunc("/getuid", getUIDHandler).Methods("GET") api.HandleFunc("/keyinfo/{wallet}", m.getKeyInfoHandler).Methods("GET") api.HandleFunc("/list/{name}", authRequire(getListHandler)).Methods("GET") api.HandleFunc("/network", getNetworkHandler).Methods("GET") api.HandleFunc("/sections", authRequire(getSectionsHandler)).Methods("GET") api.HandleFunc("/row/{name}/{id}", authRequire(getRowHandler)).Methods("GET") api.HandleFunc("/row/{name}/{column}/{id}", authRequire(getRowHandler)).Methods("GET") api.HandleFunc("/interface/page/{name}", authRequire(getPageRowHandler)).Methods("GET") api.HandleFunc("/interface/menu/{name}", authRequire(getMenuRowHandler)).Methods("GET") api.HandleFunc("/interface/snippet/{name}", authRequire(getSnippetRowHandler)).Methods("GET") api.HandleFunc("/table/{name}", authRequire(getTableHandler)).Methods("GET") api.HandleFunc("/tables", authRequire(getTablesHandler)).Methods("GET") api.HandleFunc("/config/{option}", getConfigOptionHandler).Methods("GET") api.HandleFunc("/page/validators_count/{name}", getPageValidatorsCountHandler).Methods("GET") api.HandleFunc("/content/source/{name}", authRequire(getSourceHandler)).Methods("POST") api.HandleFunc("/content/page/{name}", authRequire(getPageHandler)).Methods("POST") api.HandleFunc("/content/hash/{name}", getPageHashHandler).Methods("POST") api.HandleFunc("/content/menu/{name}", authRequire(getMenuHandler)).Methods("POST") api.HandleFunc("/content", jsonContentHandler).Methods("POST") api.HandleFunc("/login", m.loginHandler).Methods("POST") api.HandleFunc("/sendTx", authRequire(m.sendTxHandler)).Methods("POST") api.HandleFunc("/node/{name}", nodeContractHandler).Methods("POST") api.HandleFunc("/txstatus", authRequire(getTxStatusHandler)).Methods("POST") api.HandleFunc("/metrics/blocks", blocksCountHandler).Methods("GET") api.HandleFunc("/metrics/transactions", txCountHandler).Methods("GET") api.HandleFunc("/metrics/ecosystems", ecosysCountHandler).Methods("GET") api.HandleFunc("/metrics/keys", keysCountHandler).Methods("GET") api.HandleFunc("/metrics/mem", memStatHandler).Methods("GET") api.HandleFunc("/metrics/ban", banStatHandler).Methods("GET") } func (m Mode) SetBlockchainRoutes(r Router) { api := r.GetAPIVersion("/api/v2") setOtherBlockChainRoutes(api, m) api.HandleFunc("/metrics/honornodes", honorNodesCountHandler).Methods("GET") api.HandleFunc("/txinfo/{hash}", getTxInfoHandler).Methods("GET") api.HandleFunc("/txinfomultiple", getTxInfoMultiHandler).Methods("GET") api.HandleFunc("/appparam/{appID}/{name}", authRequire(m.GetAppParamHandler)).Methods("GET") api.HandleFunc("/appparams/{appID}", authRequire(m.getAppParamsHandler)).Methods("GET") api.HandleFunc("/appcontent/{appID}", authRequire(m.getAppContentHandler)).Methods("GET") api.HandleFunc("/history/{name}/{id}", authRequire(getHistoryHandler)).Methods("GET") api.HandleFunc("/balance/{wallet}", m.getBalanceHandler).Methods("GET") api.HandleFunc("/block/{id}", getBlockInfoHandler).Methods("GET") api.HandleFunc("/maxblockid", getMaxBlockHandler).Methods("GET") api.HandleFunc("/blocks", getBlocksTxInfoHandler).Methods("GET") api.HandleFunc("/detailed_blocks", getBlocksDetailedInfoHandler).Methods("GET") api.HandleFunc("/ecosystemparams", authRequire(m.getEcosystemParamsHandler)).Methods("GET") api.HandleFunc("/systemparams", authRequire(getPlatformParamsHandler)).Methods("GET") api.HandleFunc("/ecosystemparam/{name}", authRequire(m.getEcosystemParamHandler)).Methods("GET") api.HandleFunc("/ecosystemname", getEcosystemNameHandler).Methods("GET") } func NoneMiddlewareRoutes(api *mux.Router, m Mode) { api.HandleFunc("/version", getVersionHandler).Methods("GET") } func SetOtherCommonRoutes(api *mux.Router, m Mode) { api.HandleFunc("/member/{ecosystem}/{account}", getMemberHandler).Methods("GET") api.HandleFunc("/listWhere/{name}", authRequire(getListWhereHandler)).Methods("POST") api.HandleFunc("/nodelistWhere/{name}", authRequire(getListWhereHandler)).Methods("POST") api.HandleFunc("/sumWhere/{name}", authRequire(getsumWhereHandler)).Methods("POST") api.HandleFunc("/metrics/blockper/{node}/{mode}", blocksCountByNodeHandler).Methods("GET") } func setOtherBlockChainRoutes(api *mux.Router, m Mode) { api.HandleFunc("/tx_record/{hashes}", getTxRecord).Methods("GET") } func (m Mode) SetSubNodeRoutes(r Router) {} func NewRouter(m Mode) Router { r := mux.NewRouter() r.StrictSlash(true) r.Use(loggerMiddleware, recoverMiddleware, statsdMiddleware) api := Router{ main: r, apiVersions: make(map[string]*mux.Router), } m.SetCommonRoutes(api) return api } func WithCors(h http.Handler) http.Handler { return handlers.CORS( handlers.AllowedOrigins([]string{"*"}), handlers.AllowedMethods([]string{"GET", "HEAD", "POST"}), handlers.AllowedHeaders([]string{"Authorization", "Content-Type", "X-Requested-With"}), handlers.MaxAge(corsMaxAge), )(h) } ================================================ FILE: packages/api/row.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type rowResult struct { Value map[string]string `json:"value"` } type rowForm struct { Columns string `schema:"columns"` } func (f *rowForm) Validate(r *http.Request) error { if len(f.Columns) > 0 { columns := strings.Split(f.Columns, ",") list := make([]string, len(columns)) for k, column := range columns { list[k] = converter.Sanitize(column, `->`) } f.Columns = strings.Join(list, ",") } return nil } func getRowHandler(w http.ResponseWriter, r *http.Request) { form := &rowForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) client := getClient(r) logger := getLogger(r) q := sqldb.GetDB(nil).Limit(1) var ( err error table string ) table, form.Columns, err = checkAccess(params["name"], form.Columns, client) if err != nil { errorResponse(w, err) return } col := `id` if len(params["column"]) > 0 { col = converter.Sanitize(params["column"], `-`) } if converter.FirstEcosystemTables[params["name"]] { q = q.Table(table).Where(col+" = ? and ecosystem = ?", params["id"], client.EcosystemID) } else { q = q.Table(table).Where(col+" = ?", params["id"]) } if len(form.Columns) > 0 { q = q.Select(form.Columns) } rows, err := q.Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") errorResponse(w, errQuery) return } result, err := sqldb.GetResult(rows) if err != nil { errorResponse(w, err) return } if len(result) == 0 { errorResponse(w, errNotFound) return } jsonResponse(w, &rowResult{ Value: result[0], }) } ================================================ FILE: packages/api/sections.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/json" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/language" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) const defaultSectionsLimit = 100 type sectionsForm struct { paginatorForm Lang string `schema:"lang"` } func (f *sectionsForm) Validate(r *http.Request) error { if err := f.paginatorForm.Validate(r); err != nil { return err } if len(f.Lang) == 0 { f.Lang = r.Header.Get("Accept-Language") } return nil } func getSectionsHandler(w http.ResponseWriter, r *http.Request) { form := §ionsForm{} form.defaultLimit = defaultSectionsLimit if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } client := getClient(r) logger := getLogger(r) table := "1_sections" q := sqldb.GetDB(nil).Table(table).Where("ecosystem = ? AND status > 0", client.EcosystemID).Order("id ASC") result := new(listResult) err := q.Count(&result.Count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting table records count") errorResponse(w, errTableNotFound.Errorf(table)) return } rows, err := q.Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") errorResponse(w, err) return } result.List, err = sqldb.GetResult(rows) if err != nil { errorResponse(w, err) return } var sections []map[string]string for _, item := range result.List { var roles []int64 if err := json.Unmarshal([]byte(item["roles_access"]), &roles); err != nil { errorResponse(w, err) return } if len(roles) > 0 { var added bool for _, v := range roles { if v == client.RoleID { added = true break } } if !added { continue } } if item["status"] == consts.StatusMainPage { roles := &sqldb.Role{} roles.SetTablePrefix(1) role, err := roles.Get(nil, client.RoleID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Debug("Getting role by id") errorResponse(w, err) return } if role == true && roles.DefaultPage != "" { item["default_page"] = roles.DefaultPage } } item["title"] = language.LangMacro(item["title"], int(client.EcosystemID), form.Lang) sections = append(sections, item) } result.List = sections jsonResponse(w, result) } ================================================ FILE: packages/api/send_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "io" "net/http" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) type sendTxResult struct { Hashes map[string]string `json:"hashes"` } func getTxData(r *http.Request, key string) ([]byte, error) { logger := getLogger(r) file, _, err := r.FormFile(key) if err != nil { logger.WithError(err).Error("request.FormFile") return nil, err } defer file.Close() var txData []byte if txData, err = io.ReadAll(file); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading multipart file") return nil, err } return txData, nil } func (m Mode) sendTxHandler(w http.ResponseWriter, r *http.Request) { client := getClient(r) if transaction.IsKeyBanned(client.KeyID) { errorResponse(w, errBanned.Errorf(client.KeyID, transaction.BannedTill(client.KeyID))) return } err := r.ParseMultipartForm(multipartBuf) if err != nil { errorResponse(w, err, http.StatusBadRequest) return } result := &sendTxResult{Hashes: make(map[string]string)} var mtx = make(map[string][]byte, 0) for key := range r.MultipartForm.File { txData, err := getTxData(r, key) if err != nil { errorResponse(w, err) return } mtx[key] = txData } for key := range r.Form { txData, err := hex.DecodeString(r.FormValue(key)) if err != nil { errorResponse(w, err) return } mtx[key] = txData } hash, err := txHandlerBatches(r, m, mtx) if err != nil { errorResponse(w, err) return } for _, key := range hash { result.Hashes[key] = key } jsonResponse(w, result) } func txHandlerBatches(r *http.Request, m Mode, mtx map[string][]byte) ([]string, error) { client := getClient(r) logger := getLogger(r) var txData [][]byte for _, datum := range mtx { txData = append(txData, datum) } if int64(len(txData)) > syspar.GetMaxTxSize() { logger.WithFields(log.Fields{"type": consts.ParameterExceeded, "max_size": syspar.GetMaxTxSize(), "size": len(txData)}).Error("transaction size exceeds max size") transaction.BadTxForBan(client.KeyID) return nil, errLimitTxSize.Errorf(len(txData)) } hash, err := m.ClientTxProcessor.ProcessClientTxBatches(txData, client.KeyID, logger) if err != nil { return nil, err } return hash, nil } ================================================ FILE: packages/api/smart_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "math/rand" "net/url" "strconv" "strings" "testing" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type smartParams struct { Params map[string]string Results map[string]string } type smartContract struct { Name string Value string Params []smartParams } func TestUpperName(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } rnd := crypto.RandSeq(4) form := url.Values{"Name": {"testTable" + rnd}, "ApplicationId": {"1"}, "Columns": {`[{"name":"num","type":"text", "conditions":"true"}, {"name":"text", "type":"text","conditions":"true"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} err := postTx(`NewTable`, &form) if err != nil { t.Error(err) return } form = url.Values{`Value`: {`contract AddRow` + rnd + ` { data { } conditions { } action { DBInsert("testTable` + rnd + `", {num: "fgdgf", text: "124234"}) } }`}, "ApplicationId": {"1"}, `Conditions`: {`true`}} if err := postTx(`NewContract`, &form); err != nil { t.Error(err) return } if err := postTx(`AddRow`+rnd, &url.Values{}); err != nil { t.Error(err) return } } func TestSmartFields(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var cntResult getContractResult err := sendGet(`contract/MainCondition`, nil, &cntResult) if err != nil { t.Error(err) return } if len(cntResult.Fields) != 0 { t.Error(`MainCondition fields must be empty`) return } if cntResult.Name != `@1MainCondition` { t.Errorf(`MainCondition name is wrong: %s`, cntResult.Name) return } if err := postTx(`MainCondition`, &url.Values{}); err != nil { t.Error(err) return } } func TestMoneyTransfer(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } form := url.Values{`Amount`: {`53330000`}, `Recipient`: {`0005-2070-2000-0006-0200`}} if err := postTx(`MoneyTransfer`, &form); err != nil { t.Error(err) return } form = url.Values{`Amount`: {`2440000`}, `Recipient`: {`1109-7770-3360-6764-7059`}, `Comment`: {`Test`}} if err := postTx(`MoneyTransfer`, &form); err != nil { t.Error(err) return } form = url.Values{`Amount`: {`53330000`}, `Recipient`: {`0005207000`}} if err := postTx(`MoneyTransfer`, &form); cutErr(err) != `{"type":"error","error":"Recipient 0005207000 is invalid"}` { t.Error(err) return } size := 1000000 big := make([]byte, size) rand.Seed(time.Now().UnixNano()) for i := 0; i < size; i++ { big[i] = '0' + byte(rand.Intn(10)) } form = url.Values{`Amount`: {string(big)}, `Recipient`: {`0005-2070-2000-0006-0200`}} if err := postTx(`MoneyTransfer`, &form); err.Error() != `400 {"error": "E_LIMITFORSIGN", "msg": "Length of forsign is too big (1000106)" , "params": ["1000106"]}` { t.Error(err) return } } func TestRoleAccess(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`page`) menu := `government` value := `P(test,test paragraph)` form := url.Values{"Name": {name}, "Value": {value}, "Menu": {menu}, "ApplicationId": {`1`}, "Conditions": {`RoleAccess(10,1)`}} assert.NoError(t, postTx(`NewPage`, &form)) var ret listResult assert.NoError(t, sendGet(`list/pages`, nil, &ret)) id := strconv.FormatInt(ret.Count, 10) form = url.Values{"Id": {id}, "Value": {"Div(){Ooops}"}, "Conditions": {`RoleAccess(65)`}} assert.NoError(t, postTx(`EditPage`, &form)) form = url.Values{"Id": {id}, "Value": {"Div(){Update}"}} assert.EqualError(t, postTx(`EditPage`, &form), `{"type":"panic","error":"Access denied"}`) } func TestDBFind(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`tbl`) form := url.Values{"Name": {name}, "ApplicationId": {"1"}, "Columns": {`[{"name":"txt","type":"varchar", "conditions":"true"}, {"name":"Name", "type":"varchar","index": "0", "conditions":"{\"read\":\"true\",\"update\":\"true\"}"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) form = url.Values{`Value`: {`contract sub` + name + ` { action { DBInsert("` + name + `", {txt:"ok", name: "thisis"}) DBInsert("` + name + `", {txt:"test", name: "test"}) $result = DBFind("` + name + `").Columns("name").Where({txt:"test"}).One("name") } }`}, `Conditions`: {`true`}, "ApplicationId": {"1"}} assert.NoError(t, postTx(`NewContract`, &form)) _, ret, err := postTxResult(`sub`+name, &url.Values{}) assert.Equal(t, `heading`, ret) var retPage contentResult value := `DBFind(` + name + `, src).Columns(name).Where({txt:test})` form = url.Values{"Name": {name}, "Value": {value}, "ApplicationId": {`1`}, "Menu": {`default_menu`}, "Conditions": {"ContractConditions(`MainCondition`)"}} assert.NoError(t, postTx(`NewPage`, &form)) assert.NoError(t, sendPost(`content/page/`+name, &url.Values{}, &retPage)) if err != nil { t.Error(err) return } if RawToString(retPage.Tree) != `[{"tag":"dbfind","attr":{"columns":["name","id"],"data":[["test","2"]],"name":"`+name+`","source":"src","types":["text","text"],"where":"{txt:test}"}}]` { t.Error(fmt.Errorf(`wrong tree %s`, RawToString(retPage.Tree))) return } } func TestPage(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`page`) menuname := randName(`menu`) menu := `government` value := `P(test,test paragraph)` form := url.Values{"Name": {name}, "Value": {`Param Value`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewParameter`, &form)) err := postTx(`NewParameter`, &form) assert.Equal(t, fmt.Sprintf(`{"type":"warning","error":"Parameter %s already exists"}`, name), cutErr(err)) form = url.Values{"Name": {menuname}, "Value": {`first second third`}, "Title": {`My Menu`}, "Conditions": {`true`}} assert.NoError(t, postTx(`NewMenu`, &form)) err = postTx(`NewMenu`, &form) assert.Equal(t, fmt.Sprintf(`{"type":"warning","error":"Menu %s already exists"}`, menuname), cutErr(err)) form = url.Values{"Id": {`7123`}, "Value": {`New Param Value`}, "Conditions": {`ContractConditions("MainCondition")`}} err = postTx(`EditParameter`, &form) assert.Equal(t, `{"type":"panic","error":"Item 7123 has not been found"}`, cutErr(err)) form = url.Values{"Id": {`16`}, "Value": {`Changed Param Value`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`EditParameter`, &form)) name = randName(`page`) form = url.Values{"Name": {name}, "Value": {value}, "ApplicationId": {`1`}, "Menu": {menu}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewPage`, &form)) err = postTx(`NewPage`, &form) assert.Equal(t, fmt.Sprintf(`{"type":"warning","error":"Page %s already exists"}`, name), cutErr(err)) err = postTx(`NewPage`, &form) if cutErr(err) != fmt.Sprintf(`{"type":"warning","error":"Page %s already exists"}`, name) { t.Error(err) return } form = url.Values{"Name": {`app` + name}, "Value": {value}, "ValidateCount": {"2"}, "ValidateMode": {"1"}, "ApplicationId": {`1`}, "Menu": {menu}, "Conditions": {`ContractConditions("MainCondition")`}} err = postTx(`NewPage`, &form) if err != nil { t.Error(err) return } var ret listResult err = sendGet(`list/pages`, nil, &ret) if err != nil { t.Error(err) return } id := strconv.FormatInt(ret.Count, 10) form = url.Values{"Id": {id}, "ValidateCount": {"2"}, "ValidateMode": {"1"}} err = postTx(`EditPage`, &form) if err != nil { t.Error(err) return } var row rowResult err = sendGet(`row/pages/`+id, nil, &row) if err != nil { t.Error(err) return } if row.Value["validate_mode"] != `1` { t.Errorf(`wrong validate value %s`, row.Value["validate_mode"]) return } form = url.Values{"Id": {id}, "Value": {value}, "ValidateCount": {"1"}, "ValidateMode": {"0"}} err = postTx(`EditPage`, &form) if err != nil { t.Error(err) return } err = sendGet(`row/pages/`+id, nil, &row) if err != nil { t.Error(err) return } if row.Value["validate_mode"] != `0` { t.Errorf(`wrong validate value %s`, row.Value["validate_mode"]) return } form = url.Values{"Id": {id}, "Value": {value}, "ValidateCount": {"1"}, "ValidateMode": {"0"}} err = postTx(`EditPage`, &form) if err != nil { t.Error(err) return } err = sendGet(`row/pages/`+id, nil, &row) if err != nil { t.Error(err) return } if row.Value["validate_mode"] != `0` { t.Errorf(`wrong validate value %s`, row.Value["validate_mode"]) return } form = url.Values{"Name": {name}, "Value": {value}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewSnippet`, &form)) err = postTx(`NewSnippet`, &form) assert.EqualError(t, err, fmt.Sprintf(`{"type":"warning","error":"Block %s already exists"}`, name)) form = url.Values{"Id": {`1`}, "Name": {name}, "Value": {value}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`EditSnippet`, &form)) form = url.Values{"Id": {`1`}, "Value": {value + `Span(Test)`}, "Menu": {menu}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`EditPage`, &form)) form = url.Values{"Id": {`1112`}, "Value": {value + `Span(Test)`}, "Menu": {menu}, "Conditions": {`ContractConditions("MainCondition")`}} err = postTx(`EditPage`, &form) assert.Equal(t, `{"type":"panic","error":"Item 1112 has not been found"}`, cutErr(err)) form = url.Values{"Id": {`1`}, "Value": {`Span(Append)`}} assert.NoError(t, postTx(`AppendPage`, &form)) } func TestNewTableOnly(t *testing.T) { assert.NoError(t, keyLogin(1)) name := "MMy_s_test_table" form := url.Values{"Name": {name}, "ApplicationId": {"1"}, "Columns": {`[{"name":"MyName","type":"varchar", "conditions":"true"}, {"name":"Name", "type":"varchar","index": "0", "conditions":"{\"read\":\"true\",\"update\":\"true\"}"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} require.NoError(t, postTx(`NewTable`, &form)) var ret tableResult require.NoError(t, sendGet(`table/`+name, nil, &ret)) fmt.Printf("%+v\n", ret) } func TestUpperTable(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`Tab_`) form := url.Values{"Name": {name}, "ApplicationId": {"1"}, "Columns": {`[{"name":"MyName","type":"varchar", "conditions":"true"}, {"name":"Name", "type":"varchar","index": "0", "conditions":"{\"read\":\"true\",\"update\":\"true\"}"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) form = url.Values{"TableName": {name}, "Name": {`newCol`}, "Type": {"varchar"}, "Index": {"0"}, "UpdatePerm": {"true"}, "ReadPerm": {"true"}} assert.NoError(t, postTx(`NewColumn`, &form)) form = url.Values{"TableName": {name}, "Name": {`newCol`}, "UpdatePerm": {"true"}, "ReadPerm": {"true"}} assert.NoError(t, postTx(`EditColumn`, &form)) } func TestNewTable(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`tbl`) form := url.Values{"Name": {`1_` + name}, "ApplicationId": {"1"}, "Columns": {`[{"name":"MyName","type":"varchar", "conditions":"true"}, {"name":"Name", "type":"varchar","index": "0", "conditions":"{\"read\":\"true\",\"update\":\"true\"}"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) form = url.Values{"TableName": {`1_` + name}, "Name": {`newCol`}, "Type": {"varchar"}, "Index": {"0"}, "Permissions": {"true"}} assert.NoError(t, postTx(`NewColumn`, &form)) form = url.Values{`Value`: {`contract sub` + name + ` { action { DBInsert("1_` + name + `", {"name": "ok"}) DBUpdate("1_` + name + `", 1, {"name": "test value"} ) $result = DBFind("1_` + name + `").Columns("name").WhereId(1).One("name") } }`}, `Conditions`: {`true`}, "ApplicationId": {"1"}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(`sub`+name, &url.Values{}) assert.NoError(t, err) assert.Equal(t, msg, "test value") form = url.Values{"Name": {name}, "ApplicationId": {"1"}, "Columns": {`[{"name":"MyName","type":"varchar", "index": "1", "conditions":"true"}, {"name":"Amount", "type":"number","index": "0", "conditions":"true"}, {"name":"Doc", "type":"json","index": "0", "conditions":"true"}, {"name":"Active", "type":"character","index": "0", "conditions":"true"}]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) assert.EqualError(t, postTx(`NewTable`, &form), fmt.Sprintf(`{"type":"panic","error":"table %s exists"}`, name)) form = url.Values{"Name": {name}, "Permissions": {`{"insert": "ContractConditions(\"MainCondition\")", "update" : "true", "new_column": "ContractConditions(\"MainCondition\")"}`}} assert.NoError(t, postTx(`EditTable`, &form)) form = url.Values{"TableName": {name}, "Name": {`newDoc`}, "Type": {"json"}, "Index": {"0"}, "Permissions": {"true"}} assert.NoError(t, postTx(`NewColumn`, &form)) form = url.Values{"TableName": {name}, "Name": {`newCol`}, "Type": {"varchar"}, "Index": {"0"}, "Permissions": {"true"}} assert.NoError(t, postTx(`NewColumn`, &form)) err = postTx(`NewColumn`, &form) if err.Error() != `{"type":"panic","error":"column newcol exists"}` { t.Error(err) return } form = url.Values{"TableName": {name}, "Name": {`newCol`}, "Permissions": {"ContractConditions(\"MainCondition\")"}} assert.NoError(t, postTx(`EditColumn`, &form)) upname := strings.ToUpper(name) form = url.Values{"TableName": {upname}, "Name": {`UPCol`}, "Type": {"varchar"}, "Index": {"0"}, "Permissions": {"true"}} assert.NoError(t, postTx(`NewColumn`, &form)) form = url.Values{"TableName": {upname}, "Name": {`upCOL`}, "Permissions": {"ContractConditions(\"MainCondition\")"}} assert.NoError(t, postTx(`EditColumn`, &form)) form = url.Values{"Name": {upname}, "Permissions": {`{"insert": "ContractConditions(\"MainCondition\")", "update" : "true", "new_column": "ContractConditions(\"MainCondition\")"}`}} assert.NoError(t, postTx(`EditTable`, &form)) var ret tablesResult assert.NoError(t, sendGet(`tables`, nil, &ret)) } type invalidPar struct { Name string Value string } func TestUpdatePlatformParam(t *testing.T) { assert.NoError(t, keyLogin(1)) form := url.Values{"Name": {`max_columns`}, "Value": {`49`}} assert.NoError(t, postTx(`UpdatePlatformParam`, &form)) var sysList paramsResult assert.NoError(t, sendGet(`systemparams?names=max_columns`, nil, &sysList)) assert.Len(t, sysList.List, 1) assert.Equal(t, "49", sysList.List[0].Value) name := randName(`test`) form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { action { var costlen int costlen = SysParamInt("price_exec_len") + 1 UpdatePlatformParam("Name,Value","max_columns","51") DBUpdatePlatformParam("price_exec_len", Str(costlen), "true" ) if SysParamInt("price_exec_len") != costlen { error "Incorrect updated value" } DBUpdatePlatformParam("max_indexes", "4", "false" ) } }`}, "ApplicationId": {"1"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) err := postTx(name, &form) if err != nil { assert.EqualError(t, err, `{"type":"panic","error":"Access denied"}`) } assert.NoError(t, sendGet(`systemparams?names=max_columns,max_indexes`, nil, &sysList)) if len(sysList.List) != 2 || !((sysList.List[0].Value == `51` && sysList.List[1].Value == `4`) || (sysList.List[0].Value == `4` && sysList.List[1].Value == `51`)) { t.Error(`Wrong max_column or max_indexes value`) return } err = postTx(name, &form) if err == nil || err.Error() != `{"type":"panic","error":"Access denied"}` { t.Error(`incorrect access to system parameter`) return } notvalid := []invalidPar{ {`gap_between_blocks`, `100000`}, {`rollback_blocks`, `-1`}, {`price_create_page`, `-20`}, {`max_block_size`, `0`}, {`max_fuel_tx`, `20string`}, {`fuel_rate`, `string`}, {`fuel_rate`, `[test]`}, {`fuel_rate`, `[["name", "100"]]`}, {`taxes_wallet`, `[["1", "0"]]`}, {`taxes_wallet`, `[{"1", "50"}]`}, {`honor_nodes`, `[["", "http://127.0.0.1", "100", "c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7"]]`}, {`honor_nodes`, `[["127.0.0.1", "", "100", "c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0"]]`}, {`honor_nodes`, `[["127.0.0.1", "http://127.0.0.1", "0", "c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0"]]`}, {"honor_nodes", "[]"}, } for _, item := range notvalid { assert.Error(t, postTx(`UpdatePlatformParam`, &url.Values{`Name`: {item.Name}, `Value`: {item.Value}})) assert.NoError(t, sendGet(`systemparams?names=`+item.Name, nil, &sysList)) assert.Len(t, sysList.List, 1, `have got wrong parameter `+item.Name) if len(sysList.List[0].Value) == 0 { continue } err = postTx(`UpdatePlatformParam`, &url.Values{`Name`: {item.Name}, `Value`: {sysList.List[0].Value}}) assert.NoError(t, err, item.Name, sysList.List[0].Value, sysList.List[0]) } } func TestUpdateHonorNodesWithEmptyArray(t *testing.T) { require.NoErrorf(t, keyLogin(1), "on login") byteNodes := `[{"tcp_address":"127.0.0.1:7078", "api_address":"https://127.0.0.1:7079", "key_id":"-3122230976936134914", "public_key":"d512e7bbaaa8889e2e471d730bbae663bd291a345153ff34d1d9896e36832408eb9f238deca8d410aeb282ff8547ba3f056c5b2a64e2d0b03928e6dd1336e918"}, {"tcp_address":"127.0.0.1:7080", "api_address":"https://127.0.0.1:7081", "key_id":"-3928816940965469512", "public_key":"9fdf51cd74e3a03fbe776a7122e2f28e3d560467d96a624296656a3a2120653e6347572a50693077cc8b8309ea1ea4a33cb84b9e62874a2d762aca85fad84bf7"}]` form := &url.Values{ "Name": {"honor_nodes"}, "Value": {string(byteNodes)}, } require.NoError(t, postTx(`UpdatePlatformParam`, form)) } /* func TestHelper_InsertNodeKey(t *testing.T) { require.NoErrorf(t, keyLogin(1), "on login") form := url.Values{ `Value`: {`contract InsertNodeKey { data { KeyID string PubKey string } conditions {} action { DBInsert("keys", {id: $KeyID, pub: $PubKey,amount: "100000000000000000000"}) } }`}, `ApplicationId`: {`1`}, `Conditions`: {`true`}, } if err := postTx(`NewContract`, &form); err != nil { t.Error(err) return } form = url.Values{ `KeyID`: {"-3928816940965469512"}, `PubKey`: {"704dfabedb65099a8f05f9e20a2e2f04da2e2b4fc9fd8a5a487278bd1212a020a3b469c4756e6f3fc4f7162373e8da576085fb840a8c666d58085e631be501d6"}, } if err := postTx(`InsertNodeKey`, &form); err != nil { t.Error(err) return } } */ func TestValidateConditions(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } baseForm := url.Values{"Id": {"1"}, "Value": {"Test"}, "Conditions": {"incorrectConditions"}} contracts := map[string]url.Values{ "EditContract": baseForm, "EditParameter": baseForm, "EditMenu": baseForm, "EditPage": {"Id": {"1"}, "Value": {"Test"}, "Conditions": {"incorrectConditions"}, "Menu": {"1"}}, } expectedErr := `{"type":"panic","error":"unknown identifier incorrectConditions"}` for contract, form := range contracts { err := postTx(contract, &form) if err.Error() != expectedErr { t.Errorf("contract %s expected '%s' got '%s'", contract, expectedErr, err) return } } } func TestPartitialEdit(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`part`) form := url.Values{"Name": {name}, "Value": {"Span(Original text)"}, "Menu": {"original_menu"}, "ApplicationId": {"1"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewPage`, &form)) var retList listResult assert.NoError(t, sendGet(`list/pages`, nil, &retList)) idItem := strconv.FormatInt(retList.Count, 10) value := `Span(Temp)` menu := `temp_menu` assert.NoError(t, postTx(`EditPage`, &url.Values{ "Id": {idItem}, "Value": {value}, "Menu": {menu}, })) var ret rowResult assert.NoError(t, sendGet(`row/pages/`+idItem, nil, &ret)) assert.Equal(t, value, ret.Value["value"]) assert.Equal(t, menu, ret.Value["menu"]) value = `Span(Updated)` menu = `default_menu` conditions := `true` assert.NoError(t, postTx(`EditPage`, &url.Values{"Id": {idItem}, "Value": {value}})) assert.NoError(t, postTx(`EditPage`, &url.Values{"Id": {idItem}, "Menu": {menu}})) assert.NoError(t, postTx(`EditPage`, &url.Values{"Id": {idItem}, "Conditions": {conditions}})) assert.NoError(t, sendGet(`row/pages/`+idItem, nil, &ret)) assert.Equal(t, value, ret.Value["value"]) assert.Equal(t, menu, ret.Value["menu"]) form = url.Values{"Name": {name}, "Value": {`MenuItem(One)`}, "Title": {`My Menu`}, "ApplicationId": {"1"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewMenu`, &form)) assert.NoError(t, sendGet(`list/menu`, nil, &retList)) idItem = strconv.FormatInt(retList.Count, 10) value = `MenuItem(Two)` assert.NoError(t, postTx(`EditMenu`, &url.Values{"Id": {idItem}, "Value": {value}})) assert.NoError(t, postTx(`EditMenu`, &url.Values{"Id": {idItem}, "Conditions": {conditions}})) assert.NoError(t, sendGet(`row/menu/`+idItem, nil, &ret)) assert.Equal(t, value, ret.Value["value"]) assert.Equal(t, conditions, ret.Value["conditions"]) form = url.Values{"Name": {name}, "Value": {`Span(Snippet)`}, "ApplicationId": {"1"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewSnippet`, &form)) assert.NoError(t, sendGet(`list/snippets`, nil, &retList)) idItem = strconv.FormatInt(retList.Count, 10) value = `Span(Updated block)` assert.NoError(t, postTx(`EditSnippet`, &url.Values{"Id": {idItem}, "Value": {value}})) assert.NoError(t, postTx(`EditSnippet`, &url.Values{"Id": {idItem}, "Conditions": {conditions}})) assert.NoError(t, sendGet(`row/snippets/`+idItem, nil, &ret)) assert.Equal(t, value, ret.Value["value"]) assert.Equal(t, conditions, ret.Value["conditions"]) } func TestContractEdit(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } name := randName(`part`) form := url.Values{"Value": {`contract ` + name + ` { action { $result = "before" } }`}, "ApplicationId": {"1"}, "Conditions": {`ContractConditions("MainCondition")`}} err := postTx(`NewContract`, &form) if err != nil { t.Error(err) return } var retList listResult err = sendGet(`list/contracts`, nil, &retList) if err != nil { t.Error(err) return } idItem := strconv.FormatInt(retList.Count, 10) value := `contract ` + name + ` { action { $result = "after" } }` conditions := `true` wallet := "1231234123412341230" err = postTx(`EditContract`, &url.Values{"Id": {idItem}, "Value": {value}}) if err != nil { t.Error(err) return } err = postTx(`EditContract`, &url.Values{"Id": {idItem}, "Conditions": {conditions}, "WalletId": {wallet}}) if err != nil { t.Error(err) return } var ret rowResult err = sendGet(`row/contracts/`+idItem, nil, &ret) if err != nil { t.Error(err) return } if ret.Value["value"] != value || ret.Value["conditions"] != conditions || ret.Value["wallet_id"] != wallet { t.Errorf(`wrong parameters of contract`) return } _, msg, err := postTxResult(name, &url.Values{}) if err != nil { t.Error(err) return } if msg != "after" { t.Errorf(`the wrong result of the contract %s`, msg) } } func TestDelayedContracts(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } form := url.Values{ "Contract": {"UnknownContract"}, "EveryBlock": {"10"}, "Limit": {"2"}, "Conditions": {"true"}, } err := postTx("NewDelayedContract", &form) assert.EqualError(t, err, `{"type":"error","error":"Unknown contract @1UnknownContract"}`) form.Set("Contract", "MainCondition") err = postTx("NewDelayedContract", &form) assert.NoError(t, err) form.Set("BlockID", "1") err = postTx("NewDelayedContract", &form) assert.EqualError(t, err, `{"type":"error","error":"The blockID must be greater than the current blockID"}`) form = url.Values{ "Id": {"1"}, "Contract": {"MainCondition"}, "EveryBlock": {"10"}, "Conditions": {"true"}, "Deleted": {"1"}, } err = postTx("EditDelayedContract", &form) assert.NoError(t, err) } func TestJSON(t *testing.T) { assert.NoError(t, keyLogin(1)) contract := randName("JSONEncode") assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + contract + ` { action { var a array, m map m["k1"] = 1 m["k2"] = 2 a[0] = m a[1] = m info JSONEncode(a) } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, })) assert.EqualError(t, postTx(contract, &url.Values{}), `{"type":"info","error":"[{\"k1\":1,\"k2\":2},{\"k1\":1,\"k2\":2}]"}`) contract = randName("JSONDecode") assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + contract + ` { data { Input string } action { info Sprintf("%v", JSONDecode($Input)) } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, })) cases := []struct { source string result string }{ {`"test"`, `{"type":"info","error":"test"}`}, {`["test"]`, `{"type":"info","error":"[test]"}`}, {`{"test":1}`, `{"type":"info","error":"map[test:1]"}`}, {`[{"test":1}]`, `{"type":"info","error":"[map[test:1]]"}`}, {`{"test":1`, `{"type":"panic","error":"unexpected end of JSON input"}`}, } for _, v := range cases { assert.EqualError(t, postTx(contract, &url.Values{"Input": {v.source}}), v.result) } } func TestBytesToString(t *testing.T) { assert.NoError(t, keyLogin(1)) contract := randName("BytesToString") assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + contract + ` { data { Data bytes } action { $result = BytesToString($Data) } }`}, "Conditions": {"true"}, "ApplicationId": {"1"}, })) content := crypto.RandSeq(100) _, res, err := postTxResult(contract, &contractParams{ "Data": []byte(content), }) assert.NoError(t, err) assert.Equal(t, content, res) } func TestMoneyDigits(t *testing.T) { assert.NoError(t, keyLogin(1)) contract := randName("MoneyDigits") assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + contract + ` { data { Value money } action { $result = $Value } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, })) _, result, err := postTxResult(contract, &url.Values{ "Value": {"1"}, }) assert.NoError(t, err) d := decimal.New(1, int32(consts.MoneyDigits)) assert.Equal(t, d.StringFixed(0), result) } func TestMemoryLimit(t *testing.T) { assert.NoError(t, keyLogin(1)) contract := randName("Contract") assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + contract + ` { data { Count int "optional" } action { var a array while (true) { $Count = $Count + 1 a[Len(a)] = JSONEncode(a) } } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, })) assert.EqualError(t, postTx(contract, &url.Values{}), `{"type":"panic","error":"Memory limit exceeded"}`) } func TestStack(t *testing.T) { assert.NoError(t, keyLogin(1)) parent := randName("Parent") child := randName("Child") assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + child + ` { action { $result = $stack } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, })) assert.NoError(t, postTx("NewContract", &url.Values{ "Value": {`contract ` + parent + ` { action { var arr array arr[0] = $stack arr[1] = ` + child + `() $result = arr } }`}, "ApplicationId": {"1"}, "Conditions": {"true"}, })) _, res, err := postTxResult(parent, &url.Values{}) assert.NoError(t, err) assert.Equal(t, fmt.Sprintf("[[@1%s] [@1%[1]s @1%s]]", parent, child), res) } func TestPageHistory(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`page`) value := `P(test,test paragraph)` form := url.Values{"Name": {name}, "Value": {value}, "ApplicationId": {`1`}, "Menu": {"default_menu"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewPage`, &form)) var ret listResult assert.NoError(t, sendGet(`list/pages`, nil, &ret)) id := strconv.FormatInt(ret.Count, 10) assert.NoError(t, postTx(`EditPage`, &url.Values{"Id": {id}, "Value": {"Div(style){ok}"}})) assert.NoError(t, postTx(`EditPage`, &url.Values{"Id": {id}, "Conditions": {"true"}})) form = url.Values{"Name": {randName(`menu`)}, "Value": {`MenuItem(First)MenuItem(Second)`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewMenu`, &form)) assert.NoError(t, sendGet(`list/menu`, nil, &ret)) idmenu := strconv.FormatInt(ret.Count, 10) assert.NoError(t, postTx(`EditMenu`, &url.Values{"Id": {idmenu}, "Conditions": {"true"}})) assert.NoError(t, postTx(`EditMenu`, &url.Values{"Id": {idmenu}, "Value": {"MenuItem(Third)"}})) assert.NoError(t, postTx(`EditMenu`, &url.Values{"Id": {idmenu}, "Value": {"MenuItem(Third)"}, "Conditions": {"false"}})) form = url.Values{"Value": {`contract C` + name + `{ action {}}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} _, idCont, err := postTxResult(`NewContract`, &form) assert.NoError(t, err) assert.NoError(t, postTx(`EditContract`, &url.Values{"Id": {idCont}, "Value": {`contract C` + name + `{ action {Println("OK")}}`}, "Conditions": {"true"}})) form = url.Values{`Value`: {`contract Get` + name + ` { data { IdPage int IdMenu int IdCont int } action { var ret array ret = GetHistory("pages", $IdPage) $result = Str(Len(ret)) ret = GetHistory("menu", $IdMenu) $result = $result + Str(Len(ret)) ret = GetHistory("contracts", $IdCont) $result = $result + Str(Len(ret)) } }`}, "ApplicationId": {`1`}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) form = url.Values{`Value`: {`contract GetRow` + name + ` { data { IdPage int } action { var ret array var row got map ret = GetHistory("pages", $IdPage) row = ret[1] got = GetHistoryRow("pages", $IdPage, Int(row["id"])) if got["block_id"] != row["block_id"] { error "GetPageHistory" } } }`}, "ApplicationId": {`1`}, `Conditions`: {`true`}} assert.NoError(t, postTx(`NewContract`, &form)) _, msg, err := postTxResult(`Get`+name, &url.Values{"IdPage": {id}, "IdMenu": {idmenu}, "IdCont": {idCont}}) assert.NoError(t, err) assert.Equal(t, `231`, msg) form = url.Values{"Name": {name + `1`}, "Value": {value}, "ApplicationId": {`1`}, "Menu": {"default_menu"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewPage`, &form)) assert.NoError(t, postTx(`Get`+name, &url.Values{"IdPage": {converter.Int64ToStr( converter.StrToInt64(id) + 1)}, "IdMenu": {idmenu}, "IdCont": {idCont}})) assert.EqualError(t, postTx(`Get`+name, &url.Values{"IdPage": {`1000000`}, "IdMenu": {idmenu}, "IdCont": {idCont}}), `{"type":"panic","error":"Record has not been found"}`) assert.NoError(t, postTx(`GetRow`+name, &url.Values{"IdPage": {id}})) var retTemp contentResult assert.NoError(t, sendPost(`content`, &url.Values{`template`: {fmt.Sprintf(`GetHistory(MySrc, "pages", %s)`, id)}}, &retTemp)) if len(RawToString(retTemp.Tree)) < 400 { t.Error(fmt.Errorf(`wrong tree %s`, RawToString(retTemp.Tree))) } } ================================================ FILE: packages/api/table.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/json" "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) type columnInfo struct { Name string `json:"name"` Type string `json:"type"` Perm string `json:"perm"` } type tableResult struct { Name string `json:"name"` Insert string `json:"insert"` NewColumn string `json:"new_column"` Update string `json:"update"` Read string `json:"read,omitempty"` Filter string `json:"filter,omitempty"` Conditions string `json:"conditions"` AppID string `json:"app_id"` Columns []columnInfo `json:"columns"` } func getTableHandler(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) logger := getLogger(r) client := getClient(r) prefix := client.Prefix() table := &sqldb.Table{} table.SetTablePrefix(prefix) _, err := table.Get(nil, strings.ToLower(params["name"])) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting table") errorResponse(w, err) return } if len(table.Name) == 0 { errorResponse(w, errTableNotFound.Errorf(params["name"])) return } var columnsMap map[string]string err = json.Unmarshal([]byte(table.Columns), &columnsMap) if err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("Unmarshalling table columns to json") errorResponse(w, err) return } columns := make([]columnInfo, 0) for key, value := range columnsMap { colType, err := sqldb.NewDbTransaction(nil).GetColumnType(prefix+`_`+params["name"], key) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting column type from db") errorResponse(w, err) return } columns = append(columns, columnInfo{ Name: key, Perm: value, Type: colType, }) } jsonResponse(w, &tableResult{ Name: table.Name, Insert: table.Permissions.Insert, NewColumn: table.Permissions.NewColumn, Update: table.Permissions.Update, Read: table.Permissions.Read, Filter: table.Permissions.Filter, Conditions: table.Conditions, AppID: converter.Int64ToStr(table.AppID), Columns: columns, }) } ================================================ FILE: packages/api/tables.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type tableInfo struct { Name string `json:"name"` Count string `json:"count"` } type tablesResult struct { Count int64 `json:"count"` List []tableInfo `json:"list"` } func getTablesHandler(w http.ResponseWriter, r *http.Request) { form := &paginatorForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadGateway) return } client := getClient(r) logger := getLogger(r) prefix := client.Prefix() table := &sqldb.Table{} table.SetTablePrefix(prefix) count, err := table.Count() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting records count from tables") errorResponse(w, err) return } rows, err := sqldb.GetDB(nil).Table(table.TableName()).Where("ecosystem = ?", client.EcosystemID).Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") errorResponse(w, err) return } list, err := sqldb.GetResult(rows) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting names from tables") errorResponse(w, err) return } result := &tablesResult{ Count: count, List: make([]tableInfo, len(list)), } for i, item := range list { err = sqldb.GetTableQuery(item["name"], client.EcosystemID).Count(&count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting count from table") errorResponse(w, err) return } result.List[i].Name = item["name"] result.List[i].Count = converter.Int64ToStr(count) } jsonResponse(w, result) } ================================================ FILE: packages/api/tables_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "fmt" "net/url" "testing" "github.com/stretchr/testify/assert" ) func TestTables(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret tablesResult err := sendGet(`tables`, nil, &ret) if err != nil { t.Error(err) return } if int64(ret.Count) < 7 { t.Error(fmt.Errorf(`The number of tables %d < 7`, ret.Count)) return } } func TestTable(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } var ret tableResult err := sendGet(`table/keys`, nil, &ret) if err != nil { t.Error(err) return } if len(ret.Columns) == 0 { t.Errorf(`Wrong result columns`) return } err = sendGet(`table/contracts`, nil, &ret) if err != nil { t.Error(err) return } } func TestTableName(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } form := url.Values{"Name": {`test`}, "Columns": {`[{"name":"MyName","type":"varchar", "index": "0", "conditions":{"update":"true", "read":"true"}}]`}, "ApplicationId": {"1"}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.EqualError(t, postTx(`NewTable`, &form), `{"type":"panic","error":"Name test must only contain latin, digit and '_', '-' characters"}`) form = url.Values{"Name": {`latin`}, "Columns": {`[{"name":"test","type":"varchar", "index": "0", "conditions":{"update":"true", "read":"true"}}]`}, "ApplicationId": {"1"}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.EqualError(t, postTx(`NewTable`, &form), `{"type":"panic","error":"Name latin must only contain latin, digit and '_', '-' characters"}`) name := randName(`tbl`) form = url.Values{"Name": {`tbl-` + name}, "Columns": {`[{"name":"MyName","type":"varchar", "index": "0", "conditions":{"update":"true", "read":"true"}}]`}, "ApplicationId": {"100"}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} err := postTx(`NewTable`, &form) if err != nil { t.Error(err) return } form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { action { DBInsert("tbl-` + name + `", {"MyName": "test"}) DBUpdate("tbl-` + name + `", 1, {"MyName": "New test"}) }}`}, "ApplicationId": {`100`}, "Conditions": {`ContractConditions("MainCondition")`}} err = postTx("NewContract", &form) if err != nil { t.Error(err) return } err = postTx(name, &url.Values{}) if err != nil { t.Error(err) return } var ret tableResult err = sendGet(`table/tbl-`+name, nil, &ret) if err != nil { t.Error(err) return } if len(ret.Columns) == 0 || ret.AppID != `100` { t.Errorf(`wrong table columns or app_id`) return } var retList listResult err = sendGet(`list/tbl-`+name, nil, &retList) if err != nil { t.Error(err) return } if retList.Count != 1 { t.Errorf(`wrong table count`) return } forTest := tplList{ {`DBFind(tbl-` + name + `,my).Columns("id,myname").WhereId(1)`, `[{"tag":"dbfind","attr":{"columns":["id","myname"],"data":[["1","New test"]],"name":"tbl-` + name + `","source":"my","types":["text","text"],"whereid":"1"}}]`}, } var retCont contentResult for _, item := range forTest { err := sendPost(`content`, &url.Values{`template`: {item.input}}, &retCont) if err != nil { t.Error(err) return } if RawToString(retCont.Tree) != item.want { t.Error(fmt.Errorf(`wrong tree %s != %s`, RawToString(retCont.Tree), item.want)) return } } } func TestJSONTable(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`json`) form := url.Values{"Name": {name}, "Columns": {`[{"name":"MyName","type":"varchar", "index": "0", "conditions":"true"}, {"name":"Doc", "type":"json","index": "0", "conditions":"true"}]`}, "ApplicationId": {`1`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) checkGet := func(want string) { _, msg, err := postTxResult(name+`Get`, &url.Values{"Id": {`2`}}) assert.NoError(t, err) assert.Equal(t, want, msg) } form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { action { var ret1, ret2 int ret1 = DBInsert("` + name + `", {MyName: "test",Doc: "{\"type\": \"0\"}"}) var mydoc map mydoc["type"] = "document" mydoc["ind"] = 2 mydoc["check"] = "99" mydoc["doc"] = "Some text." ret2 = DBInsert("` + name + `", {MyName: "test2",Doc: mydoc}) DBInsert("` + name + `", {MyName: "test3",Doc: "{\"title\": {\"name\":\"Test att\",\"text\":\"low\"}}"}) DBInsert("` + name + `", {MyName: "test4",doc: "{\"languages\": {\"arr_id\":{\"1\":\"0\",\"2\":\"0\",\"3\":\"0\"}}}"}) DBInsert("` + name + `", {MyName: "test5",Doc: "{\"app_id\": \"33\"}"}) }}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `Get { data { Id int } action { var ret map var list array var one out tmp where empty string ret = DBFind("` + name + `").Columns("Myname,doc,Doc->Ind").WhereId($Id).Row() out = ret["doc.ind"] out = out + DBFind("` + name + `").Columns("myname,doc->Type").WhereId($Id).One("Doc->type") list = DBFind("` + name + `").Columns(["Myname", "doc", "Doc->Ind"]).Where({"Doc->ind": "101"}) out = out + Str(Len(list)) tmp = DBFind("` + name + `").Columns("doc->title->name").WhereId(3).One("doc->title->name") where = DBFind("` + name + `").Columns("doc->title->name").Where({"doc->title->text":"low"}).One("doc->title->name") one = DBFind("` + name + `").Where({"doc->title->text":"low"}).One("doc->title->text") empty = DBFind("` + name + `").WhereId(4).One("doc->languages->arr_id->2") $result = out + Str(DBFind("` + name + `").WhereId($Id).One("doc->check")) + tmp + where +one + empty } }`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `Upd { action { DBUpdate("` + name + `", 1, {"Doc": "{\"type\": \"doc\", \"ind\": \"3\", \"check\": \"33\"}"}) var mydoc map mydoc["type"] = "doc" mydoc["doc"] = "Some test text." DBUpdate("` + name + `", 2, {"myname": "test3", "Doc": mydoc}) }}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + `UpdOne { data { Type int } action { DBUpdate("` + name + `", 1, {"myname": "New name", "Doc->Ind": $Type, "Doc->type": "new\"doc\" val"}) DBUpdate("` + name + `", 2, {"myname": "New name","Doc->Ind": $Type, "Doc->type": "new\"doc\""}) DBUpdate("` + name + `", 3, {"doc->flag": "Flag","doc->sub": 100}) DBUpdate("` + name + `", 3, {"doc->temp":"Temp"}) }} `}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) assert.NoError(t, postTx(name, &url.Values{})) checkGet(`2document099Test attTest attlow0`) assert.NoError(t, postTx(name+`Upd`, &url.Values{})) checkGet(`doc0Test attTest attlow0`) assert.NoError(t, postTx(name+`UpdOne`, &url.Values{"Type": {"101"}})) checkGet(`101new"doc"2Test attTest attlow0`) form = url.Values{"Name": {`res` + name}, "Value": {`contract res` + name + ` { data { Id int } action { $result = DBFind("contracts").WhereId($Id).Row() }}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) form = url.Values{"Name": {`run` + name}, "Value": {`contract run` + name + ` { action { $temp = res` + name + `("Id",10) $result = $temp["id"] }}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) _, msg, err := postTxResult(`run`+name, &url.Values{}) assert.NoError(t, err) assert.Equal(t, "10", msg) forTest := tplList{ {`DBFind(` + name + `).Columns("id,doc->app_id").WhereId(5).Vars(buffer)Span(#buffer_doc_app_id#)`, `[{"tag":"dbfind","attr":{"columns":["id","doc.app_id"],"data":[["5","33"]],"name":"` + name + `","types":["text","text"],"whereid":"5"}},{"tag":"span","children":[{"tag":"text","text":"33"}]}]`}, {`DBFind(` + name + `,my).Columns("id").Where({"doc->title->text":"low"})`, `[{"tag":"dbfind","attr":{"columns":["id"],"data":[["3"]],"name":"` + name + `","source":"my","types":["text"],"where":"{"doc-\u003etitle-\u003etext":"low"}"}}]`}, {`DBFind(` + name + `,my).Columns("id,doc->title->name").WhereId(3).Vars(prefix)Div(){#prefix_id# = #prefix_doc_title_name#}`, `[{"tag":"dbfind","attr":{"columns":["id","doc.title.name"],"data":[["3","Test att"]],"name":"` + name + `","source":"my","types":["text","text"],"whereid":"3"}},{"tag":"div","children":[{"tag":"text","text":"3 = Test att"}]}]`}, {`DBFind(` + name + `,my).Columns("id,doc->languages->arr_id").WhereId(4).Custom(aa){Span(#doc.languages.arr_id#)}`, `[{"tag":"dbfind","attr":{"columns":["id","doc.languages.arr_id","aa"],"data":[["4","{"1": "0", "2": "0", "3": "0"}","[{"tag":"span","children":[{"tag":"text","text":"{\\"1\\": \\"0\\", \\"2\\": \\"0\\", \\"3\\": \\"0\\"}"}]}]"]],"name":"` + name + `","source":"my","types":["text","text","tags"],"whereid":"4"}}]`}, {`DBFind(` + name + `,my).Columns("id,doc->title->name").WhereId(3)`, `[{"tag":"dbfind","attr":{"columns":["id","doc.title.name"],"data":[["3","Test att"]],"name":"` + name + `","source":"my","types":["text","text"],"whereid":"3"}}]`}, {`DBFind(` + name + `,my).Columns("doc").WhereId(3)`, `[{"tag":"dbfind","attr":{"columns":["doc","id"],"data":[["{"sub": "100", "flag": "Flag", "temp": "Temp", "title": {"name": "Test att", "text": "low"}}","3"]],"name":"` + name + `","source":"my","types":["text","text"],"whereid":"3"}}]`}, {`DBFind(` + name + `,my).Columns("id,doc,doc->type").Where({doc->ind:101, doc->check:33})`, `[{"tag":"dbfind","attr":{"columns":["id","doc","doc.type"],"data":[["1","{"ind": "101", "type": "new\\"doc\\" val", "check": "33"}","new"doc" val"]],"name":"` + name + `","source":"my","types":["text","text","text"],"where":"{doc-\u003eind:101, doc-\u003echeck:33}"}}]`}, {`DBFind(` + name + `,my).Columns("id,doc,doc->type").WhereId(2).Vars(my) Span(#my_id##my_doc_type#)`, `[{"tag":"dbfind","attr":{"columns":["id","doc","doc.type"],"data":[["2","{"doc": "Some test text.", "ind": "101", "type": "new\\"doc\\""}","new"doc""]],"name":"` + name + `","source":"my","types":["text","text","text"],"whereid":"2"}},{"tag":"span","children":[{"tag":"text","text":"2new"doc""}]}]`}, {`DBFind(` + name + `,my).Columns("id,doc->type").WhereId(2)`, `[{"tag":"dbfind","attr":{"columns":["id","doc.type"],"data":[["2","new"doc""]],"name":"` + name + `","source":"my","types":["text","text"],"whereid":"2"}}]`}, {`DBFind(` + name + `,my).Columns("doc->type").Order(id).Custom(mytype, OK:#doc.type#)`, `[{"tag":"dbfind","attr":{"columns":["doc.type","id","mytype"],"data":[["new"doc" val","1","[{"tag":"text","text":"OK:new"doc" val"}]"],["new"doc"","2","[{"tag":"text","text":"OK:new"doc""}]"],["","3","[{"tag":"text","text":"OK:NULL"}]"],["","4","[{"tag":"text","text":"OK:NULL"}]"],["","5","[{"tag":"text","text":"OK:NULL"}]"]],"name":"` + name + `","order":"id","source":"my","types":["text","text","tags"]}}]`}, } var ret contentResult for i, item := range forTest { if i > 100 { break } assert.NoError(t, sendPost(`content`, &url.Values{`template`: {item.input}}, &ret)) assert.Equal(t, item.want, RawToString(ret.Tree)) } } func TestTableDesc(t *testing.T) { if err := keyLogin(1); err != nil { t.Error(err) return } name := randName(`tbl`) form := url.Values{"Name": {name}, "Columns": {`[{"name":"desc","type":"varchar", "index": "0", "conditions":{"update":"true", "read":"true"}}]`}, "ApplicationId": {"1"}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}} assert.NoError(t, postTx(`NewTable`, &form)) form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { action { DBInsert("` + name + `", {"desc": "test"}) DBUpdate("` + name + `", 1, {"desc": "new test"}) $result = DBFind("` + name + `").Columns("desc").WhereId(1).One("desc") var vals map vals = DBRow("pages").Columns("NAME, menu").Where({id:1}) $result = $result + vals["name"] }}`}, "ApplicationId": {"1"}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) _, msg, err := postTxResult(name, &url.Values{}) assert.NoError(t, err) if msg != `new testdefault_page` { t.Errorf(`wrong msg %s`, msg) } form = url.Values{ "template": {`DBFind("` + name + `", src1)`}, } var ret contentResult assert.NoError(t, sendPost(`content`, &form, &ret)) if RawToString(ret.Tree) != `[{"tag":"dbfind","attr":{"columns":["id","desc"],"data":[["1","new test"]],"name":"`+name+`","source":"src1","types":["text","text"]}}]` { t.Error(fmt.Errorf(`wrong tree %s`, RawToString(ret.Tree))) return } } ================================================ FILE: packages/api/template_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "crypto/md5" "encoding/base64" "fmt" "io" "math/rand" "net/http" "net/url" "testing" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" "github.com/stretchr/testify/assert" ) type tplItem struct { input string want string } type tplList []tplItem func TestAPI(t *testing.T) { var ( ret contentResult retHash, retHash2 hashResult err error msg string ) if err := keyLogin(1); err != nil { t.Error(err) return } name := randName(`page`) value := `Div(,#ecosystem_id#) Div(,#key_id#) Div(,#role_id#)` form := url.Values{"Name": {name}, "Value": {value}, "ApplicationId": {`1`}, "Menu": {`default_menu`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx(`NewPage`, &form)) assert.NoError(t, sendPost(`content/hash/`+name, &url.Values{}, &retHash)) if len(retHash.Hash) != 64 { t.Error(`wrong hash ` + retHash.Hash) return } form = url.Values{"Name": {name}, "Value": {`contract ` + name + ` { action { $result = $key_id }}`}, "ApplicationId": {`1`}, "Conditions": {`ContractConditions("MainCondition")`}} assert.NoError(t, postTx("NewContract", &form)) _, msg, err = postTxResult(name, &url.Values{}) assert.NoError(t, err) gAddress = `` gPrivate = `` gPublic = `` gAuth = `` assert.NoError(t, sendPost(`content/hash/`+name, &url.Values{`ecosystem`: {`1`}, `keyID`: {msg}, `roleID`: {`0`}}, &retHash2)) if retHash.Hash != retHash2.Hash { t.Error(`Wrong hash`) return } if err := keyLogin(1); err != nil { t.Error(err) return } err = sendPost(`content/page/default_page`, &url.Values{}, &ret) if err != nil { t.Error(err) return } for _, item := range forTest { err := sendPost(`content`, &url.Values{`template`: {item.input}}, &ret) if err != nil { t.Error(err) return } if RawToString(ret.Tree) != item.want { t.Error(fmt.Errorf("wrong tree \r\n%s != \r\n%s", RawToString(ret.Tree), item.want)) return } } err = sendPost(`content/page/mypage`, &url.Values{}, &ret) if err != nil && err.Error() != `404 {"error":"E_NOTFOUND","msg":"Page not found"}` { t.Error(err) return } err = sendPost(`content/menu/default_menu`, &url.Values{}, &ret) if err != nil { t.Error(err) return } } var forTest = tplList{ {`DBFind(contracts, src).Columns("id").Where({"app_id": 1, ,"id": {"$gt": 2}})`, `[{"tag":"text","text":"unexpected comma"}]`}, {`DBFind(contracts, src).Columns("id").Where({"app_id": 1, "id": {"$gt": 2},})`, `[{"tag":"text","text":"unexpected comma"}]`}, {`SetVar(w_filter, ` + "`" + `"id": {"$lt": "2"}` + "`" + `)SetVar(w, {#w_filter# #w_search#}) DBFind("contracts", src).Columns("id,name").Where(#w#)Table(src)`, `[{"tag":"dbfind","attr":{"columns":["id","name"],"data":[["1","MainCondition"]],"name":"contracts","source":"src","types":["text","text"],"where":"{"id": {"$lt": "2"} }"}},{"tag":"table","attr":{"source":"src"}}]`}, {`DBFind("contracts", src).Columns("id,name").Where({"id": {"$lt": "2"} })`, `[{"tag":"dbfind","attr":{"columns":["id","name"],"data":[["1","MainCondition"]],"name":"contracts","source":"src","types":["text","text"],"where":"{"id": {"$lt": "2"} }"}}]`}, {`SetVar(where, {"$or": ["name": #poa#,"valueN": #poa#]}) DBFind(contracts, src).Columns("id").Where(#where#)`, `[{"tag":"text","text":"pq: column "valuen" does not exist in query select "id" from "1_contracts" where ("name" = '' or "valuen" = '') order by id [[]]"}]`}, {`DBFind("@1roles_participants", src).Where({"ecosystem": #ecosystem_id#, "role->id": {"$in": []}, "member->member_id": #key_id#, "deleted": 0})`, `[{"tag":"dbfind","attr":{"columns":["id","role","member","appointed","date_created","date_deleted","deleted","ecosystem"],"data":[],"name":"@1roles_participants","source":"src","types":[],"where":"{"ecosystem": 1, "role-\u003eid": {"$in": []}, "member-\u003emember_id": 2665397054248150876, "deleted": 0}"}}]`}, {`DBFind("@1roles_participants").Where({"ecosystem": #ecosystem_id#, "role->id": {"$in": []}, "member->member_id": #key_id#, "deleted": 0}).Vars(v)`, `[{"tag":"dbfind","attr":{"columns":["id","role","member","appointed","date_created","date_deleted","deleted","ecosystem"],"data":[],"name":"@1roles_participants","types":[],"where":"{"ecosystem": 1, "role-\u003eid": {"$in": []}, "member-\u003emember_id": 2665397054248150876, "deleted": 0}"}}]`}, {`DBFind(@1pages).Where({{id:{$neq:5}}, {id:2}, id:{$neq:6}, $or:[id:6, {id:1}, {id:2}, id:3]}).Columns("id,name").Order(id)`, `[{"tag":"dbfind","attr":{"columns":["id","name"],"data":[["1","developer_index"],["3","notifications"]],"name":"@1pages","order":"id","types":["text","text"],"where":"{{id:{$neq:5}}, {id:2}, id:{$neq:6}, $or:[id:6, {id:1}, {id:2}, id:3]}"}}]`}, {`DBFind(@1pages).Where({id:[{$neq:5},{$neq:4}, 2], name:{$neq: Edit}}).Columns("id,name")`, `[{"tag":"dbfind","attr":{"columns":["id","name"],"data":[["2","developer_index"]],"name":"@1pages","types":["text","text"],"where":"{id:[{$neq:5},{$neq:4}, 2], name:{$neq: Edit}}"}}]`}, {`DBFind(@1pages).Where({id:3, name: {$neq:EditPage}, $or:[id:1, {id:5}, id:{$neq:2}, id:4]}).Columns("id,name")`, `[{"tag":"dbfind","attr":{"columns":["id","name"],"data":[["3","notifications"]],"name":"@1pages","types":["text","text"],"where":"{id:3, name: {$neq:EditPage}, $or:[id:1, {id:5}, id:{$neq:2}, id:4]}"}}]`}, {`DBFind(keys).Where("id='#key_id#'").Columns("amount").Vars(amount)`, `[{"tag":"text","text":"Where has wrong format"}]`}, {`SetVar(val, 123456789)Money(#val#)`, `[{"tag":"text","text":"0.000000000123456789"}]`}, {`SetVar(coltype, GetColumnType(members, member_name))Div(){#coltype#GetColumnType(none,none)GetColumnType()}`, `[{"tag":"div","children":[{"tag":"text","text":"varchar"}]}]`}, {`DBFind(parameters, src_par).Columns("id").Order([id]).Where({id:[{$gte:1}, {$lte:3}]}).Count(count)Span(#count#)`, `[{"tag":"dbfind","attr":{"columns":["id"],"count":"3","data":[["1"],["2"],["3"]],"name":"parameters","order":"[id]","source":"src_par","types":["text"],"where":"{id:[{$gte:1}, {$lte:3}]}"}},{"tag":"span","children":[{"tag":"text","text":"3"}]}]`}, {`SetVar(coltype, GetColumnType(members, member_name))Div(){#coltype#GetColumnType(none,none)GetColumnType()}`, `[{"tag":"div","children":[{"tag":"text","text":"varchar"}]}]`}, {`SetVar(where).(lim,3)DBFind(contracts, src).Columns(id).Order([{id:1}, {name:-1}]).Limit(#lim#).Custom(a){SetVar(where, #where# #id#)} Div(){Table(src, "=x")}Div(){Table(src)}Div(){#where#}`, `[{"tag":"dbfind","attr":{"columns":["id","a"],"data":[["1","null"],["2","null"],["3","null"]],"limit":"3","name":"contracts","order":"[{id:1}, {name:-1}]","source":"src","types":["text","tags"]}},{"tag":"div","children":[{"tag":"table","attr":{"columns":[{"Name":"x","Title":""}],"source":"src"}}]},{"tag":"div","children":[{"tag":"table","attr":{"source":"src"}}]},{"tag":"div","children":[{"tag":"text","text":" 1 2 3"}]}]`}, {`SetVar(off, 10)DBFind(contracts, src_contracts).Columns("id").Order(id).Limit(2).Offset(#off#).Custom(){}`, `[{"tag":"dbfind","attr":{"columns":["id"],"data":[["11"],["12"]],"limit":"2","name":"contracts","offset":"10","order":"id","source":"src_contracts","types":["text"]}}]`}, {`DBFind(contracts, src_pos).Columns(id).Where({id:[{$gte:1}, {$lte:3}]}).Order(id) ForList(src_pos, Index: index){ Div(list-group-item) { DBFind(parameters, src_hol).Columns(id).Where({id: #id#}).Vars("ret") SetVar(qq, #ret_id#) Div(Body: #index# ForList=#id# DBFind=#ret_id# SetVar=#qq#) } }`, `[{"tag":"dbfind","attr":{"columns":["id"],"data":[["1"],["2"],["3"]],"name":"contracts","order":"id","source":"src_pos","types":["text"],"where":"{id:[{$gte:1}, {$lte:3}]}"}},{"tag":"forlist","attr":{"index":"index","source":"src_pos"},"children":[{"tag":"div","attr":{"class":"list-group-item"},"children":[{"tag":"dbfind","attr":{"columns":["id"],"data":[["1"]],"name":"parameters","source":"src_hol","types":["text"],"where":"{id: 1}"}},{"tag":"div","children":[{"tag":"text","text":"1 ForList=1 DBFind=1 SetVar=1"}]}]},{"tag":"div","attr":{"class":"list-group-item"},"children":[{"tag":"dbfind","attr":{"columns":["id"],"data":[["2"]],"name":"parameters","source":"src_hol","types":["text"],"where":"{id: 2}"}},{"tag":"div","children":[{"tag":"text","text":"2 ForList=2 DBFind=2 SetVar=2"}]}]},{"tag":"div","attr":{"class":"list-group-item"},"children":[{"tag":"dbfind","attr":{"columns":["id"],"data":[["3"]],"name":"parameters","source":"src_hol","types":["text"],"where":"{id: 3}"}},{"tag":"div","children":[{"tag":"text","text":"3 ForList=3 DBFind=3 SetVar=3"}]}]}]}]`}, {`Data(Source: mysrc, Columns: "startdate,enddate", Data: 2017-12-10 10:11,2017-12-12 12:13 2017-12-17 16:17,2017-12-15 14:15 ).Custom(custom_id){ SetVar(Name: vStartDate, Value: DateTime(DateTime: #startdate#, Format: "YYYY-MM-DD HH:MI")) SetVar(Name: vEndDate, Value: DateTime(DateTime: #enddate#, Format: "YYYY-MM-DD HH:MI")) SetVar(Name: vCmpDate, Value: CmpTime(#vStartDate#,#vEndDate#)) P(Body: #vStartDate# #vEndDate# #vCmpDate#) }.Custom(custom_name){ P(Body: #vStartDate# #vEndDate# #vCmpDate#) }`, `[{"tag":"data","attr":{"columns":["startdate","enddate","custom_id","custom_name"],"data":[["2017-12-10 10:11","2017-12-12 12:13","[{"tag":"p","children":[{"tag":"text","text":"2017-12-10 10:11 2017-12-12 12:13 -1"}]}]","[{"tag":"p","children":[{"tag":"text","text":"2017-12-10 10:11 2017-12-12 12:13 -1"}]}]"],["2017-12-17 16:17","2017-12-15 14:15","[{"tag":"p","children":[{"tag":"text","text":"2017-12-17 16:17 2017-12-15 14:15 1"}]}]","[{"tag":"p","children":[{"tag":"text","text":"2017-12-17 16:17 2017-12-15 14:15 1"}]}]"]],"source":"mysrc","types":["text","text","tags","tags"]}}]`}, {`Strong(SysParam(taxes_size))`, `[{"tag":"strong","children":[{"tag":"text","text":"3"}]}]`}, {`SetVar(Name: vDateNow, Value: Now("YYYY-MM-DD HH:MI")) SetVar(Name: simple, Value: TestFunc(my value)) SetVar(Name: vStartDate, Value: DateTime(DateTime: #vDateNow#, Format: "YYYY-MM-DD HH:MI")) SetVar(Name: vCmpStartDate, Value: CmpTime(#vStartDate#,#vDateNow#)) Span(#vCmpStartDate# #simple#)`, `[{"tag":"span","children":[{"tag":"text","text":"-1 TestFunc(my value)"}]}]`}, {`Input(Type: text, Value: Now(MMYY))`, `[{"tag":"input","attr":{"type":"text","value":"Now(MMYY)"}}]`}, {`Button(Body: LangRes(savex), Class: btn btn-primary, Contract: EditProfile, Page:members_list,).Alert(Text: $want_save_changesx$, ConfirmButton: $yesx$, CancelButton: $nox$, Icon: question)`, `[{"tag":"button","attr":{"alert":{"cancelbutton":"$nox$","confirmbutton":"$yesx$","icon":"question","text":"$want_save_changesx$"},"class":"btn btn-primary","contract":"EditProfile","page":"members_list"},"children":[{"tag":"text","text":"savex"}]}]`}, {`Button(Body: button).Popup(Width: 100)`, `[{"tag":"button","attr":{"popup":{"width":"100"}},"children":[{"tag":"text","text":"button"}]}]`}, {`Button(Body: button).Popup(Width: 100, Header: header)`, `[{"tag":"button","attr":{"popup":{"header":"header","width":"100"}},"children":[{"tag":"text","text":"button"}]}]`}, {`Button(Body: button).Popup(Header: header)`, `[{"tag":"button","children":[{"tag":"text","text":"button"}]}]`}, {`Simple Strong(bold text)`, `[{"tag":"text","text":"Simple "},{"tag":"strong","children":[{"tag":"text","text":"bold text"}]}]`}, {`EcosysParam(gender, Source: mygender)`, `[{"tag":"data","attr":{"columns":["id","name"],"data":[["1",""]],"source":"mygender","types":["text","text"]}}]`}, {`EcosysParam(new_table)`, `[{"tag":"text","text":"ContractConditions("MainCondition")"}]`}, {`SetVar(varZero, 0) If(#varZero#>0) { the varZero should be hidden } SetVar(varNotZero, 1) If(#varNotZero#>0) { the varNotZero should be visible } If(#varUndefined#>0) { the varUndefined should be hidden }`, `[{"tag":"text","text":"the varNotZero should be visible"}]`}, {`DateTime(1257894000)`, `[{"tag":"text","text":"` + time.Unix(1257894000, 0).Format("2006-01-02 15:04:05") + `"}]`}, {`CmpTime(1257894000, 1257895000)CmpTime(1257895000, 1257894000)CmpTime(1257894000, 1257894000)`, `[{"tag":"text","text":"-110"}]`}, {`P(Guest = #guest_key#)`, `[{"tag":"p","children":[{"tag":"text","text":"Guest = 4544233900443112470"}]}]`}, } func TestMoney(t *testing.T) { var ret contentResult if err := keyLogin(1); err != nil { t.Error(err) return } size := 10000000 money := make([]byte, size) rand.Seed(time.Now().UnixNano()) for i := 0; i < size; i++ { money[i] = '0' + byte(rand.Intn(10)) } err := sendPost(`content`, &url.Values{`template`: {`Money(` + string(money) + `)`}}, &ret) if err != nil { t.Error(err) return } if RawToString(ret.Tree) != `[{"tag":"text","text":"invalid money value"}]` { t.Errorf(`wrong value %s`, RawToString(ret.Tree)) } } func TestCutoff(t *testing.T) { assert.NoError(t, keyLogin(1)) name := randName(`tbl`) form := url.Values{ "Name": {name}, "Columns": {`[ {"name":"name","type":"varchar", "index": "1", "conditions":"true"}, {"name":"long_text", "type":"text", "index":"0", "conditions":"true"}, {"name":"short_text", "type":"varchar", "index":"0", "conditions":"true"} ]`}, "Permissions": {`{"insert": "true", "update" : "true", "new_column": "true"}`}, "ApplicationId": {"1"}, } assert.NoError(t, postTx(`NewTable`, &form)) form = url.Values{ "Name": {name}, "Value": {` contract ` + name + ` { data { LongText string ShortText string } action { DBInsert("` + name + `", {name: "test", long_text: $LongText, short_text: $ShortText}) } } `}, "Conditions": {`true`}, "ApplicationId": {"1"}, } assert.NoError(t, postTx(`NewContract`, &form)) shortText := crypto.RandSeq(30) longText := crypto.RandSeq(100) assert.NoError(t, postTx(name, &url.Values{ "ShortText": {shortText}, "LongText": {longText}, })) var ret contentResult template := `DBFind(Name: ` + name + `, Source: mysrc).Cutoff("short_text,long_text")` start := time.Now() assert.NoError(t, sendPost(`content`, &url.Values{`template`: {template}}, &ret)) duration := time.Since(start) if int(duration.Seconds()) > 0 { t.Errorf(`Too much time for template parsing`) return } assert.NoError(t, postTx(name, &url.Values{ "ShortText": {shortText}, "LongText": {longText}, })) template = `DBFind("` + name + `", mysrc).Columns("id,name,short_text,long_text").Cutoff("short_text,long_text").WhereId(2).Vars(prefix)` assert.NoError(t, sendPost(`content`, &url.Values{`template`: {template}}, &ret)) linkLongText := fmt.Sprintf("/data/1_%s/2/long_text/%x", name, md5.Sum([]byte(longText))) want := `[{"tag":"dbfind","attr":{"columns":["id","name","short_text","long_text"],"cutoff":"short_text,long_text","data":[["2","test","{"link":"","title":"` + shortText + `"}","{"link":"` + linkLongText + `","title":"` + longText[:32] + `"}"]],"name":"` + name + `","source":"mysrc","types":["text","text","long_text","long_text"],"whereid":"2"}}]` if RawToString(ret.Tree) != want { t.Errorf("Wrong image tree %s != %s", RawToString(ret.Tree), want) } resp, err := http.Get(apiAddress + consts.ApiPath + linkLongText) if err != nil { t.Error(err) return } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) assert.NoError(t, err) assert.Equal(t, "attachment", resp.Header.Get("Content-Disposition")) assert.Equal(t, longText, string(data)) } var imageData = `iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAARklEQVRYw+3OMQ0AIBAEwQOzaCLBBQZfAd0XFLMCNjOyb1o7q2Ey82VYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYrwqjmwKzLUjCbwAAAABJRU5ErkJggg==` func TestBinary(t *testing.T) { assert.NoError(t, keyLogin(1)) data, err := base64.StdEncoding.DecodeString(imageData) assert.NoError(t, err) file := types.NewFile() file.Set("Body", data) params := contractParams{ "ApplicationId": "1", "Name": "file", "Data": file, } _, id, err := postTxResult("UploadFile", ¶ms) assert.NoError(t, err) hash := crypto.Hash(data) assert.NoError(t, err) hashImage := fmt.Sprintf("%x", hash) hashFindedImage := fmt.Sprintf("%x", md5.Sum(data)) cases := []struct { source string result string }{ { `Image(Src: Binary(Name: file, AppID: 1, Account: #account_id#))`, `\[{"tag":"image","attr":{"src":"/data/1_binaries/\d+/data/` + hashImage + `"}}\]`, }, { `Image(Src: Binary().ById(` + id + `)`, `\[{"tag":"image","attr":{"src":"/data/1_binaries/\d+/data/` + hashImage + `"}}\]`, }, { `SetVar(eco, 1)Image(Src: Binary().ById(` + id + `).Ecosystem(#eco#)`, `\[{"tag":"image","attr":{"src":"/data/1_binaries/\d+/data/` + hashImage + `"}}\]`, }, { `SetVar(name, file)SetVar(app_id, 1)SetVar(member_id, #key_id#)Image(Src: Binary(Name: #name#, AppID: #app_id#, MemberID: #member_id#))`, `\[{"tag":"image","attr":{"src":"/data/1_binaries/\d+/data/` + hashImage + `"}}\]`, }, { `SetVar(id, "` + id + `")Image(Src: Binary().ById(#id#)`, `\[{"tag":"image","attr":{"src":"/data/1_binaries/\d+/data/` + hashImage + `"}}\]`, }, { `DBFind(Name: binaries, Src: mysrc).Where({app_id: 1, account: #account_id#, name: "file"}).Custom(img){Image(Src: #data#)}Table(mysrc, "Image=img")`, `\[{"tag":"dbfind","attr":{"columns":\["id","app_id","account","name","data","hash","mime_type","img"\],"data":\[\["\d+","1","\d+","file","{\\"link\\":\\"/data/1_binaries/\d+/data/` + hashFindedImage + `\\",\\"title\\":\\"` + hashFindedImage + `\\"}","` + hashFindedImage + `","application/octet-stream","\[{\\"tag\\":\\"image\\",\\"attr\\":{\\"src\\":\\"/data/1_binaries/\d+/data/` + hashFindedImage + `\\"}}\]"\]\],"name":"binaries","source":"Src: mysrc","types":\["text","text","text","text","blob","text","text","tags"\],"where":"app_id=1 AND member_id = \d+ AND name = 'file'"}},{"tag":"table","attr":{"columns":\[{"Name":"img","Title":"Image"}\],"source":"mysrc"}}\]`, }, { `DBFind(Name: binaries, Src: mysrc).Where({app_id: 1, account: #account_id#, name: "file"}).Vars(prefix)Image(Src: "#prefix_data#")`, `\[{"tag":"dbfind","attr":{"columns":\["id","app_id","account","name","data","hash","mime_type"\],"data":\[\["\d+","1","\d+","file","{\\"link\\":\\"/data/1_binaries/\d+/data/` + hashFindedImage + `\\",\\"title\\":\\"` + hashFindedImage + `\\"}","` + hashFindedImage + `","application/octet-stream"\]\],"name":"binaries","source":"Src: mysrc","types":\["text","text","text","text","blob","text","text"\],"where":"app_id=1 AND member_id = \d+ AND name = 'file'"}},{"tag":"image","attr":{"src":"{\\"link\\":\\"/data/1_binaries/\d+/data/` + hashFindedImage + `\\",\\"title\\":\\"` + hashFindedImage + `\\"}"}}\]`, }, } for _, v := range cases { var ret contentResult err := sendPost(`content`, &url.Values{`template`: {v.source}}, &ret) assert.NoError(t, err) fmt.Println(v.result) assert.Regexp(t, v.result, string(ret.Tree)) } } func TestStringToBinary(t *testing.T) { assert.NoError(t, keyLogin(1)) contract := randName("binary") content := randName("content") filename := randName("file") mimeType := "text/plain" form := url.Values{ "Value": {` contract ` + contract + ` { data { Content string } conditions {} action { UploadBinary("Name,ApplicationId,Data,DataMimeType", "` + filename + `", 1, StringToBytes($Content), "text/plain") $result = $account_id } } `}, "ApplicationId": {`1`}, "Conditions": {"true"}, } assert.NoError(t, postTx("NewContract", &form)) form = url.Values{"Content": {content}} _, account, err := postTxResult(contract, &form) assert.NoError(t, err) form = url.Values{ "template": {`SetVar(link, Binary(Name: ` + filename + `, AppID: 1, Account: "` + account + `"))#link#`}, } var ret struct { Tree []struct { Link string `json:"text"` } `json:"tree"` } assert.NoError(t, sendPost(`content`, &form, &ret)) resp, err := http.Get(apiAddress + consts.ApiPath + ret.Tree[0].Link) if err != nil { t.Error(err) return } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) assert.NoError(t, err) assert.Equal(t, content, string(data)) assert.Equal(t, mimeType, resp.Header.Get("Content-Type")) assert.Equal(t, `attachment; filename="`+filename+`"`, resp.Header.Get("Content-Disposition")) } ================================================ FILE: packages/api/trash.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/smart" ) func getContract(r *http.Request, name string) *smart.Contract { vm := script.GetVM() if vm == nil { return nil } client := getClient(r) contract := smart.VMGetContract(vm, name, uint32(client.EcosystemID)) if contract == nil { return nil } return contract } func getContractInfo(contract *smart.Contract) *script.ContractInfo { return contract.Info() } ================================================ FILE: packages/api/tx_record.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" ) func getTxRecord(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) hashes := params["hashes"] var ( hashList []string resultList []any ) if len(hashes) > 0 { hashList = strings.Split(hashes, ",") } for _, hashStr := range hashList { if result, err := sqldb.GetTxRecord(nil, hashStr); err == nil { resultList = append(resultList, result) } } jsonResponse(w, &resultList) return } ================================================ FILE: packages/api/txinfo.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "bytes" "encoding/hex" "errors" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/common" "github.com/IBAX-io/go-ibax/packages/types" "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/gorilla/mux" ) type txinfoResult struct { BlockID string `json:"blockid"` Confirm int `json:"confirm"` Data *smart.TxInfo `json:"data,omitempty"` } type txInfoForm struct { nopeValidator ContractInfo bool `schema:"contractinfo"` Data string `schema:"data"` } type multiTxInfoResult struct { Results map[string]*txinfoResult `json:"results"` } func getTxInfo(r *http.Request, txHash string, getInfo bool) (*txinfoResult, error) { var status txinfoResult hash, err := hex.DecodeString(txHash) if err != nil { return nil, errHashWrong } ltx := &sqldb.LogTransaction{Hash: hash} found, err := ltx.GetByHash(nil, hash) if err != nil { return nil, err } if !found { return &status, nil } status.BlockID = converter.Int64ToStr(ltx.Block) var confirm sqldb.Confirmation found, err = confirm.GetConfirmation(ltx.Block) if err != nil { return nil, err } if found { status.Confirm = int(confirm.Good) } if getInfo { status.Data, err = transactionData(ltx.Block, hex.EncodeToString(ltx.Hash)) if err != nil { return nil, err } status.Data.Status = ltx.Status status.Data.Ecosystem = ltx.EcosystemID } return &status, nil } func getTxInfoHandler(w http.ResponseWriter, r *http.Request) { form := &txInfoForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } params := mux.Vars(r) status, err := getTxInfo(r, params["hash"], form.ContractInfo) if err != nil { errorResponse(w, err) return } jsonResponse(w, status) } func getTxInfoMultiHandler(w http.ResponseWriter, r *http.Request) { form := &txInfoForm{} if err := parseForm(r, form); err != nil { errorResponse(w, err, http.StatusBadRequest) return } result := &multiTxInfoResult{} result.Results = map[string]*txinfoResult{} hashes := strings.Split(form.Data, ",") for _, hash := range hashes { status, err := getTxInfo(r, hash, form.ContractInfo) if err != nil { errorResponse(w, err) return } result.Results[hash] = status } jsonResponse(w, result) } func transactionData(blockId int64, txHash string) (*smart.TxInfo, error) { info := &smart.TxInfo{} bk := &sqldb.BlockChain{} f, err := bk.Get(blockId) if err != nil { return nil, err } if !f { return nil, errors.New("not found") } blck, err := block.UnmarshallBlock(bytes.NewBuffer(bk.Data), false) if err != nil { return nil, err } for _, tx := range blck.Transactions { hashStr := hex.EncodeToString(tx.Hash()) //find next if hashStr != txHash { continue } info.Address = converter.AddressToString(tx.KeyID()) info.Hash = hashStr info.Size = common.StorageSize(len(tx.Payload())).TerminalString() info.CreatedAt = tx.Timestamp() if tx.IsSmartContract() { info.Expedite = tx.SmartContract().TxSmart.Expedite if tx.SmartContract().TxContract != nil { info.ContractName = tx.SmartContract().TxContract.Name } info.Params = tx.SmartContract().TxData if tx.Type() == types.TransferSelfTxType { info.Params = make(map[string]any) info.Params["transferSelf"] = tx.SmartContract().TxSmart.TransferSelf } if tx.Type() == types.UtxoTxType { info.Params = make(map[string]any) info.Params["utxo"] = tx.SmartContract().TxSmart.UTXO } } //find out break break } info.BlockId = blck.Header.BlockId info.BlockHash = hex.EncodeToString(blck.Header.BlockHash) return info, nil } ================================================ FILE: packages/api/txstatus.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "encoding/hex" "encoding/json" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type txstatusError struct { Type string `json:"type,omitempty"` Error string `json:"error,omitempty"` Id string `json:"id,omitempty"` } type txstatusResult struct { BlockID string `json:"blockid"` Message *txstatusError `json:"errmsg,omitempty"` Result string `json:"result"` Penalty int64 `json:"penalty"` } func getTxStatus(r *http.Request, hash string) (*txstatusResult, error) { logger := getLogger(r) var status txstatusResult if _, err := hex.DecodeString(hash); err != nil { logger.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("decoding tx hash from hex") return nil, errHashWrong } ts := &sqldb.TransactionStatus{} found, err := ts.Get([]byte(converter.HexToBin(hash))) if err != nil { logger.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("getting transaction status by hash") return nil, err } if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "key": []byte(converter.HexToBin(hash))}).Debug("getting transaction status by hash") return nil, errHashNotFound.Errorf(hash) } checkErr := func() { if len(ts.Error) > 0 { if err := json.Unmarshal([]byte(ts.Error), &status.Message); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "text": ts.Error, "error": err}).Warn("unmarshalling txstatus error") status.Message = &txstatusError{ Type: "txError", Error: ts.Error, } } } } if ts.BlockID > 0 { status.BlockID = converter.Int64ToStr(ts.BlockID) status.Penalty = ts.Penalty if ts.Penalty == 1 { checkErr() } else { status.Result = ts.Error } } else { checkErr() } return &status, nil } type multiTxStatusResult struct { Results map[string]*txstatusResult `json:"results"` } type txstatusRequest struct { Hashes []string `json:"hashes"` } func getTxStatusHandler(w http.ResponseWriter, r *http.Request) { result := &multiTxStatusResult{} result.Results = map[string]*txstatusResult{} var request txstatusRequest if err := json.Unmarshal([]byte(r.FormValue("data")), &request); err != nil { errorResponse(w, errHashWrong) return } for _, hash := range request.Hashes { status, err := getTxStatus(r, hash) if err != nil { errorResponse(w, err) return } result.Results[hash] = status } jsonResponse(w, result) } ================================================ FILE: packages/api/version.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package api import ( "github.com/IBAX-io/go-ibax/packages/service/node" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" ) func getVersionHandler(w http.ResponseWriter, r *http.Request) { jsonResponse(w, consts.Version()+" "+node.NodePauseType().String()) } ================================================ FILE: packages/block/block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package block import ( "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) var ( ErrIncorrectRollbackHash = errors.New("Rollback hash doesn't match") ErrEmptyBlock = errors.New("Block doesn't contain transactions") ErrIncorrectBlockTime = utils.WithBan(errors.New("Incorrect block time")) ) // Block is storing block data type Block struct { *types.BlockData PrevRollbacksHash []byte Transactions []*transaction.Transaction GenBlock bool // it equals true when we are generating a new block Notifications []types.Notifications OutputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo ClassifyTxsMap map[int][]*transaction.Transaction PrevSysPar map[string]string EcoParams []sqldb.EcoParam // combustion percent,digits for each ecosystem } // GetLogger is returns logger func (b *Block) GetLogger() *log.Entry { return log.WithFields(log.Fields{"block_id": b.Header.BlockId, "block_time": b.Header.Timestamp, "block_wallet_id": b.Header.KeyId, "block_state_id": b.Header.EcosystemId, "block_hash": b.Header.BlockHash, "block_version": b.Header.Version}) } func (b *Block) IsGenesis() bool { return b.Header.BlockId == 1 } func (b *Block) limitMode() transaction.LimitMode { if b == nil { return transaction.GetLetPreprocess() } if b.GenBlock { return transaction.GetLetGenBlock() } return transaction.GetLetParsing() } // InsertBlockWOForks is inserting blocks func InsertBlockWOForksNew(data []byte, classifyTxsMap map[int][]*transaction.Transaction, genBlock, firstBlock bool) error { block, err := ProcessBlockByBinData(data, !firstBlock) if err != nil { return err } block.GenBlock = genBlock if !firstBlock { block.ClassifyTxsMap = classifyTxsMap } if err := block.Check(); err != nil { return err } err = block.PlaySafe() if err != nil { return err } log.WithFields(log.Fields{"block_id": block.Header.BlockId}).Debug("block was inserted successfully") return nil } ================================================ FILE: packages/block/check.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package block import ( "bytes" "fmt" "time" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/protocols" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // Check is checking block func (b *Block) Check() error { // skip validation for first block if b.IsGenesis() { return nil } logger := b.GetLogger() if b.PrevHeader.BlockId != b.Header.BlockId-1 { var err error b.PrevHeader, err = GetBlockHeaderFromBlockChain(b.Header.BlockId - 1) if err != nil { logger.WithFields(log.Fields{"type": consts.InvalidObject}).Error("block id is larger then previous more than on 1") return err } } if b.Header.Timestamp > time.Now().Unix() { logger.WithFields(log.Fields{"type": consts.ParameterExceeded}).Error("block time is larger than now") return ErrIncorrectBlockTime } var ( exists bool err error ) if syspar.IsHonorNodeMode() { // is this block too early? Allowable error = error_time exists, err = protocols.NewBlockTimeCounter().BlockForTimeExists(time.Unix(b.Header.Timestamp, 0), int(b.Header.NodePosition)) } if err != nil { logger.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Error("calculating block time") return err } if exists { logger.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Warn("incorrect block time") return utils.WithBan(fmt.Errorf("%s %d", ErrIncorrectBlockTime, b.PrevHeader.Timestamp)) } if !bytes.Equal(b.PrevRollbacksHash, b.PrevHeader.RollbacksHash) { return ErrIncorrectRollbackHash } // check each transaction txCounter := make(map[int64]int) txHashes := make(map[string]struct{}) for i, t := range b.Transactions { hexHash := string(converter.BinToHex(t.Hash())) // check for duplicate transactions if _, ok := txHashes[hexHash]; ok { logger.WithFields(log.Fields{"tx_hash": hexHash, "type": consts.DuplicateObject}).Warning("duplicate transaction") return utils.ErrInfo(fmt.Errorf("duplicate transaction %s", hexHash)) } txHashes[hexHash] = struct{}{} // check for max transaction per user in one block txCounter[t.KeyID()]++ if txCounter[t.KeyID()] > syspar.GetMaxBlockUserTx() { return utils.WithBan(utils.ErrInfo(fmt.Errorf("max_block_user_transactions"))) } err := t.Check(b.Header.Timestamp) if err != nil { transaction.MarkTransactionBad(t.Hash(), err.Error()) delete(txHashes, hexHash) b.Transactions = append(b.Transactions[:i], b.Transactions[i+1:]...) return errors.Wrap(err, "check transaction") } } // hash compare could be failed in the case of fork err = b.CheckSign() if err != nil { transaction.CleanCache() return err } return nil } func (b *Block) CheckSign() error { if b.IsGenesis() || conf.Config.IsSubNode() || b.PrevHeader == nil { return nil } nodePub, err := syspar.GetNodePublicKeyByPosition(b.Header.NodePosition) if err != nil { return fmt.Errorf("%v: %w", fmt.Sprintf("get node public key by position '%d'", b.Header.NodePosition), err) } if len(nodePub) == 0 { return fmt.Errorf("empty nodePublicKey") } _, err = utils.CheckSign([][]byte{nodePub}, []byte(b.ForSign()), b.Header.Sign, true) if err != nil { return errors.Wrap(err, "checking block header sign") } return nil } ================================================ FILE: packages/block/db.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package block import ( "bytes" "encoding/json" "fmt" "sort" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/protocols" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "gorm.io/gorm" ) // ProcessBlockByBinData is processing block with in table previous block func ProcessBlockByBinData(data []byte, checkSize bool) (*Block, error) { if checkSize && int64(len(data)) > syspar.GetMaxBlockSize() { log.WithFields(log.Fields{"check_size": checkSize, "size": len(data), "max_size": syspar.GetMaxBlockSize(), "type": consts.ParameterExceeded}).Error("binary block size exceeds max block size") return nil, types.ErrMaxBlockSize(syspar.GetMaxBlockSize(), len(data)) } block, err := UnmarshallBlock(bytes.NewBuffer(data), true) if err != nil { return nil, errors.Wrap(types.ErrUnmarshallBlock, err.Error()) } block.PrevHeader, err = GetBlockHeaderFromBlockChain(block.Header.BlockId - 1) if err != nil { return nil, err } if block.PrevHeader == nil { return nil, errors.New("block previous header nil") } return block, nil } func (b *Block) GetRollbacksHash(dbTx *sqldb.DbTransaction) ([]byte, error) { r := &sqldb.RollbackTx{} diff, err := r.GetRollbacksDiff(dbTx, b.Header.BlockId) if err != nil { return nil, err } return crypto.Hash(diff), nil } func GetRollbacksHashWithDiffArr(dbTx *sqldb.DbTransaction, bId int64) ([]byte, error) { rollbackTx := sqldb.RollbackTx{} rollbackTxs, err := rollbackTx.GetBlockRollbackTransactions(dbTx, bId) if err != nil { return nil, err } arr := make([]string, 0) for _, row := range rollbackTxs { data, err := json.Marshal(row) if err != nil { continue } arr = append(arr, crypto.HashHex(data)) } spentInfos, err := sqldb.GetBlockOutputs(dbTx, bId) if err != nil { return nil, err } for _, row := range spentInfos { data, err := json.Marshal(row) if err != nil { continue } arr = append(arr, crypto.HashHex(data)) } sort.Strings(arr) marshal, _ := json.Marshal(arr) return crypto.Hash(marshal), nil } // InsertIntoBlockchain inserts a block into the blockchain func (b *Block) InsertIntoBlockchain(dbTx *sqldb.DbTransaction) error { blockID := b.Header.BlockId bl := &sqldb.BlockChain{} err := bl.DeleteById(dbTx, blockID) if err != nil { b.GetLogger().WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting block by id") return err } var rHash []byte rHash, err = GetRollbacksHashWithDiffArr(dbTx, blockID) if err != nil { log.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Error("getting rollbacks hash") return err } if b.GenBlock { b.Header.RollbacksHash = rHash if err = b.repeatMarshallBlock(); err != nil { return err } } blockchain := &sqldb.BlockChain{ ID: blockID, Hash: b.Header.BlockHash, Data: b.BinData, EcosystemID: b.Header.EcosystemId, KeyID: b.Header.KeyId, NodePosition: b.Header.NodePosition, Time: b.Header.Timestamp, RollbacksHash: rHash, Tx: int32(len(b.TxFullData)), ConsensusMode: b.Header.ConsensusMode, CandidateNodes: b.Header.CandidateNodes, } var validBlockTime bool if blockID > 1 && syspar.IsHonorNodeMode() { validBlockTime, err = protocols.NewBlockTimeCounter().BlockForTimeExists(time.Unix(blockchain.Time, 0), int(blockchain.NodePosition)) if err != nil { log.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Error("block validation") return err } if validBlockTime { err = fmt.Errorf("invalid block time: %d", b.Header.Timestamp) log.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Error("invalid block time") return err } } if err = blockchain.Create(dbTx); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating block") return err } if err := b.upsertInfoBlock(dbTx, blockchain); err != nil { return err } if b.SysUpdate { b.SysUpdate = false if err := syspar.SysUpdate(dbTx); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating syspar") return err } } return nil } // GetBlockHeaderFromBlockChain is retrieving block data from blockchain func GetBlockHeaderFromBlockChain(blockID int64) (*types.BlockHeader, error) { if blockID < 1 { return &types.BlockHeader{}, nil } block := new(sqldb.BlockChain) if _, err := block.Get(blockID); err != nil { return nil, errors.Wrapf(err, "find block by ID %d", blockID) } header, err := types.ParseBlockHeader(bytes.NewBuffer(block.Data), syspar.GetMaxBlockSize()) if err != nil { return nil, errors.Wrapf(err, "parse block header by ID %d", blockID) } header.BlockHash = block.Hash header.RollbacksHash = block.RollbacksHash return header, nil } // GetDataFromFirstBlock returns data of first block func GetDataFromFirstBlock() (data *types.FirstBlock, ok bool) { block := &sqldb.BlockChain{} isFound, err := block.Get(1) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting record of first block") return } if !isFound { return } pb, err := UnmarshallBlock(bytes.NewBuffer(block.Data), true) if err != nil { log.WithFields(log.Fields{"type": consts.ParserError, "error": err}).Error("parsing data of first block") return } if len(pb.Transactions) == 0 { log.WithFields(log.Fields{"type": consts.ParserError}).Error("list of parsers is empty") return } t := pb.Transactions[0] tx, ok := t.Inner.(*transaction.FirstBlockParser) if !ok { log.WithFields(log.Fields{"type": consts.ParserError}).Error("getting data of first block") return } data = tx.Data syspar.SetFirstBlockTimestamp(time.UnixMilli(tx.Timestamp).Unix()) syspar.SysUpdate(nil) return } // upsertInfoBlock updates info_block table func (b *Block) upsertInfoBlock(dbTx *sqldb.DbTransaction, block *sqldb.BlockChain) error { ib := &sqldb.InfoBlock{ Hash: block.Hash, BlockID: block.ID, Time: block.Time, EcosystemID: block.EcosystemID, KeyID: block.KeyID, NodePosition: converter.Int64ToStr(block.NodePosition), RollbacksHash: block.RollbacksHash, ConsensusMode: block.ConsensusMode, CandidateNodes: block.CandidateNodes, } if block.ID == 1 { ib.CurrentVersion = fmt.Sprintf("%d", consts.BlockVersion) err := ib.Create(dbTx) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating info block") return fmt.Errorf("error insert into info_block %s", err) } } else { ib.Sent = 0 if err := ib.Update(dbTx); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating info block") return fmt.Errorf("error while updating info_block %s", err) } } return nil } type AfterTxs struct { UsedTx [][]byte Rts []*sqldb.RollbackTx Lts []*sqldb.LogTransaction UpdTxStatus []*pbgo.TxResult } func (b *Block) GenAfterTxs() *AfterTxs { after := b.AfterTxs playTx := &AfterTxs{ Rts: make([]*sqldb.RollbackTx, len(after.Rts)), Lts: make([]*sqldb.LogTransaction, len(after.Txs)), UpdTxStatus: make([]*pbgo.TxResult, len(after.Txs)), } for i := 0; i < len(after.Rts); i++ { tx := after.Rts[i] rt := new(sqldb.RollbackTx) rt.BlockID = tx.BlockId rt.NameTable = tx.NameTable rt.Data = tx.Data rt.TableID = tx.TableId rt.TxHash = tx.TxHash rt.DataHash = tx.DataHash playTx.Rts[i] = rt } for i := 0; i < len(after.Txs); i++ { tx := after.Txs[i] playTx.UsedTx = append(playTx.UsedTx, tx.UsedTx) lt := new(sqldb.LogTransaction) lt.Block = tx.Lts.Block lt.Hash = tx.Lts.Hash //lt.TxData = tx.Lts.TxData lt.Timestamp = tx.Lts.Timestamp lt.Address = tx.Lts.Address lt.EcosystemID = tx.Lts.EcosystemId lt.ContractName = tx.Lts.ContractName lt.Status = int64(tx.Lts.InvokeStatus) playTx.Lts[i] = lt u := new(pbgo.TxResult) u = tx.UpdTxStatus playTx.UpdTxStatus[i] = u } return playTx } func (b *Block) AfterPlayTxs(dbTx *sqldb.DbTransaction) error { playTx := b.GenAfterTxs() return sqldb.GetDB(dbTx).Transaction(func(tx *gorm.DB) error { //if !b.GenBlock && !b.IsGenesis() && conf.Config.BlockSyncMethod.Method == types.BlockSyncMethod_SQLDML.String() { // for i := 0; i < len(b.AfterTxs.TxBinLogSql); i++ { // if err := tx.Exec(string(b.AfterTxs.TxBinLogSql[i])).Error; err != nil { // return errors.Wrap(err, "batches exec sql for tx") // } // } //} if err := sqldb.DeleteTransactions(tx, playTx.UsedTx); err != nil { return errors.Wrap(err, "batches delete used transactions") } if err := sqldb.CreateLogTransactionBatches(tx, playTx.Lts); err != nil { return errors.Wrap(err, "batches insert log_transactions") } spentInfos := sqldb.GetAllOutputs(b.OutputsMap) if len(spentInfos) > 0 { if err := sqldb.CreateSpentInfoBatches(tx, spentInfos); err != nil { return errors.Wrap(err, "batches insert spent_info") } } if err := sqldb.CreateBatchesRollbackTx(tx, playTx.Rts); err != nil { return errors.Wrap(err, "batches insert rollback tx") } if err := sqldb.UpdateBlockMsgBatches(tx, b.Header.BlockId, playTx.UpdTxStatus); err != nil { return errors.Wrap(err, "batches update block msg transaction status") } return nil }) } ================================================ FILE: packages/block/play.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package block import ( "bytes" "encoding/hex" "fmt" "strconv" "sync" "github.com/IBAX-io/go-ibax/packages/common/random" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/notificator" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // PlaySafe is inserting block safely func (b *Block) PlaySafe() error { logger := b.GetLogger() dbTx, err := sqldb.StartTransaction() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("starting db transaction") return err } err = b.ProcessTxs(dbTx) if err != nil { dbTx.Rollback() return err } if b.GenBlock && len(b.TxFullData) == 0 { dbTx.Commit() return ErrEmptyBlock } if err := b.InsertIntoBlockchain(dbTx); err != nil { dbTx.Rollback() return err } err = dbTx.Commit() if err != nil { return err } for _, q := range b.Notifications { q.Send() } return nil } type badTxStruct struct { index int hash []byte msg string keyID int64 } func (b *Block) ProcessTxs(dbTx *sqldb.DbTransaction) (err error) { afters := &types.AfterTxs{ Rts: make([]*types.RollbackTx, 0), Txs: make([]*types.AfterTx, 0), } txsMap := b.ClassifyTxsMap processedTx := make([][]byte, 0, len(b.Transactions)) processBadTx := func() chan badTxStruct { ch := make(chan badTxStruct) go func() { for badTxItem := range ch { transaction.BadTxForBan(badTxItem.keyID) _ = transaction.MarkTransactionBad(badTxItem.hash, badTxItem.msg) } }() return ch } txBadChan := processBadTx() defer func() { close(txBadChan) if b.IsGenesis() || b.GenBlock || b.AfterTxs != nil { b.AfterTxs = afters } if b.GenBlock { b.TxFullData = processedTx } if errA := b.AfterPlayTxs(dbTx); errA != nil { if err == nil { err = errA } else if err != nil { err = fmt.Errorf("%v; %w", err, errA) } return } }() //if !b.GenBlock && !b.IsGenesis() && conf.Config.BlockSyncMethod.Method == types.BlockSyncMethod_SQLDML.String() { // if b.SysUpdate { // if err := syspar.SysUpdate(dbTx); err != nil { // return fmt.Errorf("updating syspar: %w", err) // } // } // return nil //} var keyIds []int64 var keyIdsMap = make(map[int64]bool) var ecosystemIds []int64 var ecosystemIdsMap = make(map[int64]bool) for indexTx := 0; indexTx < len(b.Transactions); indexTx++ { t := b.Transactions[indexTx] if !keyIdsMap[t.KeyID()] { keyIdsMap[t.KeyID()] = true keyIds = append(keyIds, t.KeyID()) } if t.IsSmartContract() && !ecosystemIdsMap[t.SmartContract().TxSmart.EcosystemID] { ecosystemIdsMap[t.SmartContract().TxSmart.EcosystemID] = true ecosystemIds = append(ecosystemIds, t.SmartContract().TxSmart.EcosystemID) } } // query all keys utxo outputs, err := sqldb.GetTxOutputs(dbTx, keyIds) if err != nil { return err } b.OutputsMap = make(map[sqldb.KeyUTXO][]sqldb.SpentInfo) sqldb.PutAllOutputsMap(outputs, b.OutputsMap) // query all ecosystems combination percent ecoParams, err := sqldb.GetEcoParam(dbTx, ecosystemIds) if err != nil { return err } b.EcoParams = ecoParams // UTXO multiple ecosystem fuelRate b.PrevSysPar = syspar.GetSysParCache() var wg sync.WaitGroup // StopNetworkTxType if len(txsMap[types.StopNetworkTxType]) > 0 { transactions := txsMap[types.StopNetworkTxType] err := b.serialExecuteTxs(dbTx, txBadChan, afters, &processedTx, transactions, lock) delete(txsMap, types.StopNetworkTxType) if err != nil { return err } return nil } // FirstBlockTxType if b.IsGenesis() { transactions := make([]*transaction.Transaction, 0) for _, tx := range b.Transactions { t, err := transaction.UnmarshallTransaction(bytes.NewBuffer(tx.FullData), false) if err != nil { return err } transactions = append(transactions, t) } err := b.serialExecuteTxs(dbTx, txBadChan, afters, &processedTx, transactions, lock) transactions = make([]*transaction.Transaction, 0) if err != nil { return err } } // DelayTxType if len(txsMap[types.DelayTxType]) > 0 { transactions := txsMap[types.DelayTxType] err := b.serialExecuteTxs(dbTx, txBadChan, afters, &processedTx, transactions, lock) delete(txsMap, types.DelayTxType) if err != nil { return err } } // TransferSelf if len(txsMap[types.TransferSelfTxType]) > 0 { transactions := txsMap[types.TransferSelfTxType] walletAddress := make(map[int64]int64) groupTransferSelfTxs(transactions, walletAddress) for _, transactions := range transferSelfTxsGroupMap { wg.Add(1) go func(_dbTx *sqldb.DbTransaction, _txBadChan chan badTxStruct, _transactions []*transaction.Transaction, _afters *types.AfterTxs, _processedTx *[][]byte, _lock *sync.RWMutex) { defer wg.Done() err := b.serialExecuteTxs(_dbTx, _txBadChan, _afters, _processedTx, _transactions, _lock) if err != nil { return } }(dbTx, txBadChan, transactions, afters, &processedTx, lock) } wg.Wait() transferSelfTxsGroupMap = make(map[string][]*transaction.Transaction, 0) transferSelfGroupTxsList = make([]*transaction.Transaction, 0) transferSelfGroupSerial = 1 delete(txsMap, types.TransferSelfTxType) } //Utxo && Smart contract if len(txsMap[types.UtxoTxType]) > 0 || len(txsMap[types.SmartContractTxType]) > 0 { transactions := txsMap[types.UtxoTxType] // utxo group walletAddress := make(map[int64]int64) groupUtxoTxs(transactions, walletAddress) if len(txsMap[types.SmartContractTxType]) > 0 { utxoTxsGroupMap[strconv.Itoa(0)] = txsMap[types.SmartContractTxType] } for _, transactions := range utxoTxsGroupMap { wg.Add(1) go func(_dbTx *sqldb.DbTransaction, _txBadChan chan badTxStruct, _transactions []*transaction.Transaction, _afters *types.AfterTxs, _processedTx *[][]byte, _lock *sync.RWMutex) { defer wg.Done() err := b.serialExecuteTxs(_dbTx, _txBadChan, _afters, _processedTx, _transactions, _lock) if err != nil { return } }(dbTx, txBadChan, transactions, afters, &processedTx, lock) } wg.Wait() utxoTxsGroupMap = make(map[string][]*transaction.Transaction, 0) utxoGroupTxsList = make([]*transaction.Transaction, 0) utxoGroupSerial = 1 delete(txsMap, types.UtxoTxType) delete(txsMap, types.SmartContractTxType) } return nil } func (b *Block) serialExecuteTxs(dbTx *sqldb.DbTransaction, txBadChan chan badTxStruct, afters *types.AfterTxs, processedTx *[][]byte, txs []*transaction.Transaction, _lock *sync.RWMutex) error { _lock.Lock() defer _lock.Unlock() limits := transaction.NewLimits(b.limitMode()) rand := random.NewRand(b.Header.Timestamp) logger := b.GetLogger() for curTx := 0; curTx < len(txs); curTx++ { t := txs[curTx] err := dbTx.Savepoint(consts.SetSavePointMarkBlock(hex.EncodeToString(t.Hash()))) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "tx_hash": t.Hash()}).Error("using savepoint") return err } err = t.WithOption(notificator.NewQueue(), b.GenBlock, b.Header, b.PrevHeader, dbTx, rand.BytesSeed(t.Hash()), limits, consts.SetSavePointMarkBlock(hex.EncodeToString(t.Hash())), b.OutputsMap, b.PrevSysPar, b.EcoParams) if err != nil { return err } err = t.Play() if err != nil { if err == transaction.ErrNetworkStopping { // Set the node in a pause state node.PauseNodeActivity(node.PauseTypeStopingNetwork) return err } errRoll := t.DbTransaction.RollbackSavepoint(consts.SetSavePointMarkBlock(hex.EncodeToString(t.Hash()))) if errRoll != nil { return fmt.Errorf("%v; %w", err, errRoll) } if b.GenBlock { if errors.Cause(err) == transaction.ErrLimitStop { if curTx == 0 { txBadChan <- badTxStruct{index: curTx, hash: t.Hash(), msg: err.Error(), keyID: t.KeyID()} return err } break } } txBadChan <- badTxStruct{index: curTx, hash: t.Hash(), msg: err.Error(), keyID: t.KeyID()} if t.SysUpdate { if err := syspar.SysUpdate(t.DbTransaction); err != nil { return fmt.Errorf("updating syspar: %w", err) } t.SysUpdate = false } if b.GenBlock { continue } return err } if t.SysUpdate { b.SysUpdate = true t.SysUpdate = false } if t.Notifications.Size() > 0 { b.Notifications = append(b.Notifications, t.Notifications) } var ( after = &types.AfterTx{} eco = int64(1) contract string code pbgo.TxInvokeStatusCode ) if t.IsSmartContract() { eco = t.SmartContract().TxSmart.EcosystemID code = t.TxResult.Code if t.SmartContract().TxContract != nil { contract = t.SmartContract().TxContract.Name } } after.UsedTx = t.Hash() after.Lts = &types.LogTransaction{ Block: t.BlockHeader.BlockId, Hash: t.Hash(), //TxData: t.FullData, Timestamp: t.Timestamp(), Address: t.KeyID(), EcosystemId: eco, ContractName: contract, InvokeStatus: code, } after.UpdTxStatus = t.TxResult afters.Txs = append(afters.Txs, after) afters.Rts = append(afters.Rts, t.RollBackTx...) //afters.TxBinLogSql = append(afters.TxBinLogSql, t.DbTransaction.BinLogSql...) *processedTx = append(*processedTx, t.FullData) sqldb.UpdateTxInputs(t.Hash(), t.TxInputsMap, b.OutputsMap) sqldb.InsertTxOutputs(t.Hash(), t.TxOutputsMap, b.OutputsMap) } return nil } var ( utxoTxsGroupMap = make(map[string][]*transaction.Transaction) utxoGroupTxsList = make([]*transaction.Transaction, 0) utxoGroupSerial uint16 = 1 lock = &sync.RWMutex{} ) func groupUtxoTxs(txs []*transaction.Transaction, walletAddress map[int64]int64) map[string][]*transaction.Transaction { if len(txs) == 0 { return utxoTxsGroupMap } crrentGroupTxsSize := len(utxoGroupTxsList) size := len(txs) for i := 0; i < size; i++ { if len(walletAddress) == 0 { walletAddress[txs[i].KeyID()] = txs[i].KeyID() walletAddress[txs[i].SmartContract().TxSmart.UTXO.ToID] = txs[i].SmartContract().TxSmart.UTXO.ToID utxoGroupTxsList = append(utxoGroupTxsList, txs[i]) txs = txs[1:] size = len(txs) i-- continue } if walletAddress[txs[i].KeyID()] != 0 || walletAddress[txs[i].SmartContract().TxSmart.UTXO.ToID] != 0 { walletAddress[txs[i].KeyID()] = txs[i].KeyID() walletAddress[txs[i].SmartContract().TxSmart.UTXO.ToID] = txs[i].SmartContract().TxSmart.UTXO.ToID utxoGroupTxsList = append(utxoGroupTxsList, txs[i]) txs = append(txs[:i], txs[i+1:]...) size = len(txs) i-- } } if crrentGroupTxsSize < len(utxoGroupTxsList) { if len(txs) == 0 { utxoTxsGroupMap[strconv.Itoa(int(utxoGroupSerial))] = utxoGroupTxsList return utxoTxsGroupMap } return groupUtxoTxs(txs, walletAddress) } if len(utxoGroupTxsList) > 0 { tempUtxoGroupTxsList := utxoGroupTxsList utxoTxsGroupMap[strconv.Itoa(int(utxoGroupSerial))] = tempUtxoGroupTxsList utxoGroupSerial++ utxoGroupTxsList = make([]*transaction.Transaction, 0) walletAddress = make(map[int64]int64) } return groupUtxoTxs(txs, walletAddress) } var ( transferSelfTxsGroupMap = make(map[string][]*transaction.Transaction) transferSelfGroupTxsList = make([]*transaction.Transaction, 0) transferSelfGroupSerial uint16 = 1 ) func groupTransferSelfTxs(txs []*transaction.Transaction, walletAddress map[int64]int64) map[string][]*transaction.Transaction { if len(txs) == 0 { return transferSelfTxsGroupMap } crrentGroupTxsSize := len(transferSelfGroupTxsList) size := len(txs) for i := 0; i < size; i++ { if len(walletAddress) == 0 { walletAddress[txs[i].KeyID()] = txs[i].KeyID() transferSelfGroupTxsList = append(transferSelfGroupTxsList, txs[i]) txs = txs[1:] size = len(txs) i-- continue } if walletAddress[txs[i].KeyID()] != 0 { walletAddress[txs[i].KeyID()] = txs[i].KeyID() transferSelfGroupTxsList = append(transferSelfGroupTxsList, txs[i]) txs = append(txs[:i], txs[i+1:]...) size = len(txs) i-- } } if crrentGroupTxsSize < len(transferSelfGroupTxsList) { if len(txs) == 0 { transferSelfTxsGroupMap[strconv.Itoa(int(transferSelfGroupSerial))] = transferSelfGroupTxsList return transferSelfTxsGroupMap } return groupTransferSelfTxs(txs, walletAddress) } if len(transferSelfGroupTxsList) > 0 { tempTransferSelfGroupTxsList := transferSelfGroupTxsList transferSelfTxsGroupMap[strconv.Itoa(int(transferSelfGroupSerial))] = tempTransferSelfGroupTxsList transferSelfGroupSerial++ transferSelfGroupTxsList = make([]*transaction.Transaction, 0) walletAddress = make(map[int64]int64) } return groupTransferSelfTxs(txs, walletAddress) } ================================================ FILE: packages/block/serialization.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package block import ( "bytes" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" ) func (b *Block) repeatMarshallBlock() error { newBlockData, err := MarshallBlock( types.WithCurHeader(b.Header), types.WithPrevHeader(b.PrevHeader), types.WithAfterTxs(b.AfterTxs), types.WithSysUpdate(b.SysUpdate), types.WithTxFullData(b.TxFullData)) if err != nil { return errors.Wrap(err, "marshalling repeat block") } var nb = new(Block) nb, err = UnmarshallBlock(bytes.NewBuffer(newBlockData), true) if err != nil { return errors.Wrap(err, "parsing repeat block") } b.BinData = newBlockData b.Transactions = nb.Transactions b.MerkleRoot = nb.MerkleRoot return nil } func MarshallBlock(opts ...types.BlockDataOption) ([]byte, error) { block := &types.BlockData{} if err := block.Apply(opts...); err != nil { return nil, err } return block.MarshallBlock(syspar.GetNodePrivKey()) } func UnmarshallBlock(blockBuffer *bytes.Buffer, fill bool) (*Block, error) { var ( contractNames []string classifyTxsMap = make(map[int][]*transaction.Transaction) block = &types.BlockData{} ) if err := block.UnmarshallBlock(blockBuffer.Bytes()); err != nil { return nil, err } if block.Header.BlockId != 1 { allDelayedContract, err := sqldb.GetAllDelayedContract() if err != nil { return nil, err } for _, contract := range allDelayedContract { contractNames = append(contractNames, contract.Contract) } } transactions := make([]*transaction.Transaction, 0) for i := 0; i < len(block.TxFullData); i++ { tx, err := transaction.UnmarshallTransaction(bytes.NewBuffer(block.TxFullData[i]), fill) if err != nil { return nil, err } if tx.Type() == types.StopNetworkTxType { classifyTxsMap[types.StopNetworkTxType] = append(classifyTxsMap[types.StopNetworkTxType], tx) transactions = append(transactions, tx) continue } if tx.IsSmartContract() { if tx.Type() == types.TransferSelfTxType { classifyTxsMap[types.TransferSelfTxType] = append(classifyTxsMap[types.TransferSelfTxType], tx) transactions = append(transactions, tx) continue } if tx.Type() == types.UtxoTxType { classifyTxsMap[types.UtxoTxType] = append(classifyTxsMap[types.UtxoTxType], tx) transactions = append(transactions, tx) continue } if utils.StringInSlice(contractNames, tx.SmartContract().TxContract.Name) { classifyTxsMap[types.DelayTxType] = append(classifyTxsMap[types.DelayTxType], tx) transactions = append(transactions, tx) continue } classifyTxsMap[types.SmartContractTxType] = append(classifyTxsMap[types.SmartContractTxType], tx) } transactions = append(transactions, tx) } return &Block{ BlockData: block, PrevRollbacksHash: block.PrevHeader.RollbacksHash, ClassifyTxsMap: classifyTxsMap, Transactions: transactions, }, nil } ================================================ FILE: packages/chain/daemonsctl/daemonsctl.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemonsctl import ( "context" "github.com/IBAX-io/go-ibax/packages/modes" ) // RunAllDaemons start daemons, load contracts and tcpserver func RunAllDaemons(ctx context.Context) error { return modes.GetDaemonLoader().Load(ctx) } ================================================ FILE: packages/chain/notandroid.go ================================================ //go:build !android && !ios /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package chain import ( "net" "net/http" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) func httpListener(ListenHTTPHost string, route http.Handler) { l, err := net.Listen("tcp", ListenHTTPHost) log.WithFields(log.Fields{"host": ListenHTTPHost, "type": consts.NetworkError}).Debug("trying to listen at") if err == nil { log.WithFields(log.Fields{"host": ListenHTTPHost}).Info("listening at") } else { log.WithFields(log.Fields{"host": ListenHTTPHost, "error": err, "type": consts.NetworkError}).Debug("cannot listen at host") } go func() { srv := &http.Server{Handler: route} err = srv.Serve(l) if err != nil { log.WithFields(log.Fields{"host": ListenHTTPHost, "error": err, "type": consts.NetworkError}).Error("serving http at host") panic(err) } }() } ================================================ FILE: packages/chain/start.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package chain import ( "context" "crypto/tls" "fmt" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/service/jsonrpc" "math/rand" "net/http" "os" "path/filepath" "time" "github.com/IBAX-io/go-ibax/packages/api" "github.com/IBAX-io/go-ibax/packages/chain/daemonsctl" "github.com/IBAX-io/go-ibax/packages/chain/system" logtools "github.com/IBAX-io/go-ibax/packages/common/log" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/daemons" "github.com/IBAX-io/go-ibax/packages/modes" "github.com/IBAX-io/go-ibax/packages/network/httpserver" "github.com/IBAX-io/go-ibax/packages/publisher" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/statsd" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) func init() { jwtSecret := []byte(crypto.RandSeq(15)) jsonrpc.InitJwtSecret(jwtSecret) api.InitJwtSecret(jwtSecret) } // Start starts the main code of the program func Start() { var err error defer func() { if r := recover(); r != nil { log.WithFields(log.Fields{"panic": r, "type": consts.PanicRecoveredError}).Error("recovered panic") panic(r) } }() exitErr := func(code int) { system.RemovePidFile() sqldb.GormClose() statsd.Close() os.Exit(code) } f := utils.LockOrDie(conf.Config.DirPathConf.LockFilePath) defer f.Unlock() if err := utils.MakeDirectory(conf.Config.DirPathConf.TempDir); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "dir": conf.Config.DirPathConf.TempDir}).Error("can't create temporary directory") exitErr(1) } // save the current pid and version if err := system.CreatePidFile(); err != nil { log.Errorf("can't create pid: %s", err) exitErr(1) } defer system.RemovePidFile() log.WithFields(log.Fields{"work_dir": conf.Config.DirPathConf.DataDir, "version": consts.Version()}).Info("started with") if err = initLogs(); err != nil { log.Errorf("logs init failed: %v\n", utils.ErrInfo(err)) exitErr(1) } if conf.Config.FuncBench { log.Warning("Warning! Access checking is disabled in some built-in functions") } publisher.InitCentrifugo(conf.Config.Centrifugo) initStatsd() rand.Seed(time.Now().UTC().UnixNano()) smart.InitVM() if err := syspar.ReadNodeKeys(); err != nil { log.Errorf("can't read node keys: %s", err) exitErr(1) } if err = sqldb.GormInit(conf.Config.DB); err != nil { log.WithFields(log.Fields{ "db_user": conf.Config.DB.User, "db_password": conf.Config.DB.Password, "db_name": conf.Config.DB.Name, "type": consts.DBError, }).Error("can't init gorm") exitErr(1) } if sqldb.DBConn != nil { if err := sqldb.UpdateSchema(); err != nil { log.WithError(err).Error("on running update migrations") exitErr(1) } candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err == nil && len(candidateNodes) > 0 { syspar.SetRunModel(consts.CandidateNodeMode) } else { syspar.SetRunModel(consts.HonorNodeMode) } ctx, cancel := context.WithCancel(context.Background()) utils.CancelFunc = cancel utils.ReturnCh = make(chan string) // The installation process is already finished (where user has specified DB and where wallet has been restarted) err = daemonsctl.RunAllDaemons(ctx) log.Info("Daemons started") if err != nil { exitErr(1) } } daemons.WaitForSignals() initRoutes(conf.Config.HTTP.Str()) select {} } func initStatsd() { if err := statsd.Init(conf.Config.StatsD); err != nil { log.WithFields(log.Fields{"type": consts.StatsdError, "error": err}).Fatal("cannot initialize statsd") } } func initLogs() error { switch conf.Config.Log.LogFormat { case "json": log.SetFormatter(&log.JSONFormatter{}) default: log.SetFormatter(&log.TextFormatter{}) } switch conf.Config.Log.LogTo { case "stdout": log.SetOutput(os.Stdout) case "syslog": facility := conf.Config.Log.Syslog.Facility tag := conf.Config.Log.Syslog.Tag sysLogHook, err := logtools.NewSyslogHook(facility, tag) if err != nil { log.WithError(err).Error("Unable to connect to local syslog daemon") } else { log.AddHook(sysLogHook) } default: fileName := filepath.Join(conf.Config.DirPathConf.DataDir, conf.Config.Log.LogTo) openMode := os.O_APPEND if _, err := os.Stat(fileName); os.IsNotExist(err) { openMode = os.O_CREATE } f, err := os.OpenFile(fileName, os.O_WRONLY|openMode, 0755) if err != nil { fmt.Fprintln(os.Stderr, "Can't open log file: ", fileName) return err } log.SetOutput(f) } switch conf.Config.Log.LogLevel { case "DEBUG": log.SetLevel(log.DebugLevel) case "INFO": log.SetLevel(log.InfoLevel) case "WARN": log.SetLevel(log.WarnLevel) case "ERROR": log.SetLevel(log.ErrorLevel) default: log.SetLevel(log.InfoLevel) } log.AddHook(logtools.ContextHook{}) log.AddHook(logtools.HexHook{}) return nil } func initRoutes(listenHost string) { handler := modes.RegisterRoutes() handler = httpserver.NewMaxBodyReader(handler, conf.Config.LocalConf.HTTPServerMaxBodySize) if conf.Config.JsonRPC.Enabled { handler = modes.RegisterJsonRPCRoutes(handler) } handler = api.WithCors(handler) if conf.Config.TLSConf.Enabled { if len(conf.Config.TLSConf.TLSCert) == 0 || len(conf.Config.TLSConf.TLSKey) == 0 { log.Fatal("-tls-cert/TLSCert and -tls-key/TLSKey must be specified with -tls/TLS") } if _, err := os.Stat(conf.Config.TLSConf.TLSCert); os.IsNotExist(err) { log.WithError(err).Fatalf(`Filepath -tls-cert/TLSCert = %s is invalid`, conf.Config.TLSConf.TLSCert) } if _, err := os.Stat(conf.Config.TLSConf.TLSKey); os.IsNotExist(err) { log.WithError(err).Fatalf(`Filepath -tls-key/TLSKey = %s is invalid`, conf.Config.TLSConf.TLSKey) } go func() { s := &http.Server{ Addr: listenHost, Handler: handler, TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS12, SessionTicketsDisabled: true, //ClientAuth: tls.RequireAndVerifyClientCert, }, } err := s.ListenAndServeTLS(conf.Config.TLSConf.TLSCert, conf.Config.TLSConf.TLSKey) //err := http.ListenAndServeTLS(listenHost, conf.Config.TLSCert, conf.Config.TLSKey, handler) if err != nil { log.WithFields(log.Fields{"host": listenHost, "error": err, "type": consts.NetworkError}).Fatal("Listening TLS server") } }() log.WithFields(log.Fields{"host": listenHost}).Info("listening with TLS at") return } httpListener(listenHost, handler) } ================================================ FILE: packages/chain/system/pid.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package system import ( "encoding/json" "fmt" "os" "strconv" "strings" "time" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" log "github.com/sirupsen/logrus" ) // CreatePidFile creats pid file func CreatePidFile() error { killOldPid() pid := os.Getpid() pidAndVer, err := json.Marshal(map[string]string{ "pid": converter.IntToStr(pid), "version": consts.Version(), }) if err != nil { log.WithFields(log.Fields{"pid": pid, "error": err, "type": consts.JSONMarshallError}).Error("marshalling pid to json") return err } return os.WriteFile(conf.Config.GetPidPath(), pidAndVer, 0644) } // RemovePidFile removes pid file func RemovePidFile() error { return os.Remove(conf.Config.GetPidPath()) } // ReadPidFile reads pid file func ReadPidFile() (int, error) { pidPath := conf.Config.GetPidPath() if _, err := os.Stat(pidPath); err != nil { return 0, nil } data, err := os.ReadFile(pidPath) if err != nil { log.WithFields(log.Fields{"path": pidPath, "error": err, "type": consts.IOError}).Error("reading pid file") return 0, err } pid, err := strconv.Atoi(strings.TrimSpace(string(data))) if err != nil { log.WithFields(log.Fields{"data": data, "error": err, "type": consts.ConversionError}).Error("pid file data to int") } return pid, err } func killOldPid() { pidPath := conf.Config.GetPidPath() if _, err := os.Stat(pidPath); err == nil { dat, err := os.ReadFile(pidPath) if err != nil { log.WithFields(log.Fields{"path": pidPath, "error": err, "type": consts.IOError}).Error("reading pid file") } var pidMap map[string]string err = json.Unmarshal(dat, &pidMap) if err != nil { log.WithFields(log.Fields{"data": dat, "error": err, "type": consts.JSONUnmarshallError}).Error("unmarshalling pid map") } log.WithFields(log.Fields{"path": conf.Config.DirPathConf.DataDir + pidMap["pid"]}).Debug("old pid path") KillPid(pidMap["pid"]) if fmt.Sprintf("%s", err) != "null" { // give 15 sec to end the previous process for i := 0; i < 5; i++ { if _, err := os.Stat(conf.Config.GetPidPath()); err == nil { time.Sleep(time.Second) } else { break } } } } } ================================================ FILE: packages/chain/system/unix.go ================================================ //go:build (linux || freebsd || darwin) && (386 || amd64 || arm64) /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package system import ( "syscall" "github.com/IBAX-io/go-ibax/packages/converter" log "github.com/sirupsen/logrus" ) // KillPid is killing process by PID func KillPid(pid string) error { err := syscall.Kill(converter.StrToInt(pid), syscall.SIGTERM) if err != nil { log.WithFields(log.Fields{"pid": pid, "signal": syscall.SIGTERM}).Error("Error killing process with pid") return err } return nil } ================================================ FILE: packages/chain/system/windows.go ================================================ //go:build windows /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package system import ( "fmt" "os/exec" "regexp" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // KillPid kills the process with the specified pid func KillPid(pid string) error { if sqldb.DBConn != nil { sd := &sqldb.StopDaemon{StopTime: time.Now().Unix()} err := sd.Create() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Error creating StopDaemon") return err } } rez, err := exec.Command("tasklist", "/fi", "PID eq "+pid).Output() if err != nil { log.WithFields(log.Fields{"type": consts.CommandExecutionError, "err": err, "cmd": "tasklist /fi PID eq" + pid}).Error("Error executing command") return err } if string(rez) == "" { return fmt.Errorf("null") } log.WithFields(log.Fields{"cmd": "tasklist /fi PID eq " + pid}).Debug("command execution result") if ok, _ := regexp.MatchString(`(?i)PID`, string(rez)); !ok { return fmt.Errorf("null") } return nil } ================================================ FILE: packages/clbmanager/config.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clbmanager import ( "fmt" "os/exec" "path/filepath" ) const ( inidDBCommand = "initDatabase" genKeysCommand = "generateKeys" startCommand = "start" ) // ChildCLBConfig struct to manage child entry type ChildCLBConfig struct { Executable string Name string Directory string DBUser string DBPassword string ConfigFileName string LogTo string LogLevel string HTTPPort int } func (c ChildCLBConfig) configCommand() *exec.Cmd { args := []string{ "config", fmt.Sprintf("--path=%s", c.configPath()), fmt.Sprintf("--dbUser=%s", c.DBUser), fmt.Sprintf("--dbPassword=%s", c.DBPassword), fmt.Sprintf("--dbName=%s", c.Name), fmt.Sprintf("--httpPort=%d", c.HTTPPort), fmt.Sprintf("--dataDir=%s", c.Directory), fmt.Sprintf("--keysDir=%s", c.Directory), fmt.Sprintf("--logTo=%s", c.LogTo), fmt.Sprintf("--logLevel=%s", c.LogLevel), "--clbMode=CLB", } return exec.Command(c.Executable, args...) } func (c ChildCLBConfig) initDBCommand() *exec.Cmd { return c.getCommand(inidDBCommand) } func (c ChildCLBConfig) generateKeysCommand() *exec.Cmd { return c.getCommand(genKeysCommand) } func (c ChildCLBConfig) startCommand() *exec.Cmd { return c.getCommand(startCommand) } func (c ChildCLBConfig) configPath() string { return filepath.Join(c.Directory, c.ConfigFileName) } func (c ChildCLBConfig) getCommand(commandName string) *exec.Cmd { args := []string{ commandName, fmt.Sprintf("--config=%s", c.configPath()), } return exec.Command(c.Executable, args...) } ================================================ FILE: packages/clbmanager/manager.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clbmanager import ( "errors" "fmt" "os" "path" "path/filepath" "strings" "time" "unicode" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/ochinchina/go-ini" pConf "github.com/ochinchina/supervisord/config" "github.com/ochinchina/supervisord/process" log "github.com/sirupsen/logrus" ) const ( childFolder = "configs" createRoleTemplate = `CREATE ROLE %s WITH ENCRYPTED PASSWORD '%s' NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN` createDBTemplate = `CREATE DATABASE %s OWNER %s` dropDBTemplate = `DROP DATABASE IF EXISTS %s` dropOwnedTemplate = `DROP OWNED BY %s CASCADE` dropDBRoleTemplate = `DROP ROLE IF EXISTS %s` commandTemplate = `%s start --config=%s` alreadyExistsErrorTemplate = `clb '%s' already exists` ) var ( errWrongMode = errors.New("node must be running as CLBMaster") errIncorrectCLBName = errors.New("the name cannot begit with a number and must contain alphabetical symbols and numbers") ) // CLBManager struct type CLBManager struct { processes *process.Manager execPath string childConfigsPath string } var ( Manager *CLBManager ) func prepareWorkDir() (string, error) { childConfigsPath := path.Join(conf.Config.DirPathConf.DataDir, childFolder) if _, err := os.Stat(childConfigsPath); os.IsNotExist(err) { if err := os.Mkdir(childConfigsPath, 0700); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("creating configs directory") return "", err } } return childConfigsPath, nil } // CreateCLB creates one instance of CLB func (mgr *CLBManager) CreateCLB(name, dbUser, dbPassword string, port int) error { if err := checkCLBName(name); err != nil { log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Error("on check CLB name") return errIncorrectCLBName } var err error var cancelChain []func() defer func() { if err == nil { return } for _, cancelFunc := range cancelChain { cancelFunc() } }() config := ChildCLBConfig{ Executable: mgr.execPath, Name: name, Directory: path.Join(mgr.childConfigsPath, name), DBUser: dbUser, DBPassword: dbPassword, ConfigFileName: consts.DefaultConfigFile, HTTPPort: port, LogTo: fmt.Sprintf("%s_%s", name, conf.Config.Log.LogTo), LogLevel: conf.Config.Log.LogLevel, } if mgr.processes == nil { log.WithFields(log.Fields{"type": consts.WrongModeError, "error": errWrongMode}).Error("creating new CLB") return errWrongMode } if err = mgr.createCLBDB(name, dbUser, dbPassword); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on creating CLB DB") return fmt.Errorf(alreadyExistsErrorTemplate, name) } cancelChain = append(cancelChain, func() { dropDb(name, dbUser) }) dirPath := path.Join(mgr.childConfigsPath, name) if directoryExists(dirPath) { err = fmt.Errorf(alreadyExistsErrorTemplate, name) log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err, "dirPath": dirPath}).Error("on check directory") return err } if err = mgr.initCLBDir(name); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "DirName": name, "error": err}).Error("on init CLB dir") return err } cancelChain = append(cancelChain, func() { dropCLBDir(mgr.childConfigsPath, name) }) cmd := config.configCommand() if err = cmd.Run(); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "args": cmd.Args, "error": err}).Error("on run config command") return err } if err = config.generateKeysCommand().Run(); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "args": cmd.Args, "error": err}).Error("on run generateKeys command") return err } if err = config.initDBCommand().Run(); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "args": cmd.Args}).Error("on run initDB command") return err } procConfEntry := pConf.NewEntry(config.Directory) procConfEntry.Name = "program:" + name command := fmt.Sprintf("%s start --config=%s", config.Executable, filepath.Join(config.Directory, consts.DefaultConfigFile)) log.Infoln(command) section := ini.NewSection(procConfEntry.Name) section.Add("command", command) proc := process.NewProcess("clbMaster", procConfEntry) mgr.processes.Add(name, proc) mgr.processes.Find(name).Start(true) return nil } // ListProcess returns list of process names with state of process func (mgr *CLBManager) ListProcess() (map[string]string, error) { if mgr.processes == nil { log.WithFields(log.Fields{"type": consts.WrongModeError, "error": errWrongMode}).Error("get CLB list") return nil, errWrongMode } list := make(map[string]string) mgr.processes.ForEachProcess(func(p *process.Process) { list[p.GetName()] = p.GetState().String() }) return list, nil } func (mgr *CLBManager) ListProcessWithPorts() (map[string]string, error) { list, err := mgr.ListProcess() if err != nil { return list, err } for name, status := range list { path := path.Join(mgr.childConfigsPath, name, consts.DefaultConfigFile) c := &conf.GlobalConfig{} if err := conf.LoadConfigToVar(path, c); err != nil { log.WithFields(log.Fields{"type": "dbError", "error": err, "path": path}).Warn("on loading child CLB config") continue } list[name] = fmt.Sprintf("%s %d", status, c.HTTP.Port) } return list, err } // DeleteCLB stop CLB process and remove CLB folder func (mgr *CLBManager) DeleteCLB(name string) error { if mgr.processes == nil { log.WithFields(log.Fields{"type": consts.WrongModeError, "error": errWrongMode}).Error("deleting CLB") return errWrongMode } mgr.StopCLB(name) mgr.processes.Remove(name) clbDir := path.Join(mgr.childConfigsPath, name) clbConfigPath := filepath.Join(clbDir, consts.DefaultConfigFile) clbConfig, err := conf.GetConfigFromPath(clbConfigPath) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Errorf("Getting config from path %s", clbConfigPath) return fmt.Errorf(`CLB '%s' is not exists`, name) } time.Sleep(1 * time.Second) if err := dropDb(clbConfig.DB.Name, clbConfig.DB.User); err != nil { return err } return os.RemoveAll(clbDir) } // StartCLB find process and then start him func (mgr *CLBManager) StartCLB(name string) error { if mgr.processes == nil { log.WithFields(log.Fields{"type": consts.WrongModeError, "error": errWrongMode}).Error("starting CLB") return errWrongMode } proc := mgr.processes.Find(name) if proc == nil { err := fmt.Errorf(`CLB '%s' is not exists`, name) log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Error("on find CLB process") return err } state := proc.GetState() if state == process.Stopped || state == process.Exited || state == process.Fatal { proc.Start(true) log.WithFields(log.Fields{"clb_name": name}).Info("CLB started") return nil } err := fmt.Errorf("CLB '%s' is %s", name, state) log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Error("on starting CLB") return err } // StopCLB find process with definded name and then stop him func (mgr *CLBManager) StopCLB(name string) error { if mgr.processes == nil { log.WithFields(log.Fields{"type": consts.WrongModeError, "error": errWrongMode}).Error("on stopping CLB process") return errWrongMode } proc := mgr.processes.Find(name) if proc == nil { err := fmt.Errorf(`CLB '%s' is not exists`, name) log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Error("on find CLB process") return err } state := proc.GetState() if state == process.Running || state == process.Starting { proc.Stop(true) log.WithFields(log.Fields{"clb_name": name}).Info("CLB is stoped") return nil } err := fmt.Errorf("CLB '%s' is %s", name, state) log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Error("on stoping CLB") return err } func (mgr *CLBManager) createCLBDB(clbName, login, pass string) error { if err := sqldb.DBConn.Exec(fmt.Sprintf(createRoleTemplate, login, pass)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating CLB DB User") return err } if err := sqldb.DBConn.Exec(fmt.Sprintf(createDBTemplate, clbName, login)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating CLB DB") if err := sqldb.GetDB(nil).Exec(fmt.Sprintf(dropDBRoleTemplate, login)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "role": login}).Error("on deleting clb role") return err } return err } return nil } func (mgr *CLBManager) initCLBDir(clbName string) error { clbDirName := path.Join(mgr.childConfigsPath, clbName) if _, err := os.Stat(clbDirName); os.IsNotExist(err) { if err := os.Mkdir(clbDirName, 0700); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("creating CLB directory") return err } } return nil } func InitCLBManager() { if !conf.Config.IsCLBMaster() { return } execPath, err := os.Executable() if err != nil { log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Fatal("on determine executable path") } childConfigsPath, err := prepareWorkDir() if err != nil { log.WithFields(log.Fields{"type": consts.CLBManagerError, "error": err}).Fatal("on prepare child configs folder") } Manager = &CLBManager{ processes: process.NewManager(), execPath: execPath, childConfigsPath: childConfigsPath, } list, err := os.ReadDir(childConfigsPath) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err, "path": childConfigsPath}).Fatal("on read child CLB directory") } for _, item := range list { if item.IsDir() { procDir := path.Join(Manager.childConfigsPath, item.Name()) commandStr := fmt.Sprintf(commandTemplate, Manager.execPath, filepath.Join(procDir, consts.DefaultConfigFile)) log.Info(commandStr) confEntry := pConf.NewEntry(procDir) confEntry.Name = "program:" + item.Name() section := ini.NewSection(confEntry.Name) section.Add("command", commandStr) section.Add("redirect_stderr", "true") section.Add("autostart", "true") section.Add("autorestart", "true") proc := process.NewProcess("clbMaster", confEntry) Manager.processes.Add(item.Name(), proc) proc.Start(true) } } } func dropDb(name, role string) error { if err := sqldb.NewDbTransaction(nil).DropDatabase(name); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "db_name": name}).Error("Deleting clb db") return err } if err := sqldb.GetDB(nil).Exec(fmt.Sprintf(dropDBRoleTemplate, role)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "role": role}).Error("on deleting clb role") } return nil } func dropCLBDir(configsPath, clbName string) error { path := path.Join(configsPath, clbName) if directoryExists(path) { os.RemoveAll(path) } log.WithFields(log.Fields{"path": path}).Error("droping dir is not exists") return nil } func directoryExists(path string) bool { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { return false } } return true } func checkCLBName(name string) error { name = strings.ToLower(name) for i, c := range name { if unicode.IsDigit(c) && i == 0 { return errors.New("the name cannot begin with a number") } if !unicode.IsDigit(c) && !unicode.Is(unicode.Latin, c) { return errors.New("Incorrect symbol") } } return nil } func (mgr *CLBManager) configByName(name string) (*conf.GlobalConfig, error) { path := path.Join(mgr.childConfigsPath) c := &conf.GlobalConfig{} err := conf.LoadConfigToVar(path, c) return c, err } ================================================ FILE: packages/common/crypto/asymalgo/asymalgo.go ================================================ package asymalgo import ( "bytes" "encoding/hex" "fmt" "math/big" ) // ParseSign converts the hex signature to r and s big number func ParseSign(sign string) (*big.Int, *big.Int, error) { var ( binSign []byte err error ) // var off int parse := func(bsign []byte) []byte { blen := int(bsign[1]) if blen > len(bsign)-2 { return nil } ret := bsign[2 : 2+blen] if len(ret) > 32 { ret = ret[len(ret)-32:] } else if len(ret) < 32 { ret = append(bytes.Repeat([]byte{0}, 32-len(ret)), ret...) } return ret } if len(sign) > 128 { binSign, err = hex.DecodeString(sign) if err != nil { return nil, nil, fmt.Errorf("decoding sign from string: %w", err) } left := parse(binSign[2:]) if left == nil || int(binSign[3])+6 > len(binSign) { return nil, nil, fmt.Errorf(`wrong left parsing`) } right := parse(binSign[4+binSign[3]:]) if right == nil { return nil, nil, fmt.Errorf(`wrong right parsing`) } sign = hex.EncodeToString(append(left, right...)) } else if len(sign) < 128 { return nil, nil, fmt.Errorf(`wrong len of signature %d`, len(sign)) } all, err := hex.DecodeString(sign[:]) if err != nil { return nil, nil, fmt.Errorf("wrong signature size: %w", err) } return new(big.Int).SetBytes(all[:32]), new(big.Int).SetBytes(all[len(all)-32:]), nil } // FillLeft is filling slice func FillLeft(slice []byte) []byte { if len(slice) >= 32 { return slice } return append(make([]byte, 32-len(slice)), slice...) } ================================================ FILE: packages/common/crypto/asymalgo/error.go ================================================ package asymalgo import "errors" var ( // ErrSigningEmpty is Signing empty value error ErrSigningEmpty = errors.New("Signing empty value") // ErrCheckingSignEmpty is Checking sign of empty error ErrCheckingSignEmpty = errors.New("Cheking sign of empty") // ErrIncorrectSign is Incorrect sign ErrIncorrectSign = errors.New("Incorrect sign") ) ================================================ FILE: packages/common/crypto/asymalgo/p256.go ================================================ package asymalgo import ( "crypto/ecdsa" "crypto/elliptic" crand "crypto/rand" "encoding/hex" "fmt" "math/big" "github.com/IBAX-io/go-ibax/packages/consts" ) type P256 struct{} func (e *P256) GenKeyPair() ([]byte, []byte, error) { var curve elliptic.Curve curve = elliptic.P256() private, err := ecdsa.GenerateKey(curve, crand.Reader) if err != nil { return nil, nil, err } return private.D.Bytes(), append(FillLeft(private.PublicKey.X.Bytes()), FillLeft(private.PublicKey.Y.Bytes())...), nil } func (e *P256) Sign(privateKey, hash []byte) ([]byte, error) { if len(hash) == 0 { return nil, ErrSigningEmpty } priv := new(ecdsa.PrivateKey) priv.PublicKey.Curve = elliptic.P256() priv.D = new(big.Int).SetBytes(privateKey) r, s, err := ecdsa.Sign(crand.Reader, priv, hash) if err != nil { return nil, err } return append(FillLeft(r.Bytes()), FillLeft(s.Bytes())...), nil } func (e *P256) Verify(public, hash, signature []byte) (bool, error) { if len(public) == 0 { return false, ErrCheckingSignEmpty } if len(hash) == 0 { return false, fmt.Errorf("invalid parameters len(data) == 0") } if len(public) != consts.PubkeySizeLength { return false, fmt.Errorf("invalid parameters len(public) = %d", len(public)) } if len(signature) == 0 { return false, fmt.Errorf("invalid parameters len(signature) == 0") } pubkey := new(ecdsa.PublicKey) pubkey.Curve = elliptic.P256() pubkey.X = new(big.Int).SetBytes(public[0:consts.PrivkeyLength]) pubkey.Y = new(big.Int).SetBytes(public[consts.PrivkeyLength:]) r, s, err := ParseSign(hex.EncodeToString(signature)) if err != nil { return false, err } verify := ecdsa.Verify(pubkey, hash, r, s) if !verify { return false, ErrIncorrectSign } return true, nil } func (e *P256) PrivateToPublic(key []byte) ([]byte, error) { pubkeyCurve := elliptic.P256() priv := new(ecdsa.PrivateKey) priv.PublicKey.Curve = pubkeyCurve priv.D = new(big.Int).SetBytes(key) priv.PublicKey.X, priv.PublicKey.Y = pubkeyCurve.ScalarBaseMult(key) return append(FillLeft(priv.PublicKey.X.Bytes()), FillLeft(priv.PublicKey.Y.Bytes())...), nil } ================================================ FILE: packages/common/crypto/asymalgo/secp256k1.go ================================================ package asymalgo import ( "crypto/ecdsa" crand "crypto/rand" "encoding/hex" "fmt" "math/big" "github.com/IBAX-io/go-ibax/packages/consts" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) type Secp256k1 struct{} func (s *Secp256k1) GenKeyPair() ([]byte, []byte, error) { priv, err := ecdsa.GenerateKey(secp.S256(), crand.Reader) if err != nil { return nil, nil, err } pub := append(FillLeft(priv.X.Bytes()), FillLeft(priv.Y.Bytes())...) return priv.D.Bytes(), pub, nil } func (s *Secp256k1) Sign(privateKey, hash []byte) ([]byte, error) { if len(hash) == 0 { return nil, ErrSigningEmpty } pubkeyCurve := secp.S256() bi := new(big.Int).SetBytes(privateKey) priv := new(ecdsa.PrivateKey) priv.PublicKey.Curve = pubkeyCurve priv.D = bi r, s_, err := ecdsa.Sign(crand.Reader, priv, hash) if err != nil { return nil, err } ret := append(FillLeft(r.Bytes()), FillLeft(s_.Bytes())...) return ret, nil } func (s *Secp256k1) Verify(public, hash, signature []byte) (bool, error) { if len(public) == 0 { return false, ErrCheckingSignEmpty } if len(hash) == 0 { return false, fmt.Errorf("invalid parameters len(data) == 0") } if len(public) != consts.PubkeySizeLength { return false, fmt.Errorf("invalid parameters len(public) = %d", len(public)) } if len(signature) == 0 { return false, fmt.Errorf("invalid parameters len(signature) == 0") } pubkeyCurve := secp.S256() pubkey := new(ecdsa.PublicKey) pubkey.Curve = pubkeyCurve pubkey.X = new(big.Int).SetBytes(public[0:consts.PrivkeyLength]) pubkey.Y = new(big.Int).SetBytes(public[consts.PrivkeyLength:]) r, s_, err := ParseSign(hex.EncodeToString(signature)) if err != nil { return false, err } verify := ecdsa.Verify(pubkey, hash, r, s_) if !verify { return false, ErrIncorrectSign } return true, nil } func (s *Secp256k1) PrivateToPublic(key []byte) ([]byte, error) { priv := secp.PrivKeyFromBytes(key) return priv.PubKey().SerializeUncompressed()[1:], nil } ================================================ FILE: packages/common/crypto/asymalgo/sm2.go ================================================ package asymalgo import ( "crypto/rand" "fmt" "math/big" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/tjfoc/gmsm/sm2" ) type SM2 struct{} func (s *SM2) GenKeyPair() ([]byte, []byte, error) { priv, err := sm2.GenerateKey(rand.Reader) if err != nil { return nil, nil, err } return priv.D.Bytes(), append(FillLeft(priv.PublicKey.X.Bytes()), FillLeft(priv.PublicKey.Y.Bytes())...), nil } func (s *SM2) Sign(privateKey, hash []byte) ([]byte, error) { pubkeyCurve := sm2.P256Sm2() bi := new(big.Int).SetBytes(privateKey) priv := new(sm2.PrivateKey) priv.PublicKey.Curve = pubkeyCurve priv.D = bi priv.PublicKey.X, priv.PublicKey.Y = pubkeyCurve.ScalarBaseMult(bi.Bytes()) return priv.Sign(rand.Reader, hash, nil) } func (s *SM2) Verify(public, hash, signature []byte) (bool, error) { if len(public) == 0 { return false, ErrCheckingSignEmpty } if len(hash) == 0 { return false, fmt.Errorf("invalid parameters len(data) == 0") } if len(public) != consts.PubkeySizeLength { return false, fmt.Errorf("invalid parameters len(public) = %d", len(public)) } if len(signature) == 0 { return false, fmt.Errorf("invalid parameters len(signature) == 0") } pubkey := new(sm2.PublicKey) pubkey.Curve = sm2.P256Sm2() pubkey.X = new(big.Int).SetBytes(public[0:consts.PrivkeyLength]) pubkey.Y = new(big.Int).SetBytes(public[consts.PrivkeyLength:]) verify := pubkey.Verify(hash, signature) if !verify { return false, ErrIncorrectSign } return true, nil } func (s *SM2) PrivateToPublic(key []byte) ([]byte, error) { pubkeyCurve := sm2.P256Sm2() bi := new(big.Int).SetBytes(key) priv := new(sm2.PrivateKey) priv.PublicKey.Curve = pubkeyCurve priv.D = bi priv.PublicKey.X, priv.PublicKey.Y = pubkeyCurve.ScalarBaseMult(key) return append(FillLeft(priv.PublicKey.X.Bytes()), FillLeft(priv.PublicKey.Y.Bytes())...), nil } ================================================ FILE: packages/common/crypto/base58/base58.go ================================================ package base58 import ( "bytes" "crypto/sha256" "encoding/hex" "math/big" ) var base58Alphabets = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") func ToHexAddress(address string) string { return hex.EncodeToString(base58Decode([]byte(address))) } func FromHexAddress(hexAddress string) (string, error) { addrByte, err := hex.DecodeString(hexAddress) if err != nil { return "", err } sha := sha256.New() sha.Write(addrByte) shaStr := sha.Sum(nil) sha2 := sha256.New() sha2.Write(shaStr) shaStr2 := sha2.Sum(nil) addrByte = append(addrByte, shaStr2[:4]...) return string(base58Encode(addrByte)), nil } func base58Encode(input []byte) []byte { x := big.NewInt(0).SetBytes(input) base := big.NewInt(58) zero := big.NewInt(0) mod := &big.Int{} var result []byte for x.Cmp(zero) != 0 { x.DivMod(x, base, mod) result = append(result, base58Alphabets[mod.Int64()]) } reverseBytes(result) return result } func base58Decode(input []byte) []byte { result := big.NewInt(0) for _, b := range input { charIndex := bytes.IndexByte(base58Alphabets, b) result.Mul(result, big.NewInt(58)) result.Add(result, big.NewInt(int64(charIndex))) } decoded := result.Bytes() if input[0] == base58Alphabets[0] { decoded = append([]byte{0x00}, decoded...) } return decoded[:len(decoded)-4] } func reverseBytes(data []byte) { for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { data[i], data[j] = data[j], data[i] } } ================================================ FILE: packages/common/crypto/checksum.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package crypto import "hash/crc64" var ( table64 *crc64.Table ) func init() { table64 = crc64.MakeTable(crc64.ECMA) } // CalcChecksum is calculates checksum, returns crc64 sum. func CalcChecksum(input []byte) uint64 { return crc64.Checksum(input, table64) } ================================================ FILE: packages/common/crypto/converter.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package crypto import ( "crypto/sha512" "encoding/hex" "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" ) // CutPub removes the first 04 byte func CutPub(pubKey []byte) []byte { if len(pubKey) == 65 && pubKey[0] == 4 { pubKey = pubKey[1:] } return pubKey } // Address gets int64 address from the public key func Address(pubKey []byte) int64 { pubKey = CutPub(pubKey) h := Hash(pubKey) h512 := sha512.Sum512(h[:]) crc := CalcChecksum(h512[:]) return buildChecksumConvert(crc) } // AddressSeed gets int64 address from the seed. // format: `IBAX Address By Seed:` + seed func AddressSeed(seed string) int64 { h := Hash([]byte("IBAX Address By Seed:" + seed)) h512 := sha512.Sum512(h[:]) crc := CalcChecksum(h512[:]) return buildChecksumConvert(crc) } func buildChecksumConvert(crc uint64) int64 { num := strconv.FormatUint(crc, 10) val := RepeatPrefixed(num) v := val[:len(val)-1] sum := converter.CheckSum(v) uSum := uint64(sum) return int64(crc - (crc % 10) + uSum) } func RepeatPrefixed(input string) []byte { const size = consts.AddressLength if len(input) > size { input = input[:size] } return []byte(strings.Repeat("0", size-len(input)) + input) } // KeyToAddress converts a public key to chain address XXXX-...-XXXX. func KeyToAddress(pubKey []byte) string { return converter.AddressToString(Address(pubKey)) } // GetWalletIDByPublicKey converts public key to wallet id func GetWalletIDByPublicKey(publicKey []byte) (int64, error) { key, _ := HexToPub(string(publicKey)) return int64(Address(key)), nil } // HexToPub encodes hex string to []byte of pub key func HexToPub(pub string) ([]byte, error) { key, err := hex.DecodeString(pub) if err != nil { return nil, err } return CutPub(key), nil } // PubToHex decodes []byte of pub key to hex string func PubToHex(pub []byte) string { if len(pub) == 64 { pub = append([]byte{4}, pub...) } return hex.EncodeToString(pub) } ================================================ FILE: packages/common/crypto/crypto.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package crypto import ( "encoding/hex" "fmt" log "github.com/sirupsen/logrus" "github.com/IBAX-io/go-ibax/packages/common/crypto/asymalgo" "github.com/IBAX-io/go-ibax/packages/common/crypto/hashalgo" ) var ( asymAlgo AsymAlgo hashAlgo HashAlgo ) func NewAsymAlgo(a AsymAlgo) AsymProvider { switch a { case AsymAlgo_ECC_P256: return &asymalgo.P256{} case AsymAlgo_ECC_Secp256k1: return &asymalgo.Secp256k1{} case AsymAlgo_SM2: return &asymalgo.SM2{} } panic(fmt.Errorf("curve algo [%v] is not supported yet", a)) } func InitAsymAlgo(s string) { v, ok := AsymAlgo_value[s] if !ok { log.Fatal(fmt.Errorf("curve algo [%v] is not supported yet, Run 'go-ibax config --help' for details", s)) } asymAlgo = AsymAlgo(v) return } func GetAsymProvider() AsymProvider { return NewAsymAlgo(asymAlgo) } func NewHashAlgo(a HashAlgo) HashProvider { switch a { case HashAlgo_SHA256: return &hashalgo.SHA256{} case HashAlgo_SM3: return &hashalgo.SM3{} case HashAlgo_KECCAK256: return &hashalgo.Keccak256{} case HashAlgo_SHA3_256: return &hashalgo.Sha3256{} } panic(fmt.Errorf("hash algo [%v] is not supported yet", a)) } func InitHashAlgo(s string) { v, ok := HashAlgo_value[s] if !ok { log.Fatal(fmt.Errorf("hash algo [%v] is not supported yet, Run 'go-ibax config --help' for details", s)) } hashAlgo = HashAlgo(v) return } func GetHashProvider() HashProvider { return NewHashAlgo(hashAlgo) } // GenKeyPair generates a random pair of private and public binary keys. func GenKeyPair() ([]byte, []byte, error) { return GetAsymProvider().GenKeyPair() } // GenHexKeys generates a random pair of private and public hex keys. func GenHexKeys() (string, string, error) { priv, pub, err := GenKeyPair() if err != nil { return ``, ``, err } return hex.EncodeToString(priv), PubToHex(pub), nil } func Sign(privateKey, data []byte) ([]byte, error) { return GetAsymProvider().Sign(privateKey, Hash(data)) } func Verify(public, data, signature []byte) (bool, error) { return GetAsymProvider().Verify(public, Hash(data), signature) } // PrivateToPublic returns the public key for the specified private key. func PrivateToPublic(key []byte) ([]byte, error) { return GetAsymProvider().PrivateToPublic(key) } func SignString(privateKeyHex, data string) ([]byte, error) { privateKey, err := hex.DecodeString(privateKeyHex) if err != nil { return nil, fmt.Errorf("decoding private key from hex: %w", err) } return Sign(privateKey, []byte(data)) } func GetHMAC(secret string, message string) ([]byte, error) { return GetHashProvider().GetHMAC(secret, message) } func Hash(msg []byte) []byte { return GetHashProvider().GetHash(msg) } func DoubleHash(msg []byte) []byte { return GetHashProvider().DoubleHash(msg) } func HashHex(input []byte) string { return hex.EncodeToString(Hash(input)) } ================================================ FILE: packages/common/crypto/crypto.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: crypto.proto package crypto import ( fmt "fmt" proto "github.com/gogo/protobuf/proto" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // AsymAlgo is asymmetric algorithms type AsymAlgo int32 const ( AsymAlgo_ECC_P256 AsymAlgo = 0 AsymAlgo_ECC_Secp256k1 AsymAlgo = 1 AsymAlgo_SM2 AsymAlgo = 2 AsymAlgo_ECC_P512 AsymAlgo = 3 ) var AsymAlgo_name = map[int32]string{ 0: "ECC_P256", 1: "ECC_Secp256k1", 2: "SM2", 3: "ECC_P512", } var AsymAlgo_value = map[string]int32{ "ECC_P256": 0, "ECC_Secp256k1": 1, "SM2": 2, "ECC_P512": 3, } func (x AsymAlgo) String() string { return proto.EnumName(AsymAlgo_name, int32(x)) } func (AsymAlgo) EnumDescriptor() ([]byte, []int) { return fileDescriptor_527278fb02d03321, []int{0} } // SymAlgo is symmetric algorithms type SymAlgo int32 const ( SymAlgo_AES SymAlgo = 0 SymAlgo_SM4 SymAlgo = 1 ) var SymAlgo_name = map[int32]string{ 0: "AES", 1: "SM4", } var SymAlgo_value = map[string]int32{ "AES": 0, "SM4": 1, } func (x SymAlgo) String() string { return proto.EnumName(SymAlgo_name, int32(x)) } func (SymAlgo) EnumDescriptor() ([]byte, []int) { return fileDescriptor_527278fb02d03321, []int{1} } // HashAlgo is hash algorithms type HashAlgo int32 const ( HashAlgo_SHA256 HashAlgo = 0 HashAlgo_KECCAK256 HashAlgo = 1 HashAlgo_SM3 HashAlgo = 2 HashAlgo_SHA3_256 HashAlgo = 3 ) var HashAlgo_name = map[int32]string{ 0: "SHA256", 1: "KECCAK256", 2: "SM3", 3: "SHA3_256", } var HashAlgo_value = map[string]int32{ "SHA256": 0, "KECCAK256": 1, "SM3": 2, "SHA3_256": 3, } func (x HashAlgo) String() string { return proto.EnumName(HashAlgo_name, int32(x)) } func (HashAlgo) EnumDescriptor() ([]byte, []int) { return fileDescriptor_527278fb02d03321, []int{2} } func init() { proto.RegisterEnum("crypto.AsymAlgo", AsymAlgo_name, AsymAlgo_value) proto.RegisterEnum("crypto.SymAlgo", SymAlgo_name, SymAlgo_value) proto.RegisterEnum("crypto.HashAlgo", HashAlgo_name, HashAlgo_value) } func init() { proto.RegisterFile("crypto.proto", fileDescriptor_527278fb02d03321) } var fileDescriptor_527278fb02d03321 = []byte{ // 243 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0xb4, 0x9c, 0xb8, 0x38, 0x1c, 0x8b, 0x2b, 0x73, 0x1d, 0x73, 0xd2, 0xf3, 0x85, 0x78, 0xb8, 0x38, 0x5c, 0x9d, 0x9d, 0xe3, 0x03, 0x8c, 0x4c, 0xcd, 0x04, 0x18, 0x84, 0x04, 0xb9, 0x78, 0x41, 0xbc, 0xe0, 0xd4, 0xe4, 0x02, 0x23, 0x53, 0xb3, 0x6c, 0x43, 0x01, 0x46, 0x21, 0x76, 0x2e, 0xe6, 0x60, 0x5f, 0x23, 0x01, 0x26, 0xb8, 0x4a, 0x53, 0x43, 0x23, 0x01, 0x66, 0x2d, 0x69, 0x2e, 0xf6, 0x60, 0xa8, 0x11, 0xec, 0x5c, 0xcc, 0x8e, 0xae, 0xc1, 0x02, 0x0c, 0x10, 0xa5, 0x26, 0x02, 0x8c, 0x5a, 0x36, 0x5c, 0x1c, 0x1e, 0x89, 0xc5, 0x19, 0x60, 0x59, 0x2e, 0x2e, 0xb6, 0x60, 0x0f, 0x47, 0x88, 0xf1, 0xbc, 0x5c, 0x9c, 0xde, 0xae, 0xce, 0xce, 0x8e, 0xde, 0x20, 0x2e, 0xd4, 0x68, 0x63, 0x88, 0xd1, 0xc1, 0x1e, 0x8e, 0xc6, 0xf1, 0x20, 0x61, 0x66, 0x27, 0xef, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xf7, 0x74, 0x72, 0x8c, 0xd0, 0xcd, 0xcc, 0xd7, 0x4f, 0xcf, 0xd7, 0xcd, 0x4c, 0x4a, 0xac, 0xd0, 0x2f, 0x48, 0x4c, 0xce, 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x4f, 0xce, 0xcf, 0xcd, 0xcd, 0xcf, 0xd3, 0x87, 0xf8, 0x35, 0x89, 0x0d, 0xec, 0x75, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4a, 0xcd, 0xcc, 0xc1, 0x0a, 0x01, 0x00, 0x00, } ================================================ FILE: packages/common/crypto/crypto_test.go ================================================ package crypto import ( "encoding/hex" "fmt" "log" "testing" ) func TestGetCryptoer(t *testing.T) { InitAsymAlgo("ECC_P256") InitHashAlgo("SHA256") src := []byte("Hello") encodedStr := hex.EncodeToString(src) fmt.Println("Message in []byte: ", src) fmt.Printf("%s\n", encodedStr) prv, pub, err := GenKeyPair() if err != nil { return } prvStr := hex.EncodeToString(prv) pubStr := hex.EncodeToString(pub) fmt.Println("privateKey is:", prv, "publicKey", pub) fmt.Println("privateKeyString is:", prvStr, "publicKeyString is:", pubStr) addr := Address(pub) fmt.Println("Address is:", addr) signedDataByte, err := Sign(prv, src) if err != nil { log.Fatal(err) } signedDataStr := hex.EncodeToString(signedDataByte) fmt.Println("signedDataByte is:", signedDataByte) fmt.Println("signedDataString is:", signedDataStr) // priv test // signedDataString := "3045022100929be5f360d10270bc67b6f9d28c47c257e472fdbdf66a3037022e47143bf94c0220246f9e378444d1d0fa81f613fb93c3c420e0a1abd0f3138cf10788492f690fc8" // signedDataByte, err := hex.DecodeString(signedDataString) // if err != nil { // log.Fatal(err) // } // pubString := "7c66ce7703e6e4c4e31ba36c6eee29de345a8e9d36611f6bd2c809d3d0d47788fe3a66ab1970a8ea7d8b1f46e67956a481d638a0ab92a9aaaf0fbd2151af702e" // pub, err := hex.DecodeString(pubString) // if err != nil { // log.Fatal(err) // } fmt.Println("signedDataByPriv is:", signedDataByte) //signature=3045022046468a5a6c62adff1d7c6a864dae3878a34f9d07be66b71f8ba34fc5a80d0e45022100caba64be8cf53709975a6a209c5c2197b01054f9b22edf25e99dfb8ea50a0633 _, err = Verify(pub, src, signedDataByte) if err != nil { log.Fatal(err) } } ================================================ FILE: packages/common/crypto/ecies/ecccrypt.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package ecies import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "log" "math/big" "runtime" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/common/crypto" ) func init() { log.SetFlags(log.Ldate | log.Lshortfile) } // //Ecc /*func EccEnCrypt(plainText []byte,prv2 *ecies.PrivateKey)(crypText []byte,err error){ ct, err := ecies.Encrypt(rand.Reader, &prv2.PublicKey, plainText, nil, nil) return ct, err } // func EccDeCrypt(cryptText []byte,prv2 *ecies.PrivateKey) ([]byte, error) { pt, err := prv2.Decrypt(cryptText, nil, nil) return pt, err }*/ // func EccPubEncrypt(plainText []byte, pub *ecdsa.PublicKey) (cryptText []byte, err error) { // defer func() { if err := recover(); err != nil { switch err.(type) { case runtime.Error: log.Println("runtime err:", err, "check key ") default: log.Println("error:", err) } } }() publicKey := ImportECDSAPublic(pub) // crypttext, err := Encrypt(rand.Reader, publicKey, plainText, nil, nil) return crypttext, err } // func EccPriDeCrypt(cryptText []byte, priv *ecdsa.PrivateKey) (msg []byte, err error) { // privateKey := ImportECDSA(priv) // plainText, err := privateKey.Decrypt(cryptText, nil, nil) return plainText, err } func EccCryptoKey(plainText []byte, publickey string) (cryptoText []byte, err error) { pubbuff, err := crypto.HexToPub(publickey) if err != nil { return nil, err } pub, err := GetPublicKeys(pubbuff) if err != nil { return nil, err } return EccPubEncrypt(plainText, pub) } func EccDeCrypto(cryptoText []byte, prikey []byte) ([]byte, error) { pri, err := GetPrivateKeys(prikey) if err != nil { return nil, err } return EccPriDeCrypt(cryptoText, pri) } // GetPrivateKeys return func GetPrivateKeys(privateKey []byte) (ret *ecdsa.PrivateKey, err error) { var pubkeyCurve elliptic.Curve pubkeyCurve = elliptic.P256() bi := new(big.Int).SetBytes(privateKey) priv := new(ecdsa.PrivateKey) priv.PublicKey.Curve = pubkeyCurve priv.D = bi return priv, nil } // GetPublicKeys return func GetPublicKeys(public []byte) (*ecdsa.PublicKey, error) { pubkey := new(ecdsa.PublicKey) if len(public) != consts.PubkeySizeLength { return pubkey, fmt.Errorf("invalid parameters len(public) = %d", len(public)) } pubkey.Curve = elliptic.P256() pubkey.X = new(big.Int).SetBytes(public[0:consts.PrivkeyLength]) pubkey.Y = new(big.Int).SetBytes(public[consts.PrivkeyLength:]) return pubkey, nil } ================================================ FILE: packages/common/crypto/ecies/ecies.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package ecies import ( "crypto" "crypto/aes" "crypto/cipher" "crypto/ecdsa" "crypto/elliptic" "crypto/hmac" "crypto/sha256" "crypto/sha512" "crypto/subtle" "fmt" "hash" "io" "math/big" ) var ( ErrImport = fmt.Errorf("ecies: failed to import key") ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve") ErrInvalidParams = fmt.Errorf("ecies: invalid ECIES parameters") ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key") ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity") ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big") ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") ) // PublicKey is a representation of an elliptic curve public key. type PublicKey struct { X *big.Int Y *big.Int elliptic.Curve Params *ECIESParams } // Export an ECIES public key as an ECDSA public key. func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey { return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y} } // Import an ECDSA public key as an ECIES public key. func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey { return &PublicKey{ X: pub.X, Y: pub.Y, Curve: pub.Curve, Params: ParamsFromCurve(pub.Curve), } } // PrivateKey is a representation of an elliptic curve private key. type PrivateKey struct { PublicKey D *big.Int } // Export an ECIES private key as an ECDSA private key. func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey { pub := &prv.PublicKey pubECDSA := pub.ExportECDSA() return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D} } // Import an ECDSA private key as an ECIES private key. func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey { pub := ImportECDSAPublic(&prv.PublicKey) return &PrivateKey{*pub, prv.D} } // Generate an elliptic curve public / private keypair. If params is nil, // the recommended default parameters for the key will be chosen. func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) { pb, x, y, err := elliptic.GenerateKey(curve, rand) if err != nil { return } prv = new(PrivateKey) prv.PublicKey.X = x prv.PublicKey.Y = y prv.PublicKey.Curve = curve prv.D = new(big.Int).SetBytes(pb) if params == nil { params = ParamsFromCurve(curve) } prv.PublicKey.Params = params return } // MaxSharedKeyLength returns the maximum length of the shared key the // public key can produce. func MaxSharedKeyLength(pub *PublicKey) int { return (pub.Curve.Params().BitSize + 7) / 8 } // ECDH key agreement method used to establish secret keys for encryption. func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) { if prv.PublicKey.Curve != pub.Curve { return nil, ErrInvalidCurve } if skLen+macLen > MaxSharedKeyLength(pub) { return nil, ErrSharedKeyTooBig } x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) if x == nil { return nil, ErrSharedKeyIsPointAtInfinity } sk = make([]byte, skLen+macLen) skBytes := x.Bytes() copy(sk[len(sk)-len(skBytes):], skBytes) return sk, nil } var ( ErrKeyDataTooLong = fmt.Errorf("ecies: can't supply requested key data") ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long") ErrInvalidMessage = fmt.Errorf("ecies: invalid message") ) var ( big2To32 = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil) big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1)) ) func incCounter(ctr []byte) { if ctr[3]++; ctr[3] != 0 { return } if ctr[2]++; ctr[2] != 0 { return } if ctr[1]++; ctr[1] != 0 { return } if ctr[0]++; ctr[0] != 0 { return } } // NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) { if s1 == nil { s1 = make([]byte, 0) } reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8) if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 { fmt.Println(big2To32M1) return nil, ErrKeyDataTooLong } counter := []byte{0, 0, 0, 1} k = make([]byte, 0) for i := 0; i <= reps; i++ { hash.Write(counter) hash.Write(z) hash.Write(s1) k = append(k, hash.Sum(nil)...) hash.Reset() incCounter(counter) } k = k[:kdLen] return } // messageTag computes the MAC of a message (called the tag) as per // SEC 1, 3.5. func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { mac := hmac.New(hash, km) mac.Write(msg) mac.Write(shared) tag := mac.Sum(nil) return tag } // Generate an initialisation vector for CTR mode. func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { iv = make([]byte, params.BlockSize) _, err = io.ReadFull(rand, iv) return } // symEncrypt carries out CTR encryption using the block cipher specified in the // parameters. func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) { c, err := params.Cipher(key) if err != nil { return } iv, err := generateIV(params, rand) if err != nil { return } ctr := cipher.NewCTR(c, iv) ct = make([]byte, len(m)+params.BlockSize) copy(ct, iv) ctr.XORKeyStream(ct[params.BlockSize:], m) return } // symDecrypt carries out CTR decryption using the block cipher specified in // the parameters func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) { c, err := params.Cipher(key) if err != nil { return } ctr := cipher.NewCTR(c, ct[:params.BlockSize]) m = make([]byte, len(ct)-params.BlockSize) ctr.XORKeyStream(m, ct[params.BlockSize:]) return } // Encrypt encrypts a message using ECIES as specified in SEC 1, 5.1. // // s1 and s2 contain shared information that is not part of the resulting // ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the // shared information parameters aren't being used, they should be nil. func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) { params := pub.Params if params == nil { if params = ParamsFromCurve(pub.Curve); params == nil { err = ErrUnsupportedECIESParameters return } } R, err := GenerateKey(rand, pub.Curve, params) if err != nil { return } hash := params.Hash() z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen) if err != nil { return } K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) if err != nil { return } Ke := K[:params.KeyLen] Km := K[params.KeyLen:] hash.Write(Km) Km = hash.Sum(nil) hash.Reset() em, err := symEncrypt(rand, params, Ke, m) if err != nil || len(em) <= params.BlockSize { return } d := messageTag(params.Hash, Km, em, s2) Rb := elliptic.Marshal(pub.Curve, R.PublicKey.X, R.PublicKey.Y) ct = make([]byte, len(Rb)+len(em)+len(d)) copy(ct, Rb) copy(ct[len(Rb):], em) copy(ct[len(Rb)+len(em):], d) return } // Decrypt decrypts an ECIES ciphertext. func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { if len(c) == 0 { return nil, ErrInvalidMessage } params := prv.PublicKey.Params if params == nil { if params = ParamsFromCurve(prv.PublicKey.Curve); params == nil { err = ErrUnsupportedECIESParameters return } } hash := params.Hash() var ( rLen int hLen = hash.Size() mStart int mEnd int ) switch c[0] { case 2, 3, 4: rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4 if len(c) < (rLen + hLen + 1) { err = ErrInvalidMessage return } default: err = ErrInvalidPublicKey return } mStart = rLen mEnd = len(c) - hLen R := new(PublicKey) R.Curve = prv.PublicKey.Curve R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen]) if R.X == nil { err = ErrInvalidPublicKey return } if !R.Curve.IsOnCurve(R.X, R.Y) { err = ErrInvalidCurve return } z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) if err != nil { return } K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) if err != nil { return } Ke := K[:params.KeyLen] Km := K[params.KeyLen:] hash.Write(Km) Km = hash.Sum(nil) hash.Reset() d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 { err = ErrInvalidMessage return } m, err = symDecrypt(params, Ke, c[mStart:mEnd]) return } // func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) { return paramsFromCurve[curve] } var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ //ethcrypto.S256(): ECIES_AES128_SHA256, elliptic.P256(): ECIES_AES128_SHA256, elliptic.P384(): ECIES_AES256_SHA384, elliptic.P521(): ECIES_AES256_SHA512, } var ( ECIES_AES128_SHA256 = &ECIESParams{ Hash: sha256.New, hashAlgo: crypto.SHA256, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 16, } ECIES_AES256_SHA256 = &ECIESParams{ Hash: sha256.New, hashAlgo: crypto.SHA256, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 32, } ECIES_AES256_SHA384 = &ECIESParams{ Hash: sha512.New384, hashAlgo: crypto.SHA384, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 32, } ECIES_AES256_SHA512 = &ECIESParams{ Hash: sha512.New, hashAlgo: crypto.SHA512, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 32, } ) type ECIESParams struct { Hash func() hash.Hash // hash function hashAlgo crypto.Hash Cipher func([]byte) (cipher.Block, error) // symmetric cipher BlockSize int // block size of symmetric cipher KeyLen int // length of symmetric key } ================================================ FILE: packages/common/crypto/hashalgo/hashalgo.go ================================================ package hashalgo ================================================ FILE: packages/common/crypto/hashalgo/keccak256.go ================================================ package hashalgo import ( "crypto/hmac" "golang.org/x/crypto/sha3" ) type Keccak256 struct{} func (k *Keccak256) GetHMAC(secret string, message string) ([]byte, error) { mac := hmac.New(sha3.NewLegacyKeccak256, []byte(secret)) mac.Write([]byte(message)) return mac.Sum(nil), nil } func (k *Keccak256) GetHash(msg []byte) []byte { return k.usingKeccak256(msg) } func (k *Keccak256) DoubleHash(msg []byte) []byte { return k.usingKeccak256(k.usingKeccak256(msg)) } func (k *Keccak256) usingKeccak256(data []byte) []byte { d := sha3.NewLegacyKeccak256() d.Write(data) return d.Sum(nil) } ================================================ FILE: packages/common/crypto/hashalgo/sha256.go ================================================ package hashalgo import ( "crypto/hmac" "crypto/sha256" ) type SHA256 struct{} func (s *SHA256) GetHMAC(secret string, message string) ([]byte, error) { mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(message)) return mac.Sum(nil), nil } func (s *SHA256) GetHash(msg []byte) []byte { return s.usingSha256(msg) } func (s *SHA256) DoubleHash(msg []byte) []byte { return s.usingSha256(s.usingSha256(msg)) } func (s *SHA256) usingSha256(data []byte) []byte { out := sha256.Sum256(data) return out[:] } ================================================ FILE: packages/common/crypto/hashalgo/sha3_256.go ================================================ package hashalgo import ( "crypto/hmac" "golang.org/x/crypto/sha3" ) type Sha3256 struct{} func (s *Sha3256) GetHMAC(secret string, message string) ([]byte, error) { mac := hmac.New(sha3.New256, []byte(secret)) mac.Write([]byte(message)) return mac.Sum(nil), nil } func (s *Sha3256) GetHash(msg []byte) []byte { return s.usingSha3(msg) } func (s *Sha3256) DoubleHash(msg []byte) []byte { return s.usingSha3(s.usingSha3(msg)) } func (s *Sha3256) usingSha3(data []byte) []byte { hashed := sha3.Sum256(data) return hashed[:] } ================================================ FILE: packages/common/crypto/hashalgo/sm3.go ================================================ package hashalgo import ( "crypto/hmac" "github.com/tjfoc/gmsm/sm3" ) type SM3 struct{} func (s *SM3) GetHMAC(secret string, message string) ([]byte, error) { mac := hmac.New(sm3.New, []byte(secret)) mac.Write([]byte(message)) return mac.Sum(nil), nil } func (s *SM3) GetHash(msg []byte) []byte { return s.usingSM3(msg) } func (s *SM3) DoubleHash(msg []byte) []byte { return s.usingSM3(s.usingSM3(msg)) } func (s *SM3) usingSM3(data []byte) []byte { return sm3.Sm3Sum(data) } ================================================ FILE: packages/common/crypto/provider.go ================================================ package crypto type AsymProvider interface { GenKeyPair() ([]byte, []byte, error) Sign(privateKey, hash []byte) ([]byte, error) // Verify checks if forSign has been signed with corresponding to public the private key Verify(public, hash, sign []byte) (bool, error) PrivateToPublic(key []byte) ([]byte, error) } type HashProvider interface { // GetHMAC returns HMAC hash GetHMAC(secret string, msg string) ([]byte, error) // GetHash returns hash of passed bytes GetHash(msg []byte) []byte // DoubleHash returns double hash of passed bytes DoubleHash(msg []byte) []byte } ================================================ FILE: packages/common/crypto/random.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package crypto import ( "math/rand" "time" ) // RandSeq is returning random string func RandSeq(n int) string { var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") rand.Seed(time.Now().UnixNano()) b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } ================================================ FILE: packages/common/crypto/symalgo/aes/aes.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package aes import ( "bytes" "crypto/aes" "crypto/cipher" crand "crypto/rand" "fmt" "github.com/IBAX-io/go-ibax/packages/consts" ) // Encrypt is encrypting func Encrypt(msg []byte, key []byte, iv []byte) ([]byte, error) { return encryptCBC(msg, key, iv) } // Decrypt is decrypting func Decrypt(msg []byte, key []byte, iv []byte) ([]byte, error) { return decryptCBC(msg, key, iv) } // SharedEncrypt creates a shared key and encrypts text. The first 32 characters are the created public key. // The cipher text can be only decrypted with the original private key. //func SharedEncrypt(public, text []byte) ([]byte, error) { // priv, pub, err := genBytesKeys() // if err != nil { // return nil, err // } // shared, err := getSharedKey(priv, public) // if err != nil { // return nil, err // } // val, err := Encrypt(shared, text, pub) // return val, err //} // CBCEncrypt encrypts the text by using the key parameter. It uses CBC mode of AES. func encryptCBC(text, key, iv []byte) ([]byte, error) { if iv == nil { iv = make([]byte, consts.BlockSize) if _, err := crand.Read(iv); err != nil { return nil, err } } else if len(iv) < consts.BlockSize { return nil, fmt.Errorf(`wrong size of iv %d`, len(iv)) } else if len(iv) > consts.BlockSize { iv = iv[:consts.BlockSize] } block, err := aes.NewCipher(key) if err != nil { return nil, err } plaintext := pKCS7Padding(text, consts.BlockSize) mode := cipher.NewCBCEncrypter(block, iv) encrypted := make([]byte, len(plaintext)) mode.CryptBlocks(encrypted, plaintext) return append(iv, encrypted...), nil } // CBCDecrypt decrypts the text by using key. It uses CBC mode of AES. func decryptCBC(ciphertext, key, iv []byte) ([]byte, error) { if iv == nil { iv = ciphertext[:consts.BlockSize] ciphertext = ciphertext[consts.BlockSize:] } if len(ciphertext) < consts.BlockSize || len(ciphertext)%consts.BlockSize != 0 { return nil, fmt.Errorf(`wrong size of cipher %d`, len(ciphertext)) } block, err := aes.NewCipher(key) if err != nil { return nil, err } ret := make([]byte, len(ciphertext)) cipher.NewCBCDecrypter(block, iv[:consts.BlockSize]).CryptBlocks(ret, ciphertext) if ret, err = pKCS7UnPadding(ret); err != nil { return nil, err } return ret, nil } // pKCS7Padding realizes PKCS#7 encoding which is described in RFC 5652. func pKCS7Padding(src []byte, blockSize int) []byte { padding := blockSize - len(src)%blockSize return append(src, bytes.Repeat([]byte{byte(padding)}, padding)...) } // pKCS7UnPadding realizes PKCS#7 decoding. func pKCS7UnPadding(src []byte) ([]byte, error) { length := len(src) padLength := int(src[length-1]) for i := length - padLength; i < length; i++ { if int(src[i]) != padLength { return nil, fmt.Errorf(`incorrect input of PKCS7UnPadding`) } } return src[:length-int(src[length-1])], nil } func PKCS5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } func PKCS5UnPadding(origData []byte) []byte { length := len(origData) unpadding := int(origData[length-1]) return origData[:(length - unpadding)] } func AesEncrypt(origData, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() origData = PKCS5Padding(origData, blockSize) blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) crypted := make([]byte, len(origData)) blockMode.CryptBlocks(crypted, origData) return crypted, nil } func AesDecrypt(crypted, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) origData := make([]byte, len(crypted)) blockMode.CryptBlocks(origData, crypted) origData = PKCS5UnPadding(origData) return origData, nil } ================================================ FILE: packages/common/crypto/symalgo/aes/aes_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package aes import ( "encoding/base64" "fmt" "testing" ) func TestAesEncryptAndDecrypt(t *testing.T) { var aeskey = []byte("123456789012345612345678") // AES-128(16bytes) AES-256(32bytes) pass := []byte("This is my private data!") fmt.Printf("password:%v\n", string(aeskey)) fmt.Printf("src data:%v\n", string(pass)) xpass, err := AesEncrypt(pass, aeskey) if err != nil { fmt.Println(err) return } pass64 := base64.StdEncoding.EncodeToString(xpass) fmt.Printf("encode:%v\n", pass64) bytesPass, err := base64.StdEncoding.DecodeString(pass64) if err != nil { fmt.Println(err) return } tpass, err := AesDecrypt(bytesPass, aeskey) if err != nil { fmt.Println(err) return } fmt.Printf("aesdecrypt:%s\n", tpass) } ================================================ FILE: packages/common/crypto/x509/cert.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package x509 import ( "crypto/x509" "encoding/pem" "errors" ) var ( errParseCert = errors.New("Failed to parse certificate") errParseRootCert = errors.New("Failed to parse root certificate") ) type Cert struct { cert *x509.Certificate } func (c *Cert) Validate(pem []byte) error { roots := x509.NewCertPool() if ok := roots.AppendCertsFromPEM(pem); !ok { return errParseRootCert } if _, err := c.cert.Verify(x509.VerifyOptions{Roots: roots}); err != nil { return err } return nil } func (c *Cert) EqualBytes(bs ...[]byte) bool { for _, b := range bs { other, err := parseCert(b) if err != nil { return false } if c.cert.Equal(other) { return true } } return false } func parseCert(b []byte) (*x509.Certificate, error) { block, _ := pem.Decode(b) if block == nil { return nil, errParseCert } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, err } return cert, nil } func ParseCert(b []byte) (c *Cert, err error) { cert, err := parseCert(b) if err != nil { return nil, err } return &Cert{cert}, nil } ================================================ FILE: packages/common/log/filename_hook.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package log import ( "path" "runtime" "strings" "time" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/sirupsen/logrus" ) // ContextHook storing nothing but behavior type ContextHook struct{} // Levels returns all log levels func (hook ContextHook) Levels() []logrus.Level { return logrus.AllLevels } // Fire the log entry func (hook ContextHook) Fire(entry *logrus.Entry) error { var pc []uintptr if _, skip := entry.Data["nocontext"]; skip { delete(entry.Data, "nocontext") return nil } if conf.Config.Log.LogLevel == "DEBUG" { pc = make([]uintptr, 15, 15) } else { pc = make([]uintptr, 4, 4) } cnt := runtime.Callers(6, pc) count := 0 for i := 0; i < cnt; i++ { fu := runtime.FuncForPC(pc[i] - 1) name := fu.Name() if !strings.Contains(name, "github.com/sirupsen/logrus") { file, line := fu.FileLine(pc[i] - 1) if count == 0 { entry.Data["file"] = path.Base(file) entry.Data["func"] = path.Base(name) entry.Data["line"] = line entry.Data["time"] = time.Now().Format(time.RFC3339) if conf.Config.Log.LogLevel != "DEBUG" { break } } if count >= 1 { if count == 1 { entry.Data["from"] = []string{} } entry.Data["from"] = append(entry.Data["from"].([]string), path.Base(name)) } count += 1 } } return nil } ================================================ FILE: packages/common/log/hex_hook.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package log import ( "encoding/hex" "github.com/sirupsen/logrus" ) type HexHook struct{} // Levels returns all log levels func (hook HexHook) Levels() []logrus.Level { return logrus.AllLevels } // Fire the log entry func (hook HexHook) Fire(entry *logrus.Entry) error { for i := range entry.Data { if b, ok := entry.Data[i].([]byte); ok { entry.Data[i] = hex.EncodeToString(b) } } return nil } ================================================ FILE: packages/common/log/syslog_hook.go ================================================ //go:build !windows && !nacl && !plan9 /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package log import ( "log/syslog" lSyslog "github.com/sirupsen/logrus/hooks/syslog" ) var SyslogFacilityPriority map[string]syslog.Priority func init() { SyslogFacilityPriority = map[string]syslog.Priority{ "kern": syslog.LOG_KERN, "user": syslog.LOG_USER, "mail": syslog.LOG_MAIL, "daemon": syslog.LOG_DAEMON, "auth": syslog.LOG_AUTH, "syslog": syslog.LOG_SYSLOG, "lpr": syslog.LOG_LPR, "news": syslog.LOG_NEWS, "uucp": syslog.LOG_UUCP, "cron": syslog.LOG_CRON, "authpriv": syslog.LOG_AUTHPRIV, "ftp": syslog.LOG_FTP, "local0": syslog.LOG_LOCAL0, "local1": syslog.LOG_LOCAL1, "local2": syslog.LOG_LOCAL2, "local3": syslog.LOG_LOCAL3, "local4": syslog.LOG_LOCAL4, "local5": syslog.LOG_LOCAL5, "local6": syslog.LOG_LOCAL6, "local7": syslog.LOG_LOCAL7, } } func NewSyslogHook(priority string, tag string) (*lSyslog.SyslogHook, error) { return lSyslog.NewSyslogHook("", "", SyslogFacilityPriority[priority], tag) } ================================================ FILE: packages/common/log/syslog_hook_windows.go ================================================ //go:build windows /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package log import ( "github.com/sirupsen/logrus" ) // SyslogHook to send logs via syslog. type SyslogHook struct { SyslogNetwork string SyslogRaddr string } func NewSyslogHook(appName, facility string) (*SyslogHook, error) { return &SyslogHook{"", "localhost"}, nil } func (hook *SyslogHook) Fire(entry *logrus.Entry) error { return nil } func (hook *SyslogHook) Levels() []logrus.Level { return logrus.AllLevels } ================================================ FILE: packages/common/random/rand.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package random import ( "math/rand" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" ) type Rand struct { src *rand.Rand } func (r *Rand) BytesSeed(b []byte) *rand.Rand { seed := crypto.CalcChecksum(b) r.src.Seed(int64(seed)) return r.src } func NewRand(seed int64) *Rand { return &Rand{ src: rand.New(rand.NewSource(seed)), } } func RandInt(min, max int) int { if min >= max || min == 0 || max == 0 { return max } return rand.Intn(max-min) + min } const ( KC_RAND_KIND_NUM = 0 // number KC_RAND_KIND_LOWER = 1 // KC_RAND_KIND_UPPER = 2 // KC_RAND_KIND_ALL = 3 // ) // func Krand(size int64, kind int) []byte { ikind, kinds, result := kind, [][]int{{10, 48}, {26, 97}, {26, 65}}, make([]byte, size) is_all := kind > 2 || kind < 0 rand.Seed(time.Now().UnixNano()) for i := 0; i < int(size); i++ { if is_all { // random ikind ikind = rand.Intn(3) } scope, base := kinds[ikind][0], kinds[ikind][1] result[i] = uint8(base + rand.Intn(scope)) } return result } // func RandNumber(size int64) string { result := Krand(size, KC_RAND_KIND_ALL) return string(result[:]) } ================================================ FILE: packages/common/random/rand_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package random import ( "testing" "github.com/stretchr/testify/assert" ) func TestRand(t *testing.T) { cases := [][]int64{ {3434102771992637744, 1523931518789473682}, {3434102771992637744, 1523931518789473682}, } rand := NewRand(0) for _, values := range cases { r := rand.BytesSeed([]byte("reset")) for _, v := range values { assert.Equal(t, v, r.Int63()) } } } ================================================ FILE: packages/common/size.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package common import ( "fmt" ) // StorageSize is a wrapper around a float value that supports user friendly // formatting. type StorageSize float64 // String implements the stringer interface. func (s StorageSize) String() string { if s > 1099511627776 { return fmt.Sprintf("%.2f TiB", s/1099511627776) } else if s > 1073741824 { return fmt.Sprintf("%.2f GiB", s/1073741824) } else if s > 1048576 { return fmt.Sprintf("%.2f MiB", s/1048576) } else if s > 1024 { return fmt.Sprintf("%.2f KiB", s/1024) } else { return fmt.Sprintf("%.2f B", s) } } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (s StorageSize) TerminalString() string { if s > 1099511627776 { return fmt.Sprintf("%.2fTiB", s/1099511627776) } else if s > 1073741824 { return fmt.Sprintf("%.2fGiB", s/1073741824) } else if s > 1048576 { return fmt.Sprintf("%.2fMiB", s/1048576) } else if s > 1024 { return fmt.Sprintf("%.2fKiB", s/1024) } else { return fmt.Sprintf("%.2fB", s) } } ================================================ FILE: packages/conf/conf.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package conf import ( "fmt" "os" "path/filepath" "strconv" "github.com/BurntSushi/toml" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) // Config global parameters var Config GlobalConfig // Str converts HostPort pair to string format func (h HostPort) Str() string { return fmt.Sprintf("%s:%d", h.Host, h.Port) } // GetPidPath returns path to pid file func (c *GlobalConfig) GetPidPath() string { return c.DirPathConf.PidFilePath } // LoadConfig from configFile // the function has side effect updating global var Config func LoadConfig(path string) error { err := LoadConfigToVar(path, &Config) if err != nil { log.WithError(err).Fatal("Loading config") } log.WithFields(log.Fields{"path": path}).Info("Loading config") registerCrypto(Config.CryptoSettings) return nil } func LoadConfigToVar(path string, v *GlobalConfig) error { _, err := os.Stat(path) if os.IsNotExist(err) { return errors.Errorf("Unable to load config file %s", path) } viper.SetConfigFile(path) err = viper.ReadInConfig() if err != nil { return errors.Wrapf(err, "reading config") } err = viper.Unmarshal(v) if err != nil { return errors.Wrapf(err, "marshalling config to global struct variable") } return nil } // GetConfigFromPath read config from path and returns GlobalConfig struct func GetConfigFromPath(path string) (*GlobalConfig, error) { log.WithFields(log.Fields{"path": path}).Info("Loading clb config") _, err := os.Stat(path) if os.IsNotExist(err) { return nil, errors.Errorf("Unable to load config file %s", path) } viper.SetConfigFile(path) err = viper.ReadInConfig() if err != nil { return nil, errors.Wrapf(err, "reading config") } c := &GlobalConfig{} err = viper.Unmarshal(c) if err != nil { return c, errors.Wrapf(err, "marshalling config to global struct variable") } return c, nil } // SaveConfig save global parameters to configFile func SaveConfig(path string) error { dir := filepath.Dir(path) if _, err := os.Stat(dir); os.IsNotExist(err) { err := os.Mkdir(dir, 0775) if err != nil { return errors.Wrapf(err, "creating dir %s", dir) } } cf, err := os.Create(path) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("Create config file failed") return err } defer cf.Close() err = toml.NewEncoder(cf).Encode(Config) if err != nil { return err } return nil } // FillRuntimePaths fills paths from runtime parameters func FillRuntimePaths() error { if Config.DirPathConf.DataDir == "" { //cwd, err := os.Getwd() //if err != nil { // return errors.Wrapf(err, "getting current wd") //} //Config.DataDir = filepath.Join(cwd, consts.DefaultWorkdirName) Config.DirPathConf.DataDir = filepath.Join(consts.DefaultWorkdirName) } if Config.DirPathConf.KeysDir == "" { Config.DirPathConf.KeysDir = Config.DirPathConf.DataDir } if Config.DirPathConf.TempDir == "" { Config.DirPathConf.TempDir = filepath.Join(os.TempDir(), consts.DefaultTempDirName) } if Config.DirPathConf.FirstBlockPath == "" { Config.DirPathConf.FirstBlockPath = filepath.Join(Config.DirPathConf.DataDir, consts.FirstBlockFilename) } if Config.DirPathConf.PidFilePath == "" { Config.DirPathConf.PidFilePath = filepath.Join(Config.DirPathConf.DataDir, consts.DefaultPidFilename) } if Config.DirPathConf.LockFilePath == "" { Config.DirPathConf.LockFilePath = filepath.Join(Config.DirPathConf.DataDir, consts.DefaultLockFilename) } return nil } // FillRuntimeKey fills parameters of keys from runtime parameters func FillRuntimeKey() error { keyIDFileName := filepath.Join(Config.DirPathConf.KeysDir, consts.KeyIDFilename) keyIDBytes, err := os.ReadFile(keyIDFileName) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err, "path": keyIDFileName}).Error("reading KeyID file") return err } Config.KeyID, err = strconv.ParseInt(string(keyIDBytes), 10, 64) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err, "value": string(keyIDBytes)}).Error("converting keyID to int") return errors.New("converting keyID to int") } return nil } // GetNodesAddr returns address of nodes func GetNodesAddr() []string { return Config.BootNodes.NodesAddr[:] } func registerCrypto(c CryptoSettings) { crypto.InitAsymAlgo(c.Cryptoer) crypto.InitHashAlgo(c.Hasher) } ================================================ FILE: packages/conf/runmode.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package conf type RunMode string const ( // running mode node RunMode = "NONE" clbMaster RunMode = "CLBMaster" clb RunMode = "CLB" subNode RunMode = "SubNode" ) // IsCLBMaster returns true if mode equal clbMaster func (rm RunMode) IsCLBMaster() bool { return rm == clbMaster } // IsCLB returns true if mode equal clb func (rm RunMode) IsCLB() bool { return rm == clb } // IsNode returns true if mode not equal to any CLB func (rm RunMode) IsNode() bool { return rm == node } // IsSupportingCLB returns true if mode support clb func (rm RunMode) IsSupportingCLB() bool { return rm.IsCLB() || rm.IsCLBMaster() } func (rm RunMode) IsSubNode() bool { return rm == subNode } // IsCLB check running mode func (c GlobalConfig) IsCLB() bool { return RunMode(c.LocalConf.RunNodeMode).IsCLB() } // IsCLBMaster check running mode func (c GlobalConfig) IsCLBMaster() bool { return RunMode(c.LocalConf.RunNodeMode).IsCLBMaster() } // IsSupportingCLB check running mode func (c GlobalConfig) IsSupportingCLB() bool { return RunMode(c.LocalConf.RunNodeMode).IsSupportingCLB() } // IsNode check running mode func (c GlobalConfig) IsNode() bool { return RunMode(c.LocalConf.RunNodeMode).IsNode() } // IsSubNode check running mode func (c GlobalConfig) IsSubNode() bool { return RunMode(c.LocalConf.RunNodeMode).IsSubNode() } ================================================ FILE: packages/conf/syspar/honornode.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package syspar import ( "encoding/json" "errors" "fmt" "net/url" "reflect" "strconv" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" log "github.com/sirupsen/logrus" ) const publicKeyLength = 64 var ( errHonorNodeInvalidValues = errors.New("invalid values of the honor_nodes parameter") errHonorNodeDuplicatePublicKey = errors.New("duplicate publicKey values of the honor_nodes parameter") errHonorNodeDuplicateAPIAddress = errors.New("duplicate api address values of the honor_nodes parameter") errHonorNodeDuplicateTCPAddress = errors.New("duplicate tcp address values of the honor_nodes parameter") ) type honorNodeJSON struct { TCPAddress string `json:"tcp_address"` APIAddress string `json:"api_address"` PublicKey string `json:"public_key"` UnbanTime json.Number `json:"unban_time,er"` Stopped bool `json:"stopped"` } // HonorNode is storing honor node data type HonorNode struct { TCPAddress string APIAddress string PublicKey []byte UnbanTime time.Time Stopped bool } // UnmarshalJSON is custom json unmarshaller func (fn *HonorNode) UnmarshalJSON(b []byte) (err error) { data := honorNodeJSON{} if err = json.Unmarshal(b, &data); err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err, "value": string(b)}).Error("Unmarshalling honor nodes to json") return err } fn.TCPAddress = data.TCPAddress fn.APIAddress = data.APIAddress fn.Stopped = data.Stopped if fn.PublicKey, err = crypto.HexToPub(data.PublicKey); err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err, "value": data.PublicKey}).Error("converting honor nodes public key from hex") return err } fn.UnbanTime = time.Unix(converter.StrToInt64(data.UnbanTime.String()), 0) if err = fn.Validate(); err != nil { return err } return nil } func (fn *HonorNode) MarshalJSON() ([]byte, error) { jfn := honorNodeJSON{ TCPAddress: fn.TCPAddress, APIAddress: fn.APIAddress, PublicKey: crypto.PubToHex(fn.PublicKey), UnbanTime: json.Number(strconv.FormatInt(fn.UnbanTime.Unix(), 10)), } data, err := json.Marshal(jfn) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("Marshalling honor nodes to json") return nil, err } return data, nil } // ValidateURL returns error if the URL is invalid func validateURL(rawurl string) error { u, err := url.ParseRequestURI(rawurl) if err != nil { return err } if len(u.Scheme) == 0 { return fmt.Errorf("invalid scheme: %s", rawurl) } if len(u.Host) == 0 { return fmt.Errorf("invalid host: %s", rawurl) } return nil } // Validate checks values func (fn *HonorNode) Validate() error { if len(fn.PublicKey) != publicKeyLength || len(fn.TCPAddress) == 0 { return errHonorNodeInvalidValues } if err := validateURL(fn.APIAddress); err != nil { return err } return nil } func DuplicateHonorNode(fn []*HonorNode) error { n := len(fn) var dup error for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { if j > 0 && reflect.DeepEqual(fn[i].PublicKey, fn[j].PublicKey) { dup = errHonorNodeDuplicatePublicKey break } if j > 0 && reflect.DeepEqual(fn[i].APIAddress, fn[j].APIAddress) { dup = errHonorNodeDuplicateAPIAddress break } if j > 0 && reflect.DeepEqual(fn[i].TCPAddress, fn[j].TCPAddress) { dup = errHonorNodeDuplicateTCPAddress break } } if vali := fn[i].Validate(); vali != nil { return vali } } return dup } ================================================ FILE: packages/conf/syspar/honornode_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package syspar import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestHonorNode(t *testing.T) { cases := []struct { value, err string formattingErr bool }{ {value: `[{"tcp_address":"127.0.0.1", "api_address":"https://127.0.0.1", "key_id":"100", "public_key":"c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0", "unban_time": 111111}]`, err: ``}, {value: `[{"tcp_address":"", "api_address":"https://127.0.0.1", "key_id":"100", "public_key":"c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0", "unban_time": 111111}]`, err: `Invalid values of the honor_nodes parameter`}, {value: `[{"tcp_address":"127.0.0.1", "api_address":"127.0.0.1", "key_id":"100", "public_key":"c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0", "unban_time": 111111}]`, err: `parse 127.0.0.1: invalid URI for request`}, {value: `[{"tcp_address":"127.0.0.1", "api_address":"https://", "key_id":"100", "public_key":"c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0", "unban_time": 111111}]`, err: `Invalid host: https://`}, {value: `[{"tcp_address":"127.0.0.1", "api_address":"https://127.0.0.1", "key_id":"0", "public_key":"c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d0", "unban_time": 111111}]`, err: `Invalid values of the honor_nodes parameter`}, {value: `[{"tcp_address":"127.0.0.1", "api_address":"https://127.0.0.1", "key_id":"100", "public_key":"c1a9e7b2fb8cea2a272e183c3e27e2d59a3ebe613f51873a46885c9201160bd263ef43b583b631edd1284ab42483712fd2ccc40864fe9368115ceeee47a7c7d00000000000", "unban_time": 111111}]`, err: `Invalid values of the honor_nodes parameter`}, {value: `[{}}]`, err: `invalid character '}' after array element`, formattingErr: true}, } for _, v := range cases { // Testing Unmarshalling string -> struct var fs []*HonorNode err := json.Unmarshal([]byte(v.value), &fs) if len(v.err) == 0 { assert.NoError(t, err) } else { assert.EqualError(t, err, v.err) } // Testing Marshalling struct -> string blah, err := json.Marshal(fs) require.NoError(t, err) // Testing Unmarshaling string (from struct) -> struct var unfs []HonorNode err = json.Unmarshal(blah, &unfs) if !v.formattingErr && len(v.err) != 0 { assert.EqualError(t, err, v.err) } } } ================================================ FILE: packages/conf/syspar/syspar.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package syspar import ( "bytes" "encoding/hex" "encoding/json" "errors" "fmt" "os" "path/filepath" "sync" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) const ( // NumberNodes is the number of nodes NumberNodes = `number_of_nodes` // FuelRate is the rate FuelRate = `fuel_rate` // HonorNodes is the list of nodes HonorNodes = `honor_nodes` // GapsBetweenBlocks is the time between blocks GapsBetweenBlocks = `gap_between_blocks` // MaxBlockSize is the maximum size of the block MaxBlockSize = `max_block_size` // MaxTxSize is the maximum size of the transaction MaxTxSize = `max_tx_size` // MaxForsignSize is the maximum size of the forsign of transaction MaxForsignSize = `max_forsign_size` // MaxBlockFuel is the maximum fuel of the block MaxBlockFuel = `max_fuel_block` // MaxTxFuel is the maximum fuel of the transaction MaxTxFuel = `max_fuel_tx` // MaxTxCount is the maximum count of the transactions MaxTxCount = `max_tx_block` // MaxBlockGenerationTime is the time limit for block generation (in ms) MaxBlockGenerationTime = `max_block_generation_time` // MaxColumns is the maximum columns in tables MaxColumns = `max_columns` // MaxIndexes is the maximum indexes in tables MaxIndexes = `max_indexes` // MaxBlockUserTx is the maximum number of user's transactions in one block MaxBlockUserTx = `max_tx_block_per_user` // SizeFuel is the fuel cost of 1024 bytes of the transaction data SizeFuel = `price_tx_data` // TaxesWallet is the address for taxess TaxesWallet = `taxes_wallet` // RbBlocks1 rollback from queue_bocks RbBlocks1 = `rollback_blocks` // BlockReward value of reward, which is chrged on block generation BlockReward = "block_reward" // IncorrectBlocksPerDay is value of incorrect blocks per day before global ban IncorrectBlocksPerDay = `incorrect_blocks_per_day` // NodeBanTime is value of ban time for bad nodes (in ms) NodeBanTime = `node_ban_time` // LocalNodeBanTime is value of local ban time for bad nodes (in ms) LocalNodeBanTime = `local_node_ban_time` // TaxesSize is the value of the taxes TaxesSize = `taxes_size` // PriceTxSize is the size of a user's resource in the database PriceTxSize = `price_tx_size` // PriceCreateRate is new element rate, include table,contract,column,ecosystem,page,menu PriceCreateRate = `price_create_rate` // Test equals true or 1 if we have a test blockchain Test = `test` // PrivateBlockchain is value defining blockchain mode PrivateBlockchain = `private_blockchain` // CostDefault is the default maximum cost of F CostDefault = int64(20000000) PriceExec = "price_exec_" AccessExec = "access_exec_" PriceCreateExec = "price_create_exec_" PayFreeContract = "pay_free_contract" ) var ( cache = map[string]string{} nodes = make(map[string]*HonorNode) nodesByPosition = make([]*HonorNode, 0) fuels = make(map[int64]string) wallets = make(map[int64]string) mutex = &sync.RWMutex{} firstBlockData *types.FirstBlock firstBlockTimestamp int64 errFirstBlockData = errors.New("failed to get data of the first block") errNodeDisabled = errors.New("node is disabled") nodePubKey []byte nodePrivKey []byte cacheTableColType = make([]map[string]string, 0) runModel uint8 ) func ReadNodeKeys() (err error) { var ( nprivkey []byte ) nprivkey, err = os.ReadFile(filepath.Join(conf.Config.DirPathConf.KeysDir, consts.NodePrivateKeyFilename)) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading node private key from file") return } nodePrivKey, err = hex.DecodeString(string(nprivkey)) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("decoding node private key from hex") return } nodePubKey, err = crypto.PrivateToPublic(nodePrivKey) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("converting node private key to public") return } return } func GetSysParCache() map[string]string { var cp = make(map[string]string, len(cache)) for k, v := range cache { cp[k] = v } return cp } func GetNodePubKey() []byte { return nodePubKey } func GetNodePrivKey() []byte { return nodePrivKey } // SysUpdate reloads/updates values of platform parameters func SysUpdate(dbTx *sqldb.DbTransaction) error { var err error platformParameters, err := sqldb.GetAllPlatformParameters(dbTx, nil, nil, nil) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all platform parameters") return err } mutex.Lock() defer mutex.Unlock() for _, param := range platformParameters { cache[param.Name] = param.Value } if len(cache[HonorNodes]) > 0 { if err = updateNodes(); err != nil { return err } } getParams := func(name string) (map[int64]string, error) { res := make(map[int64]string) if len(cache[name]) > 0 { ifuels := make([][]string, 0) err = json.Unmarshal([]byte(cache[name]), &ifuels) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling params from json") return res, err } for _, item := range ifuels { if len(item) < 2 { continue } res[converter.StrToInt64(item[0])] = item[1] } } return res, nil } fuels, err = getParams(FuelRate) wallets, err = getParams(TaxesWallet) return err } func updateNodes() (err error) { items := make([]*HonorNode, 0) if len(cache[HonorNodes]) > 0 { err = json.Unmarshal([]byte(cache[HonorNodes]), &items) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err, "v": cache[HonorNodes]}).Error("unmarshalling honor nodes from json") return err } } if len(items) > 1 { if err = DuplicateHonorNode(items); err != nil { return err } } nodes = make(map[string]*HonorNode) nodesByPosition = []*HonorNode{} for i := 0; i < len(items); i++ { nodes[hex.EncodeToString(items[i].PublicKey)] = items[i] if !items[i].Stopped { nodesByPosition = append(nodesByPosition, items[i]) } } return nil } // addHonorNodeKeys adds node by keys to list of nodes func addHonorNodeKeys(publicKey []byte) { nodesByPosition = append(nodesByPosition, &HonorNode{ PublicKey: publicKey, }) } func GetNodes() []HonorNode { mutex.RLock() defer mutex.RUnlock() result := make([]HonorNode, 0, len(nodesByPosition)) for _, node := range nodesByPosition { result = append(result, *node) } return result } func GetThisNodePosition() (int64, error) { return GetNodePositionByPublicKey(GetNodePubKey()) } func GetHonorNodeType() bool { d, err := GetNodePositionByPublicKey(GetNodePubKey()) if err == nil { return true } if d == 0 && err != nil { return false } return false } // GetNodePositionByPublicKey is returning node position by key id func GetNodePositionByPublicKey(publicKey []byte) (int64, error) { mutex.RLock() defer mutex.RUnlock() for i, item := range nodesByPosition { if item.Stopped { if bytes.Equal(item.PublicKey, publicKey) { return 0, errNodeDisabled } continue } if bytes.Equal(item.PublicKey, publicKey) { return int64(i), nil } } return 0, fmt.Errorf("incorrect public key") } // GetCountOfActiveNodes is count of nodes with stopped = false func GetCountOfActiveNodes() int64 { return int64(len(nodesByPosition)) } // GetNumberOfNodes is count number of nodes func GetNumberOfNodes() int64 { return int64(len(nodesByPosition)) } func GetNumberOfNodesFromDB(transaction *sqldb.DbTransaction) int64 { var bk sqldb.BlockChain f, err := bk.GetMaxBlock() if err != nil || !f { return 1 } if bk.ConsensusMode == consts.CandidateNodeMode { var candidate sqldb.CandidateNode var total int64 pledgeAmount, err := sqldb.GetPledgeAmount() if err != nil { return 1 } err = sqldb.GetDB(transaction).Table(candidate.TableName()).Where("deleted = 0 AND earnest_total >= ?", pledgeAmount).Limit(SysInt(NumberNodes)).Count(&total).Error if err != nil { return 1 } if total < 1 { total = 1 } return total } sp := &sqldb.PlatformParameter{} sp.GetTransaction(transaction, HonorNodes) var honorNodes []map[string]any if len(sp.Value) > 0 { if err := json.Unmarshal([]byte(sp.Value), &honorNodes); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err, "value": sp.Value}).Error("unmarshalling honor nodes from JSON") } } if len(honorNodes) == 0 { return 1 } return int64(len(honorNodes)) } // GetNodeByPosition is retrieving node by position func GetNodeByPosition(position int64) (*HonorNode, error) { mutex.RLock() defer mutex.RUnlock() if int64(len(nodesByPosition)) <= position { return nil, fmt.Errorf("incorrect position") } return nodesByPosition[position], nil } func GetNodeByHost(host string) (HonorNode, error) { mutex.RLock() defer mutex.RUnlock() for _, n := range nodes { if n.TCPAddress == host { return *n, nil } } return HonorNode{}, fmt.Errorf("incorrect host") } // GetNodeHostByPosition is retrieving node host by position func GetNodeHostByPosition(position int64) (string, error) { mutex.RLock() defer mutex.RUnlock() if IsCandidateNodeMode() { candidateNode := &sqldb.CandidateNode{} err := candidateNode.GetCandidateNodeById(position) if err != nil { return "", err } nodePublicKey, err := hex.DecodeString(candidateNode.NodePubKey) if err != nil { return "", err } nodePublicKey = crypto.CutPub(nodePublicKey) return candidateNode.TcpAddress, nil } nodeData, err := GetNodeByPosition(position) if err != nil { return "", err } return nodeData.TCPAddress, nil } // GetNodePublicKeyByPosition is retrieving node public key by position func GetNodePublicKeyByPosition(position int64) ([]byte, error) { mutex.RLock() defer mutex.RUnlock() if IsCandidateNodeMode() { candidateNode := &sqldb.CandidateNode{} err := candidateNode.GetCandidateNodeById(position) if err != nil { return nil, err } nodePublicKey, err := hex.DecodeString(candidateNode.NodePubKey) if err != nil { return nil, err } nodePublicKey = crypto.CutPub(nodePublicKey) return nodePublicKey, nil } if int64(len(nodesByPosition)) <= position { return nil, fmt.Errorf("incorrect position") } nodeData, err := GetNodeByPosition(position) if err != nil { return nil, err } return nodeData.PublicKey, nil } // SysInt64 is converting sys string to int64 func SysInt64(name string) int64 { return converter.StrToInt64(SysString(name)) } // SysInt is converting sys string to int func SysInt(name string) int { return converter.StrToInt(SysString(name)) } // GetSizeFuel is returns fuel size func GetSizeFuel() int64 { return SysInt64(SizeFuel) } // GetFuelRate is returning fuel rate func GetFuelRate(ecosystem int64) string { mutex.RLock() defer mutex.RUnlock() if ret, ok := fuels[ecosystem]; ok { return ret } return `` } // HasFuelRate is returns fuels exist func HasFuelRate(ecosystem int64) (string, bool) { mutex.RLock() defer mutex.RUnlock() if ret, ok := fuels[ecosystem]; ok { return ret, ok } return "", false } // GetTaxesWallet is returns taxes wallet func GetTaxesWallet(ecosystem int64) string { mutex.RLock() defer mutex.RUnlock() if ret, ok := wallets[ecosystem]; ok { return ret } return `` } // HasTaxesWallet is returns taxes exist func HasTaxesWallet(ecosystem int64) (string, bool) { mutex.RLock() defer mutex.RUnlock() if ret, ok := wallets[ecosystem]; ok { return ret, ok } return "", false } // GetMaxBlockSize is returns max block size func GetMaxBlockSize() int64 { return converter.StrToInt64(SysString(MaxBlockSize)) } // GetMaxBlockFuel is returns max block fuel func GetMaxBlockFuel() int64 { return converter.StrToInt64(SysString(MaxBlockFuel)) } // GetMaxTxFuel is returns max tx fuel func GetMaxTxFuel() int64 { return converter.StrToInt64(SysString(MaxTxFuel)) } // GetMaxBlockGenerationTime is returns max block generation time (in ms) func GetMaxBlockGenerationTime() int64 { return converter.StrToInt64(SysString(MaxBlockGenerationTime)) } // GetGapsBetweenBlocks is returns gaps between blocks func GetGapsBetweenBlocks() int64 { return converter.StrToInt64(SysString(GapsBetweenBlocks)) } // GetMaxBlockTimeDuration return max block time duration func GetMaxBlockTimeDuration() time.Duration { return time.Millisecond*time.Duration(GetMaxBlockGenerationTime()) + time.Second*time.Duration(GetGapsBetweenBlocks()) } // GetMaxTxSize is returns max tx size func GetMaxTxSize() int64 { return converter.StrToInt64(SysString(MaxTxSize)) } // GetMaxTxTextSize is returns max tx text size func GetMaxForsignSize() int64 { return converter.StrToInt64(SysString(MaxForsignSize)) } // GetMaxTxCount is returns max tx count func GetMaxTxCount() int { return converter.StrToInt(SysString(MaxTxCount)) } // GetMaxColumns is returns max columns func GetMaxColumns() int { return converter.StrToInt(SysString(MaxColumns)) } // GetMaxIndexes is returns max indexes func GetMaxIndexes() int { return converter.StrToInt(SysString(MaxIndexes)) } // GetMaxBlockUserTx is returns max tx block user func GetMaxBlockUserTx() int { return converter.StrToInt(SysString(MaxBlockUserTx)) } func IsTestMode() bool { return SysString(Test) == `true` || SysString(Test) == `1` } func GetIncorrectBlocksPerDay() int { return converter.StrToInt(SysString(IncorrectBlocksPerDay)) } func GetNodeBanTime() time.Duration { return time.Millisecond * time.Duration(converter.StrToInt64(SysString(NodeBanTime))) } func GetLocalNodeBanTime() time.Duration { return time.Millisecond * time.Duration(converter.StrToInt64(SysString(LocalNodeBanTime))) } // GetDefaultRemoteHosts returns array of hostnames excluding myself func GetDefaultRemoteHosts() []string { ret := make([]string, 0) mutex.RLock() defer mutex.RUnlock() nodeKey := hex.EncodeToString(GetNodePubKey()) for pubKey, item := range nodes { if pubKey != nodeKey && !item.Stopped { ret = append(ret, item.TCPAddress) } } if len(ret) == 0 && len(conf.Config.BootNodes.NodesAddr) > 0 { ret = append(ret, conf.Config.BootNodes.NodesAddr[0]) } return ret } // GetRemoteHosts returns array of hostnames excluding myself func GetRemoteHosts() []string { ret := make([]string, 0) mutex.RLock() defer mutex.RUnlock() nodeKey := hex.EncodeToString(GetNodePubKey()) for pubKey, item := range nodes { if pubKey != nodeKey && !item.Stopped { ret = append(ret, item.TCPAddress) } } return ret } // SysString returns string value of the system parameter func SysString(name string) string { mutex.RLock() ret := cache[name] mutex.RUnlock() return ret } // GetRbBlocks1 is returns RbBlocks1 func GetRbBlocks1() int64 { return SysInt64(RbBlocks1) } // HasSys returns boolean whether this system parameter exists func HasSys(name string) bool { mutex.RLock() _, ok := cache[name] mutex.RUnlock() return ok } func SetFirstBlockTimestamp(data int64) { mutex.Lock() defer mutex.Unlock() firstBlockTimestamp = data } // SetFirstBlockData sets data of first block to global variable func SetFirstBlockData(data *types.FirstBlock) { mutex.Lock() defer mutex.Unlock() firstBlockData = data // If list of nodes is empty, then used node from the first block if len(nodesByPosition) == 0 { addHonorNodeKeys(firstBlockData.NodePublicKey) nodesByPosition = []*HonorNode{{ PublicKey: firstBlockData.NodePublicKey, Stopped: false, }} } } func GetFirstBlockTimestamp() int64 { mutex.RLock() defer mutex.RUnlock() return firstBlockTimestamp } // GetFirstBlockData gets data of first block from global variable func GetFirstBlockData() (*types.FirstBlock, error) { mutex.RLock() defer mutex.RUnlock() if firstBlockData == nil { return nil, errFirstBlockData } return firstBlockData, nil } // IsPrivateBlockchain returns the value of private_blockchain system parameter or true func IsPrivateBlockchain() bool { par := SysString(PrivateBlockchain) return len(par) > 0 && par != `0` && par != `false` } func GetMaxCost() int64 { cost := GetMaxTxFuel() if cost == 0 { cost = CostDefault } return cost } func GetAccessExec(s string) string { return SysString(AccessExec + s) } func GetPriceExec(s string) (price int64, ok bool) { if ok = HasSys(PriceExec + s); !ok { return } price = SysInt64(PriceExec + s) return } func GetPriceCreateExec(s string) (price int64) { if ok := HasSys(PriceCreateExec + s); !ok { return } price = SysInt64(PriceCreateExec + s) return } // SysTableColType reloads/updates values of all ecosystem table column data type func SysTableColType(dbTx *sqldb.DbTransaction) error { var err error mutex.RLock() defer mutex.RUnlock() cacheTableColType, err = dbTx.GetAllTransaction(` SELECT table_name,column_name,data_type,character_maximum_length FROM information_schema.columns Where table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name ~ '[\d]' AND data_type = 'bytea' ORDER BY ordinal_position ASC;`, -1) if err != nil { return err } return nil } func GetTableColType() []map[string]string { mutex.RLock() defer mutex.RUnlock() return cacheTableColType } func IsByteColumn(table, column string) bool { for _, row := range GetTableColType() { if row["table_name"] == table && row["column_name"] == column { return true } } return false } func SetRunModel(setVal uint8) { runModel = setVal } func IsHonorNodeMode() bool { return runModel == consts.HonorNodeMode } func IsCandidateNodeMode() bool { return runModel == consts.CandidateNodeMode } ================================================ FILE: packages/conf/types.go ================================================ package conf type ( // HostPort endpoint in form "str:int" HostPort struct { Host string // ipaddr, hostname, or "0.0.0.0" Port int // must be in range 1..65535 } // DBConfig database connection parameters DBConfig struct { Name string Host string Port int User string Password string LockTimeout int // lock_timeout in milliseconds IdleInTxTimeout int // postgres parameter idle_in_transaction_session_timeout MaxIdleConns int // sets the maximum number of connections in the idle connection pool MaxOpenConns int // sets the maximum number of open connections to the database } //RedisConfig get redis information from config.yml RedisConfig struct { Enable bool Host string Port int Password string DbName int } // StatsDConfig statd connection parameters StatsDConfig struct { Host string Port int Name string } // CentrifugoConfig connection params CentrifugoConfig struct { Secret string URL string Key string } // Syslog represents parameters of syslog Syslog struct { Facility string Tag string } // LogConfig represents parameters of log LogConfig struct { LogTo string LogLevel string LogFormat string Syslog Syslog } // TokenMovementConfig smtp config for token movement TokenMovementConfig struct { Host string Port int Username string Password string To string From string Subject string } // BanKeyConfig parameters BanKeyConfig struct { BadTime int // control time period in minutes BanTime int // ban time in minutes BadTx int // maximum bad tx during badTime minutes } TLSConfig struct { Enabled bool // TLS is on/off. It is required for https TLSCert string // TLSCert is a filepath of the fullchain of certificate. TLSKey string // TLSKey is a filepath of the private key. } DirectoryConfig struct { DataDir string // application work dir (cwd by default) PidFilePath string LockFilePath string TempDir string // temporary dir KeysDir string // place for private keys files: NodePrivateKey, PrivateKey FirstBlockPath string } BootstrapNodeConfig struct { NodesAddr []string } CryptoSettings struct { Cryptoer string Hasher string } //LocalConfig TODO: uncategorized LocalConfig struct { RunNodeMode string HTTPServerMaxBodySize int64 NetworkID int64 MaxPageGenerationTime int64 // in milliseconds } BlockSyncMethod struct { Method string } // GlobalConfig is storing all startup config as global struct GlobalConfig struct { KeyID int64 `toml:"-"` ConfigPath string `toml:"-"` TestRollBack bool `toml:"-"` FuncBench bool `toml:"-"` LocalConf LocalConfig DirPathConf DirectoryConfig BootNodes BootstrapNodeConfig TLSConf TLSConfig TCPServer HostPort HTTP HostPort JsonRPC struct { Enabled bool Namespace string } DB DBConfig Redis RedisConfig StatsD StatsDConfig Centrifugo CentrifugoConfig Log LogConfig TokenMovement TokenMovementConfig BanKey BanKeyConfig CryptoSettings CryptoSettings BlockSyncMethod BlockSyncMethod } ) ================================================ FILE: packages/consts/conf.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package consts const ( // DefaultConfigFile name of config file (toml format) DefaultConfigFile = "config.toml" // DefaultTempDirName is default name of temporary directory DefaultTempDirName = "ibax-temp" // DefaultWorkdirName name of working directory DefaultWorkdirName = "data" // DefaultPidFilename is default filename of pid file DefaultPidFilename = "go-ibax.pid" // DefaultLockFilename is default filename of lock file DefaultLockFilename = "go-ibax.lock" // FirstBlockFilename name of first block binary file FirstBlockFilename = "1block" // PrivateKeyFilename name of wallet private key file PrivateKeyFilename = "PrivateKey" // PublicKeyFilename name of wallet public key file PublicKeyFilename = "PublicKey" // NodePrivateKeyFilename name of node private key file NodePrivateKeyFilename = "NodePrivateKey" // NodePublicKeyFilename name of node public key file NodePublicKeyFilename = "NodePublicKey" // KeyIDFilename generated KeyID KeyIDFilename = "KeyID" ) ================================================ FILE: packages/consts/consts.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package consts import ( "fmt" "strings" "time" ) // VERSION is current version const VERSION = "1.4.3" const BvRollbackHash = 2 const BvIncludeRollbackHash = 3 // BlockVersion is block version const BlockVersion = BvIncludeRollbackHash // DefaultTcpPort used when port number missed in host addr const DefaultTcpPort = 7078 // FounderAmount is the starting amount of founder const FounderAmount = 5250000 // MoneyDigits is numbers of digits for tokens 1000000000000 const MoneyDigits = 12 // WaitConfirmedNodes is used in confirmations const WaitConfirmedNodes = 10 // MinConfirmedNodes The number of nodes which should have the same block as we have for regarding this block belongs to the major part of DC-net. For get_confirmed_block_id() const MinConfirmedNodes = 0 // MaxTxForw How fast could the time of transaction pass const MaxTxForw = 600 // MaxTxBack transaction may wander in the net for a day and then get into a block const MaxTxBack = 86400 // RoundFix is rounding constant const RoundFix = 0.00000000001 // ReadTimeout is timeout for TCP const ReadTimeout = 20 // WriteTimeout is timeout for TCP const WriteTimeout = 20 // AddressLength is length of address const AddressLength = 20 // PubkeySizeLength is pubkey length const PubkeySizeLength = 64 // PrivkeyLength is privkey length const PrivkeyLength = 32 // BlockSize is size of block const BlockSize = 16 // HashSize is size of hash const HashSize = 32 const AvailableBCGap = 4 const DefaultNodesConnectDelay = 6 const MaxTXAttempt = 10 // ChainSize 1M = 1048576 byte const ChainSize = 1 << 20 // DefaultTokenSymbol define default token symbol const DefaultTokenSymbol = "IBXC" // DefaultTokenName define default token name const DefaultTokenName = "IBAX Coin" // DefaultEcosystemName define default ecosystem name const DefaultEcosystemName = "platform ecosystem" // ApiPath is the beginning of the api url var ApiPath = `/api/v2/` // BuildInfo should be defined through -ldflags var BuildInfo string const ( // RollbackResultFilename rollback result file RollbackResultFilename = "rollback_result" // FromToPerDayLimit day limit token transfer between accounts FromToPerDayLimit = 10000 // TokenMovementQtyPerBlockLimit block limit token transfer TokenMovementQtyPerBlockLimit = 100 // TCPConnTimeout timeout of tcp connection TCPConnTimeout = 5 * time.Second // TxRequestExpire is expiration time for request of transaction TxRequestExpire = 1 * time.Minute // DefaultCLB always is 1 DefaultCLB = 1 // MoneyLength is the maximum number of digits in money value MoneyLength = 30 DefaultTokenEcosystem = 1 // ShiftContractID is the offset of tx identifiers ShiftContractID = 5000 // ContractList is the number of contracts per page on loading ContractList = 200 // Guest key GuestPublic = "ef0ab117793962b7b3ee8d2ae94b58bbd7db1aa856a7dc623fdb28ad530090b0bcf5cb81b4d6912a249f1ab30921f414ad88383208cd8ba26ae2a9c3eb543772" GuestKey = "-110277540701013350" GuestAddress = "1833-6466-5330-0853-8266" // StatusMainPage is a status for Main Page StatusMainPage = `2` NoneCLB = "none" DBFindLimit = 10000 HonorNodeMode = 1 CandidateNodeMode = 2 ) const ( SavePointMarkBlock = "block" SavePointMarkTx = "tx" ) func Version() string { return strings.TrimSpace(strings.Join([]string{VERSION, BuildInfo}, " ")) } func SetSavePointMarkBlock(idTx string) string { return fmt.Sprintf("\"%s-%s\";", SavePointMarkBlock, idTx) } const ( UTXO_Type_First_Block = 1 //Initialize the first block UTXO_Type_Self_UTXO = 11 UTXO_Type_Self_Account = 12 UTXO_Type_Packaging = 20 UTXO_Type_Taxes = 21 UTXO_Type_Output = 22 UTXO_Type_Combustion = 23 UTXO_Type_Transfer = 26 ) ================================================ FILE: packages/consts/log_types.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package consts // LogEventType is storing numeric event type type LogEventType int // Types of log errors const ( NetworkError = "Network" JSONMarshallError = "JSONMarshall" JSONUnmarshallError = "JSONUnmarshall" CommandExecutionError = "CommandExecution" ConversionError = "Conversion" TypeError = "Type" ProtocolError = "Protocol" MarshallingError = "Marshall" UnmarshallingError = "Unmarshall" ParseError = "Parse" IOError = "IO" CryptoError = "Crypto" ContractError = "Contract" DBError = "DB" PanicRecoveredError = "Panic" ConnectionError = "Connection" ConfigError = "Config" VMError = "VM" JustWaiting = "JustWaiting" Ntpdate = "Ntpdate" BlockError = "Block" ParserError = "Parser" ContextError = "Context" SessionError = "Session" RouteError = "Route" NotFound = "NotFound" Found = "Found" EmptyObject = "EmptyObject" InvalidObject = "InvalidObject" DuplicateObject = "DuplicateObject" UnknownObject = "UnknownObject" ParameterExceeded = "ParameterExceeded" DivisionByZero = "DivisionByZero" EvalError = "Eval" JWTError = "JWT" AccessDenied = "AccessDenied" SizeDoesNotMatch = "SizeDoesNotMatch" NoIndex = "NoIndex" NoFunds = "NoFunds" BlockIsFirst = "BlockIsFirst" IncorrectCallingContract = "IncorrectCallingContract" WritingFile = "WritingFile" CentrifugoError = "CentrifugoError" StatsdError = "StatsdError" MigrationError = "MigrationError" AutoupdateError = "AutoupdateError" BCRelevanceError = "BCRelevanceError" BCActualizationError = "BCActualizationError" SchedulerError = "SchedulerError" SyncProcess = "SyncProcess" WrongModeError = "WrongModeError" CLBManagerError = "CLBManagerError" TCPClientError = "TCPClientError" BadTxError = "BadTxError" TimeCalcError = "BlockTimeCounterError" RegisterError = "RegisterError" JsonRpcError = "JsonRpcError" ) ================================================ FILE: packages/consts/used_stop_certs.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package consts // UsedStopNetworkCerts contains a list of certificates that were used to stop the network var UsedStopNetworkCerts = [][]byte{} ================================================ FILE: packages/converter/address.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package converter import ( "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/consts" ) type Hole struct { K int64 S string } const ( BlackHoleAddr = "BlackHole" WhiteHoleAddr = "WhiteHole" ) var ( HoleAddrMap = map[string]Hole{ BlackHoleAddr: {K: 0, S: "0000-0000-0000-0000-0000"}, WhiteHoleAddr: {K: 5555, S: "0000-0000-0000-0000-5555"}, } ) // AddressToID converts the string representation of the wallet number to a numeric func AddressToID(input string) (addr int64) { input = strings.TrimSpace(input) if len(input) < 2 { return 0 } if input[0] == '-' { uaddr, err := strconv.ParseInt(input, 10, 64) if err != nil { } addr = uaddr } else if has4LineContain(input) { addr = StringToAddress(input) } else { uaddr, err := strconv.ParseUint(input, 10, 64) if err != nil { } addr = int64(uaddr) } if IDToAddress(addr) == `invalid` { return 0 } return } // IDToAddress converts the identifier of account to a string of the form xxxx-xxxx-xxxx-xxxx-xxxx. func IDToAddress(id int64) (out string) { out = AddressToString(id) if !IsValidAddress(out) { out = `invalid` } return } // AddressToString converts int64 address to chain address as xxxx-xxxx-xxxx-xxxx-xxxx. func AddressToString(int int64) (str string) { return AddressToStringUint64(uint64(int)) } func AddressToStringUint64(uint uint64) (str string) { num := strconv.FormatUint(uint, 10) val := []byte(strings.Repeat("0", consts.AddressLength-len(num)) + num) for i := 0; i <= 4; i++ { if i == 4 { str += string(val[i*4:]) break } str += string(val[i*4:(i+1)*4]) + `-` } return } // StringToAddress converts string chain address to int64 address. The input address can be a positive or negative // number, or chain address in xxxx-xxxx-xxxx-xxxx-xxxx format. Returns 0 when error occurs. func StringToAddress(str string) (result int64) { var ( err error ret uint64 ) if len(str) == 0 { return 0 } //string of int64 if str[0] == '-' { var id int64 id, err = strconv.ParseInt(str, 10, 64) if err != nil { return 0 } str = strconv.FormatUint(uint64(id), 10) } if len(str) < consts.AddressLength { str = strings.Repeat(`0`, consts.AddressLength-len(str)) + str } val := []byte(strings.Replace(str, `-`, ``, -1)) if len(val) != consts.AddressLength { return 0 } if ret, err = strconv.ParseUint(string(val), 10, 64); err != nil { return 0 } if CheckSum(val[:len(val)-1]) != int(val[len(val)-1]-'0') { return 0 } result = int64(ret) return } // IsValidAddress checks if the specified address is chain address. func IsValidAddress(address string) bool { val := []byte(strings.Replace(address, `-`, ``, -1)) if len(val) != consts.AddressLength { return false } if _, err := strconv.ParseUint(string(val), 10, 64); err != nil { return false } return CheckSum(val[:len(val)-1]) == int(val[len(val)-1]-'0') } // CheckSum calculates the 0-9 check sum of []byte func CheckSum(val []byte) int { var one, two int for i, ch := range val { digit := int(ch - '0') if i&1 == 1 { one += digit } else { two += digit } } checksum := (two + 3*one) % 10 if checksum > 0 { checksum = 10 - checksum } return checksum } func has4LineContain(str string) bool { return strings.Count(str, "-") == 4 } ================================================ FILE: packages/converter/converter.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package converter import ( "bytes" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "math" "reflect" "regexp" "sort" "strconv" "strings" "time" "unicode" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) var ErrSliceSize = errors.New("Slice size larger than buffer size") var FirstEcosystemTables = map[string]bool{ `keys`: true, `menu`: true, `pages`: true, `snippets`: true, `languages`: true, `contracts`: true, `tables`: true, `parameters`: true, `history`: true, `sections`: true, `members`: true, `roles`: true, `roles_participants`: true, `notifications`: true, `applications`: true, `binaries`: true, `buffer_data`: true, `app_params`: true, `views`: true, } func EncodeLenInt64(data *[]byte, x int64) *[]byte { var length int buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(x)) for length = 8; length > 0 && buf[length-1] == 0; length-- { } *data = append(append(*data, byte(length)), buf[:length]...) return data } func EncodeLenInt64InPlace(x int64) []byte { buf := make([]byte, 9) value := buf[1:] binary.LittleEndian.PutUint64(value, uint64(x)) var length byte for length = 8; length > 0 && value[length-1] == 0; length-- { } buf[0] = length return buf[:length+1] } func EncodeLenByte(out *[]byte, buf []byte) *[]byte { *out = append(append(*out, EncodeLength(int64(len(buf)))...), buf...) return out } // EncodeLength encodes int64 number to []byte. If it is less than 128 then it returns []byte{length}. // Otherwise, it returns (0x80 | len of int64) + int64 as BigEndian []byte // // 67 => 0x43 // 1024 => 0x820400 // 1000000 => 0x830f4240 func EncodeLength(length int64) []byte { if length >= 0 && length <= 127 { return []byte{byte(length)} } buf := make([]byte, 9) binary.BigEndian.PutUint64(buf[1:], uint64(length)) i := 1 for ; buf[i] == 0 && i < 8; i++ { } buf[0] = 0x80 | byte(9-i) return append(buf[:1], buf[i:]...) } // DecodeLenInt64 gets int64 from []byte and shift the slice. The []byte should be // encoded with EncodeLengthPlusInt64. func DecodeLenInt64(data *[]byte) (int64, error) { if len(*data) == 0 { return 0, nil } length := int((*data)[0]) + 1 if len(*data) < length { log.WithFields(log.Fields{"data_length": len(*data), "length": length, "type": consts.UnmarshallingError}).Error("length of data is smaller then encoded length") return 0, fmt.Errorf(`length of data %d < %d`, len(*data), length) } buf := make([]byte, 8) copy(buf, (*data)[1:length]) x := int64(binary.LittleEndian.Uint64(buf)) *data = (*data)[length:] return x, nil } func DecodeLenInt64Buf(buf *bytes.Buffer) (int64, error) { if buf.Len() == 0 { return 0, nil } val, err := buf.ReadByte() if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("cannot read bytes from buffer") return 0, err } length := int(val) if buf.Len() < length { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "data_length": buf.Len(), "length": length}).Error("length of data is smaller then encoded length") return 0, fmt.Errorf(`length of data %d < %d`, buf.Len(), length) } data := make([]byte, 8) copy(data, buf.Next(length)) return int64(binary.LittleEndian.Uint64(data)), nil } // DecodeLength decodes []byte to int64 and shifts buf. Bytes must be encoded with EncodeLength function. // // 0x43 => 67 // 0x820400 => 1024 // 0x830f4240 => 1000000 func DecodeLength(buf *[]byte) (ret int64, err error) { if len(*buf) == 0 { return } length := (*buf)[0] if (length & 0x80) != 0 { length &= 0x7F if len(*buf) < int(length+1) { log.WithFields(log.Fields{"data_length": len(*buf), "length": int(length + 1)}).Error("length of data is smaller then encoded length") return 0, fmt.Errorf(`input slice has small size`) } ret = int64(binary.BigEndian.Uint64(append(make([]byte, 8-length), (*buf)[1:length+1]...))) } else { ret = int64(length) length = 0 } *buf = (*buf)[length+1:] return } func DecodeLengthBuf(buf *bytes.Buffer) (int, error) { if buf.Len() == 0 { return 0, nil } length, err := buf.ReadByte() if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("cannot read bytes from buffer") return 0, err } if (length & 0x80) == 0 { return int(length), nil } length &= 0x7F if buf.Len() < int(length) { log.WithFields(log.Fields{"data_length": buf.Len(), "length": int(length), "type": consts.UnmarshallingError}).Error("length of data is smaller then encoded length") return 0, fmt.Errorf(`input slice has small size`) } n := int(binary.BigEndian.Uint64(append(make([]byte, 8-length), buf.Next(int(length))...))) if n < 0 { return 0, fmt.Errorf(`input slice has negative size`) } return n, nil } func DecodeBytesBuf(buf *bytes.Buffer) ([]byte, error) { n, err := DecodeLengthBuf(buf) if err != nil { return nil, err } if buf.Len() < n { return nil, ErrSliceSize } return buf.Next(n), nil } // BinMarshal converts v parameter to []byte slice. func BinMarshal(out *[]byte, v any) (*[]byte, error) { var err error t := reflect.ValueOf(v) if *out == nil { *out = make([]byte, 0, 2048) } switch t.Kind() { case reflect.Uint8, reflect.Int8: *out = append(*out, uint8(t.Uint())) case reflect.Uint32: tmp := make([]byte, 4) binary.BigEndian.PutUint32(tmp, uint32(t.Uint())) *out = append(*out, tmp...) case reflect.Int32: if uint32(t.Int()) < 128 { *out = append(*out, uint8(t.Int())) } else { var i uint8 tmp := make([]byte, 4) binary.BigEndian.PutUint32(tmp, uint32(t.Int())) for ; i < 4; i++ { if tmp[i] != uint8(0) { break } } *out = append(*out, 128+4-i) *out = append(*out, tmp[i:]...) } case reflect.Float64: bin := float2Bytes(t.Float()) *out = append(*out, bin...) case reflect.Int64: EncodeLenInt64(out, t.Int()) case reflect.Uint64: tmp := make([]byte, 8) binary.BigEndian.PutUint64(tmp, t.Uint()) *out = append(*out, tmp...) case reflect.String: *out = append(append(*out, EncodeLength(int64(t.Len()))...), []byte(t.String())...) case reflect.Struct: for i := 0; i < t.NumField(); i++ { if out, err = BinMarshal(out, t.Field(i).Interface()); err != nil { return out, err } } case reflect.Slice: *out = append(append(*out, EncodeLength(int64(t.Len()))...), t.Bytes()...) case reflect.Ptr: if out, err = BinMarshal(out, t.Elem().Interface()); err != nil { return out, err } default: return out, fmt.Errorf(`unsupported type of BinMarshal`) } return out, nil } func BinUnmarshalBuff(buf *bytes.Buffer, v any) error { t := reflect.ValueOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() } if buf.Len() == 0 { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": "input slice is empty"}).Error("input slice is empty") return fmt.Errorf(`input slice is empty`) } switch t.Kind() { case reflect.Uint8, reflect.Int8: val, err := buf.ReadByte() if err != nil { return err } t.SetUint(uint64(val)) case reflect.Uint32: t.SetUint(uint64(binary.BigEndian.Uint32(buf.Next(4)))) case reflect.Int32: val, err := buf.ReadByte() if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading bytes from buffer") return err } if val < 128 { t.SetInt(int64(val)) } else { var i uint8 size := val - 128 tmp := make([]byte, 4) if buf.Len() <= int(size) || size > 4 { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "data_length": buf.Len(), "length": int(size)}).Error("bin unmarshalling int32") return fmt.Errorf(`wrong input data`) } for ; i < size; i++ { byteVal, err := buf.ReadByte() if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading bytes from buffer") return err } tmp[4-size+i] = byteVal } t.SetInt(int64(binary.BigEndian.Uint32(tmp))) } case reflect.Float64: t.SetFloat(bytes2Float(buf.Next(8))) case reflect.Int64: val, err := DecodeLenInt64Buf(buf) if err != nil { return err } t.SetInt(val) case reflect.Uint64: t.SetUint(binary.BigEndian.Uint64(buf.Next(8))) case reflect.String: val, err := DecodeLengthBuf(buf) if err != nil { return err } if buf.Len() < val { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "data_length": buf.Len(), "length": val}).Error("bin unmarshalling string") return fmt.Errorf(`input slice is short`) } t.SetString(string(buf.Next(val))) case reflect.Struct: for i := 0; i < t.NumField(); i++ { if err := BinUnmarshalBuff(buf, t.Field(i).Addr().Interface()); err != nil { return err } } case reflect.Slice: val, err := DecodeLengthBuf(buf) if err != nil { return err } if buf.Len() < val { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "data_length": buf.Len(), "length": val}).Error("bin unmarshalling slice") return fmt.Errorf(`input slice is short`) } t.SetBytes(buf.Next(val)) default: log.WithFields(log.Fields{"type": consts.UnmarshallingError, "value_type": t.Kind()}).Error("BinUnmrashal unsupported type") return fmt.Errorf(`unsupported type of BinUnmarshal %v`, t.Kind()) } return nil } // BinUnmarshal converts []byte slice which has been made with BinMarshal to v func BinUnmarshal(out *[]byte, v any) error { t := reflect.ValueOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() } if len(*out) == 0 { return fmt.Errorf(`input slice is empty`) } switch t.Kind() { case reflect.Uint8, reflect.Int8: val := uint64((*out)[0]) t.SetUint(val) *out = (*out)[1:] case reflect.Uint32: t.SetUint(uint64(binary.BigEndian.Uint32((*out)[:4]))) *out = (*out)[4:] case reflect.Int32: val := (*out)[0] if val < 128 { t.SetInt(int64(val)) *out = (*out)[1:] } else { var i uint8 size := val - 128 tmp := make([]byte, 4) if len(*out) <= int(size) || size > 4 { return fmt.Errorf(`wrong input data`) } for ; i < size; i++ { tmp[4-size+i] = (*out)[i+1] } t.SetInt(int64(binary.BigEndian.Uint32(tmp))) *out = (*out)[size+1:] } case reflect.Float64: t.SetFloat(bytes2Float((*out)[:8])) *out = (*out)[8:] case reflect.Int64: val, err := DecodeLenInt64(out) if err != nil { return err } t.SetInt(val) case reflect.Uint64: t.SetUint(binary.BigEndian.Uint64((*out)[:8])) *out = (*out)[8:] case reflect.String: val, err := DecodeLength(out) if err != nil { return err } if len(*out) < int(val) { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "data_length": len(*out), "length": int(val)}).Error("input slice is short") return fmt.Errorf(`input slice is short`) } t.SetString(string((*out)[:val])) *out = (*out)[val:] case reflect.Struct: for i := 0; i < t.NumField(); i++ { if err := BinUnmarshal(out, t.Field(i).Addr().Interface()); err != nil { return err } } case reflect.Slice: val, err := DecodeLength(out) if err != nil { return err } if len(*out) < int(val) { return fmt.Errorf(`input slice is short`) } t.SetBytes((*out)[:val]) *out = (*out)[val:] default: log.WithFields(log.Fields{"type": consts.UnmarshallingError, "value_type": t.Kind()}).Error("BinUnmrashal unsupported type") return fmt.Errorf(`unsupported type of BinUnmarshal %v`, t.Kind()) } return nil } // Sanitize deletes unaccessable characters from input string func Sanitize(name string, available string) string { out := make([]rune, 0, len(name)) for _, ch := range name { if ch > 127 || (ch >= '0' && ch <= '9') || ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || strings.IndexRune(available, ch) >= 0 { out = append(out, ch) } } return string(out) } // SanitizeScript deletes unaccessable characters from input string func SanitizeScript(input string) string { return strings.Replace(strings.Replace(input, ``, `script>`, -1) } // SanitizeName deletes unaccessable characters from name string func SanitizeName(input string) string { return Sanitize(input, `- `) } // SanitizeNumber deletes unaccessable characters from number or name string func SanitizeNumber(input string) string { return Sanitize(input, `+.- `) } func EscapeSQL(name string) string { return strings.Replace(strings.Replace(strings.Replace(name, `"`, `""`, -1), `;`, ``, -1), `'`, `''`, -1) } // EscapeName deletes unaccessable characters for input name(s) func EscapeName(name string) string { out := make([]byte, 1, len(name)+2) out[0] = '"' available := `() ,` for _, ch := range []byte(name) { if (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || strings.IndexByte(available, ch) >= 0 { out = append(out, ch) } } if strings.IndexAny(string(out), available) >= 0 { return string(out[1:]) } return string(append(out, '"')) } // Float2Bytes converts float64 to []byte func float2Bytes(float float64) []byte { ret := make([]byte, 8) binary.LittleEndian.PutUint64(ret, math.Float64bits(float)) return ret } // Bytes2Float converts []byte to float64 func bytes2Float(bytes []byte) float64 { return math.Float64frombits(binary.LittleEndian.Uint64(bytes)) } // UInt32ToStr converts uint32 to string func UInt32ToStr(num uint32) string { return strconv.FormatInt(int64(num), 10) } // Int64ToStr converts int64 to string func Int64ToStr(num int64) string { return strconv.FormatInt(num, 10) } // Int64ToByte converts int64 to []byte func Int64ToByte(num int64) []byte { return []byte(strconv.FormatInt(num, 10)) } // IntToStr converts integer to string func IntToStr(num int) string { return strconv.Itoa(num) } // DecToBin converts interface to []byte func DecToBin(v any, sizeBytes int64) []byte { var dec int64 switch v.(type) { case int: dec = int64(v.(int)) case int64: dec = v.(int64) case uint64: dec = int64(v.(uint64)) case string: dec = StrToInt64(v.(string)) } Hex := fmt.Sprintf("%0"+Int64ToStr(sizeBytes*2)+"x", dec) return HexToBin([]byte(Hex)) } // BinToHex converts interface to hex []byte func BinToHex(v any) []byte { var bin []byte switch v.(type) { case []byte: bin = v.([]byte) case int64: bin = Int64ToByte(v.(int64)) case string: bin = []byte(v.(string)) } return []byte(fmt.Sprintf("%x", bin)) } // HexToBin converts hex interface to binary []byte func HexToBin(ihexdata any) []byte { var hexdata string switch ihexdata.(type) { case []byte: hexdata = string(ihexdata.([]byte)) case int64: hexdata = Int64ToStr(ihexdata.(int64)) case string: hexdata = ihexdata.(string) } var str []byte str, err := hex.DecodeString(hexdata) if err != nil { log.WithFields(log.Fields{"data": hexdata, "error": err, "type": consts.ConversionError}).Error("decoding string to hex") log.Printf("HexToBin error: %s", err) } return str } // BinToDec converts input binary []byte to int64 func BinToDec(bin []byte) int64 { var a uint64 l := len(bin) for i, b := range bin { shift := uint64((l - i - 1) * 8) a |= uint64(b) << shift } return int64(a) } // BinToDecBytesShift converts the input binary []byte to int64 and shifts the input bin func BinToDecBytesShift(bin *[]byte, num int64) int64 { return BinToDec(BytesShift(bin, num)) } // BytesShift returns the index bytes of the input []byte and shift str pointer func BytesShift(str *[]byte, index int64) (ret []byte) { if int64(len(*str)) < index || index == 0 { *str = (*str)[:0] return []byte{} } ret, *str = (*str)[:index], (*str)[index:] return } // InterfaceToStr converts the interfaces to the string func InterfaceToStr(v any) (string, error) { var str string if v == nil { return ``, nil } switch v.(type) { case int: str = IntToStr(v.(int)) case float64: str = Float64ToStr(v.(float64)) case int64: str = Int64ToStr(v.(int64)) case string: str = v.(string) case []byte: str = string(v.([]byte)) default: if reflect.TypeOf(v).String() == `map[string]interface {}` || reflect.TypeOf(v).String() == `*types.Map` { if out, err := json.Marshal(v); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.JSONMarshallError}).Error("marshalling map for jsonb") return ``, err } else { str = string(out) } } else if reflect.TypeOf(v).String() == `decimal.Decimal` { str = v.(decimal.Decimal).String() } } return str, nil } // InterfaceSliceToStr converts the slice of interfaces to the slice of strings func InterfaceSliceToStr(i []any) (strs []string, err error) { var val string for _, v := range i { val, err = InterfaceToStr(v) if err != nil { return } strs = append(strs, val) } return } // InterfaceToFloat64 converts the interfaces to the float64 func InterfaceToFloat64(i any) float64 { var result float64 switch i.(type) { case int: result = float64(i.(int)) case float64: result = i.(float64) case int64: result = float64(i.(int64)) case string: result = StrToFloat64(i.(string)) case []byte: result = BytesToFloat64(i.([]byte)) } return result } // BytesShiftReverse gets []byte from the end of the input and cut the input pointer to []byte func BytesShiftReverse(str *[]byte, v any) []byte { var index int64 switch v.(type) { case int: index = int64(v.(int)) case int64: index = v.(int64) } var substr []byte slen := int64(len(*str)) if slen < index { index = slen } substr = (*str)[slen-index:] *str = (*str)[:slen-index] return substr } // StrToInt64 converts string to int64 func StrToInt64(s string) int64 { ret, _ := strconv.ParseInt(s, 10, 64) return ret } // BytesToInt64 converts []bytes to int64 func BytesToInt64(s []byte) int64 { ret, _ := strconv.ParseInt(string(s), 10, 64) return ret } // StrToUint64 converts string to the unsinged int64 func StrToUint64(s string) uint64 { ret, _ := strconv.ParseUint(s, 10, 64) return ret } // StrToInt converts string to integer func StrToInt(s string) int { i, _ := strconv.Atoi(s) return i } // Float64ToStr converts float64 to string func Float64ToStr(f float64) string { return strconv.FormatFloat(f, 'f', 13, 64) } // StrToFloat64 converts string to float64 func StrToFloat64(s string) float64 { Float64, _ := strconv.ParseFloat(s, 64) return Float64 } // BytesToFloat64 converts []byte to float64 func BytesToFloat64(s []byte) float64 { Float64, _ := strconv.ParseFloat(string(s), 64) return Float64 } // BytesToInt converts []byte to integer func BytesToInt(s []byte) int { i, _ := strconv.Atoi(string(s)) return i } // StrToMoney rounds money string to float64 func StrToMoney(str string) float64 { ind := strings.Index(str, ".") var newStr string if ind != -1 { end := 2 if len(str[ind+1:]) > 1 { end = 3 } newStr = str[:ind] + "." + str[ind+1:ind+end] } else { newStr = str } return StrToFloat64(newStr) } // EncodeLengthPlusData encoding interface into []byte func EncodeLengthPlusData(idata any) []byte { var data []byte switch idata.(type) { case int64: data = Int64ToByte(idata.(int64)) case string: data = []byte(idata.(string)) case []byte: data = idata.([]byte) } //log.Debug("data: %x", data) //log.Debug("len data: %d", len(data)) return append(EncodeLength(int64(len(data))), data...) } // FormatMoney converts minimal unit to legibility unit. For example, value * 10 ^ -digit func FormatMoney(exp string, digit int32) (string, error) { if len(exp) == 0 { return `0`, nil } if strings.IndexByte(exp, '.') >= 0 { return `0`, fmt.Errorf(`wrong money format %s`, exp) } if digit < 0 { return `0`, fmt.Errorf(`digit must be positive`) } if len(exp) > consts.MoneyLength { return `0`, fmt.Errorf(`too long money`) } retDec, err := decimal.NewFromString(exp) if err != nil { return `0`, err } return retDec.Shift(-digit).String(), nil } // EscapeForJSON replaces quote to slash and quote func EscapeForJSON(data string) string { return strings.Replace(data, `"`, `\"`, -1) } // ValidateEmail validates email func ValidateEmail(email string) bool { Re := regexp.MustCompile(`^(?i)[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) return Re.MatchString(email) } // ParseName gets a state identifier and the name of the contract or table // from the full name like @[id]name func ParseName(in string) (id int64, name string) { re := regexp.MustCompile(`(?is)^@(\d+)(\w[_\w\d]*)$`) ret := re.FindStringSubmatch(in) if len(ret) == 3 { id = StrToInt64(ret[1]) name = ret[2] } return } // ParseTable return // format 1:@[id]tblname -> parse id_tblname // format 2:[defaultEcosystem]_tblname func ParseTable(tblname string, defaultEcosystem int64) string { ecosystem, name := ParseName(tblname) if ecosystem == 0 { if FirstEcosystemTables[tblname] { ecosystem = 1 } else { ecosystem = defaultEcosystem } name = tblname } return strings.ToLower(fmt.Sprintf(`%d_%s`, ecosystem, Sanitize(name, ``))) } func SubNodeParseTable(tblname string, defaultEcosystem int64) string { ecosystem, name := ParseName(tblname) if ecosystem == 0 { if FirstEcosystemTables[tblname] { ecosystem = 1 } else { ecosystem = defaultEcosystem } name = tblname } //return strings.ToLower(fmt.Sprintf(`%d_%s`, ecosystem, Sanitize(name, ``))) return strings.ToLower(fmt.Sprintf(`%s`, Sanitize(name, ``))) } // SliceReverse reverses the slice of int64 func SliceReverse(s []int64) []int64 { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } return s } // SortMap sorts map to the slice of maps func SortMap(m map[int64]string) []map[int64]string { var keys []int for k := range m { keys = append(keys, int(k)) } sort.Ints(keys) var result []map[int64]string for _, k := range keys { result = append(result, map[int64]string{int64(k): m[int64(k)]}) } return result } // RSortMap sorts map to the reversed slice of maps func RSortMap(m map[int64]string) []map[int64]string { var keys []int for k := range m { keys = append(keys, int(k)) } sort.Sort(sort.Reverse(sort.IntSlice(keys))) var result []map[int64]string for _, k := range keys { result = append(result, map[int64]string{int64(k): m[int64(k)]}) } return result } // InSliceString searches the string in the slice of strings func InSliceString(search string, slice []string) bool { for _, v := range slice { if v == search { return true } } return false } // StripTags replaces < and > to < and > func StripTags(value string) string { return strings.Replace(strings.Replace(value, `<`, `<`, -1), `>`, `>`, -1) } // IsLatin checks if the specified string contains only latin character, digits and '-', '_'. func IsLatin(name string) bool { for _, ch := range []byte(name) { if !((ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { return false } } return true } // Escape deletes unaccessable characters func Escape(data string) string { out := make([]rune, 0, len(data)) available := `_ ,=!-'()"?*$#{}<>: ` for _, ch := range []rune(data) { if (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || strings.IndexByte(available, byte(ch)) >= 0 || unicode.IsLetter(ch) || ch >= 128 { out = append(out, ch) } } return string(out) } // FieldToBytes returns the value of n-th field of v as []byte func FieldToBytes(v any, num int) []byte { t := reflect.ValueOf(v) ret := make([]byte, 0, 2048) if t.Kind() == reflect.Struct && num < t.NumField() { field := t.Field(num) switch field.Kind() { case reflect.Uint8, reflect.Uint32, reflect.Uint64: ret = append(ret, []byte(fmt.Sprintf("%d", field.Uint()))...) case reflect.Int8, reflect.Int32, reflect.Int64: ret = append(ret, []byte(fmt.Sprintf("%d", field.Int()))...) case reflect.Float64: ret = append(ret, []byte(fmt.Sprintf("%f", field.Float()))...) case reflect.String: ret = append(ret, []byte(field.String())...) case reflect.Slice: ret = append(ret, field.Bytes()...) // case reflect.Ptr: // case reflect.Struct: // default: } } return ret } // NumString insert spaces between each three digits. 7123456 => 7 123 456 func NumString(in string) string { if strings.IndexByte(in, '.') >= 0 { lr := strings.Split(in, `.`) return NumString(lr[0]) + `.` + lr[1] } buf := []byte(in) out := make([]byte, len(in)+4) for len(buf) > 3 { out = append(append([]byte(` `), buf[len(buf)-3:]...), out...) buf = buf[:len(buf)-3] } return string(append(buf, out...)) } func Round(num float64) int64 { //log.Debug("num", num) //num += ROUND_FIX // return int(StrToFloat64(Float64ToStr(num)) + math.Copysign(0.5, num)) //log.Debug("num", num) return int64(num + math.Copysign(0.5, num)) } // RoundWithPrecision rounds float64 value func RoundWithPrecision(num float64, precision int) float64 { num += consts.RoundFix output := math.Pow(10, float64(precision)) return float64(Round(num*output)) / output } // RoundWithoutPrecision is round float64 without precision func RoundWithoutPrecision(num float64) int64 { //log.Debug("num", num) //num += ROUND_FIX // return int(StrToFloat64(Float64ToStr(num)) + math.Copysign(0.5, num)) //log.Debug("num", num) return int64(num + math.Copysign(0.5, num)) } // ValueToInt converts interface (string or int64) to int64 func ValueToInt(v any) (ret int64, err error) { switch val := v.(type) { case float64: ret = int64(val) case int64: ret = val case string: if len(val) == 0 { return 0, nil } ret, err = strconv.ParseInt(val, 10, 64) if err != nil { errText := err.Error() if strings.Contains(errText, `:`) { errText = errText[strings.LastIndexByte(errText, ':'):] } else { errText = `` } err = fmt.Errorf(`%s is not a valid integer %s`, val, errText) } case decimal.Decimal: ret = val.IntPart() case json.Number: ret, err = val.Int64() default: if v == nil { return 0, nil } err = fmt.Errorf(`%v is not a valid integer`, val) } if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err, "value": fmt.Sprint(v)}).Error("converting value to int") } return } func ValueToDecimal(v any) (ret decimal.Decimal, err error) { switch val := v.(type) { case float64: ret = decimal.NewFromFloat(val).Floor() case string: ret, err = decimal.NewFromString(val) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err, "value": val}).Error("converting value from string to decimal") } else { ret = ret.Floor() } case int64: ret = decimal.New(val, 0) default: ret = val.(decimal.Decimal) } return } func Int64ToDateStr(date int64, format string) string { t := time.Unix(date, 0) return t.Format(format) } func Int64Toint(dat int64) (int, error) { str := strconv.FormatInt(dat, 10) return strconv.Atoi(str) } func MarshalJson(v any) string { buff, err := json.Marshal(v) if err != nil { log.WithFields(log.Fields{"v": v, "error": err}).Error("marshalJson error") } return string(buff) } ================================================ FILE: packages/daemons/block_generator.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "bytes" "context" "sync/atomic" "time" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/protocols" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // BlockGenerator is daemon that generates blocks func BlockGenerator(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } DBLock() defer DBUnlock() candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err == nil && len(candidateNodes) > 0 { syspar.SetRunModel(consts.CandidateNodeMode) return BlockGeneratorCandidate(ctx, d) } syspar.SetRunModel(consts.HonorNodeMode) d.sleepTime = time.Second if node.IsNodePaused() { return nil } nodePosition, err := syspar.GetThisNodePosition() if err != nil { // we are not honor node and can't generate new blocks d.sleepTime = syspar.GetMaxBlockTimeDuration() d.logger.WithFields(log.Fields{"type": consts.JustWaiting, "error": err}).Debug("we are not honor node, sleep for 10 seconds") return nil } // we need fresh myNodePosition after locking nodePosition, err = syspar.GetThisNodePosition() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting node position by key id") return err } btc := protocols.NewBlockTimeCounter() st := time.Now() if exists, err := btc.BlockForTimeExists(st, int(nodePosition)); exists || err != nil { return nil } timeToGenerate, err := btc.TimeToGenerate(st, int(nodePosition)) if err != nil { d.logger.WithFields(log.Fields{"type": consts.BlockError, "error": err, "position": nodePosition}).Debug("calculating block time") return err } if !timeToGenerate { d.logger.WithFields(log.Fields{"type": consts.JustWaiting}).Debug("not my generation time") return nil } //if !NtpDriftFlag { // d.logger.WithFields(log.Fields{"type": consts.Ntpdate}).Error("ntp time not ntpdate") // return nil //} //var cf sqldb.Confirmation //cfg, err := cf.CheckAllowGenBlock() //if err != nil { // d.logger.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Debug("confirmation block not allow") // return err //} // //if !cfg { // d.logger.WithFields(log.Fields{"type": consts.JustWaiting}).Debug("not my confirmation time") // return nil //} prevBlock := &sqldb.InfoBlock{} _, err = prevBlock.Get() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting previous block") return err } NodePrivateKey, NodePublicKey := utils.GetNodeKeys() if len(NodePrivateKey) < 1 { d.logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node private key is empty") return errors.New(`node private key is empty`) } dtx := DelayedTx{ privateKey: NodePrivateKey, publicKey: NodePublicKey, logger: d.logger, time: st.Unix(), } txs, err := dtx.RunForDelayBlockID(prevBlock.BlockID + 1) if err != nil { return err } trs, classifyTxsMap, err := processTransactionsNew(d.logger, txs, st) if err != nil { return err } // Block generation will be started only if we have transactions if len(trs) == 0 { return nil } header := &types.BlockHeader{ BlockId: prevBlock.BlockID + 1, Timestamp: st.Unix(), EcosystemId: 0, KeyId: conf.Config.KeyID, NetworkId: conf.Config.LocalConf.NetworkID, NodePosition: nodePosition, Version: consts.BlockVersion, ConsensusMode: consts.HonorNodeMode, } prev := &types.BlockHeader{ BlockId: prevBlock.BlockID, BlockHash: prevBlock.Hash, RollbacksHash: prevBlock.RollbacksHash, } err = generateProcessBlockNew(header, prev, trs, classifyTxsMap) if err != nil { return err } //go notificator.CheckTokenMovementLimits(nil, conf.Config.TokenMovement, header.BlockId) return nil } func generateNextBlock(blockHeader, prevBlock *types.BlockHeader, trs [][]byte) ([]byte, error) { return block.MarshallBlock( types.WithCurHeader(blockHeader), types.WithPrevHeader(prevBlock), types.WithTxFullData(trs)) } func processTransactionsNew(logger *log.Entry, txs []*sqldb.Transaction, st time.Time) ([][]byte, map[int][]*transaction.Transaction, error) { classifyTxsMap := make(map[int][]*transaction.Transaction) var done = make(<-chan time.Time, 1) if syspar.IsHonorNodeMode() { btc := protocols.NewBlockTimeCounter() _, endTime, err := btc.RangeByTime(st) if err != nil { log.WithFields(log.Fields{"type": consts.TimeCalcError, "error": err}).Error("on getting end time of generation") return nil, nil, err } done = time.After(endTime.Sub(st)) } trs, err := sqldb.GetAllUnusedTransactions(nil, syspar.GetMaxTxCount()-len(txs)) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all unused transactions") return nil, nil, err } limits := transaction.NewLimits(transaction.GetLetPreprocess()) type badTxStruct struct { hash []byte msg string keyID int64 } processBadTx := func(dbTx *sqldb.DbTransaction) chan badTxStruct { ch := make(chan badTxStruct) go func() { for badTxItem := range ch { transaction.BadTxForBan(badTxItem.keyID) _ = transaction.MarkTransactionBad(badTxItem.hash, badTxItem.msg) } }() return ch } txBadChan := processBadTx(nil) defer func() { close(txBadChan) }() // Checks preprocessing count limits txList := make([][]byte, 0, len(trs)) txs = append(txs, trs...) allDelayedContract, err := sqldb.GetAllDelayedContract() if err != nil { return nil, nil, err } var contractNames []string for _, contract := range allDelayedContract { contractNames = append(contractNames, contract.Contract) } for i, txItem := range txs { if syspar.IsHonorNodeMode() { select { case <-done: return txList, classifyTxsMap, nil default: } } bufTransaction := bytes.NewBuffer(txItem.Data) tr, err := transaction.UnmarshallTransaction(bufTransaction, true) if err != nil { if tr != nil { txBadChan <- badTxStruct{hash: tr.Hash(), msg: err.Error(), keyID: tr.KeyID()} } continue } if err := tr.Check(st.Unix()); err != nil { txBadChan <- badTxStruct{hash: tr.Hash(), msg: err.Error(), keyID: tr.KeyID()} continue } if txItem.GetTransactionRateStopNetwork() { classifyTxsMap[types.StopNetworkTxType] = append(classifyTxsMap[types.StopNetworkTxType], tr) txList = append(txList[:0], txs[i].Data) break } if tr.IsSmartContract() { err = limits.CheckLimit(tr.Inner) if errors.Cause(err) == transaction.ErrLimitStop && i > 0 { break } else if err != nil { if err != transaction.ErrLimitSkip { txBadChan <- badTxStruct{hash: tr.Hash(), msg: err.Error(), keyID: tr.KeyID()} } continue } if tr.Type() == types.TransferSelfTxType { classifyTxsMap[types.TransferSelfTxType] = append(classifyTxsMap[types.TransferSelfTxType], tr) txList = append(txList, txs[i].Data) continue } if tr.Type() == types.UtxoTxType { classifyTxsMap[types.UtxoTxType] = append(classifyTxsMap[types.UtxoTxType], tr) txList = append(txList, txs[i].Data) continue } if utils.StringInSlice(contractNames, tr.SmartContract().TxContract.Name) { classifyTxsMap[types.DelayTxType] = append(classifyTxsMap[types.DelayTxType], tr) txList = append(txList, txs[i].Data) continue } classifyTxsMap[types.SmartContractTxType] = append(classifyTxsMap[types.SmartContractTxType], tr) } txList = append(txList, txs[i].Data) } return txList, classifyTxsMap, nil } ================================================ FILE: packages/daemons/block_generator_candidate.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "bytes" "context" "encoding/hex" "encoding/json" "errors" "math" "sort" "strconv" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) func BlockGeneratorCandidate(ctx context.Context, d *daemon) error { defer func() { d.sleepTime = syspar.GetMaxBlockTimeDuration() }() if node.IsNodePaused() { return nil } prevBlock := &sqldb.InfoBlock{} _, err := prevBlock.Get() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting previous block") return err } NodePrivateKey, NodePublicKey := utils.GetNodeKeys() if len(NodePrivateKey) < 1 { d.logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node private key is empty") return errors.New(`node private key is empty`) } if len(NodePublicKey) < 1 { d.logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node public key is empty") return errors.New(`node public key is empty`) } candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err != nil { log.WithError(err).Error("getting candidate node list") return err } currentCandidateNode, nodePosition := GetThisNodePosition(candidateNodes, prevBlock) if !nodePosition { d.sleepTime = 4 * time.Second d.logger.WithFields(log.Fields{"type": consts.JustWaiting, "error": err}).Debug("we are not honor node, sleep for 10 seconds") return nil } st := time.Now() dtx := DelayedTx{ privateKey: NodePrivateKey, publicKey: NodePublicKey, logger: d.logger, time: st.Unix(), } txs, err := dtx.RunForDelayBlockID(prevBlock.BlockID + 1) if err != nil { return err } trs, classifyTxsMap, err := processTransactionsNew(d.logger, txs, st) if err != nil { return err } // Block generation will be started only if we have transactions if len(trs) == 0 { return nil } lastBlockInterval := time.Unix(prevBlock.Time, 0) timeDifference := st.Sub(lastBlockInterval) if timeDifference <= syspar.GetMaxBlockTimeDuration() { time.Sleep(syspar.GetMaxBlockTimeDuration() - timeDifference) st = time.Now() } nodes := make([]types.BlockCandidateNode, len(candidateNodes)) for i, candidateNode := range candidateNodes { bcn := types.BlockCandidateNode{ ID: candidateNode.ID, ReplyCount: candidateNode.ReplyCount, } nodes[i] = bcn } candidateNodesByte, _ := json.Marshal(nodes) header := &types.BlockHeader{ BlockId: prevBlock.BlockID + 1, Timestamp: st.Unix(), EcosystemId: 0, KeyId: conf.Config.KeyID, NetworkId: conf.Config.LocalConf.NetworkID, NodePosition: currentCandidateNode.ID, Version: consts.BlockVersion, ConsensusMode: consts.CandidateNodeMode, CandidateNodes: candidateNodesByte, } prev := &types.BlockHeader{ BlockId: prevBlock.BlockID, BlockHash: prevBlock.Hash, RollbacksHash: prevBlock.RollbacksHash, } err = generateProcessBlockNew(header, prev, trs, classifyTxsMap) if err != nil { return err } return nil } func GetThisNodePosition(candidateNodes sqldb.CandidateNodes, prevBlock *sqldb.InfoBlock) (sqldb.CandidateNode, bool) { candidateNode := sqldb.CandidateNode{} if len(candidateNodes) == 0 { firstBlock, err := syspar.GetFirstBlockData() if err != nil { return candidateNode, false } nodePubKey := syspar.GetNodePubKey() if bytes.Equal(firstBlock.NodePublicKey, nodePubKey) { candidateNode.ID = 0 candidateNode.NodePubKey = hex.EncodeToString(nodePubKey) syspar.SetRunModel(consts.HonorNodeMode) return candidateNode, true } return candidateNode, false } if len(candidateNodes) == 1 { nodePubKey := candidateNodes[0].NodePubKey pk, err := hex.DecodeString(nodePubKey) if err != nil { return candidateNode, false } pk = crypto.CutPub(pk) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("decoding node private key from hex") return candidateNode, false } if bytes.Equal(pk, syspar.GetNodePubKey()) { return candidateNodes[0], true } return candidateNode, false } if len(candidateNodes) == 2 { _, NodePublicKey := utils.GetNodeKeys() NodePublicKey = "04" + NodePublicKey compare := func(c []sqldb.CandidateNode) (sqldb.CandidateNode, bool) { var ( generateBlockNode sqldb.CandidateNode isGenerateBlockNode bool extNode bool node1 = candidateNodes[0] node2 = candidateNodes[1] ) for i := 0; i < len(c); i++ { if prevBlock.NodePosition == strconv.FormatInt(c[i].ID, 10) { extNode = true break } } if extNode { for _, node := range c { if NodePublicKey == node.NodePubKey && prevBlock.NodePosition != strconv.FormatInt(node.ID, 10) { isGenerateBlockNode = true generateBlockNode = node break } } return generateBlockNode, isGenerateBlockNode } generateBlockNode = node1 switch node1.ReferendumTotal.Cmp(node2.ReferendumTotal) { case 0: switch node1.EarnestTotal.Cmp(node2.EarnestTotal) { case 0: if node1.ReplyCount < node2.ReplyCount { generateBlockNode = node2 } else if node1.ReplyCount == node2.ReplyCount { if node1.DateReply < node2.DateReply { generateBlockNode = node2 } } case -1: generateBlockNode = node2 } case -1: generateBlockNode = node2 } return generateBlockNode, generateBlockNode.NodePubKey == NodePublicKey } return compare(candidateNodes) } if len(candidateNodes) > 2 { candidateNodesSqrt := math.Sqrt(float64(len(candidateNodes))) candidateNodesCeil := math.Ceil(candidateNodesSqrt) startBlockId := prevBlock.BlockID - int64(candidateNodesCeil) subBlocks, err := sqldb.GetBlockchain(startBlockId, prevBlock.BlockID, sqldb.OrderASC) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting recent block") return candidateNode, false } size := len(candidateNodes) nodeCount := len(candidateNodes) for _, subBlock := range subBlocks { for j := 0; j < size; j++ { if candidateNodes[j].ID == subBlock.NodePosition { candidateNodes = append(candidateNodes[:j], candidateNodes[j+1:]...) size = len(candidateNodes) } } } sort.Sort(candidateNodes) if len(candidateNodes) > 0 { var ( maxIndex int isHonorNode bool ) for i, node := range candidateNodes { isHonorNode = agreeCount(int64(nodeCount), node.ReplyCount) if isHonorNode { maxIndex = i break } } _, NodePublicKey := utils.GetNodeKeys() if len(NodePublicKey) < 1 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node public key is empty") return candidateNode, false } NodePublicKey = "04" + NodePublicKey if isHonorNode { if NodePublicKey == candidateNodes[maxIndex].NodePubKey { return candidateNodes[maxIndex], true } } } } return candidateNode, false } func agreeCount(candidateNodes int64, replyCount int64) bool { lessReplyCount := math.Ceil(float64(candidateNodes) / 2) if replyCount >= int64(lessReplyCount) { return true } return false } ================================================ FILE: packages/daemons/block_generator_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "encoding/hex" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) const ( callDelayedContract = "CallDelayedContract" firstEcosystemID = 1 ) // DelayedTx represents struct which works with delayed contracts type DelayedTx struct { logger *log.Entry privateKey string publicKey string time int64 } // RunForDelayBlockID creates the transactions that need to be run for blockID func (dtx *DelayedTx) RunForDelayBlockID(blockID int64) ([]*sqldb.Transaction, error) { contracts, err := sqldb.GetAllDelayedContractsForBlockID(blockID) if err != nil { dtx.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting delayed contracts for block") return nil, err } txList := make([]*sqldb.Transaction, 0, len(contracts)) for _, c := range contracts { tx, err := dtx.createDelayTxByItem(c.Contract, c.KeyID, c.HighRate) if err != nil { dtx.logger.WithError(err).Debug("can't create transaction for delayed contract") return nil, err } txList = append(txList, tx) } return txList, nil } func (dtx *DelayedTx) createDelayTxByItem(name string, keyID, highRate int64) (*sqldb.Transaction, error) { vm := script.GetVM() contract := smart.VMGetContract(vm, name, uint32(firstEcosystemID)) smartTx := types.SmartTransaction{ Header: &types.Header{ ID: int(contract.Info().ID), EcosystemID: firstEcosystemID, KeyID: keyID, Time: dtx.time, NetworkID: conf.Config.LocalConf.NetworkID, }, SignedBy: smart.PubToID(dtx.publicKey), Params: map[string]any{}, } privateKey, err := hex.DecodeString(dtx.privateKey) if err != nil { return nil, err } txData, txHash, err := transaction.NewInternalTransaction(smartTx, privateKey) if err != nil { return nil, err } return transaction.CreateDelayTransactionHighRate(txData, txHash, keyID, highRate), nil } ================================================ FILE: packages/daemons/blocks_collection.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "encoding/hex" "fmt" "sync/atomic" "time" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/rollback" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // BlocksCollection collects and parses blocks func BlocksCollection(ctx context.Context, d *daemon) error { if ctx.Err() != nil { d.logger.WithFields(log.Fields{"type": consts.ContextError, "error": ctx.Err()}).Error("context error") return ctx.Err() } return blocksCollection(ctx, d) } func blocksCollection(ctx context.Context, d *daemon) (err error) { if !atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { return nil } defer atomic.StoreUint32(&d.atomic, 0) //if !NtpDriftFlag { // d.logger.WithFields(log.Fields{"type": consts.Ntpdate}).Error("ntp time not ntpdate") // return nil //} host, maxBlockID, err := getHostWithMaxID(ctx, d.logger) if err != nil { d.logger.WithError(err).Warn("on checking best host") return err } infoBlock := &sqldb.InfoBlock{} found, err := infoBlock.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting cur blockID") return err } if !found { log.WithFields(log.Fields{"type": consts.NotFound, "error": err}).Error("Info block not found") return errors.New("Info block not found") } if infoBlock.BlockID >= maxBlockID { log.WithFields(log.Fields{"blockID": infoBlock.BlockID, "maxBlockID": maxBlockID}).Debug("Max block is already in the host") return nil } DBLock() defer func() { node.NodeDoneUpdatingBlockchain() DBUnlock() }() // update our chain till maxBlockID from the host return UpdateChain(ctx, d, host, maxBlockID) } // UpdateChain load from host all blocks from our last block to maxBlockID func UpdateChain(ctx context.Context, d *daemon, host string, maxBlockID int64) error { // get current block id from our blockchain curBlock := &sqldb.InfoBlock{} if _, err := curBlock.Get(); err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting info block") return err } if ctx.Err() != nil { d.logger.WithFields(log.Fields{"type": consts.ContextError, "error": ctx.Err()}).Error("context error") return ctx.Err() } playRawBlock := func(rb []byte) error { var lastBlockID, lastBlockTime int64 var err error var bl *block.Block defer func(err2 *error) { if err2 != nil { banNodePause(host, lastBlockID, lastBlockTime, *err2) } }(&err) bl, err = block.ProcessBlockByBinData(rb, true) if err != nil { d.logger.WithFields(log.Fields{"error": err, "type": consts.BlockError}).Error("processing block") return err } curBlock := &sqldb.InfoBlock{} if _, err = curBlock.Get(); err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting info block") return err } if curBlock.BlockID != bl.PrevHeader.BlockId { d.logger.WithFields(log.Fields{"type": consts.BlockError}).Error("info block compare with previous block") return fmt.Errorf("info block compare with previous block err curBlock: %d, PrevBlock: %d", curBlock.BlockID, bl.PrevHeader.BlockId) } lastBlockID = bl.Header.BlockId lastBlockTime = bl.Header.Timestamp if err = bl.Check(); err != nil { var replaceCount int64 = 1 if err == block.ErrIncorrectRollbackHash { replaceCount++ } d.logger.WithFields(log.Fields{"error": err, "from_host": host, "different": fmt.Errorf("not match previous block %d, prev_position %d, current_position %d", bl.PrevHeader.BlockId, bl.PrevHeader.NodePosition, bl.Header.NodePosition), "type": consts.BlockError, "replaceCount": replaceCount}).Error("checking block hash") //if it is forked, replace the previous blocks to ones from the host if errReplace := ReplaceBlocksFromHost(ctx, host, bl.PrevHeader.BlockId, replaceCount); errReplace != nil { return errReplace } return err } return bl.PlaySafe() } var count int st := time.Now() d.logger.WithFields(log.Fields{"min_block": curBlock.BlockID, "max_block": maxBlockID, "count": maxBlockID - curBlock.BlockID}).Info("starting downloading blocks") for blockID := curBlock.BlockID + 1; blockID <= maxBlockID; blockID += int64(network.BlocksPerRequest) { if loopErr := func() error { ctxDone, cancel := context.WithCancel(ctx) defer func() { cancel() d.logger.WithFields(log.Fields{"count": count, "time": time.Since(st).String()}).Info("blocks downloaded") }() rawBlocksChan, err := tcpclient.GetBlocksBodies(ctxDone, host, blockID, false) if err != nil { d.logger.WithFields(log.Fields{"error": err, "type": consts.BlockError}).Error("getting block body") return err } for rawBlock := range rawBlocksChan { if err = playRawBlock(rawBlock); err != nil { // d.logger.WithFields(log.Fields{"error": err, "type": consts.BlockError}).Error("playing raw block") return err } if candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)); err == nil && len(candidateNodes) > 0 { syspar.SetRunModel(consts.CandidateNodeMode) } else { syspar.SetRunModel(consts.HonorNodeMode) } count++ } return nil }(); loopErr != nil { return loopErr } } return nil } func banNodePause(host string, blockID, blockTime int64, err error) { if err == nil || !utils.IsBanError(err) { return } reason := err.Error() //log.WithFields(log.Fields{"host": host, "block_id": blockID, "block_time": blockTime, "err": err}).Error("ban node") n, err := syspar.GetNodeByHost(host) if err != nil { log.WithError(err).Error("getting node by host") return } err = node.GetNodesBanService().RegisterBadBlock(n, blockID, blockTime, reason, false) if err != nil { log.WithFields(log.Fields{"error": err, "node": hex.EncodeToString(n.PublicKey), "block": blockID}).Error("registering bad block from node") } } // GetHostWithMaxID returns host with maxBlockID func getHostWithMaxID(ctx context.Context, logger *log.Entry) (host string, maxBlockID int64, err error) { selectMode := SelectModel{} hosts, err := selectMode.GetHostWithMaxID() if err != nil { logger.WithError(err).Error("on filtering banned hosts") } host, maxBlockID, err = tcpclient.HostWithMaxBlock(ctx, hosts) if len(hosts) == 0 || err == tcpclient.ErrNodesUnavailable { hosts = conf.GetNodesAddr() return tcpclient.HostWithMaxBlock(ctx, hosts) } return } // ReplaceBlocksFromHost replaces blockchain received from the host. // Number (replaceCount) of blocks starting from blockID will be re-played. func ReplaceBlocksFromHost(ctx context.Context, host string, blockID, replaceCount int64) error { blocks, err := getBlocks(ctx, host, blockID, replaceCount) if err != nil { return err } transaction.CleanCache() // mark all transaction as unverified _, err = sqldb.MarkVerifiedAndNotUsedTransactionsUnverified() if err != nil { log.WithFields(log.Fields{ "error": err, "type": consts.DBError, }).Error("marking verified and not used transactions unverified") return utils.ErrInfo(err) } // get starting blockID from slice of blocks if len(blocks) > 0 { blockID = blocks[len(blocks)-1].Header.BlockId } // we have the slice of blocks for applying // first of all we should rollback old blocks b := &sqldb.BlockChain{} myRollbackBlocks, err := b.GetBlocksFrom(blockID-1, "desc", 0) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("getting rollback blocks from blockID") return utils.ErrInfo(err) } for _, b := range myRollbackBlocks { err := rollback.RollbackBlock(b.Data) if err != nil { return utils.ErrInfo(err) } } script.SavepointSmartVMObjects() err = processBlocks(blocks) if err != nil { script.RollbackSmartVMObjects() return err } script.ReleaseSmartVMObjects() return err } func getBlocks(ctx context.Context, host string, blockID, minCount int64) ([]*block.Block, error) { rollback := syspar.GetRbBlocks1() blocks := make([]*block.Block, 0) nextBlockID := blockID ctx, cancel := context.WithCancel(ctx) defer cancel() // load the block bodies from the host blocksCh, err := tcpclient.GetBlocksBodies(ctx, host, blockID, true) if err != nil { return nil, utils.WithBan(errors.Wrapf(err, "Getting bodies of blocks by id %d", blockID)) } for binaryBlock := range blocksCh { if blockID < 2 { break } // if the limit of blocks received from the node was exaggerated if len(blocks) >= int(rollback) { break } bl, err := block.ProcessBlockByBinData(binaryBlock, true) if err != nil { return nil, err } if bl.Header.BlockId != nextBlockID { log.WithFields(log.Fields{"header_block_id": bl.Header.BlockId, "block_id": blockID, "type": consts.InvalidObject}).Error("block ids does not match") return nil, utils.WithBan(errors.New("bad block_data['block_id']")) } // the public key of the one who has generated this block nodePublicKey, err := syspar.GetNodePublicKeyByPosition(bl.Header.NodePosition) if err != nil { log.WithFields(log.Fields{"header_block_id": bl.Header.BlockId, "block_id": blockID, "type": consts.InvalidObject}).Error("block ids does not match") return nil, utils.ErrInfo(err) } // save the block blocks = append(blocks, bl) // check the signature _, okSignErr := utils.CheckSign([][]byte{nodePublicKey}, []byte(bl.ForSign()), bl.Header.Sign, true) if okSignErr == nil && len(blocks) >= int(minCount) { break } nextBlockID-- } return blocks, nil } func processBlocks(blocks []*block.Block) error { // go through new blocks from the smallest block_id to the largest block_id prevBlocks := make(map[int64]*block.Block, 0) for i := len(blocks) - 1; i >= 0; i-- { b := blocks[i] if _, ok := prevBlocks[b.Header.BlockId-1]; ok { b.PrevHeader = prevBlocks[b.Header.BlockId-1].Header } if err := b.Check(); err != nil { return err } if err := b.PlaySafe(); err != nil { return err } prevBlocks[b.Header.BlockId] = b } return nil } ================================================ FILE: packages/daemons/candidate_node_votings.go ================================================ package daemons import ( "context" "encoding/hex" "encoding/json" "sync" "sync/atomic" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) type VotingRes struct { VoteMsgInfo network.VoteMsg `json:"voteMsgInfo"` Err string `json:"err"` } type VotingTotal struct { Data map[string]VotingRes `json:"data"` AgreeQuantity int64 `json:"agreeQuantity"` LocalAddress string `json:"localAddress"` St int64 `json:"st"` } func ToUpdateMachineStatus(currentTcpAddress, tcpAddress string, ch chan map[string]VotingRes, logger *log.Entry) error { data, err := tcpclient.UpdateMachineStatus(currentTcpAddress, tcpAddress, logger) voteMsg := &network.VoteMsg{} if err != nil { logger.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "tcpAddress": tcpAddress}).Error("sending request error") voteMsg.Msg = "tcp connection error" voteMsg.LocalAddress = currentTcpAddress voteMsg.TcpAddress = tcpAddress voteMsg.Time = time.Now().UnixMilli() ch <- map[string]VotingRes{ tcpAddress: {*voteMsg, voteMsg.Msg}, } return err } err = json.Unmarshal(data, &voteMsg) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError}).Error("JSONUnmarshallError") return err } ch <- map[string]VotingRes{ tcpAddress: {*voteMsg, ""}, } return nil } func ToBroadcastNodeConnInfo(votingTotal VotingTotal, tcpAddress string, logger *log.Entry) error { data, err := json.Marshal(votingTotal) if err != nil { return err } err = tcpclient.BroadcastNodeConnInfo(tcpAddress, data, logger) if err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "tcpAddress": tcpAddress}).Error("sending request error") return err } return nil } func CandidateNodeVoting(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } var ( candidateNodes sqldb.CandidateNodes err error agreeQuantity int64 st int64 ) defer func() { d.sleepTime = time.Minute }() candidateNodes, err = sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err != nil { return err } if len(candidateNodes) == 0 { return nil } _, NodePublicKey := utils.GetNodeKeys() NodePublicKey = "04" + NodePublicKey var ( isHonorNode bool currentTcpAddress string ) for _, node := range candidateNodes { if NodePublicKey == node.NodePubKey { isHonorNode = true currentTcpAddress = node.TcpAddress break } } if !isHonorNode { return nil } ch := make(chan map[string]VotingRes, len(candidateNodes)) var wg sync.WaitGroup for _, node := range candidateNodes { wg.Add(1) go func(tcpAddress string) { defer wg.Done() err = ToUpdateMachineStatus(currentTcpAddress, tcpAddress, ch, d.logger) if err != nil { d.logger.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "tcpAddress": tcpAddress}).Error("sending voting request error") } }(node.TcpAddress) } wg.Wait() nodeConnMap := make(map[string]VotingRes, len(ch)) for i := 0; i < cap(ch); i++ { serverVotingInfo, ok := <-ch if !ok { break } for tcpAddress, res := range serverVotingInfo { if res.Err != "" { nodeConnMap[tcpAddress] = res continue } err := checkServerSign(res.VoteMsgInfo) if err != nil { res.VoteMsgInfo.Msg = "Signature verification failed" res.VoteMsgInfo.Agree = false d.logger.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "tcpAddress": tcpAddress}).Error("sign error") } if res.VoteMsgInfo.Agree { agreeQuantity++ } nodeConnMap[tcpAddress] = res } } close(ch) st = time.Now().UnixMilli() if len(nodeConnMap) > 0 { var wg sync.WaitGroup for _, node := range candidateNodes { wg.Add(1) go func(tcpAddress string) { defer wg.Done() votingTotal := VotingTotal{Data: nodeConnMap, AgreeQuantity: agreeQuantity, LocalAddress: currentTcpAddress, St: st} err = ToBroadcastNodeConnInfo(votingTotal, tcpAddress, d.logger) if err != nil { d.logger.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "tcpAddress": tcpAddress}).Error("broadcast node conn info error") } }(node.TcpAddress) } wg.Wait() } return nil } func checkServerSign(serverVoteMsg network.VoteMsg) error { candidateNodeSql := &sqldb.CandidateNode{} err := candidateNodeSql.GetCandidateNodeByAddress(serverVoteMsg.TcpAddress) if err != nil { return err } pk, err := hex.DecodeString(candidateNodeSql.NodePubKey) if err != nil { return err } pk = crypto.CutPub(pk) _, err = crypto.Verify(pk, []byte(serverVoteMsg.VerifyVoteForSign()), serverVoteMsg.Sign) if err != nil { return err } return nil } ================================================ FILE: packages/daemons/common.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "fmt" "github.com/IBAX-io/go-ibax/packages/transaction" "strings" "time" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/statsd" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) var ( // MonitorDaemonCh is monitor daemon channel MonitorDaemonCh = make(chan []string, 100) NtpDriftFlag = false ) type daemon struct { goRoutineName string sleepTime time.Duration logger *log.Entry atomic uint32 } var daemonsList = map[string]func(context.Context, *daemon) error{ "BlocksCollection": BlocksCollection, "BlockGenerator": BlockGenerator, "Disseminator": Disseminator, "QueueParserTx": QueueParserTx, "QueueParserBlocks": QueueParserBlocks, "Confirmations": Confirmations, "Scheduler": Scheduler, "CandidateNodeVoting": CandidateNodeVoting, //"ExternalNetwork": ExternalNetwork, } var rollbackList = []string{ "BlocksCollection", "Confirmations", } func daemonLoop(ctx context.Context, goRoutineName string, handler func(context.Context, *daemon) error, retCh chan string) { logger := log.WithFields(log.Fields{"daemon_name": goRoutineName}) defer func() { if r := recover(); r != nil { logger.WithFields(log.Fields{"type": consts.PanicRecoveredError, "error": r}).Error("panic in daemon") panic(r) } }() err := WaitDB(ctx) if err != nil { return } d := &daemon{ goRoutineName: goRoutineName, sleepTime: 100 * time.Millisecond, logger: logger, } idleDelay := time.NewTimer(d.sleepTime) //defer idleDelay.Stop() for { idleDelay.Reset(d.sleepTime) select { case <-ctx.Done(): logger.Info("daemon done his work") retCh <- goRoutineName return case <-idleDelay.C: MonitorDaemonCh <- []string{d.goRoutineName, converter.Int64ToStr(time.Now().Unix())} startTime := time.Now() counterName := statsd.DaemonCounterName(goRoutineName) handler(ctx, d) statsd.Client.TimingDuration(counterName+statsd.Time, time.Now().Sub(startTime), 1.0) } } } // StartDaemons starts daemons func StartDaemons(ctx context.Context, daemonsToStart []string) { go WaitStopTime() daemonsTable := make(map[string]string) go func() { for { daemonNameAndTime := <-MonitorDaemonCh daemonsTable[daemonNameAndTime[0]] = daemonNameAndTime[1] if time.Now().Unix()%10 == 0 { log.Debugf("daemonsTable: %v\n", daemonsTable) } } }() //go Ntp_Work(ctx) // ctx, cancel := context.WithCancel(context.Background()) // utils.CancelFunc = cancel // utils.ReturnCh = make(chan string) if conf.Config.TestRollBack { daemonsToStart = rollbackList } log.WithFields(log.Fields{"daemons_to_start": daemonsToStart}).Info("starting daemons") for _, name := range daemonsToStart { handler, ok := daemonsList[name] if ok { go daemonLoop(ctx, name, handler, utils.ReturnCh) log.WithFields(log.Fields{"daemon_name": name}).Info("started") utils.DaemonsCount++ continue } log.WithFields(log.Fields{"daemon_name": name}).Warning("unknown daemon name") } } func getHostPort(h string) string { if strings.Contains(h, ":") { return h } return fmt.Sprintf("%s:%d", h, consts.DefaultTcpPort) } //ntp func Ntp_Work(ctx context.Context) { var count = 0 for { select { case <-ctx.Done(): log.Error("Ntp_Work done his work") return case <-time.After(time.Second * 4): b, err := utils.CheckClockDrift() if err != nil { log.WithFields(log.Fields{"daemon_name Ntp_Work err": err.Error()}).Error("Ntp_Work") } else { if b { NtpDriftFlag = true count = 0 } else { count++ } if count > 10 { var sp sqldb.PlatformParameter count, err := sp.GetNumberOfHonorNodes() if err != nil { log.WithFields(log.Fields{"Ntp_Work GetNumberOfHonorNodes err": err.Error()}).Error("GetNumberOfHonorNodes") } else { if NtpDriftFlag && count > 1 { NtpDriftFlag = false } } } } } } } func generateProcessBlockNew(blockHeader, prevBlock *types.BlockHeader, trs [][]byte, classifyTxsMap map[int][]*transaction.Transaction) error { blockBin, err := generateNextBlock(blockHeader, prevBlock, trs) if err != nil { return err } //err = block.InsertBlockWOForks(blockBin, true, false) err = block.InsertBlockWOForksNew(blockBin, classifyTxsMap, true, false) if err != nil { log.WithError(err).Error("on inserting new block") return err } log.WithFields(log.Fields{"block": blockHeader.String(), "type": consts.SyncProcess}).Debug("Generated block ID") return nil } func GetRemoteGoodHosts() ([]string, error) { if syspar.IsHonorNodeMode() { return node.GetNodesBanService().FilterBannedHosts(syspar.GetRemoteHosts()) } hosts := make([]string, 0) candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err != nil { log.WithError(err).Error("getting candidate node list") return nil, err } for _, node := range candidateNodes { hosts = append(hosts, node.TcpAddress) } return hosts, nil } ================================================ FILE: packages/daemons/confirmations.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "sync/atomic" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) var tick int // Confirmations gets and checks blocks from nodes // Getting amount of nodes, which has the same hash as we do func Confirmations(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } // the first 2 minutes we sleep for 10 sec for blocks to be collected tick++ d.sleepTime = 1 * time.Second if tick < 12 { d.sleepTime = 10 * time.Second } var startBlockID int64 // check last blocks, but not more than 5 confirmations := &sqldb.Confirmation{} _, err := confirmations.GetGoodBlock(consts.MinConfirmedNodes) if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting good block") return err } ConfirmedBlockID := confirmations.BlockID infoBlock := &sqldb.InfoBlock{} _, err = infoBlock.Get() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting info block") return err } lastBlockID := infoBlock.BlockID if lastBlockID == 0 { return nil } if lastBlockID-ConfirmedBlockID > 5 { startBlockID = ConfirmedBlockID + 1 d.sleepTime = 10 * time.Second tick = 0 // reset the tick } if startBlockID == 0 { startBlockID = lastBlockID } return confirmationsBlocks(ctx, d, lastBlockID, startBlockID) } func confirmationsBlocks(ctx context.Context, d *daemon, lastBlockID, startBlockID int64) error { for blockID := lastBlockID; blockID >= startBlockID; blockID-- { if err := ctx.Err(); err != nil { d.logger.WithFields(log.Fields{"type": consts.ContextError, "error": err}).Error("error in context") return err } block := sqldb.BlockChain{} _, err := block.Get(blockID) if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting block by ID") return err } hashStr := string(converter.BinToHex(block.Hash)) d.logger.WithFields(log.Fields{"hash": hashStr}).Debug("checking hash") if len(hashStr) == 0 { d.logger.WithFields(log.Fields{"hash": hashStr, "type": consts.NotFound}).Debug("hash not found") continue } var hosts []string hosts, err = GetRemoteGoodHosts() if err != nil { return err } ch := make(chan string) for i := 0; i < len(hosts); i++ { host, err := tcpclient.NormalizeHostAddress(hosts[i], consts.DefaultTcpPort) if err != nil { d.logger.WithFields(log.Fields{"host": hosts[i], "type": consts.ParseError, "error": err}).Error("wrong host address") continue } d.logger.WithFields(log.Fields{"host": host, "block_id": blockID}).Debug("checking block id confirmed at node") go func() { IsReachable(host, blockID, ch, d.logger) }() } var answer string var st0, st1 int64 for i := 0; i < len(hosts); i++ { answer = <-ch if answer == hashStr { st1++ } else { st0++ } } confirmation := &sqldb.Confirmation{} confirmation.GetConfirmation(blockID) confirmation.BlockID = blockID confirmation.Good = int32(st1) confirmation.Bad = int32(st0) confirmation.Time = time.Now().Unix() if err = confirmation.Save(); err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("saving confirmation") return err } if blockID > startBlockID && st1 >= consts.MinConfirmedNodes { break } } return nil } // IsReachable checks if there is blockID on the host func IsReachable(host string, blockID int64, ch0 chan string, logger *log.Entry) { ch := make(chan string, 1) go func() { ch <- tcpclient.CheckConfirmation(host, blockID, logger) }() select { case reachable := <-ch: ch0 <- reachable case <-time.After(consts.WaitConfirmedNodes * time.Second): ch0 <- "0" } } ================================================ FILE: packages/daemons/disseminator.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "sync/atomic" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // Disseminator is send to all nodes from nodes_connections the following data // if we are honor node: sends blocks and transactions hashes // else send the full transactions func Disseminator(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } DBLock() defer DBUnlock() var ( isHonorNode = true selectMode = SelectModel{} ) myNodePosition, err := selectMode.GetThisNodePosition() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("finding node") isHonorNode = false } if isHonorNode { // send blocks and transactions hashes d.logger.Debug("we are honor_node, sending hashes") return sendBlockWithTxHashes(ctx, myNodePosition, d.logger) } // we are not honor node for this StateID and WalletID, so just send transactions d.logger.Debug("we are honor_node, sending transactions") return sendTransactions(ctx, d.logger) } func sendTransactions(ctx context.Context, logger *log.Entry) error { // get unsent transactions trs, err := sqldb.GetAllUnsentTransactions(syspar.GetMaxTxCount()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all unsent transactions") return err } if trs == nil || len(*trs) == 0 { logger.Info("transactions not found") return nil } hosts := make([]string, 0) if syspar.IsHonorNodeMode() { hosts = syspar.GetDefaultRemoteHosts() } else { candidateNodes, err := GetCandidateNodes() if err != nil { return err } for _, node := range candidateNodes { hosts = append(hosts, node.TcpAddress) } } if err := tcpclient.SendTransacitionsToAll(ctx, hosts, *trs); err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("on sending transactions") return err } if len(hosts) > 0 { // set all transactions as sent var hashArr [][]byte for _, tr := range *trs { hashArr = append(hashArr, tr.Hash) } if err := sqldb.MarkTransactionSentBatches(hashArr); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("marking transaction sent") return err } } return nil } // send block and transactions hashes func sendBlockWithTxHashes(ctx context.Context, honorNodeID int64, logger *log.Entry) error { block, err := sqldb.BlockGetUnsent() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting unsent blocks") return err } trs, err := sqldb.GetAllUnsentTransactions(syspar.GetMaxTxCount()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting unsent transactions") return err } if (trs == nil || len(*trs) == 0) && block == nil { // it's nothing to send logger.Debug("nothing to send") return nil } selectModel := SelectModel{}.GetWorkMode() _, ok := selectModel.(*HonorNodeMode) var ( hosts []string banHosts []string candidateNodes sqldb.CandidateNodes ) if ok { hosts, banHosts, err = node.GetNodesBanService().FilterHosts(syspar.GetRemoteHosts()) } else { candidateNodes, err = GetCandidateNodes() for _, node := range candidateNodes { hosts = append(hosts, node.TcpAddress) } } if err != nil { log.WithError(err).Error("on getting remotes hosts") return err } if len(banHosts) > 0 { if err := tcpclient.SendFullBlockToAll(ctx, banHosts, nil, *trs, honorNodeID); err != nil { log.WithFields(log.Fields{"type": consts.TCPClientError, "error": err}).Warn("on sending block with hashes to ban hosts") return err } } if err := tcpclient.SendFullBlockToAll(ctx, hosts, block, *trs, honorNodeID); err != nil { log.WithFields(log.Fields{"type": consts.TCPClientError, "error": err}).Warn("on sending block with hashes to all") return err } // mark all transactions and block as sent if block != nil { err = block.MarkSent() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("marking block sent") return err } } if trs != nil { var hashArr [][]byte for _, tr := range *trs { hashArr = append(hashArr, tr.Hash) } if err := sqldb.MarkTransactionSentBatches(hashArr); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("marking transaction sent") return err } } return nil } ================================================ FILE: packages/daemons/external_network.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "encoding/hex" "encoding/json" "fmt" "net/url" "sync/atomic" "time" "github.com/IBAX-io/go-ibax/packages/api" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" log "github.com/sirupsen/logrus" ) const ( errExternalNone = iota // 0 - no error errExternalTx // 1 - tx error errExternalAttempt // 2 - attempt error errExternalTimeout // 3 - timeout of getting txstatus maxAttempts = 10 statusTimeout = 60 externalDeamonTimeout = 2 apiExt = `/api/v2/` ) var ( nodePrivateKey []byte nodeKeyID int64 nodePublicKey string authNet = map[string]string{} ) func loginNetwork(urlPath string) (connect *api.Connect, err error) { if len(nodePrivateKey) == 0 { var pubKey []byte nodePrivateKey = syspar.GetNodePrivKey() if pubKey, err = crypto.PrivateToPublic(nodePrivateKey); err != nil { return } nodeKeyID = crypto.Address(pubKey) nodePublicKey = crypto.PubToHex(pubKey) } connect = &api.Connect{ Auth: authNet[urlPath], PrivateKey: nodePrivateKey, PublicKey: nodePublicKey, Root: urlPath, } if err = connect.Login(); err != nil { authNet[urlPath] = connect.Auth } return } func SendExternalTransaction() error { var ( err error connect *api.Connect delList []int64 hash string ) toWait := map[string][]sqldb.ExternalBlockchain{} incAttempt := func(id int64) { if err = sqldb.IncExternalAttempt(id); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("IncAttempt") } } sendResult := func(item sqldb.ExternalBlockchain, block, errCode int64, resText string) { defer func() { delList = append(delList, item.Id) }() if len(item.ResultContract) == 0 { return } if err := transaction.CreateContract(item.ResultContract, nodeKeyID, map[string]any{ "Status": errCode, "Msg": resText, "Block": block, "UID": item.Uid, }, nodePrivateKey); err != nil { log.WithFields(log.Fields{"type": consts.ContractError, "err": err}).Error("CreateContract") } } list, err := sqldb.GetExternalList() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("GetExternalList") return err } timeOut := time.Now().Unix() - 10*(syspar.GetGapsBetweenBlocks()+ syspar.GetMaxBlockGenerationTime()/1000) for _, item := range list { root := item.Url + apiExt if item.Sent == 0 { if timeOut > item.TxTime { delList = append(delList, item.Id) continue } if connect, err = loginNetwork(root); err != nil { log.WithFields(log.Fields{"type": consts.AccessDenied, "error": err}).Error("loginNetwork") return err } values := url.Values{"UID": {item.Uid}} var params map[string]any if err = json.Unmarshal([]byte(item.Value), ¶ms); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("Unmarshal params") delList = append(delList, item.Id) continue } for key, val := range params { values[key] = []string{fmt.Sprint(val)} } values["nowait"] = []string{"1"} values["txtime"] = []string{converter.Int64ToStr(item.TxTime)} _, hash, err = connect.PostTxResult(item.ExternalContract, &values) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("PostContract") if item.Attempts >= maxAttempts-1 { sendResult(item, 0, errExternalAttempt, ``) } else { incAttempt(item.Id) } } else { log.WithFields(log.Fields{"hash": hash, "txtime": values["txtime"][0], "nodeKey": converter.Int64ToStr(nodeKeyID)}).Info("SendExternalTransaction") bHash, err := hex.DecodeString(hash) if err != nil { log.WithFields(log.Fields{"type": consts.ParseError, "error": err}).Error("DecodeHex") incAttempt(item.Id) } else if err = sqldb.HashExternalTx(item.Id, bHash); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("HashExternal") } } } else { toWait[item.Url] = append(toWait[item.Url], item) } } for _, waitList := range toWait { if connect, err = loginNetwork(waitList[0].Url + apiExt); err != nil { log.WithFields(log.Fields{"type": consts.AccessDenied, "error": err}).Error("loginNetwork") continue } var hashes []string for _, item := range waitList { hashes = append(hashes, hex.EncodeToString(item.Hash)) } results, err := connect.WaitTxList(hashes) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("WaitTxList") continue } timeOut = time.Now().Unix() - statusTimeout for _, item := range waitList { if result, ok := results[hex.EncodeToString(item.Hash)]; ok { errCode := int64(errExternalNone) if result.BlockID == 0 { errCode = errExternalTx } sendResult(item, result.BlockID, errCode, result.Msg) } else if timeOut > item.TxTime { sendResult(item, 0, errExternalTimeout, ``) } } } if len(delList) > 0 { if err = sqldb.DelExternalList(delList); err != nil { return err } } return nil } // ExternalNetwork sends txinfo to the external network func ExternalNetwork(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } d.sleepTime = externalDeamonTimeout * time.Second return SendExternalTransaction() } ================================================ FILE: packages/daemons/genesis.go ================================================ package daemons import ( "context" "os" "github.com/pkg/errors" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) func InitialLoad(logger *log.Entry) error { // check for initial load toLoad, err := needLoad(logger) if err != nil { return err } if toLoad { logger.Debug("start first block loading") if err := firstLoad(logger); err != nil { logger.WithError(err).Error("cant load first block form file or host") return err } if err := sqldb.UpdateSchema(); err != nil { return err } } return nil } // init first block from file or from embedded value func loadFirstBlock(logger *log.Entry) error { newBlock, err := os.ReadFile(conf.Config.DirPathConf.FirstBlockPath) if err != nil && len(conf.Config.BootNodes.NodesAddr) == 0 { return errors.Wrap(err, "reading first block from file path") } if len(conf.Config.BootNodes.NodesAddr) > 0 { ctxDone, cancel := context.WithCancel(context.Background()) defer func() { cancel() }() host, _, err := getHostWithMaxID(ctxDone, logger) if err != nil { return errors.Wrap(err, "reading host") } rawBlocksChan, err := tcpclient.GetBlocksBodies(ctxDone, host, 1, true) if err != nil { return err } for rawBlock := range rawBlocksChan { newBlock = rawBlock } } if err = block.InsertBlockWOForksNew(newBlock, nil, false, true); err != nil { logger.WithFields(log.Fields{"type": consts.ParserError, "error": err}).Error("inserting new block") return err } return nil } func firstLoad(logger *log.Entry) error { DBLock() defer DBUnlock() return loadFirstBlock(logger) } func needLoad(logger *log.Entry) (bool, error) { infoBlock := &sqldb.InfoBlock{} _, err := infoBlock.Get() if err != nil { logger.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("getting info block") return false, err } // we have empty blockchain, we need to load blockchain from file or other source if infoBlock.BlockID == 0 { logger.Debug("blockchain should be loaded") return true, nil } return false, nil } ================================================ FILE: packages/daemons/locking.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "sync" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" log "github.com/sirupsen/logrus" ) var mutex = sync.Mutex{} // WaitDB waits for the end of the installation func WaitDB(ctx context.Context) error { // There is could be the situation when installation is not over yet. // Database could be created but tables are not inserted yet if sqldb.DBConn != nil && CheckDB() { return nil } // poll a base with period tick := time.NewTicker(1 * time.Second) for { select { case <-tick.C: if sqldb.DBConn != nil && CheckDB() { return nil } case <-ctx.Done(): return ctx.Err() } } } // CheckDB check if installation complete or not func CheckDB() bool { install := &sqldb.Install{} err := install.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting install") } if install.Progress == sqldb.ProgressComplete { return true } return false } // DBLock locks daemons func DBLock() { mutex.Lock() } // DBUnlock unlocks database func DBUnlock() { transaction.CleanCache() mutex.Unlock() } ================================================ FILE: packages/daemons/mode.go ================================================ package daemons import ( "encoding/hex" "errors" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type Model interface { GetThisNodePosition() (int64, error) GetHostWithMaxID() (hosts []string, err error) } type HonorNodeMode struct { } func (honorNodeMode *HonorNodeMode) GetThisNodePosition() (int64, error) { return syspar.GetNodePositionByPublicKey(syspar.GetNodePubKey()) } func (honorNodeMode *HonorNodeMode) GetHostWithMaxID() ([]string, error) { nbs := node.GetNodesBanService() hosts, err := nbs.FilterBannedHosts(syspar.GetRemoteHosts()) if err != nil { log.WithError(err).Error("on filtering banned hosts") return nil, err } return hosts, nil } type CandidateNodeMode struct { } func (candidateNodeMode *CandidateNodeMode) GetThisNodePosition() (int64, error) { return GetCandidateNodePositionByPublicKey() } func (candidateNodeMode *CandidateNodeMode) GetHostWithMaxID() ([]string, error) { candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err != nil { log.WithError(err).Error("getting candidate node list") return nil, err } hosts := make([]string, len(candidateNodes)) for index, node := range candidateNodes { hosts[index] = node.TcpAddress } return hosts, nil } type SelectModel struct { } func (s *SelectModel) GetThisNodePosition() (int64, error) { return s.GetWorkMode().GetThisNodePosition() } func (s *SelectModel) GetHostWithMaxID() ([]string, error) { return s.GetWorkMode().GetHostWithMaxID() } func (s SelectModel) GetWorkMode() Model { if syspar.IsHonorNodeMode() { return &HonorNodeMode{} //1 } return &CandidateNodeMode{} //2 } func GetCandidateNodePositionByPublicKey() (int64, error) { NodePublicKey := hex.EncodeToString(syspar.GetNodePubKey()) if len(NodePublicKey) < 1 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node public key is empty") return 0, errors.New(`node public key is empty`) } candidateNode := &sqldb.CandidateNode{} if candidateNode.GetCandidateNodeByPublicKey(NodePublicKey) != nil { log.WithFields(log.Fields{"error": candidateNode.GetCandidateNodeByPublicKey(NodePublicKey)}).Error("getting candidate node error") return 0, candidateNode.GetCandidateNodeByPublicKey(NodePublicKey) } return candidateNode.ID, nil } func GetCandidateNodes() (sqldb.CandidateNodes, error) { nodePublicKey := hex.EncodeToString(syspar.GetNodePubKey()) if len(nodePublicKey) < 1 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node public key is empty") return nil, errors.New(`node public key is empty`) } candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err != nil { log.WithError(err).Error("getting candidate node error") return nil, err } ret := make(sqldb.CandidateNodes, 0) for _, node := range candidateNodes { if "04"+nodePublicKey != node.NodePubKey { ret = append(ret, node) } } return ret, nil } ================================================ FILE: packages/daemons/monitoring.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "bytes" "fmt" "net/http" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // Monitoring starts monitoring func Monitoring(w http.ResponseWriter, r *http.Request) { var buf bytes.Buffer infoBlock := &sqldb.InfoBlock{} _, err := infoBlock.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting info block") logError(w, fmt.Errorf("can't get info block: %s", err)) return } addKey(&buf, "info_block_id", infoBlock.BlockID) addKey(&buf, "info_block_hash", converter.BinToHex(infoBlock.Hash)) addKey(&buf, "info_block_time", infoBlock.Time) addKey(&buf, "info_block_key_id", infoBlock.KeyID) addKey(&buf, "info_block_ecosystem_id", infoBlock.EcosystemID) addKey(&buf, "info_block_node_position", infoBlock.NodePosition) addKey(&buf, "honor_nodes_count", syspar.GetNumberOfNodes()) block := &sqldb.BlockChain{} _, err = block.GetMaxBlock() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting max block") logError(w, fmt.Errorf("can't get max block: %s", err)) return } addKey(&buf, "last_block_id", block.ID) addKey(&buf, "last_block_hash", converter.BinToHex(block.Hash)) addKey(&buf, "last_block_time", block.Time) addKey(&buf, "last_block_wallet", block.KeyID) addKey(&buf, "last_block_state", block) addKey(&buf, "last_block_transactions", block.Tx) trCount, err := sqldb.GetTransactionCountAll() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting transaction count all") logError(w, fmt.Errorf("can't get transactions count: %s", err)) return } addKey(&buf, "transactions_count", trCount) w.Write(buf.Bytes()) } func addKey(buf *bytes.Buffer, key string, value any) error { val, err := converter.InterfaceToStr(value) if err != nil { return err } line := fmt.Sprintf("%s\t%s\n", key, val) buf.Write([]byte(line)) return nil } func logError(w http.ResponseWriter, err error) { w.Write([]byte(err.Error())) return } ================================================ FILE: packages/daemons/queue_parser_blocks.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "fmt" "sync/atomic" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) /* Take the block from the queue. If this block has the bigger block id than the last block from our chain, then find the fork * If fork begins less then variables->rollback_blocks blocks ago, than * - get the whole chain of blocks * - roll back data from our blocks * - insert the frontal data from a new chain * - if there is no error, then roll back our data from the blocks * - and insert new data * - if there are errors, then roll back to the former data * */ // QueueParserBlocks parses and applies blocks from the queue func QueueParserBlocks(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } DBLock() defer DBUnlock() infoBlock := &sqldb.InfoBlock{} _, err := infoBlock.Get() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting info block") return err } queueBlock := &sqldb.QueueBlock{} _, err = queueBlock.Get() if err != nil { d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting queue block") return err } if len(queueBlock.Hash) == 0 { d.logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("queue block not found") return err } // check if the block gets in the rollback_blocks_1 limit if queueBlock.BlockID > infoBlock.BlockID+syspar.GetRbBlocks1() { queueBlock.DeleteOldBlocks() return utils.ErrInfo("rollback_blocks_1") } // is it old block in queue ? if queueBlock.BlockID <= infoBlock.BlockID { queueBlock.DeleteOldBlocks() return utils.ErrInfo(fmt.Errorf("old block %d <= %d", queueBlock.BlockID, infoBlock.BlockID)) } if queueBlock.HonorNodeID == conf.Config.KeyID { d.logger.WithFields(log.Fields{"type": consts.DuplicateObject}).Debug("queueBlock generated by myself", queueBlock.BlockID) return utils.ErrInfo(fmt.Errorf("queueBlock generated by myself: %d", queueBlock.BlockID)) } nodeHost, err := syspar.GetNodeHostByPosition(queueBlock.HonorNodeID) if err != nil { queueBlock.DeleteQueueBlockByHash() return utils.ErrInfo(err) } blockID := queueBlock.BlockID host := utils.GetHostPort(nodeHost) // update our chain till maxBlockID from the host return UpdateChain(ctx, d, host, blockID) } ================================================ FILE: packages/daemons/queue_parser_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "sync/atomic" "github.com/IBAX-io/go-ibax/packages/transaction" ) // QueueParserTx parses transaction from the queue func QueueParserTx(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } DBLock() defer DBUnlock() // //infoBlock := &sqldb.InfoBlock{} //_, err := infoBlock.Get() //if err != nil { // d.logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting info block") // return err //} //if infoBlock.BlockId == 0 { // d.logger.Debug("no blocks for parsing") // return nil //} err := transaction.ProcessTransactionsQueue(nil) if err != nil { d.logger.WithError(err).Error("parsing transactions") return err } //for { // select { // case attempt := <-transaction.ChanTxAttempt: // if attempt { // err = transaction.ProcessTransactionsAttempt(p.DbTransaction) // if err != nil { // d.logger.WithError(err).Error("parsing transactions attempt") // return err // } // } // default: // return nil // } //} return nil } ================================================ FILE: packages/daemons/scheduler.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "context" "fmt" "sync/atomic" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/scheduler" "github.com/IBAX-io/go-ibax/packages/scheduler/contract" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) func loadContractTasks() error { stateIDs, _, err := sqldb.GetAllSystemStatesIDs() if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("get all system states ids") return err } for _, stateID := range stateIDs { if !sqldb.NewDbTransaction(nil).IsTable(fmt.Sprintf("%d_cron", stateID)) { return nil } c := sqldb.Cron{} c.SetTablePrefix(fmt.Sprintf("%d", stateID)) tasks, err := c.GetAllCronTasks() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("get all cron tasks") return err } for _, cronTask := range tasks { err = scheduler.UpdateTask(&scheduler.Task{ ID: cronTask.UID(), CronSpec: cronTask.Cron, Handler: &contract.ContractHandler{ Contract: cronTask.Contract, }, }) if err != nil { return err } } } return nil } // Scheduler starts contracts on schedule func Scheduler(ctx context.Context, d *daemon) error { if atomic.CompareAndSwapUint32(&d.atomic, 0, 1) { defer atomic.StoreUint32(&d.atomic, 0) } else { return nil } d.sleepTime = time.Hour return loadContractTasks() } ================================================ FILE: packages/daemons/stopdaemons.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "time" "github.com/IBAX-io/go-ibax/packages/chain/system" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) // WaitStopTime closes the database and stop daemons func WaitStopTime() { var first bool for { if sqldb.DBConn == nil { time.Sleep(time.Second * 3) continue } if !first { err := sqldb.NewDbTransaction(nil).Delete("stop_daemons", "") if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting from stop daemons") } first = true } dExists, err := sqldb.NewDbTransaction(nil).Single(`SELECT stop_time FROM stop_daemons`).Int64() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting stop_time from StopDaemons") } if dExists > 0 { utils.CancelFunc() for i := 0; i < utils.DaemonsCount; i++ { name := <-utils.ReturnCh log.WithFields(log.Fields{"daemon_name": name}).Debug("daemon stopped") } err := sqldb.GormClose() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("gorm close") } err = system.RemovePidFile() if err != nil { log.WithFields(log.Fields{ "type": consts.IOError, "error": err, }).Error("removing pid file") panic(err) } } time.Sleep(time.Second) } } ================================================ FILE: packages/daemons/upd_full_nodes.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons ================================================ FILE: packages/daemons/wait_for_signals.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package daemons import ( "github.com/IBAX-io/go-ibax/packages/chain/system" "github.com/IBAX-io/go-ibax/packages/statsd" "os" "os/signal" "syscall" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) /* #include #include extern void go_callback_int(); static inline void SigBreak_Handler(int n_signal){ printf("closed\n"); go_callback_int(); } static inline void waitSig() { #if (WIN32 || WIN64) signal(SIGBREAK, &SigBreak_Handler); signal(SIGINT, &SigBreak_Handler); #endif } */ import ( "C" ) //export go_callback_int func go_callback_int() { SigChan <- syscall.Signal(1) } // SigChan is a channel var SigChan chan os.Signal func waitSig() { C.waitSig() } // WaitForSignals waits for Interrupt os.Kill signals func WaitForSignals() { SigChan = make(chan os.Signal, 1) waitSig() go func() { signal.Notify(SigChan, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGQUIT) for { select { case <-SigChan: if utils.CancelFunc != nil { utils.CancelFunc() for i := 0; i < utils.DaemonsCount; i++ { name := <-utils.ReturnCh log.WithFields(log.Fields{"daemon_name": name}).Debug("daemon stopped") } log.Debug("Daemons killed") } if sqldb.DBConn != nil { err := sqldb.GormClose() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("closing gorm") } } err := system.RemovePidFile() if err != nil { log.WithFields(log.Fields{ "type": consts.IOError, "error": err, "path": conf.Config.GetPidPath(), }).Error("removing file") } statsd.Close() os.Exit(1) } } }() } ================================================ FILE: packages/language/language.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package language import ( "encoding/json" "strconv" "strings" "sync" "unicode/utf8" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) //cacheLang is cache for language, first level is lang_name, second is lang dictionary type cacheLang struct { res map[string]*map[string]string } var ( // LangList is the list of available languages. It stores two-bytes codes LangList []string lang = make(map[int]*cacheLang) mutex = &sync.RWMutex{} ) // IsLang checks if there is a language with code name func IsLang(code string) bool { if LangList == nil { return true } for _, val := range LangList { if val == code { return true } } return false } // DefLang returns the default language func DefLang() string { if LangList == nil { return `en` } return LangList[0] } // UpdateLang updates language sources for the specified state func UpdateLang(state int, name, value string) error { mutex.Lock() defer mutex.Unlock() if _, ok := lang[state]; !ok { lang[state] = &cacheLang{make(map[string]*map[string]string)} } var ires map[string]string err := json.Unmarshal([]byte(value), &ires) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "value": value, "error": err}).Error("Unmarshalling json") return err } for key, val := range ires { ires[strings.ToLower(key)] = val } if len(ires) > 0 { (*lang[state]).res[name] = &ires } return nil } // loadLang download the language sources from database for the state func loadLang(transaction *sqldb.DbTransaction, state int) error { language := &sqldb.Language{} prefix := strconv.FormatInt(int64(state), 10) languages, err := language.GetAll(transaction, prefix) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Error querying all languages") return err } list := make([]map[string]string, 0) for _, l := range languages { list = append(list, l.ToMap()) } res := make(map[string]*map[string]string) for _, ilist := range list { var ires map[string]string err := json.Unmarshal([]byte(ilist[`res`]), &ires) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "value": ilist["res"], "error": err}).Error("Unmarshalling json") } for key, val := range ires { ires[strings.ToLower(key)] = val } res[ilist[`name`]] = &ires } mutex.Lock() defer mutex.Unlock() if _, ok := lang[state]; !ok { lang[state] = &cacheLang{} } lang[state].res = res return nil } // LangText looks for the specified word through language sources and returns the meaning of the source // if it is found. Search goes according to the languages specified in 'accept' func LangText(transaction *sqldb.DbTransaction, in string, state int, accept string) (string, bool) { if strings.IndexByte(in, ' ') >= 0 || state == 0 { return in, false } ecosystem, name := converter.ParseName(in) if ecosystem != 0 { state = int(ecosystem) in = name } if state == 0 { return in, false } if _, ok := lang[state]; !ok { if err := loadLang(transaction, state); err != nil { return err.Error(), false } } mutex.RLock() defer mutex.RUnlock() langs := strings.Split(accept, `,`) if _, ok := (*lang[state]).res[in]; !ok { return in, false } if lres, ok := (*lang[state]).res[in]; ok { lng := DefLang() for _, val := range langs { val = strings.ToLower(val) if len(val) < 2 { break } if !IsLang(val[:2]) { continue } if len(val) >= 5 && val[2] == '-' { if _, ok := (*lres)[val[:5]]; ok { lng = val[:5] break } } if _, ok := (*lres)[val[:2]]; ok { lng = val[:2] break } } if len((*lres)[lng]) == 0 { for _, val := range *lres { return val, true } } return (*lres)[lng], true } return in, false } // LangMacro replaces all inclusions of $resname$ in the incoming text with the corresponding language resources, // if they exist func LangMacro(input string, state int, accept string) string { if !strings.ContainsRune(input, '$') { return input } syschar := '$' length := utf8.RuneCountInString(input) result := make([]rune, 0, length) isName := false name := make([]rune, 0, 128) clearname := func() { result = append(append(result, syschar), name...) isName = false name = name[:0] } for _, r := range input { if r != syschar { if isName { name = append(name, r) if len(name) > 64 || r < ' ' { clearname() } } else { result = append(result, r) } continue } if isName { value, ok := LangText(nil, string(name), state, accept) if ok { result = append(result, []rune(value)...) isName = false } else { result = append(append(result, syschar), name...) } name = name[:0] } else { isName = true } } if isName { result = append(append(result, syschar), name...) } return string(result) } // GetLang returns the first language from accept-language func GetLang(state int, accept string) (lng string) { lng = DefLang() for _, val := range strings.Split(accept, `,`) { if len(val) < 2 { continue } if !IsLang(val[:2]) { continue } lng = val[:2] break } return } ================================================ FILE: packages/migration/clb/applications_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var applicationsDataSQL = ` INSERT INTO "1_applications" (id, name, conditions, ecosystem) VALUES (next_id('1_applications'), 'System', 'ContractConditions("MainCondition")', '1'); ` ================================================ FILE: packages/migration/clb/clb_data_contracts.go ================================================ // Code generated by go generate; DO NOT EDIT. package clb var contractsDataSQL = ` INSERT INTO "1_contracts" (id, name, value, token_id, conditions, app_id, ecosystem) VALUES (next_id('1_contracts'), 'AccessControlMode', 'contract AccessControlMode { data { VotingId int "optional" } func decentralizedAutonomous(){ if !DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":2}).Row(){ warning "control mode DAO error" } var prev string prev = $stack[0] if Len($stack) > 3{ prev = $stack[Len($stack) - 3] } if prev != "@1VotingDecisionCheck" { warning LangRes("@1contract_start_votingdecisioncheck_only") } $voting = DBFind("@1votings").Where({"ecosystem": $ecosystem_id, "id": $VotingId,"voting->name":{"$begin":"voting_for_control_mode_template"}}).Columns("voting->type_decision,flags->success,voting->type").Row() if Int($voting["voting.type"]) != 2 { warning LangRes("@1voting_type_invalid") } if Int($voting["voting.type_decision"]) != 4 { warning LangRes("@1voting_error_decision") } if Int($voting["flags.success"]) != 1 { warning LangRes("@1voting_error_success") } } func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } if $control["control_mode"] == 2 && $VotingId{ decentralizedAutonomous() return } DeveloperCondition() } conditions { $VotingId = Int($VotingId) chooseControl() $result = $control["control_mode"] } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'AccessVoteTempRun', 'contract AccessVoteTempRun { data { ContractAccept string "optional" ContractAcceptParams map "optional" } func votingCheck(){ var app_id int app_id = Int(DBFind("@1applications").Where({"ecosystem": $ecosystem_id, "name": "Basic"}).One("id")) $templateId = Int(DBFind("@1app_params").Where({"app_id": app_id, "name": "voting_template_control_mode", "ecosystem": $ecosystem_id}).One("value")) if $templateId == 0 { warning LangRes("@1template_id_not_found") } } action { votingCheck() var temp map temp["TemplateId"] = $templateId temp["Duration"] = 7 temp["ContractAccept"] = $ContractAccept temp["ContractAcceptParams"] = JSONEncode($ContractAcceptParams) CallContract("@1VotingTemplateRun",temp) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'BindWallet', 'contract BindWallet { data { Id int } conditions { $cur = DBRow("contracts").Columns("id,conditions,wallet_id").WhereId($Id) if !$cur { error Sprintf("Contract %d does not exist", $Id) } Eval($cur["conditions"]) if $key_id != Int($cur["wallet_id"]) { error Sprintf("Wallet %d cannot activate the contract", $key_id) } } action { BndWallet($Id, $ecosystem_id) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'CallDelayedContract', 'contract CallDelayedContract { data { Id int } conditions { HonorNodeCondition() var rows array rows = DBFind("@1delayed_contracts").Where({"id": $Id, "deleted": 0}) if !Len(rows) { warning Sprintf(LangRes("@1template_delayed_contract_not_exist"), $Id) } $cur = rows[0] $limit = Int($cur["limit"]) $counter = Int($cur["counter"]) if $block < Int($cur["block_id"]) { warning Sprintf(LangRes("@1template_delayed_contract_error"), $Id, $cur["block_id"], $block) } if $limit > 0 && $counter >= $limit { warning Sprintf(LangRes("@1template_delayed_contract_limited"), $Id) } } action { $counter = $counter + 1 var block_id int block_id = $block if $limit == 0 || $limit > $counter { block_id = block_id + Int($cur["every_block"]) } DBUpdate("@1delayed_contracts", $Id, {"counter": $counter, "block_id": block_id}) var params map CallContract($cur["contract"], params) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'CheckNodesBan', 'contract CheckNodesBan { func getPermission() { var array_permissions array result i int prevContract string array_permissions = ["@1CheckNodesBan"] prevContract = $stack[0] if Len($stack) > 2 { prevContract = $stack[Len($stack) - 2] } while i < Len(array_permissions) { var contract_name string contract_name = array_permissions[i] if contract_name == prevContract { result = 1 } i = i + 1 } if result == 0 { warning LangRes("@1contract_chain_distorted") } } conditions { getPermission() HonorNodeCondition() var rows array rows = DBFind("@1delayed_contracts").Where({"contract": "@1CheckNodesBan", "deleted": 0}) if !Len(rows) { warning Sprintf(LangRes("@1template_delayed_contract_not_exist"), $Id) } $cur = rows[0] $counter = Int($cur["counter"]) + 1 $Id = Int($cur["id"]) } action { DBUpdateExt("@1delayed_contracts", {"id":$Id}, {"counter": $counter}) UpdateNodesBan($block_time) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditAppParam', 'contract EditAppParam { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("app_params", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("app_params", $Id, pars) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditApplication', 'contract EditApplication { data { ApplicationId int Conditions string "optional" } func onlyConditions() bool { return $Conditions && false } conditions { RowConditions("applications", $ApplicationId, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("applications", $ApplicationId, pars) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditColumn', 'contract EditColumn { data { TableName string Name string Permissions string } conditions { ColumnCondition($TableName, $Name, "", $Permissions) } action { PermColumn($TableName, $Name, $Permissions) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditContract', 'contract EditContract { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("contracts", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } $cur = DBFind("contracts").Columns("id,value,conditions,wallet_id,token_id").WhereId($Id).Row() if !$cur { error Sprintf("Contract %d does not exist", $Id) } if $Value { ValidateEditContractNewValue($Value, $cur["value"]) } $recipient = Int($cur["wallet_id"]) } action { UpdateContract($Id, $Value, $Conditions, $recipient, $cur["token_id"]) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditCron', 'contract EditCron { data { Id int Contract string Cron string "optional" Limit int "optional" Till string "optional date" Conditions string } conditions { ConditionById("cron", true) ValidateCron($Cron) } action { if !$Till { $Till = "1970-01-01 00:00:00" } if !HasPrefix($Contract, "@") { $Contract = "@" + Str($ecosystem_id) + $Contract } DBUpdate("cron", $Id, {"cron": $Cron,"contract": $Contract, "counter":$Limit, "till": $Till, "conditions":$Conditions}) UpdateCron($Id) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditLang', 'contract EditLang { data { Id int Trans string } conditions { EvalCondition("parameters", "changing_language", "value") $lang = DBFind("languages").Where({id: $Id}).Row() } action { EditLanguage($Id, $lang["name"], $Trans) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditMenu', 'contract EditMenu { data { Id int Value string "optional" Title string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value && !$Title } conditions { RowConditions("menu", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Title { pars["title"] = $Title } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("menu", $Id, pars) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditPage', 'contract EditPage { data { Id int Value string "optional" Menu string "optional" Conditions string "optional" ValidateCount int "optional" ValidateMode string "optional" } func onlyConditions() bool { return $Conditions && !$Value && !$Menu && !$ValidateCount } func preparePageValidateCount(count int) int { var min, max int min = Int(EcosysParam("min_page_validate_count")) max = Int(EcosysParam("max_page_validate_count")) if count < min { count = min } else { if count > max { count = max } } return count } conditions { RowConditions("pages", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } $ValidateCount = preparePageValidateCount($ValidateCount) } action { var pars map if $Value { pars["value"] = $Value } if $Menu { pars["menu"] = $Menu } if $Conditions { pars["conditions"] = $Conditions } if $ValidateCount { pars["validate_count"] = $ValidateCount } if $ValidateMode { if $ValidateMode != "1" { $ValidateMode = "0" } pars["validate_mode"] = $ValidateMode } if pars { DBUpdate("pages", $Id, pars) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditParameter', 'contract EditParameter { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { DeveloperCondition() RowConditions("@1parameters", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } if $Value { $Name = DBFind("@1parameters").Where({"id": $Id, "ecosystem": $ecosystem_id}).One("name") if $Name == "founder_account" { var account string account = IdToAddress(Int($Value)) if !DBFind("@1keys").Where({"account": account, "ecosystem": $ecosystem_id, "deleted": 0}).One("id") { warning Sprintf(LangRes("@1template_user_not_found"), $Value) } } if $Name == "max_block_user_tx" || $Name == "money_digit" || $Name == "max_sum" || $Name == "min_page_validate_count" || $Name == "max_page_validate_count" { if Size($Value) == 0 { warning LangRes("@1value_not_received") } if Int($Value) <= 0 { warning LangRes("@1value_must_greater_zero") } } } } action { var pars map if $Value { if $Value == ` + "`" + `""` + "`" + ` { pars["value"] = "" } else { pars["value"] = $Value } } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("@1parameters", $Id, pars) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditSnippet', 'contract EditSnippet { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("snippets", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("snippets", $Id, pars) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'EditTable', 'contract EditTable { data { Name string InsertPerm string UpdatePerm string NewColumnPerm string ReadPerm string "optional" } conditions { if !$InsertPerm { info("Insert condition is empty") } if !$UpdatePerm { info("Update condition is empty") } if !$NewColumnPerm { info("New column condition is empty") } var permissions map permissions["insert"] = $InsertPerm permissions["update"] = $UpdatePerm permissions["new_column"] = $NewColumnPerm if $ReadPerm { permissions["read"] = $ReadPerm } $Permissions = permissions TableConditions($Name, "", JSONEncode($Permissions)) } action { PermTable($Name, JSONEncode($Permissions)) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'HonorNodeCondition', 'contract HonorNodeCondition { conditions { var account_key int account_key = AddressToId($account_id) if IsHonorNodeKey(account_key) { return } warning "HonorNodeCondition: Sorry, you do not have access to this action" } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'Import', 'contract Import { data { Data string } conditions { $ApplicationId = 0 var app_map map app_map = DBFind("@1buffer_data").Columns("value->app_name").Where({"key": "import_info", "account": $account_id, "ecosystem": $ecosystem_id}).Row() if app_map { var app_id int ival string ival = Str(app_map["value.app_name"]) app_id = Int(DBFind("@1applications").Columns("id").Where({"name": ival, "ecosystem": $ecosystem_id}).One("id")) if app_id { $ApplicationId = app_id } } } action { var editors, creators map editors["pages"] = "EditPage" editors["snippets"] = "EditSnippet" editors["menu"] = "EditMenu" editors["app_params"] = "EditAppParam" editors["languages"] = "EditLang" editors["contracts"] = "EditContract" editors["tables"] = "" // nothing creators["pages"] = "NewPage" creators["snippets"] = "NewSnippet" creators["menu"] = "NewMenu" creators["app_params"] = "NewAppParam" creators["languages"] = "NewLang" creators["contracts"] = "NewContract" creators["tables"] = "NewTable" var dataImport array dataImport = JSONDecode($Data) var i int while i < Len(dataImport) { var item cdata map type name string cdata = dataImport[i] if cdata { cdata["ApplicationId"] = $ApplicationId type = cdata["Type"] name = cdata["Name"] // Println(Sprintf("import %v: %v", type, cdata["Name"])) var tbl string tbl = "@1" + Str(type) if type == "app_params" { item = DBFind(tbl).Where({"name": name, "ecosystem": $ecosystem_id, "app_id": $ApplicationId}).Row() } else { item = DBFind(tbl).Where({"name": name, "ecosystem": $ecosystem_id}).Row() } var contractName string if item { contractName = editors[type] cdata["Id"] = Int(item["id"]) if type == "contracts" { if item["conditions"] == "false" { // ignore updating impossible contractName = "" } } elif type == "menu" { var menu menuItem string menu = Replace(item["value"], " ", "") menu = Replace(menu, "\n", "") menu = Replace(menu, "\r", "") menuItem = Replace(cdata["Value"], " ", "") menuItem = Replace(menuItem, "\n", "") menuItem = Replace(menuItem, "\r", "") if Contains(menu, menuItem) { // ignore repeated contractName = "" } else { cdata["Value"] = item["value"] + "\n" + cdata["Value"] } } } else { contractName = creators[type] } if contractName != "" { CallContract(contractName, cdata) } } i = i + 1 } // Println(Sprintf("> time: %v", $time)) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'ImportUpload', 'contract ImportUpload { data { Data file } conditions { $Body = BytesToString($Data["Body"]) $limit = 10 // data piece size of import } action { // init buffer_data, cleaning old buffer var initJson map $import_id = Int(DBFind("@1buffer_data").Where({"account": $account_id, "key": "import", "ecosystem": $ecosystem_id}).One("id")) if $import_id { DBUpdate("@1buffer_data", $import_id, {"value": initJson}) } else { $import_id = DBInsert("@1buffer_data", {"account": $account_id, "key": "import", "value": initJson, "ecosystem": $ecosystem_id}) } $info_id = Int(DBFind("@1buffer_data").Where({"account": $account_id, "key": "import_info", "ecosystem": $ecosystem_id}).One("id")) if $info_id { DBUpdate("@1buffer_data", $info_id, {"value": initJson}) } else { $info_id = DBInsert("@1buffer_data", {"account": $account_id, "key": "import_info", "value": initJson, "ecosystem": $ecosystem_id}) } var input map arrData array input = JSONDecode($Body) arrData = input["data"] var pages_arr blocks_arr menu_arr parameters_arr languages_arr contracts_arr tables_arr array // IMPORT INFO var i lenArrData int item map lenArrData = Len(arrData) while i < lenArrData { item = arrData[i] if item["Type"] == "pages" { pages_arr = Append(pages_arr, item["Name"]) } elif item["Type"] == "snippets" { blocks_arr = Append(blocks_arr, item["Name"]) } elif item["Type"] == "menu" { menu_arr = Append(menu_arr, item["Name"]) } elif item["Type"] == "app_params" { parameters_arr = Append(parameters_arr, item["Name"]) } elif item["Type"] == "languages" { languages_arr = Append(languages_arr, item["Name"]) } elif item["Type"] == "contracts" { contracts_arr = Append(contracts_arr, item["Name"]) } elif item["Type"] == "tables" { tables_arr = Append(tables_arr, item["Name"]) } i = i + 1 } var inf map inf["app_name"] = input["name"] inf["pages"] = Join(pages_arr, ", ") inf["pages_count"] = Len(pages_arr) inf["snippets"] = Join(blocks_arr, ", ") inf["blocks_count"] = Len(blocks_arr) inf["menu"] = Join(menu_arr, ", ") inf["menu_count"] = Len(menu_arr) inf["parameters"] = Join(parameters_arr, ", ") inf["parameters_count"] = Len(parameters_arr) inf["languages"] = Join(languages_arr, ", ") inf["languages_count"] = Len(languages_arr) inf["contracts"] = Join(contracts_arr, ", ") inf["contracts_count"] = Len(contracts_arr) inf["tables"] = Join(tables_arr, ", ") inf["tables_count"] = Len(tables_arr) if 0 == inf["pages_count"] + inf["blocks_count"] + inf["menu_count"] + inf["parameters_count"] + inf["languages_count"] + inf["contracts_count"] + inf["tables_count"] { warning "Invalid or empty import file" } // IMPORT DATA // the contracts is imported in one piece, the rest is cut under the $limit var sliced contracts array i = 0 while i < lenArrData { var items array l int item map while l < $limit && (i + l < lenArrData) { item = arrData[i + l] if item["Type"] == "contracts" { contracts = Append(contracts, item) } else { items = Append(items, item) } l = l + 1 } var batch map batch["Data"] = JSONEncode(items) sliced = Append(sliced, batch) i = i + $limit } if Len(contracts) > 0 { var batch map batch["Data"] = JSONEncode(contracts) sliced = Append(sliced, batch) } input["data"] = sliced // storing DBUpdate("@1buffer_data", $import_id, {"value": input}) DBUpdate("@1buffer_data", $info_id, {"value": inf}) var name string name = Str(input["name"]) var cndns string cndns = Str(input["conditions"]) if !DBFind("@1applications").Columns("id").Where({"name": name, "ecosystem": $ecosystem_id}).One("id") { DBInsert("@1applications", {"name": name, "conditions": cndns, "ecosystem": $ecosystem_id}) } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'ListCLB', 'contract ListCLB { data {} conditions {} action { $result = GetCLBList() } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'MainCondition', 'contract MainCondition { conditions { if EcosysParam("founder_account")!=$key_id { warning "Sorry, you do not have access to this action." } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewAppParam', 'contract NewAppParam { data { ApplicationId int Name string Value string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("app_params").Columns("id").Where({"name":$Name}).One("id") { warning Sprintf( "Application parameter %s already exists", $Name) } } action { DBInsert("app_params", {app_id: $ApplicationId, name: $Name, value: $Value, conditions: $Conditions}) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewApplication', 'contract NewApplication { data { Name string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if Size($Name) == 0 { warning "Application name missing" } if DBFind("applications").Columns("id").Where({name:$Name}).One("id") { warning Sprintf( "Application %s already exists", $Name) } } action { $result = DBInsert("applications", {name: $Name,conditions: $Conditions}) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewBadBlock', 'contract NewBadBlock { data { ProducerNodeID int ConsumerNodeID int BlockID int Timestamp int Reason string } action { DBInsert("@1bad_blocks", {producer_node_id: $ProducerNodeID,consumer_node_id: $ConsumerNodeID, block_id: $BlockID, "timestamp block_time": $Timestamp, reason: $Reason}) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewCLB', 'contract NewCLB { data { CLBName string DBUser string DBPassword string CLBAPIPort int } conditions { if Size($CLBName) == 0 { warning "CLBName was not received" } if Contains($CLBName, " ") { error "CLBName can not contain spaces" } if Size($DBUser) == 0 { warning "DBUser was not received" } if Size($DBPassword) == 0 { warning "DBPassword was not received" } if $CLBAPIPort <= 0 { warning "CLB API PORT not received" } } action { $CLBName = ToLower($CLBName) $DBUser = ToLower($DBUser) CreateCLB($CLBName, $DBUser, $DBPassword, $CLBAPIPort) $result = "CLB " + $CLBName + " created" } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewContract', 'contract NewContract { data { ApplicationId int Value string Conditions string TokenEcosystem int "optional" } conditions { ValidateCondition($Conditions,$ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } $contract_name = ContractName($Value) if !$contract_name { error "must be the name" } if !$TokenEcosystem { $TokenEcosystem = 1 } else { if !SysFuel($TokenEcosystem) { warning Sprintf("Ecosystem %d is not system", $TokenEcosystem) } } } action { $result = CreateContract($contract_name, $Value, $Conditions, $TokenEcosystem, $ApplicationId) } func price() int { return SysParamInt("contract_price") } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewCron', 'contract NewCron { data { Cron string Contract string Limit int "optional" Till string "optional date" Conditions string } conditions { ValidateCondition($Conditions,$ecosystem_id) ValidateCron($Cron) } action { if !$Till { $Till = "1970-01-01 00:00:00" } if !HasPrefix($Contract, "@") { $Contract = "@" + Str($ecosystem_id) + $Contract } $result = DBInsert("cron", {owner: $key_id,cron:$Cron,contract: $Contract, counter:$Limit, till: $Till,conditions: $Conditions}) UpdateCron($result) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewEcosystem', 'contract NewEcosystem { data { Name string } action { $result = CreateEcosystem($key_id, $Name) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewLang', 'contract NewLang { data { ApplicationId int Name string Trans string } conditions { if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("languages").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Language resource %s already exists", $Name) } EvalCondition("parameters", "changing_language", "value") } action { CreateLanguage($Name, $Trans) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewMenu', 'contract NewMenu { data { Name string Value string Title string "optional" Conditions string } conditions { ValidateCondition($Conditions,$ecosystem_id) if DBFind("menu").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Menu %s already exists", $Name) } } action { DBInsert("menu", {name:$Name,value: $Value, title: $Title, conditions: $Conditions}) } func price() int { return SysParamInt("menu_price") } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewPage', 'contract NewPage { data { ApplicationId int Name string Value string Menu string Conditions string ValidateCount int "optional" ValidateMode string "optional" } func preparePageValidateCount(count int) int { var min, max int min = Int(EcosysParam("min_page_validate_count")) max = Int(EcosysParam("max_page_validate_count")) if count < min { count = min } else { if count > max { count = max } } return count } conditions { ValidateCondition($Conditions,$ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("pages").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Page %s already exists", $Name) } $ValidateCount = preparePageValidateCount($ValidateCount) if $ValidateMode { if $ValidateMode != "1" { $ValidateMode = "0" } } } action { DBInsert("pages", {name: $Name,value: $Value, menu: $Menu, validate_count:$ValidateCount,validate_mode: $ValidateMode, conditions: $Conditions,app_id: $ApplicationId}) } func price() int { return SysParamInt("page_price") } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewParameter', 'contract NewParameter { data { Name string Value string Conditions string } func warnEmpty(name value string) { if Size(value) == 0 { warning Sprintf(LangRes("@1x_parameter_empty"),name) } } conditions { DeveloperCondition() ValidateCondition($Conditions, $ecosystem_id) $Name = TrimSpace($Name) warnEmpty("Name",$Name) if DBFind("@1parameters").Where({"name": $Name, "ecosystem": $ecosystem_id}).One("id") { warning Sprintf(LangRes("@1template_parameter_exists"), $Name) } } action { DBInsert("@1parameters", {"name": $Name, "value": $Value, "conditions": $Conditions, "ecosystem": $ecosystem_id}) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewSnippet', 'contract NewSnippet { data { ApplicationId int Name string Value string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("snippets").Columns("id").Where({name:$Name}).One("id") { warning Sprintf( "Block %s already exists", $Name) } } action { DBInsert("snippets", {name: $Name, value: $Value, conditions: $Conditions, app_id: $ApplicationId}) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewTable', 'contract NewTable { data { ApplicationId int Name string Columns string Permissions string } conditions { if $ApplicationId == 0 { warning "Application id cannot equal 0" } TableConditions($Name, $Columns, $Permissions) } action { CreateTable($Name, $Columns, $Permissions, $ApplicationId) } func price() int { return SysParamInt("table_price") } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NewUser', 'contract NewUser { data { NewPubkey string } conditions { $id = PubToID($NewPubkey) if $id == 0 { error "Wrong pubkey" } if DBFind("keys").Columns("id").WhereId($id).One("id") { error "User already exists" } } action { $pub = HexToPub($NewPubkey) $account = IdToAddress($id) $amount = Money(0) DBInsert("keys", { "id": $id, "account": $account, "pub": $pub, "amount": $amount, "ecosystem": 1 }) } } ', '%[1]d', 'ContractConditions("NodeOwnerCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'NodeOwnerCondition', 'contract NodeOwnerCondition { conditions { $raw_honor_nodes = SysParamString("honor_nodes") if Size($raw_honor_nodes) == 0 { ContractConditions("MainCondition") } else { $honor_nodes = JSONDecode($raw_honor_nodes) var i int while i < Len($honor_nodes) { $fn = $honor_nodes[i] if $fn["key_id"] == $key_id { return true } i = i + 1 } warning "NodeOwnerCondition: Sorry, you do not have access to this action." } } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'RemoveCLB', 'contract RemoveCLB { data { CLBName string } conditions {} action{ $CLBName = ToLower($CLBName) DeleteCLB($CLBName) $result = "CLB " + $CLBName + " removed" } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'RunCLB', 'contract RunCLB { data { CLBName string } conditions { } action { $CLBName = ToLower($CLBName) StartCLB($CLBName) $result = "CLB " + $CLBName + " running" } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'StopCLB', 'contract StopCLB { data { CLBName string } conditions { } action { $CLBName = ToLower($CLBName) StopCLBProcess($CLBName) $result = "CLB " + $CLBName + " stopped" } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'UnbindWallet', 'contract UnbindWallet { data { Id int } conditions { $cur = DBRow("contracts").Columns("id,conditions,wallet_id").WhereId($Id) if !$cur { error Sprintf("Contract %d does not exist", $Id) } Eval($cur["conditions"]) if $key_id != Int($cur["wallet_id"]) { error Sprintf("Wallet %d cannot deactivate the contract", $key_id) } } action { UnbndWallet($Id, $ecosystem_id) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'UpdatePlatformParam', 'contract UpdatePlatformParam { data { Name string Value string Conditions string "optional" } conditions { if !GetContractByName($Name){ warning "System parameter not found" } } action { var params map params["Value"] = $Value CallContract($Name, params) DBUpdatePlatformParam($Name, $Value, $Conditions) } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'UploadBinary', 'contract UploadBinary { data { ApplicationId int Name string Data bytes DataMimeType string "optional" MemberAccount string "optional" } conditions { if Size($MemberAccount) > 0 { $UserID = $MemberAccount } else { $UserID = $account_id } $Id = Int(DBFind("@1binaries").Columns("id").Where({"app_id": $ApplicationId, "account": $UserID, "name": $Name, "ecosystem": $ecosystem_id}).One("id")) if $Id == 0 { if $ApplicationId == 0 { warning LangRes("@1aid_cannot_zero") } } } action { var hash string hash = Hash($Data) if $DataMimeType == "" { $DataMimeType = "application/octet-stream" } if $Id != 0 { DBUpdate("@1binaries", $Id, {"data": $Data, "hash": hash, "mime_type": $DataMimeType}) } else { $Id = DBInsert("@1binaries", {"app_id": $ApplicationId, "account": $UserID, "name": $Name, "data": $Data, "hash": hash, "mime_type": $DataMimeType, "ecosystem": $ecosystem_id}) } $result = $Id } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'), (next_id('1_contracts'), 'UploadFile', 'contract UploadFile { data { ApplicationId int Data file Name string "optional" } conditions { if $Name == "" { $Name = $Data["Name"] } $Body = $Data["Body"] $DataMimeType = $Data["MimeType"] } action { $Id = @1UploadBinary("ApplicationId,Name,Data,DataMimeType", $ApplicationId, $Name, $Body, $DataMimeType) $result = $Id } } ', '%[1]d', 'ContractConditions("MainCondition")', '1', '%[1]d'); ` ================================================ FILE: packages/migration/clb/data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var ( migrationInitial = ` DROP SEQUENCE IF EXISTS migration_history_id_seq CASCADE; CREATE SEQUENCE migration_history_id_seq START WITH 1; DROP TABLE IF EXISTS "migration_history"; CREATE TABLE "migration_history" ( "id" int NOT NULL default nextval('migration_history_id_seq'), "version" varchar(255) NOT NULL, "date_applied" int NOT NULL ); ALTER SEQUENCE migration_history_id_seq owned by migration_history.id; ALTER TABLE ONLY "migration_history" ADD CONSTRAINT migration_history_pkey PRIMARY KEY (id);` migrationInitialSchema = ` CREATE TABLE "system_contracts" ( "id" bigint NOT NULL DEFAULT '0', "value" text NOT NULL DEFAULT '', "wallet_id" bigint NOT NULL DEFAULT '0', "token_id" bigint NOT NULL DEFAULT '0', "active" character(1) NOT NULL DEFAULT '0', "conditions" text NOT NULL DEFAULT '' ); ALTER TABLE ONLY "system_contracts" ADD CONSTRAINT system_contracts_pkey PRIMARY KEY (id); CREATE TABLE "system_tables" ( "name" varchar(100) NOT NULL DEFAULT '', "permissions" jsonb, "columns" jsonb, "conditions" text NOT NULL DEFAULT '' ); ALTER TABLE ONLY "system_tables" ADD CONSTRAINT system_tables_pkey PRIMARY KEY (name); DROP TABLE IF EXISTS "install"; CREATE TABLE "install" ( "progress" varchar(10) NOT NULL DEFAULT '' ); DROP TABLE IF EXISTS "stop_daemons"; CREATE TABLE "stop_daemons" ( "stop_time" int NOT NULL DEFAULT '0' );` ) ================================================ FILE: packages/migration/clb/keys_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb import ( "github.com/IBAX-io/go-ibax/packages/consts" ) var keysDataSQL = ` INSERT INTO "1_keys" (id, account, pub, blocked, ecosystem) VALUES (` + consts.GuestKey + `, '` + consts.GuestAddress + `', decode('` + consts.GuestPublic + `', 'hex'), 1, '%[1]d'); ` ================================================ FILE: packages/migration/clb/menu_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var menuDataSQL = `INSERT INTO "1_menu" (id, name, value, conditions, ecosystem) VALUES (next_id('1_menu'), 'admin_menu', 'MenuItem(Title:"Application", Page:apps_list, Icon:"icon-folder") MenuItem(Title:"Ecosystem parameters", Page:params_list, Icon:"icon-settings") MenuItem(Title:"Menu", Page:menus_list, Icon:"icon-list") MenuItem(Title:"Confirmations", Page:confirmations, Icon:"icon-check") MenuItem(Title:"Import", Page:import_upload, Icon:"icon-cloud-upload") MenuItem(Title:"Export", Page:export_resources, Icon:"icon-cloud-download") MenuGroup(Title:"Resources", Icon:"icon-share"){ MenuItem(Title:"Pages", Page:app_pages, Icon:"icon-screen-desktop") MenuItem(Title:"Blocks", Page:app_blocks, Icon:"icon-grid") MenuItem(Title:"Tables", Page:app_tables, Icon:"icon-docs") MenuItem(Title:"Contracts", Page:app_contracts, Icon:"icon-briefcase") MenuItem(Title:"Application parameters", Page:app_params, Icon:"icon-wrench") MenuItem(Title:"Language resources", Page:app_langres, Icon:"icon-globe") MenuItem(Title:"Binary data", Page:app_binary, Icon:"icon-layers") } MenuItem(Title:"Dashboard", Page:admin_dashboard, Icon:"icon-wrench")', 'ContractConditions("MainCondition")', '%[1]d'); ` ================================================ FILE: packages/migration/clb/pages_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb /* var pagesDataSQL = `INSERT INTO "1_pages" (id, name, value, menu, conditions, app_id, ecosystem) VALUES (next_id('1_pages'), 'admin_index', '', 'admin_menu', 'ContractConditions("@1DeveloperCondition")', '%[5]d', '%[1]d'), (next_id('1_pages'), 'developer_index', '', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '%[5]d', '%[1]d'), (next_id('1_pages'), 'notifications', '', 'default_menu', 'ContractConditions("@1DeveloperCondition")', '%[5]d', '%[1]d'), (next_id('1_pages'), 'import_app', 'Div(content-wrapper){ DBFind(@1buffer_data).Columns("id,value->name,value->data").Where({"key": import, "account": #account_id#, "ecosystem": #ecosystem_id#}).Vars(import) DBFind(@1buffer_data).Columns("value->app_name,value->pages,value->pages_count,value->blocks,value->blocks_count,value->menu,value->menu_count,value->parameters,value->parameters_count,value->languages,value->languages_count,value->contracts,value->contracts_count,value->tables,value->tables_count").Where({"key": import_info, "account": #account_id#, "ecosystem": #ecosystem_id#}).Vars(info) SetTitle("Import - #info_value_app_name#") Data(data_info, "DataName,DataCount,DataInfo"){ Pages,"#info_value_pages_count#","#info_value_pages#" Blocks,"#info_value_blocks_count#","#info_value_blocks#" Menu,"#info_value_menu_count#","#info_value_menu#" Parameters,"#info_value_parameters_count#","#info_value_parameters#" Language resources,"#info_value_languages_count#","#info_value_languages#" Contracts,"#info_value_contracts_count#","#info_value_contracts#" Tables,"#info_value_tables_count#","#info_value_tables#" } Div(breadcrumb){ Span(Class: text-muted, Body: "Your data that you can import") } Div(panel panel-primary){ ForList(data_info){ Div(list-group-item){ Div(row){ Div(col-md-10 mc-sm text-left){ Span(Class: text-bold, Body: "#DataName#") } Div(col-md-2 mc-sm text-right){ If(#DataCount# > 0){ Span(Class: text-bold, Body: "(#DataCount#)") }.Else{ Span(Class: text-muted, Body: "(0)") } } } Div(row){ Div(col-md-12 mc-sm text-left){ If(#DataCount# > 0){ Span(Class: h6, Body: "#DataInfo#") }.Else{ Span(Class: text-muted h6, Body: "Nothing selected") } } } } } If(#import_id# > 0){ Div(list-group-item text-right){ VarAsIs(imp_data, "#import_value_data#") Button(Body: "Import", Class: btn btn-primary, Page: @1apps_list).CompositeContract(@1Import, "#imp_data#") } } } }', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '%[5]d', '%[1]d'), (next_id('1_pages'), 'import_upload', 'Div(content-wrapper){ SetTitle("Import") Div(breadcrumb){ Span(Class: text-muted, Body: "Select payload that you want to import") } Form(panel panel-primary){ Div(list-group-item){ Input(Name: Data, Type: file) } Div(list-group-item text-right){ Button(Body: "Load", Class: btn btn-primary, Contract: @1ImportUpload, Page: @1import_app) } } }', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'); ` */ var pagesDataSQL = `INSERT INTO "1_pages" (id, name, value, menu, conditions, app_id, ecosystem) VALUES (next_id('1_pages'), 'admin_index', '', 'admin_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'), (next_id('1_pages'), 'developer_index', '', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'), (next_id('1_pages'), 'notifications', '', 'default_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'), (next_id('1_pages'), 'import_app', 'Div(content-wrapper){ DBFind(@1buffer_data).Columns("id,value->name,value->data").Where({"key": import, "account": #account_id#, "ecosystem": #ecosystem_id#}).Vars(import) DBFind(@1buffer_data).Columns("value->app_name,value->pages,value->pages_count,value->blocks,value->blocks_count,value->menu,value->menu_count,value->parameters,value->parameters_count,value->languages,value->languages_count,value->contracts,value->contracts_count,value->tables,value->tables_count").Where({"key": import_info, "account": #account_id#, "ecosystem": #ecosystem_id#}).Vars(info) SetTitle("Import - #info_value_app_name#") Data(data_info, "DataName,DataCount,DataInfo"){ Pages,"#info_value_pages_count#","#info_value_pages#" Blocks,"#info_value_blocks_count#","#info_value_blocks#" Menu,"#info_value_menu_count#","#info_value_menu#" Parameters,"#info_value_parameters_count#","#info_value_parameters#" Language resources,"#info_value_languages_count#","#info_value_languages#" Contracts,"#info_value_contracts_count#","#info_value_contracts#" Tables,"#info_value_tables_count#","#info_value_tables#" } Div(breadcrumb){ Span(Class: text-muted, Body: "Your data that you can import") } Div(panel panel-primary){ ForList(data_info){ Div(list-group-item){ Div(row){ Div(col-md-10 mc-sm text-left){ Span(Class: text-bold, Body: "#DataName#") } Div(col-md-2 mc-sm text-right){ If(#DataCount# > 0){ Span(Class: text-bold, Body: "(#DataCount#)") }.Else{ Span(Class: text-muted, Body: "(0)") } } } Div(row){ Div(col-md-12 mc-sm text-left){ If(#DataCount# > 0){ Span(Class: h6, Body: "#DataInfo#") }.Else{ Span(Class: text-muted h6, Body: "Nothing selected") } } } } } If(#import_id# > 0){ Div(list-group-item text-right){ VarAsIs(imp_data, "#import_value_data#") Button(Body: "Import", Class: btn btn-primary, Page: @1apps_list).CompositeContract(@1Import, "#imp_data#") } } } }', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'), (next_id('1_pages'), 'import_upload', 'Div(content-wrapper){ SetTitle("Import") Div(breadcrumb){ Span(Class: text-muted, Body: "Select payload that you want to import") } Form(panel panel-primary){ Div(list-group-item){ Input(Name: Data, Type: file) } Div(list-group-item text-right){ Button(Body: "Load", Class: btn btn-primary, Contract: @1ImportUpload, Page: @1import_app) } } }', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'); ` ================================================ FILE: packages/migration/clb/parameters_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var parametersDataSQL = ` INSERT INTO "1_parameters" ("id","name", "value", "conditions", "ecosystem") VALUES (next_id('1_parameters'),'founder_account', '%[2]d', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'new_table', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_tables', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_language', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_page', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_menu', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_contracts', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_parameters', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_app_params', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'max_sum', '1000000', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'stylesheet', 'body { /* You can define your custom styles here or create custom CSS rules */ }', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'print_stylesheet', 'body { /* You can define your custom styles here or create custom CSS rules */ }', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'min_page_validate_count', '1', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'max_page_validate_count', '6', 'ContractConditions("@1DeveloperCondition")', '%[1]d'), (next_id('1_parameters'),'changing_snippets', 'ContractConditions("MainCondition")', 'ContractConditions("@1DeveloperCondition")', '%[1]d'); ` ================================================ FILE: packages/migration/clb/platform_parameters_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var platformParametersDataSQL = ` INSERT INTO "1_platform_parameters" ("id","name", "value", "conditions") VALUES (next_id('1_platform_parameters'),'default_ecosystem_page', 'If(#ecosystem_id# > 1){Include(@1welcome)}', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'default_ecosystem_menu', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'default_ecosystem_contract', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'gap_between_blocks', '2', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'rollback_blocks', '60', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'honor_nodes', '', 'ContractAccess("@1UpdatePlatformParam","@1NodeRemoveByKey")'), (next_id('1_platform_parameters'),'number_of_nodes', '101', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_ecosystem', '2000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_table', '25', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_column', '25', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_contract', '25', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_menu', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_page', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_block_size', '67108864', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_tx_size', '33554432', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_tx_block', '1000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_columns', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_indexes', '5', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_tx_block_per_user', '1000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_fuel_tx', '20000000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_fuel_block', '200000000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'taxes_size', '3', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'taxes_wallet', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'fuel_rate', '[["1","1000000"]]', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_address_to_id', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_id_to_address', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_hash', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_pub_to_id', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_ecosys_param', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_sys_param_string', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_sys_param_int', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_sys_fuel', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_validate_condition', '30', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_eval_condition', '20', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_has_prefix', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_contains', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_replace', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_join', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_update_lang', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_size', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_substr', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_contracts_list', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_is_object', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_compile_contract', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_flush_contract', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_eval', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_len', '5', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_bind_wallet', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_unbind_wallet', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_create_ecosystem', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_table_conditions', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_create_table', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_perm_table', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_column_condition', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_create_column', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_perm_column', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_block_generation_time', '2000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'block_reward','10','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'incorrect_blocks_per_day','10','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'node_ban_time','86400000','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'node_ban_time_local','1800000','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_tx_size', '15', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_rate', '1000000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'test','false','false'), (next_id('1_platform_parameters'),'price_tx_data', '0', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_contract_by_name', '20', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_exec_contract_by_id', '20', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'private_blockchain', '1', 'false'), (next_id('1_platform_parameters'),'price_create_application', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'external_blockchain', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'pay_free_contract', '@1CallDelayedContract', 'ContractAccess("@1UpdatePlatformParam")'); ` ================================================ FILE: packages/migration/clb/roles_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb import "github.com/IBAX-io/go-ibax/packages/consts" var rolesDataSQL = ` INSERT INTO "1_roles" ("id", "default_page", "role_name", "deleted", "role_type", "creator","roles_access", "ecosystem") VALUES (next_id('1_roles'),'', 'Admin', '0', '3', '{}', '{}', '%[1]d'), (next_id('1_roles'),'', 'Developer', '0', '3', '{}', '{}', '%[1]d'), (next_id('1_roles'),'', 'Chain Consensus asbl', '0', '3', '{}', '{"rids": "1"}', '%[1]d'), (next_id('1_roles'),'', 'Candidate for validators', '0', '3', '{}', '{}', '%[1]d'), (next_id('1_roles'),'', 'Validator', '0', '3', '{}', '{}', '%[1]d'), (next_id('1_roles'),'', 'Investor with voting rights', '0', '3', '{}', '{}', '%[1]d'), (next_id('1_roles'),'', 'Delegate', '0', '3', '{}', '{}', '%[1]d'); INSERT INTO "1_roles_participants" ("id","role" ,"member", "date_created", "ecosystem") VALUES (next_id('1_roles_participants'), '{"id": "1", "type": "3", "name": "Admin", "image_id":"0"}', '{"member_id": "%[2]d", "member_name": "founder", "image_id": "0"}', floor(extract(epoch from now())), '%[1]d'), (next_id('1_roles_participants'), '{"id": "2", "type": "3", "name": "Developer", "image_id":"0"}', '{"member_id": "%[2]d", "member_name": "founder", "image_id": "0"}', floor(extract(epoch from now())), '%[1]d'); INSERT INTO "1_members" ("id", "account", "member_name", "ecosystem") VALUES (next_id('1_members'), '%[3]s', 'founder', '%[1]d'), (next_id('1_members'), '` + consts.GuestAddress + `', 'guest', '%[1]d'); ` ================================================ FILE: packages/migration/clb/scheme.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb import ( "strings" ) // GetCLBScript returns script to create ecosystem func GetCLBScript() string { scripts := []string{ schemaCLB, snippetsDataSQL, contractsDataSQL, menuDataSQL, pagesDataSQL, parametersDataSQL, rolesDataSQL, sectionsDataSQL, tablesDataSQL, applicationsDataSQL, keysDataSQL, platformParametersDataSQL, } return strings.Join(scripts, "\r\n") } // SchemaEcosystem contains SQL queries for creating ecosystem var schemaCLB = `DROP TABLE IF EXISTS "1_keys"; CREATE TABLE "1_keys" ( "id" bigint NOT NULL DEFAULT '0', "pub" bytea NOT NULL DEFAULT '', "amount" decimal(30) NOT NULL DEFAULT '0' CHECK (amount >= 0), "multi" bigint NOT NULL DEFAULT '0', "deleted" bigint NOT NULL DEFAULT '0', "blocked" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1', "account" char(24) NOT NULL ); ALTER TABLE ONLY "1_keys" ADD CONSTRAINT "%[1]d_keys_pkey" PRIMARY KEY (id,ecosystem); DROP TABLE IF EXISTS "1_history"; CREATE TABLE "1_history" ( "id" bigint NOT NULL DEFAULT '0', "sender_id" bigint NOT NULL DEFAULT '0', "recipient_id" bigint NOT NULL DEFAULT '0', "amount" decimal(30) NOT NULL DEFAULT '0', "comment" text NOT NULL DEFAULT '', "block_id" int NOT NULL DEFAULT '0', "txhash" bytea NOT NULL DEFAULT '', "created_at" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_history" ADD CONSTRAINT "%[1]d_history_pkey" PRIMARY KEY (id); CREATE INDEX "%[1]d_history_index_sender" ON "%[1]d_history" (sender_id); CREATE INDEX "%[1]d_history_index_recipient" ON "%[1]d_history" (recipient_id); CREATE INDEX "%[1]d_history_index_block" ON "%[1]d_history" (block_id, txhash); DROP TABLE IF EXISTS "%[1]d_languages"; CREATE TABLE "%[1]d_languages" ( "id" bigint NOT NULL DEFAULT '0', "name" character varying(100) NOT NULL DEFAULT '', "res" text NOT NULL DEFAULT '', "conditions" text NOT NULL DEFAULT '', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_languages" ADD CONSTRAINT "%[1]d_languages_pkey" PRIMARY KEY (id); CREATE INDEX "%[1]d_languages_index_name" ON "%[1]d_languages" (name); DROP TABLE IF EXISTS "%[1]d_sections"; CREATE TABLE "%[1]d_sections" ( "id" bigint NOT NULL DEFAULT '0', "title" varchar(255) NOT NULL DEFAULT '', "urlname" varchar(255) NOT NULL DEFAULT '', "page" varchar(255) NOT NULL DEFAULT '', "roles_access" text NOT NULL DEFAULT '', "delete" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_sections" ADD CONSTRAINT "%[1]d_sections_pkey" PRIMARY KEY (id); DROP TABLE IF EXISTS "%[1]d_menu"; CREATE TABLE "%[1]d_menu" ( "id" bigint NOT NULL DEFAULT '0', "name" character varying(255) UNIQUE NOT NULL DEFAULT '', "title" character varying(255) NOT NULL DEFAULT '', "value" text NOT NULL DEFAULT '', "conditions" text NOT NULL DEFAULT '', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_menu" ADD CONSTRAINT "%[1]d_menu_pkey" PRIMARY KEY (id); CREATE INDEX "%[1]d_menu_index_name" ON "%[1]d_menu" (name); DROP TABLE IF EXISTS "%[1]d_pages"; CREATE TABLE "%[1]d_pages" ( "id" bigint NOT NULL DEFAULT '0', "name" character varying(255) UNIQUE NOT NULL DEFAULT '', "value" text NOT NULL DEFAULT '', "menu" character varying(255) NOT NULL DEFAULT '', "validate_count" bigint NOT NULL DEFAULT '1', "conditions" text NOT NULL DEFAULT '', "app_id" bigint NOT NULL DEFAULT '1', "validate_mode" character(1) NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_pages" ADD CONSTRAINT "%[1]d_pages_pkey" PRIMARY KEY (id); CREATE INDEX "%[1]d_pages_index_name" ON "%[1]d_pages" (name); DROP TABLE IF EXISTS "%[1]d_blocks"; CREATE TABLE "%[1]d_blocks" ( "id" bigint NOT NULL DEFAULT '0', "name" character varying(255) UNIQUE NOT NULL DEFAULT '', "value" text NOT NULL DEFAULT '', "conditions" text NOT NULL DEFAULT '', "app_id" bigint NOT NULL DEFAULT '1', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_blocks" ADD CONSTRAINT "%[1]d_blocks_pkey" PRIMARY KEY (id); CREATE INDEX "%[1]d_blocks_index_name" ON "%[1]d_blocks" (name); DROP TABLE IF EXISTS "%[1]d_signatures"; CREATE TABLE "%[1]d_signatures" ( "id" bigint NOT NULL DEFAULT '0', "name" character varying(100) NOT NULL DEFAULT '', "value" jsonb, "conditions" text NOT NULL DEFAULT '' ); ALTER TABLE ONLY "%[1]d_signatures" ADD CONSTRAINT "%[1]d_signatures_pkey" PRIMARY KEY (name); CREATE TABLE "%[1]d_contracts" ( "id" bigint NOT NULL DEFAULT '0', "name" text NOT NULL UNIQUE DEFAULT '', "value" text NOT NULL DEFAULT '', "wallet_id" bigint NOT NULL DEFAULT '0', "token_id" bigint NOT NULL DEFAULT '1', "conditions" text NOT NULL DEFAULT '', "app_id" bigint NOT NULL DEFAULT '1', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_contracts" ADD CONSTRAINT "%[1]d_contracts_pkey" PRIMARY KEY (id); DROP TABLE IF EXISTS "%[1]d_parameters"; CREATE TABLE "%[1]d_parameters" ( "id" bigint NOT NULL DEFAULT '0', "name" varchar(255) UNIQUE NOT NULL DEFAULT '', "value" text NOT NULL DEFAULT '', "conditions" text NOT NULL DEFAULT '', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_parameters" ADD CONSTRAINT "%[1]d_parameters_pkey" PRIMARY KEY ("id"); CREATE INDEX "%[1]d_parameters_index_name" ON "%[1]d_parameters" (name); DROP TABLE IF EXISTS "%[1]d_app_params"; CREATE TABLE "%[1]d_app_params" ( "id" bigint NOT NULL DEFAULT '0', "app_id" bigint NOT NULL DEFAULT '0', "name" varchar(255) UNIQUE NOT NULL DEFAULT '', "value" text NOT NULL DEFAULT '', "conditions" text NOT NULL DEFAULT '', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_app_params" ADD CONSTRAINT "%[1]d_app_params_pkey" PRIMARY KEY ("id"); CREATE INDEX "%[1]d_app_params_index_name" ON "%[1]d_app_params" (name); CREATE INDEX "%[1]d_app_params_index_app" ON "%[1]d_app_params" (app_id); DROP TABLE IF EXISTS "%[1]d_tables"; CREATE TABLE "%[1]d_tables" ( "id" bigint NOT NULL DEFAULT '0', "name" varchar(100) UNIQUE NOT NULL DEFAULT '', "permissions" jsonb, "columns" jsonb, "conditions" text NOT NULL DEFAULT '', "app_id" bigint NOT NULL DEFAULT '1', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_tables" ADD CONSTRAINT "%[1]d_tables_pkey" PRIMARY KEY ("id"); CREATE INDEX "%[1]d_tables_index_name" ON "%[1]d_tables" (name); DROP TABLE IF EXISTS "%[1]d_notifications"; CREATE TABLE "%[1]d_notifications" ( "id" bigint NOT NULL DEFAULT '0', "recipient" jsonb, "sender" jsonb, "notification" jsonb, "page_params" jsonb, "processing_info" jsonb, "page_name" varchar(255) NOT NULL DEFAULT '', "date_created" bigint NOT NULL DEFAULT '0', "date_start_processing" bigint NOT NULL DEFAULT '0', "date_closed" bigint NOT NULL DEFAULT '0', "closed" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_notifications" ADD CONSTRAINT "%[1]d_notifications_pkey" PRIMARY KEY ("id"); DROP TABLE IF EXISTS "%[1]d_roles"; CREATE TABLE "%[1]d_roles" ( "id" bigint NOT NULL DEFAULT '0', "default_page" varchar(255) NOT NULL DEFAULT '', "role_name" varchar(255) NOT NULL DEFAULT '', "deleted" bigint NOT NULL DEFAULT '0', "role_type" bigint NOT NULL DEFAULT '0', "creator" jsonb NOT NULL DEFAULT '{}', "date_created" bigint NOT NULL DEFAULT '0', "date_deleted" bigint NOT NULL DEFAULT '0', "company_id" bigint NOT NULL DEFAULT '0', "roles_access" jsonb, "image_id" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_roles" ADD CONSTRAINT "%[1]d_roles_pkey" PRIMARY KEY ("id"); CREATE INDEX "%[1]d_roles_index_deleted" ON "%[1]d_roles" (deleted); CREATE INDEX "%[1]d_roles_index_type" ON "%[1]d_roles" (role_type); DROP TABLE IF EXISTS "%[1]d_roles_participants"; CREATE TABLE "%[1]d_roles_participants" ( "id" bigint NOT NULL DEFAULT '0', "role" jsonb, "member" jsonb, "appointed" jsonb, "date_created" bigint NOT NULL DEFAULT '0', "date_deleted" bigint NOT NULL DEFAULT '0', "deleted" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_roles_participants" ADD CONSTRAINT "%[1]d_roles_participants_pkey" PRIMARY KEY ("id"); DROP TABLE IF EXISTS "%[1]d_members"; CREATE TABLE "%[1]d_members" ( "id" bigint NOT NULL DEFAULT '0', "member_name" varchar(255) NOT NULL DEFAULT '', "image_id" bigint NOT NULL DEFAULT '0', "member_info" jsonb, "ecosystem" bigint NOT NULL DEFAULT '1', "account" char(24) NOT NULL ); ALTER TABLE ONLY "%[1]d_members" ADD CONSTRAINT "%[1]d_members_pkey" PRIMARY KEY ("id"); CREATE INDEX "%[1]d_members_index_ecosystem" ON "1_sections" (ecosystem); CREATE UNIQUE INDEX "%[1]d_members_uindex_ecosystem_account" ON "1_members" (account, ecosystem); DROP TABLE IF EXISTS "%[1]d_applications"; CREATE TABLE "%[1]d_applications" ( "id" bigint NOT NULL DEFAULT '0', "name" varchar(255) NOT NULL DEFAULT '', "uuid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', "conditions" text NOT NULL DEFAULT '', "deleted" bigint NOT NULL DEFAULT '0', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_applications" ADD CONSTRAINT "%[1]d_application_pkey" PRIMARY KEY ("id"); DROP TABLE IF EXISTS "%[1]d_binaries"; CREATE TABLE "%[1]d_binaries" ( "id" bigint NOT NULL DEFAULT '0', "app_id" bigint NOT NULL DEFAULT '1', "name" varchar(255) NOT NULL DEFAULT '', "data" bytea NOT NULL DEFAULT '', "hash" varchar(32) NOT NULL DEFAULT '', "mime_type" varchar(255) NOT NULL DEFAULT '', "ecosystem" bigint NOT NULL DEFAULT '1', "account" char(24) NOT NULL ); ALTER TABLE ONLY "%[1]d_binaries" ADD CONSTRAINT "%[1]d_binaries_pkey" PRIMARY KEY (id); CREATE UNIQUE INDEX "%[1]d_binaries_uindex" ON "%[1]d_binaries" (account, ecosystem, app_id, name); DROP TABLE IF EXISTS "%[1]d_buffer_data"; CREATE TABLE "%[1]d_buffer_data" ( "id" bigint NOT NULL DEFAULT '0', "key" varchar(255) NOT NULL DEFAULT '', "value" jsonb, "ecosystem" bigint NOT NULL DEFAULT '1', "account" char(24) NOT NULL ); ALTER TABLE ONLY "%[1]d_buffer_data" ADD CONSTRAINT "%[1]d_buffer_data_pkey" PRIMARY KEY ("id"); DROP TABLE IF EXISTS "%[1]d_platform_parameters"; CREATE TABLE "%[1]d_platform_parameters" ( "id" bigint NOT NULL DEFAULT '0', "name" varchar(255) NOT NULL DEFAULT '', "value" text NOT NULL DEFAULT '', "conditions" text NOT NULL DEFAULT '', "ecosystem" bigint NOT NULL DEFAULT '1' ); ALTER TABLE ONLY "%[1]d_platform_parameters" ADD CONSTRAINT "%[1]d_platform_parameters_pkey" PRIMARY KEY (id); CREATE INDEX "%[1]d_platform_parameters_index_name" ON "%[1]d_platform_parameters" (name); DROP TABLE IF EXISTS "%[1]d_cron"; CREATE TABLE "%[1]d_cron" ( "id" bigint NOT NULL DEFAULT '0', "owner" bigint NOT NULL DEFAULT '0', "cron" varchar(255) NOT NULL DEFAULT '', "contract" varchar(255) NOT NULL DEFAULT '', "counter" bigint NOT NULL DEFAULT '0', "till" timestamp NOT NULL DEFAULT timestamp '1970-01-01 00:00:00', "conditions" text NOT NULL DEFAULT '' ); ALTER TABLE ONLY "%[1]d_cron" ADD CONSTRAINT "%[1]d_cron_pkey" PRIMARY KEY ("id"); ` ================================================ FILE: packages/migration/clb/sections_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var sectionsDataSQL = ` INSERT INTO "1_sections" ("id","title","urlname","page","roles_access", "delete", "ecosystem") VALUES ('1', 'Home', 'home', 'default_page', '', 0, '%[1]d');` ================================================ FILE: packages/migration/clb/snippets_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var snippetsDataSQL = `INSERT INTO "1_snippets" (id, name, value, conditions, app_id, ecosystem) VALUES (next_id('1_snippets'), 'pager_header', '', 'ContractConditions("@1DeveloperCondition")', '1', '1'); ` ================================================ FILE: packages/migration/clb/tables_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package clb var tablesDataSQL = `INSERT INTO "1_tables" ("id", "name", "permissions","columns", "conditions") VALUES (next_id('1_tables'), 'contracts', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "false", "value": "ContractConditions(\"MainCondition\")", "wallet_id": "ContractConditions(\"MainCondition\")", "token_id": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")"}', 'ContractAccess("@1EditTable")'), (next_id('1_tables'), 'keys', '{"insert": "true", "update": "true", "new_column": "ContractConditions(\"MainCondition\")"}', '{ "pub": "ContractConditions(\"MainCondition\")", "amount": "ContractConditions(\"MainCondition\")", "deleted": "ContractConditions(\"MainCondition\")", "blocked": "ContractConditions(\"MainCondition\")", "multi": "ContractConditions(\"MainCondition\")", "account": "false", "ecosystem": "false", "multi": "ContractConditions(\"@1MainCondition\")" }', 'ContractAccess("@1EditTable")'), (next_id('1_tables'), 'history', '{"insert": "ContractConditions(\"NodeOwnerCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"sender_id": "ContractConditions(\"MainCondition\")", "recipient_id": "ContractConditions(\"MainCondition\")", "amount": "ContractConditions(\"MainCondition\")", "comment": "ContractConditions(\"MainCondition\")", "block_id": "ContractConditions(\"MainCondition\")", "txhash": "ContractConditions(\"MainCondition\")", "created_at": "false"}', 'ContractAccess("@1EditTable")'), (next_id('1_tables'), 'languages', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"app_id": "ContractConditions(\"MainCondition\")", "name": "ContractConditions(\"MainCondition\")", "res": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")", "app_id": "ContractConditions(\"MainCondition\")"}', 'ContractAccess("@1EditTable")'), (next_id('1_tables'), 'menu', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "ContractConditions(\"MainCondition\")", "value": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")" }', 'ContractAccess("@1EditTable")'), (next_id('1_tables'), 'pages', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "ContractConditions(\"MainCondition\")", "value": "ContractConditions(\"MainCondition\")", "menu": "ContractConditions(\"MainCondition\")", "validate_count": "ContractConditions(\"MainCondition\")", "validate_mode": "ContractConditions(\"MainCondition\")", "app_id": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")" }', 'ContractAccess("@1EditTable")'), (next_id('1_tables'), 'blocks', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "ContractConditions(\"MainCondition\")", "value": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")" }', 'ContractAccess("@1EditTable")'), ('8', 'signatures', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "ContractConditions(\"MainCondition\")", "value": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")" }', 'ContractAccess("@1EditTable")'), ('9', 'members', '{"insert":"ContractAccess(\"Profile_Edit\")","update":"ContractConditions(\"MainCondition\")","new_column":"ContractConditions(\"MainCondition\")"}', '{ "image_id":"ContractAccess(\"Profile_Edit\")", "member_info":"ContractAccess(\"Profile_Edit\")", "member_name":"false", "account":"false" }', 'ContractConditions("MainCondition")'), ('10', 'roles', '{"insert":"ContractAccess(\"Roles_Create\")", "update":"ContractConditions(\"MainCondition\")", "new_column":"ContractConditions(\"MainCondition\")"}', '{"default_page":"false", "creator":"false", "deleted":"ContractAccess(\"Roles_Del\")", "company_id":"false", "date_deleted":"ContractAccess(\"Roles_Del\")", "image_id":"ContractAccess(\"Roles_Create\")", "role_name":"false", "date_created":"false", "roles_access":"ContractAccess(\"Roles_AccessManager\")", "role_type":"false"}', 'ContractConditions("MainCondition")'), ('11', 'roles_participants', '{"insert":"ContractAccess(\"Roles_Assign\",\"voting_CheckDecision\")", "update":"ContractConditions(\"MainCondition\")", "new_column":"ContractConditions(\"MainCondition\")"}', '{"deleted":"ContractAccess(\"Roles_Unassign\")", "date_deleted":"ContractAccess(\"Roles_Unassign\")", "member":"false", "role":"false", "date_created":"false", "appointed":"false"}', 'ContractConditions("MainCondition")'), ('12', 'notifications', '{"insert":"ContractAccess(\"notifications_Send\", \"CheckNodesBan\")", "update":"ContractAccess(\"notifications_Send\", \"notifications_Close\", \"notifications_Process\")", "new_column":"ContractConditions(\"MainCondition\")"}', '{"date_closed":"ContractAccess(\"notifications_Close\")", "sender":"false", "processing_info":"ContractAccess(\"notifications_Close\",\"notifications_Process\")", "date_start_processing":"ContractAccess(\"notifications_Close\",\"notifications_Process\")", "notification":"false", "page_name":"false", "page_params":"false", "closed":"ContractAccess(\"notifications_Close\")", "date_created":"false", "recipient":"false"}', 'ContractAccess("@1EditTable")'), ('13', 'sections', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"title": "ContractConditions(\"MainCondition\")", "urlname": "ContractConditions(\"MainCondition\")", "page": "ContractConditions(\"MainCondition\")", "roles_access": "ContractConditions(\"MainCondition\")", "delete": "ContractConditions(\"MainCondition\")"}', 'ContractConditions("MainCondition")'), ('14', 'applications', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "ContractConditions(\"MainCondition\")", "uuid": "false", "conditions": "ContractConditions(\"MainCondition\")", "deleted": "ContractConditions(\"MainCondition\")"}', 'ContractConditions("MainCondition")'), ('15', 'binaries', '{"insert":"ContractAccess(\"@1UploadBinary\")", "update":"ContractAccess(\"@1UploadBinary\")", "new_column":"ContractConditions(\"MainCondition\")"}', '{ "hash":"ContractAccess(\"@1UploadBinary\")", "account": "false", "data":"ContractAccess(\"@1UploadBinary\")", "name":"false", "app_id":"false", "mime_type": "ContractAccess(\"@1UploadBinary\")" }', 'ContractConditions(\"MainCondition\")'), ('16', 'parameters', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"name": "ContractConditions(\"MainCondition\")", "value": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")"}', 'ContractAccess("@1EditTable")'), ('17', 'app_params', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"app_id": "ContractConditions(\"MainCondition\")", "name": "ContractConditions(\"MainCondition\")", "value": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")"}', 'ContractAccess("@1EditTable")'), ('18', 'cron', '{"insert": "ContractConditions(\"MainCondition\")", "update": "ContractConditions(\"MainCondition\")", "new_column": "ContractConditions(\"MainCondition\")"}', '{"owner": "ContractConditions(\"MainCondition\")", "cron": "ContractConditions(\"MainCondition\")", "contract": "ContractConditions(\"MainCondition\")", "counter": "ContractConditions(\"MainCondition\")", "till": "ContractConditions(\"MainCondition\")", "conditions": "ContractConditions(\"MainCondition\")" }', 'ContractConditions("MainCondition")'), ('19', 'buffer_data', '{"insert":"true","update":"ContractConditions(\"MainCondition\")", "new_column":"ContractConditions(\"MainCondition\")"}', '{ "key": "false", "value": "true", "account": "false" }', 'ContractConditions("MainCondition")'); ` ================================================ FILE: packages/migration/contracts/clb/EditCron.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditCron { data { Id int Contract string Cron string "optional" Limit int "optional" Till string "optional date" Conditions string } conditions { ConditionById("cron", true) ValidateCron($Cron) } action { if !$Till { $Till = "1970-01-01 00:00:00" } if !HasPrefix($Contract, "@") { $Contract = "@" + Str($ecosystem_id) + $Contract } DBUpdate("cron", $Id, {"cron": $Cron,"contract": $Contract, "counter":$Limit, "till": $Till, "conditions":$Conditions}) UpdateCron($Id) } } ================================================ FILE: packages/migration/contracts/clb/ListCLB.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract ListCLB { data {} conditions {} action { $result = GetCLBList() } } ================================================ FILE: packages/migration/contracts/clb/MainCondition.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract MainCondition { conditions { if EcosysParam("founder_account")!=$key_id { warning "Sorry, you do not have access to this action." } } } ================================================ FILE: packages/migration/contracts/clb/NewCLB.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewCLB { data { CLBName string DBUser string DBPassword string CLBAPIPort int } conditions { if Size($CLBName) == 0 { warning "CLBName was not received" } if Contains($CLBName, " ") { error "CLBName can not contain spaces" } if Size($DBUser) == 0 { warning "DBUser was not received" } if Size($DBPassword) == 0 { warning "DBPassword was not received" } if $CLBAPIPort <= 0 { warning "CLB API PORT not received" } } action { $CLBName = ToLower($CLBName) $DBUser = ToLower($DBUser) CreateCLB($CLBName, $DBUser, $DBPassword, $CLBAPIPort) $result = "CLB " + $CLBName + " created" } } ================================================ FILE: packages/migration/contracts/clb/NewCron.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewCron { data { Cron string Contract string Limit int "optional" Till string "optional date" Conditions string } conditions { ValidateCondition($Conditions,$ecosystem_id) ValidateCron($Cron) } action { if !$Till { $Till = "1970-01-01 00:00:00" } if !HasPrefix($Contract, "@") { $Contract = "@" + Str($ecosystem_id) + $Contract } $result = DBInsert("cron", {owner: $key_id,cron:$Cron,contract: $Contract, counter:$Limit, till: $Till,conditions: $Conditions}) UpdateCron($result) } } ================================================ FILE: packages/migration/contracts/clb/RemoveCLB.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract RemoveCLB { data { CLBName string } conditions {} action{ $CLBName = ToLower($CLBName) DeleteCLB($CLBName) $result = "CLB " + $CLBName + " removed" } } ================================================ FILE: packages/migration/contracts/clb/RunCLB.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract RunCLB { data { CLBName string } conditions { } action { $CLBName = ToLower($CLBName) StartCLB($CLBName) $result = "CLB " + $CLBName + " running" } } ================================================ FILE: packages/migration/contracts/clb/StopCLB.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract StopCLB { data { CLBName string } conditions { } action { $CLBName = ToLower($CLBName) StopCLBProcess($CLBName) $result = "CLB " + $CLBName + " stopped" } } ================================================ FILE: packages/migration/contracts/ecosystem/DeveloperCondition.sim ================================================ // +prop AppID = '{{.AppID}}' // +prop Conditions = 'ContractConditions("MainCondition")' // This contract is used to set "developer" rights. // Usually the "developer" role is used for this. // The role ID is written to the ecosystem parameter and can be changed. // The contract requests the role ID from the ecosystem parameter and the contract checks the rights. contract DeveloperCondition { func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } } conditions { chooseControl() if $control["control_mode"] == 2{ return } // check for Founder if EcosysParam("founder_account") == AddressToId($account_id) { return } // check for Developer role var app_id int role_id string app_id = Int(DBFind("@1applications").Where({"ecosystem": $ecosystem_id, "name": "System"}).One("id")) role_id = AppParam(app_id, "role_developer", $ecosystem_id) if Size(role_id) == 0 { warning Sprintf(LangRes("@1x_not_access_action"),"DeveloperCondition") } if !RoleAccess(Int(role_id)) { warning Sprintf(LangRes("@1x_not_access_action"),"DeveloperCondition") } } } ================================================ FILE: packages/migration/contracts/ecosystem/MainCondition.sim ================================================ // +prop AppID = '{{.AppID}}' // +prop Conditions = 'ContractConditions("MainCondition")' contract MainCondition { func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } } conditions { chooseControl() if $control["control_mode"] == 2{ return } if EcosysParam("founder_account")!=$key_id { warning "MainCondition: Sorry, you do not have access to this action." } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/AccessControlMode.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract AccessControlMode { data { VotingId int "optional" } func decentralizedAutonomous(){ if !DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":2}).Row(){ warning "control mode DAO error" } var prev string prev = $stack[0] if Len($stack) > 3{ prev = $stack[Len($stack) - 3] } if prev != "@1VotingDecisionCheck" { warning LangRes("@1contract_start_votingdecisioncheck_only") } $voting = DBFind("@1votings").Where({"ecosystem": $ecosystem_id, "id": $VotingId,"voting->name":{"$begin":"voting_for_control_mode_template"}}).Columns("voting->type_decision,flags->success,voting->type").Row() if Int($voting["voting.type"]) != 2 { warning LangRes("@1voting_type_invalid") } if Int($voting["voting.type_decision"]) != 4 { warning LangRes("@1voting_error_decision") } if Int($voting["flags.success"]) != 1 { warning LangRes("@1voting_error_success") } } func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } if $control["control_mode"] == 2 && $VotingId{ decentralizedAutonomous() return } DeveloperCondition() } conditions { $VotingId = Int($VotingId) chooseControl() $result = $control["control_mode"] } } ================================================ FILE: packages/migration/contracts/first_ecosystem/AccessVoteTempRun.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract AccessVoteTempRun { data { ContractAccept string "optional" ContractAcceptParams map "optional" } func votingCheck(){ var app_id int app_id = Int(DBFind("@1applications").Where({"ecosystem": $ecosystem_id, "name": "Basic"}).One("id")) $templateId = Int(DBFind("@1app_params").Where({"app_id": app_id, "name": "voting_template_control_mode", "ecosystem": $ecosystem_id}).One("value")) if $templateId == 0 { warning LangRes("@1template_id_not_found") } } action { votingCheck() var temp map temp["TemplateId"] = $templateId temp["Duration"] = 7 temp["ContractAccept"] = $ContractAccept temp["ContractAcceptParams"] = JSONEncode($ContractAcceptParams) CallContract("@1VotingTemplateRun",temp) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/BindWallet.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract BindWallet { data { Id int } conditions { $cur = DBRow("contracts").Columns("id,conditions,wallet_id").WhereId($Id) if !$cur { error Sprintf("Contract %d does not exist", $Id) } Eval($cur["conditions"]) if $key_id != Int($cur["wallet_id"]) { error Sprintf("Wallet %d cannot activate the contract", $key_id) } } action { BndWallet($Id, $ecosystem_id) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/CallDelayedContract.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract CallDelayedContract { data { Id int } conditions { HonorNodeCondition() var rows array rows = DBFind("@1delayed_contracts").Where({"id": $Id, "deleted": 0}) if !Len(rows) { warning Sprintf(LangRes("@1template_delayed_contract_not_exist"), $Id) } $cur = rows[0] $limit = Int($cur["limit"]) $counter = Int($cur["counter"]) if $block < Int($cur["block_id"]) { warning Sprintf(LangRes("@1template_delayed_contract_error"), $Id, $cur["block_id"], $block) } if $limit > 0 && $counter >= $limit { warning Sprintf(LangRes("@1template_delayed_contract_limited"), $Id) } } action { $counter = $counter + 1 var block_id int block_id = $block if $limit == 0 || $limit > $counter { block_id = block_id + Int($cur["every_block"]) } DBUpdate("@1delayed_contracts", $Id, {"counter": $counter, "block_id": block_id}) var params map CallContract($cur["contract"], params) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/CheckNodesBan.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract CheckNodesBan { func getPermission() { var array_permissions array result i int prevContract string array_permissions = ["@1CheckNodesBan"] prevContract = $stack[0] if Len($stack) > 2 { prevContract = $stack[Len($stack) - 2] } while i < Len(array_permissions) { var contract_name string contract_name = array_permissions[i] if contract_name == prevContract { result = 1 } i = i + 1 } if result == 0 { warning LangRes("@1contract_chain_distorted") } } conditions { getPermission() HonorNodeCondition() var rows array rows = DBFind("@1delayed_contracts").Where({"contract": "@1CheckNodesBan", "deleted": 0}) if !Len(rows) { warning Sprintf(LangRes("@1template_delayed_contract_not_exist"), $Id) } $cur = rows[0] $counter = Int($cur["counter"]) + 1 $Id = Int($cur["id"]) } action { DBUpdateExt("@1delayed_contracts", {"id":$Id}, {"counter": $counter}) UpdateNodesBan($block_time) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditAppParam.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditAppParam { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("app_params", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("app_params", $Id, pars) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditApplication.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditApplication { data { ApplicationId int Conditions string "optional" } func onlyConditions() bool { return $Conditions && false } conditions { RowConditions("applications", $ApplicationId, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("applications", $ApplicationId, pars) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditColumn.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditColumn { data { TableName string Name string Permissions string } conditions { ColumnCondition($TableName, $Name, "", $Permissions) } action { PermColumn($TableName, $Name, $Permissions) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditContract.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditContract { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("contracts", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } $cur = DBFind("contracts").Columns("id,value,conditions,wallet_id,token_id").WhereId($Id).Row() if !$cur { error Sprintf("Contract %d does not exist", $Id) } if $Value { ValidateEditContractNewValue($Value, $cur["value"]) } $recipient = Int($cur["wallet_id"]) } action { UpdateContract($Id, $Value, $Conditions, $recipient, $cur["token_id"]) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditLang.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditLang { data { Id int Trans string } conditions { EvalCondition("parameters", "changing_language", "value") $lang = DBFind("languages").Where({id: $Id}).Row() } action { EditLanguage($Id, $lang["name"], $Trans) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditMenu.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditMenu { data { Id int Value string "optional" Title string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value && !$Title } conditions { RowConditions("menu", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Title { pars["title"] = $Title } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("menu", $Id, pars) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditPage.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditPage { data { Id int Value string "optional" Menu string "optional" Conditions string "optional" ValidateCount int "optional" ValidateMode string "optional" } func onlyConditions() bool { return $Conditions && !$Value && !$Menu && !$ValidateCount } func preparePageValidateCount(count int) int { var min, max int min = Int(EcosysParam("min_page_validate_count")) max = Int(EcosysParam("max_page_validate_count")) if count < min { count = min } else { if count > max { count = max } } return count } conditions { RowConditions("pages", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } $ValidateCount = preparePageValidateCount($ValidateCount) } action { var pars map if $Value { pars["value"] = $Value } if $Menu { pars["menu"] = $Menu } if $Conditions { pars["conditions"] = $Conditions } if $ValidateCount { pars["validate_count"] = $ValidateCount } if $ValidateMode { if $ValidateMode != "1" { $ValidateMode = "0" } pars["validate_mode"] = $ValidateMode } if pars { DBUpdate("pages", $Id, pars) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditParameter.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditParameter { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { DeveloperCondition() RowConditions("@1parameters", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } if $Value { $Name = DBFind("@1parameters").Where({"id": $Id, "ecosystem": $ecosystem_id}).One("name") if $Name == "founder_account" { var account string account = IdToAddress(Int($Value)) if !DBFind("@1keys").Where({"account": account, "ecosystem": $ecosystem_id, "deleted": 0}).One("id") { warning Sprintf(LangRes("@1template_user_not_found"), $Value) } } if $Name == "max_block_user_tx" || $Name == "money_digit" || $Name == "max_sum" || $Name == "min_page_validate_count" || $Name == "max_page_validate_count" { if Size($Value) == 0 { warning LangRes("@1value_not_received") } if Int($Value) <= 0 { warning LangRes("@1value_must_greater_zero") } } } } action { var pars map if $Value { if $Value == `""` { pars["value"] = "" } else { pars["value"] = $Value } } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("@1parameters", $Id, pars) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditSnippet.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditSnippet { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("snippets", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("snippets", $Id, pars) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/EditTable.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract EditTable { data { Name string InsertPerm string UpdatePerm string NewColumnPerm string ReadPerm string "optional" } conditions { if !$InsertPerm { info("Insert condition is empty") } if !$UpdatePerm { info("Update condition is empty") } if !$NewColumnPerm { info("New column condition is empty") } var permissions map permissions["insert"] = $InsertPerm permissions["update"] = $UpdatePerm permissions["new_column"] = $NewColumnPerm if $ReadPerm { permissions["read"] = $ReadPerm } $Permissions = permissions TableConditions($Name, "", JSONEncode($Permissions)) } action { PermTable($Name, JSONEncode($Permissions)) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/HonorNodeCondition.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract HonorNodeCondition { conditions { var account_key int account_key = AddressToId($account_id) if IsHonorNodeKey(account_key) { return } warning "HonorNodeCondition: Sorry, you do not have access to this action" } } ================================================ FILE: packages/migration/contracts/first_ecosystem/Import.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract Import { data { Data string } conditions { $ApplicationId = 0 var app_map map app_map = DBFind("@1buffer_data").Columns("value->app_name").Where({"key": "import_info", "account": $account_id, "ecosystem": $ecosystem_id}).Row() if app_map { var app_id int ival string ival = Str(app_map["value.app_name"]) app_id = Int(DBFind("@1applications").Columns("id").Where({"name": ival, "ecosystem": $ecosystem_id}).One("id")) if app_id { $ApplicationId = app_id } } } action { var editors, creators map editors["pages"] = "EditPage" editors["snippets"] = "EditSnippet" editors["menu"] = "EditMenu" editors["app_params"] = "EditAppParam" editors["languages"] = "EditLang" editors["contracts"] = "EditContract" editors["tables"] = "" // nothing creators["pages"] = "NewPage" creators["snippets"] = "NewSnippet" creators["menu"] = "NewMenu" creators["app_params"] = "NewAppParam" creators["languages"] = "NewLang" creators["contracts"] = "NewContract" creators["tables"] = "NewTable" var dataImport array dataImport = JSONDecode($Data) var i int while i < Len(dataImport) { var item cdata map type name string cdata = dataImport[i] if cdata { cdata["ApplicationId"] = $ApplicationId type = cdata["Type"] name = cdata["Name"] // Println(Sprintf("import %v: %v", type, cdata["Name"])) var tbl string tbl = "@1" + Str(type) if type == "app_params" { item = DBFind(tbl).Where({"name": name, "ecosystem": $ecosystem_id, "app_id": $ApplicationId}).Row() } else { item = DBFind(tbl).Where({"name": name, "ecosystem": $ecosystem_id}).Row() } var contractName string if item { contractName = editors[type] cdata["Id"] = Int(item["id"]) if type == "contracts" { if item["conditions"] == "false" { // ignore updating impossible contractName = "" } } elif type == "menu" { var menu menuItem string menu = Replace(item["value"], " ", "") menu = Replace(menu, "\n", "") menu = Replace(menu, "\r", "") menuItem = Replace(cdata["Value"], " ", "") menuItem = Replace(menuItem, "\n", "") menuItem = Replace(menuItem, "\r", "") if Contains(menu, menuItem) { // ignore repeated contractName = "" } else { cdata["Value"] = item["value"] + "\n" + cdata["Value"] } } } else { contractName = creators[type] } if contractName != "" { CallContract(contractName, cdata) } } i = i + 1 } // Println(Sprintf("> time: %v", $time)) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/ImportUpload.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract ImportUpload { data { Data file } conditions { $Body = BytesToString($Data["Body"]) $limit = 10 // data piece size of import } action { // init buffer_data, cleaning old buffer var initJson map $import_id = Int(DBFind("@1buffer_data").Where({"account": $account_id, "key": "import", "ecosystem": $ecosystem_id}).One("id")) if $import_id { DBUpdate("@1buffer_data", $import_id, {"value": initJson}) } else { $import_id = DBInsert("@1buffer_data", {"account": $account_id, "key": "import", "value": initJson, "ecosystem": $ecosystem_id}) } $info_id = Int(DBFind("@1buffer_data").Where({"account": $account_id, "key": "import_info", "ecosystem": $ecosystem_id}).One("id")) if $info_id { DBUpdate("@1buffer_data", $info_id, {"value": initJson}) } else { $info_id = DBInsert("@1buffer_data", {"account": $account_id, "key": "import_info", "value": initJson, "ecosystem": $ecosystem_id}) } var input map arrData array input = JSONDecode($Body) arrData = input["data"] var pages_arr blocks_arr menu_arr parameters_arr languages_arr contracts_arr tables_arr array // IMPORT INFO var i lenArrData int item map lenArrData = Len(arrData) while i < lenArrData { item = arrData[i] if item["Type"] == "pages" { pages_arr = Append(pages_arr, item["Name"]) } elif item["Type"] == "snippets" { blocks_arr = Append(blocks_arr, item["Name"]) } elif item["Type"] == "menu" { menu_arr = Append(menu_arr, item["Name"]) } elif item["Type"] == "app_params" { parameters_arr = Append(parameters_arr, item["Name"]) } elif item["Type"] == "languages" { languages_arr = Append(languages_arr, item["Name"]) } elif item["Type"] == "contracts" { contracts_arr = Append(contracts_arr, item["Name"]) } elif item["Type"] == "tables" { tables_arr = Append(tables_arr, item["Name"]) } i = i + 1 } var inf map inf["app_name"] = input["name"] inf["pages"] = Join(pages_arr, ", ") inf["pages_count"] = Len(pages_arr) inf["snippets"] = Join(blocks_arr, ", ") inf["blocks_count"] = Len(blocks_arr) inf["menu"] = Join(menu_arr, ", ") inf["menu_count"] = Len(menu_arr) inf["parameters"] = Join(parameters_arr, ", ") inf["parameters_count"] = Len(parameters_arr) inf["languages"] = Join(languages_arr, ", ") inf["languages_count"] = Len(languages_arr) inf["contracts"] = Join(contracts_arr, ", ") inf["contracts_count"] = Len(contracts_arr) inf["tables"] = Join(tables_arr, ", ") inf["tables_count"] = Len(tables_arr) if 0 == inf["pages_count"] + inf["blocks_count"] + inf["menu_count"] + inf["parameters_count"] + inf["languages_count"] + inf["contracts_count"] + inf["tables_count"] { warning "Invalid or empty import file" } // IMPORT DATA // the contracts is imported in one piece, the rest is cut under the $limit var sliced contracts array i = 0 while i < lenArrData { var items array l int item map while l < $limit && (i + l < lenArrData) { item = arrData[i + l] if item["Type"] == "contracts" { contracts = Append(contracts, item) } else { items = Append(items, item) } l = l + 1 } var batch map batch["Data"] = JSONEncode(items) sliced = Append(sliced, batch) i = i + $limit } if Len(contracts) > 0 { var batch map batch["Data"] = JSONEncode(contracts) sliced = Append(sliced, batch) } input["data"] = sliced // storing DBUpdate("@1buffer_data", $import_id, {"value": input}) DBUpdate("@1buffer_data", $info_id, {"value": inf}) var name string name = Str(input["name"]) var cndns string cndns = Str(input["conditions"]) if !DBFind("@1applications").Columns("id").Where({"name": name, "ecosystem": $ecosystem_id}).One("id") { DBInsert("@1applications", {"name": name, "conditions": cndns, "ecosystem": $ecosystem_id}) } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewAppParam.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewAppParam { data { ApplicationId int Name string Value string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("app_params").Columns("id").Where({"name":$Name}).One("id") { warning Sprintf( "Application parameter %s already exists", $Name) } } action { DBInsert("app_params", {app_id: $ApplicationId, name: $Name, value: $Value, conditions: $Conditions}) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewApplication.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewApplication { data { Name string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if Size($Name) == 0 { warning "Application name missing" } if DBFind("applications").Columns("id").Where({name:$Name}).One("id") { warning Sprintf( "Application %s already exists", $Name) } } action { $result = DBInsert("applications", {name: $Name,conditions: $Conditions}) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewBadBlock.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewBadBlock { data { ProducerNodeID int ConsumerNodeID int BlockID int Timestamp int Reason string } action { DBInsert("@1bad_blocks", {producer_node_id: $ProducerNodeID,consumer_node_id: $ConsumerNodeID, block_id: $BlockID, "timestamp block_time": $Timestamp, reason: $Reason}) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewContract.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewContract { data { ApplicationId int Value string Conditions string TokenEcosystem int "optional" } conditions { ValidateCondition($Conditions,$ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } $contract_name = ContractName($Value) if !$contract_name { error "must be the name" } if !$TokenEcosystem { $TokenEcosystem = 1 } else { if !SysFuel($TokenEcosystem) { warning Sprintf("Ecosystem %d is not system", $TokenEcosystem) } } } action { $result = CreateContract($contract_name, $Value, $Conditions, $TokenEcosystem, $ApplicationId) } func price() int { return SysParamInt("contract_price") } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewEcosystem.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewEcosystem { data { Name string } action { $result = CreateEcosystem($key_id, $Name) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewLang.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewLang { data { ApplicationId int Name string Trans string } conditions { if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("languages").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Language resource %s already exists", $Name) } EvalCondition("parameters", "changing_language", "value") } action { CreateLanguage($Name, $Trans) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewMenu.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewMenu { data { Name string Value string Title string "optional" Conditions string } conditions { ValidateCondition($Conditions,$ecosystem_id) if DBFind("menu").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Menu %s already exists", $Name) } } action { DBInsert("menu", {name:$Name,value: $Value, title: $Title, conditions: $Conditions}) } func price() int { return SysParamInt("menu_price") } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewPage.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewPage { data { ApplicationId int Name string Value string Menu string Conditions string ValidateCount int "optional" ValidateMode string "optional" } func preparePageValidateCount(count int) int { var min, max int min = Int(EcosysParam("min_page_validate_count")) max = Int(EcosysParam("max_page_validate_count")) if count < min { count = min } else { if count > max { count = max } } return count } conditions { ValidateCondition($Conditions,$ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("pages").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Page %s already exists", $Name) } $ValidateCount = preparePageValidateCount($ValidateCount) if $ValidateMode { if $ValidateMode != "1" { $ValidateMode = "0" } } } action { DBInsert("pages", {name: $Name,value: $Value, menu: $Menu, validate_count:$ValidateCount,validate_mode: $ValidateMode, conditions: $Conditions,app_id: $ApplicationId}) } func price() int { return SysParamInt("page_price") } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewParameter.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewParameter { data { Name string Value string Conditions string } func warnEmpty(name value string) { if Size(value) == 0 { warning Sprintf(LangRes("@1x_parameter_empty"),name) } } conditions { DeveloperCondition() ValidateCondition($Conditions, $ecosystem_id) $Name = TrimSpace($Name) warnEmpty("Name",$Name) if DBFind("@1parameters").Where({"name": $Name, "ecosystem": $ecosystem_id}).One("id") { warning Sprintf(LangRes("@1template_parameter_exists"), $Name) } } action { DBInsert("@1parameters", {"name": $Name, "value": $Value, "conditions": $Conditions, "ecosystem": $ecosystem_id}) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewSnippet.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewSnippet { data { ApplicationId int Name string Value string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("snippets").Columns("id").Where({name:$Name}).One("id") { warning Sprintf( "Block %s already exists", $Name) } } action { DBInsert("snippets", {name: $Name, value: $Value, conditions: $Conditions, app_id: $ApplicationId}) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewTable.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NewTable { data { ApplicationId int Name string Columns string Permissions string } conditions { if $ApplicationId == 0 { warning "Application id cannot equal 0" } TableConditions($Name, $Columns, $Permissions) } action { CreateTable($Name, $Columns, $Permissions, $ApplicationId) } func price() int { return SysParamInt("table_price") } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NewUser.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("NodeOwnerCondition")' contract NewUser { data { NewPubkey string "optional" Ecosystem int "optional" } func getEcosystem() { $e_id = Int($Ecosystem) if $e_id == 0 { $e_id = $ecosystem_id } $eco = DBFind("@1ecosystems").Where({"id": $e_id}).Row() if !$eco { warning Sprintf(LangRes("@1ecosystem_not_found"), $e_id) } } func canOpt() bool { return $free_membership == 1 || $e_id == 1 } conditions { getEcosystem() $newId = PubToID($NewPubkey) if $newId == 0 { warning LangRes("@1wrong_pub") } if Size($NewPubkey) == 0 { warning "You did not enter the public key" } $pub = HexToPub($NewPubkey) $account = IdToAddress($newId) $k = DBFind("@1keys").Where({"id": $newId, "account": $account, "ecosystem": $e_id}).Row() $free_membership = Int(DBFind("@1parameters").Where({"ecosystem": $e_id, "name": "free_membership"}).One("value")) } action { var iscan bool iscan = canOpt() if !iscan { warning Sprintf(LangRes("@1eco_no_open_new_user"), $eco["name"], $e_id) } if $k { var kid int kpub string kid = Int($k["id"]) kpub = $k["pub"] if Size(kpub) != 0 { warning Sprintf(LangRes("@1template_user_exists"), IdToAddress($newId)) } DBUpdateExt("@1keys", {"id": kid, "ecosystem": $e_id}, {"pub": $pub}) $result = $account }else{ DBInsert("@1keys", {"id": $newId, "account": $account, "pub": $pub, "ecosystem": $e_id}) if !DBFind("@1keys").Where({"ecosystem": 1, "account": $account}).Row() { DBInsert("@1keys", {"id": $newId, "account": $account, "pub": $pub, "ecosystem": 1}) var h map $whiteHoleBalance = DBFind("@1keys").Where({"account": $white_hole_account,"ecosystem":1}).Columns("amount").One("amount") h["sender_id"] =$white_hole_key h["sender_balance"] = $whiteHoleBalance h["recipient_id"] = $newId h["comment"] = "New User" h["block_id"] = $block h["txhash"] = $txhash h["ecosystem"] = 1 h["type"] = 4 h["created_at"] = $time DBInsert("@1history", h) } $result = $account } var h map $whiteHoleBalance = DBFind("@1keys").Where({"account": $white_hole_account,"ecosystem":$ecosystem_id}).Columns("amount").One("amount") h["sender_id"] =$white_hole_key h["sender_balance"] = $whiteHoleBalance h["recipient_id"] = $newId h["comment"] = "New User" h["block_id"] = $block h["txhash"] = $txhash h["ecosystem"] = $ecosystem_id h["type"] = 4 h["created_at"] = $time DBInsert("@1history", h) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/NodeOwnerCondition.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract NodeOwnerCondition { conditions { $raw_honor_nodes = SysParamString("honor_nodes") if Size($raw_honor_nodes) == 0 { ContractConditions("MainCondition") } else { $honor_nodes = JSONDecode($raw_honor_nodes) var i int while i < Len($honor_nodes) { $fn = $honor_nodes[i] if $fn["key_id"] == $key_id { return true } i = i + 1 } warning "NodeOwnerCondition: Sorry, you do not have access to this action." } } } ================================================ FILE: packages/migration/contracts/first_ecosystem/UnbindWallet.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract UnbindWallet { data { Id int } conditions { $cur = DBRow("contracts").Columns("id,conditions,wallet_id").WhereId($Id) if !$cur { error Sprintf("Contract %d does not exist", $Id) } Eval($cur["conditions"]) if $key_id != Int($cur["wallet_id"]) { error Sprintf("Wallet %d cannot deactivate the contract", $key_id) } } action { UnbndWallet($Id, $ecosystem_id) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/UpdatePlatformParam.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract UpdatePlatformParam { data { Name string Value string Conditions string "optional" } conditions { if !GetContractByName($Name){ warning "System parameter not found" } } action { var params map params["Value"] = $Value CallContract($Name, params) DBUpdatePlatformParam($Name, $Value, $Conditions) } } ================================================ FILE: packages/migration/contracts/first_ecosystem/UploadBinary.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract UploadBinary { data { ApplicationId int Name string Data bytes DataMimeType string "optional" MemberAccount string "optional" } conditions { if Size($MemberAccount) > 0 { $UserID = $MemberAccount } else { $UserID = $account_id } $Id = Int(DBFind("@1binaries").Columns("id").Where({"app_id": $ApplicationId, "account": $UserID, "name": $Name, "ecosystem": $ecosystem_id}).One("id")) if $Id == 0 { if $ApplicationId == 0 { warning LangRes("@1aid_cannot_zero") } } } action { var hash string hash = Hash($Data) if $DataMimeType == "" { $DataMimeType = "application/octet-stream" } if $Id != 0 { DBUpdate("@1binaries", $Id, {"data": $Data, "hash": hash, "mime_type": $DataMimeType}) } else { $Id = DBInsert("@1binaries", {"app_id": $ApplicationId, "account": $UserID, "name": $Name, "data": $Data, "hash": hash, "mime_type": $DataMimeType, "ecosystem": $ecosystem_id}) } $result = $Id } } ================================================ FILE: packages/migration/contracts/first_ecosystem/UploadFile.sim ================================================ // +prop AppID = '1' // +prop Conditions = 'ContractConditions("MainCondition")' contract UploadFile { data { ApplicationId int Data file Name string "optional" } conditions { if $Name == "" { $Name = $Data["Name"] } $Body = $Data["Body"] $DataMimeType = $Data["MimeType"] } action { $Id = @1UploadBinary("ApplicationId,Name,Data,DataMimeType", $ApplicationId, $Name, $Body, $DataMimeType) $result = $Id } } ================================================ FILE: packages/migration/contracts_data.go ================================================ // Code generated by go generate; DO NOT EDIT. package migration var contractsDataSQL = ` INSERT INTO "1_contracts" (id, name, value, token_id, conditions, app_id, ecosystem) VALUES (next_id('1_contracts'), 'DeveloperCondition', '// This contract is used to set "developer" rights. // Usually the "developer" role is used for this. // The role ID is written to the ecosystem parameter and can be changed. // The contract requests the role ID from the ecosystem parameter and the contract checks the rights. contract DeveloperCondition { func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } } conditions { chooseControl() if $control["control_mode"] == 2{ return } // check for Founder if EcosysParam("founder_account") == AddressToId($account_id) { return } // check for Developer role var app_id int role_id string app_id = Int(DBFind("@1applications").Where({"ecosystem": $ecosystem_id, "name": "System"}).One("id")) role_id = AppParam(app_id, "role_developer", $ecosystem_id) if Size(role_id) == 0 { warning Sprintf(LangRes("@1x_not_access_action"),"DeveloperCondition") } if !RoleAccess(Int(role_id)) { warning Sprintf(LangRes("@1x_not_access_action"),"DeveloperCondition") } } } ', '{{.Ecosystem}}', 'ContractConditions("MainCondition")', '{{.AppID}}', '{{.Ecosystem}}'), (next_id('1_contracts'), 'MainCondition', 'contract MainCondition { func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } } conditions { chooseControl() if $control["control_mode"] == 2{ return } if EcosysParam("founder_account")!=$key_id { warning "MainCondition: Sorry, you do not have access to this action." } } } ', '{{.Ecosystem}}', 'ContractConditions("MainCondition")', '{{.AppID}}', '{{.Ecosystem}}'); ` ================================================ FILE: packages/migration/data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration //go:generate go run ./gen/contracts.go var ( migrationInitialTables = ` {{headseq "migration_history"}} t.Column("id", "int", {"default_raw": "nextval('migration_history_id_seq')"}) t.Column("version", "string", {"default": "", "size":255}) t.Column("date_applied", "int", {}) {{footer "seq" "primary"}} {{head "block_chain"}} t.Column("id", "bigint", {"default": "0"}) t.Column("hash", "bytea", {"default": ""}) t.Column("rollbacks_hash", "bytea", {"default": ""}) t.Column("data", "bytea", {"default": ""}) t.Column("ecosystem_id", "int", {"default": "0"}) t.Column("key_id", "bigint", {"default": "0"}) t.Column("node_position", "bigint", {"default": "0"}) t.Column("time", "bigint", {"default": "0"}) t.Column("tx", "int", {"default": "0"}) t.Column("consensus_mode", "int", {"default": "1"}) t.Column("candidate_nodes", "bytea", {"default": "\x"}) {{footer "primary" "index(node_position, time)"}} {{head "confirmations"}} t.Column("block_id", "bigint", {"default": "0"}) t.Column("good", "int", {"default": "0"}) t.Column("bad", "int", {"default": "0"}) t.Column("time", "bigint", {"default": "0"}) {{footer "primary(block_id)"}} {{head "info_block"}} t.Column("hash", "bytea", {"default": ""}) t.Column("rollbacks_hash", "bytea", {"default": ""}) t.Column("block_id", "int", {"default": "0"}) t.Column("node_position", "int", {"default": "0"}) t.Column("ecosystem_id", "bigint", {"default": "0"}) t.Column("key_id", "bigint", {"default": "0"}) t.Column("time", "bigint", {"default": "0"}) t.Column("current_version", "string", {"default": "0.0.1", "size": 50}) t.Column("sent", "smallint", {"default": "0"}) t.Column("consensus_mode", "int", {"default": "1"}) t.Column("candidate_nodes", "bytea", {"default": "\x"}) {{footer "index(sent)"}} {{head "install"}} t.Column("progress", "string", {"default": "", "size":10}) {{footer}} {{head "log_transactions"}} t.Column("hash", "bytea", {"default": ""}) t.Column("block", "int", {"default": "0"}) t.Column("timestamp", "bigint", {"default": "0"}) t.Column("contract_name", "string", {"default": "", "size":255}) t.Column("address", "bigint", {"default": "0"}) t.Column("ecosystem_id", "bigint", {"default": "0"}) t.Column("status", "bigint", {"default": "0"}) {{footer "primary(hash)"}} {{head "queue_blocks"}} t.Column("hash", "bytea", {"default": ""}) t.Column("honor_node_id", "bigint", {"default": "0"}) t.Column("block_id", "int", {"default": "0"}) {{footer "primary(hash)"}} {{head "queue_tx"}} t.Column("hash", "bytea", {"default": ""}) t.Column("data", "bytea", {"default": ""}) t.Column("from_gate", "int", {"default": "0"}) t.Column("expedite", "decimal(30)", {"default_raw": "'0' CHECK (expedite >= 0)"}) t.Column("time", "bigint", {"default": "0"}) {{footer "primary(hash)"}} {{headseq "rollback_tx"}} t.Column("id", "bigint", {"default_raw": "nextval('rollback_tx_id_seq')"}) t.Column("block_id", "bigint", {"default": "0"}) t.Column("tx_hash", "bytea", {"default": ""}) t.Column("table_name", "string", {"default": "", "size":255}) t.Column("table_id", "string", {"default": "", "size":255}) t.Column("data_hash", "bytea", {"default": ""}) t.Column("data", "text", {"default": ""}) {{footer "seq" "primary" "index(table_name, table_id, block_id)"}} {{head "stop_daemons"}} t.Column("stop_time", "int", {"default": "0"}) {{footer}} {{head "spent_info"}} t.Column("input_tx_hash", "bytea", {"null": true}) t.Column("input_index", "integer", {"null": true}) t.Column("output_tx_hash", "bytea") t.Column("output_index", "integer") t.Column("output_key_id", "bigint") t.Column("output_value", "decimal(30)", {"default_raw": "'0' CHECK (output_value >= 0)"}) t.Column("ecosystem", "bigint", {"default": "1"}) t.Column("block_id", "bigint") t.Column("type", "bigint") {{footer "primary(output_tx_hash,output_key_id,output_index)" "index(block_id)" "index(input_tx_hash)" "index(output_key_id)" "index(output_tx_hash)"}} {{head "transactions"}} t.Column("hash", "bytea", {"default": ""}) t.Column("data", "bytea", {"default": ""}) t.Column("used", "smallint", {"default": "0"}) t.Column("high_rate", "smallint", {"default": "0"}) t.Column("expedite", "decimal(30)", {"default_raw": "'0' CHECK (expedite >= 0)"}) t.Column("time", "bigint", {"default": "0"}) t.Column("type", "smallint", {"default": "0"}) t.Column("key_id", "bigint", {"default": "0"}) t.Column("sent", "smallint", {"default": "0"}) t.Column("verified", "smallint", {"default": "1"}) {{footer "primary(hash)" "index(sent, used, verified, high_rate)"}} {{head "transactions_status"}} t.Column("hash", "bytea", {"default": ""}) t.Column("time", "bigint", {"default": "0"}) t.Column("type", "int", {"default": "0"}) t.Column("ecosystem", "int", {"default": "1"}) t.Column("wallet_id", "bigint", {"default": "0"}) t.Column("block_id", "int", {"default": "0"}) t.Column("error", "string", {"default": "", "size":255}) t.Column("penalty", "int", {"default": "0"}) {{footer "primary(hash)"}} ` migrationInitialSchema = ` CREATE OR REPLACE FUNCTION next_id(table_name TEXT, OUT result INT) AS $$ BEGIN EXECUTE FORMAT('SELECT COUNT(*) + 1 FROM "%s"', table_name) INTO result; RETURN; END $$ LANGUAGE plpgsql;` tentative = ` {{head "external_blockchain"}} t.Column("id", "bigint", {"default": "0"}) t.Column("value", "text", {"default": ""}) t.Column("url", "string", {"default":"", "size":255}) t.Column("external_contract", "string", {"default":"", "size":255}) t.Column("result_contract", "string", {"default":"", "size":255}) t.Column("uid", "string", {"default":"", "size":255}) t.Column("tx_time", "int", {"default":"0"}) t.Column("sent", "int", {"default":"0"}) t.Column("hash", "bytea", {"default":""}) t.Column("attempts", "int", {"default":"0"}) {{footer "primary"}} {{head "transactions_attempts"}} t.Column("hash", "bytea", {"default": ""}) t.Column("attempt", "smallint", {"default": "0"}) {{footer "primary(hash)" "index(attempt)"}} ` ) ================================================ FILE: packages/migration/ecosystem.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration import ( "bytes" "fmt" "io" "strings" "text/template" "github.com/gobuffalo/fizz" "github.com/gobuffalo/fizz/translators" ) type SqlData struct { Ecosystem int Wallet int64 Name string Founder int64 AppID int64 Account string Digits int64 TokenSymbol string TokenName string } var _ fizz.Translator = (*translators.Postgres)(nil) var pgt = translators.NewPostgres() var tblName string const ( sqlPrimary = "primary" sqlUnique = "unique" sqlIndex = "index" sqlSeq = "seq" ) func sqlHeadSequence(name string) string { ret := fmt.Sprintf(`sql("DROP SEQUENCE IF EXISTS %[1]s_id_seq CASCADE;") sql("CREATE SEQUENCE %[1]s_id_seq START WITH 1;")`, name) return ret + "\r\n" + sqlHead(name) } func sqlHead(name string) string { tblName = name return fmt.Sprintf(`sql("DROP TABLE IF EXISTS \"%[1]s\";") create_table("%[1]s") {`, name) } func sqlEnd(options ...string) (ret string) { ret = `t.DisableTimestamps() }` for _, opt := range options { var cname string if strings.HasPrefix(opt, sqlSeq) { ret += fmt.Sprintf(` sql("ALTER SEQUENCE %[1]s_id_seq owned by %[1]s.id;")`, tblName) continue } if strings.HasPrefix(opt, sqlPrimary) { if opt == sqlPrimary { opt = `PRIMARY KEY (id)` } else { opt = strings.Replace(opt, sqlPrimary, `PRIMARY KEY `, 1) } cname = "pkey" } if strings.HasPrefix(opt, sqlUnique) { pars := strings.Split(strings.Trim(opt[len(sqlUnique):], `() `), `,`) opt = strings.Replace(opt, sqlUnique, `UNIQUE `, 1) for i, val := range pars { pars[i] = strings.TrimSpace(val) } cname = strings.Join(pars, `_`) } if strings.HasPrefix(opt, sqlIndex) { pars := strings.Split(strings.Trim(opt[len(sqlIndex):], `() `), `,`) for i, val := range pars { pars[i] = strings.TrimSpace(val) } if len(pars) == 1 { ret += fmt.Sprintf(` add_index("%s", "%s", {})`, tblName, pars[0]) } else { ret += fmt.Sprintf(` add_index("%s", ["%s"], {})`, tblName, strings.Join(pars, `", "`)) } continue } ret += fmt.Sprintf(` sql("ALTER TABLE ONLY \"%[1]s\" ADD CONSTRAINT \"%[1]s_%[3]s\" %[2]s;")`, tblName, opt, cname) } return } func sqlConvert(in []string) (ret string, err error) { var item string funcs := template.FuncMap{ "head": sqlHead, "footer": sqlEnd, "headseq": sqlHeadSequence, } sqlTmpl := template.New("sql").Funcs(funcs) for _, sql := range in { var ( tmpl *template.Template out bytes.Buffer ) if tmpl, err = sqlTmpl.Parse(sql); err != nil { return } if err = tmpl.Execute(io.Writer(&out), nil); err != nil { return } item, err = fizz.AString(out.String(), pgt) if err != nil { return } ret += item + "\r\n" } return } func sqlTemplate(input []string, data any) (ret string, err error) { for _, item := range input { var ( out bytes.Buffer tmpl *template.Template ) tmpl, err = template.New("sql").Parse(item) if err != nil { return } if err = tmpl.Execute(io.Writer(&out), data); err != nil { return } ret += out.String() + "\r\n" } return } // GetEcosystemScript returns script to create ecosystem func GetEcosystemScript(data SqlData) (string, error) { return sqlTemplate([]string{ contractsDataSQL, menuDataSQL, pagesDataSQL, parametersDataSQL, membersDataSQL, sectionsDataSQL, keysDataSQL, }, data) } // GetFirstEcosystemScript returns script to update with additional data for first ecosystem func GetFirstEcosystemScript(data SqlData) (ret string, err error) { ret, err = sqlConvert([]string{ sqlFirstEcosystemSchema, }) if err != nil { return } var out string out, err = sqlTemplate([]string{ firstDelayedContractsDataSQL, firstEcosystemDataSQL, }, data) ret += out scripts := []string{ firstEcosystemContractsSQL, firstEcosystemPagesDataSQL, firstEcosystemBlocksDataSQL, platformParametersDataSQL, firstTablesDataSQL, } ret += strings.Join(scripts, "\r\n") return } // GetFirstTableScript returns script to update _tables for first ecosystem func GetFirstTableScript(data SqlData) (string, error) { return sqlTemplate([]string{ tablesDataSQL, }, data) } // GetCommonEcosystemScript returns script with common tables func GetCommonEcosystemScript() (string, error) { sql, err := sqlConvert([]string{ sqlFirstEcosystemCommon, sqlTimeZonesSQL, }) if err != nil { return ``, err } return sql + "\r\n" + timeZonesSQL, nil } ================================================ FILE: packages/migration/ecosystem_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration import ( "fmt" "os" "testing" ) func TestGetEcosystemScript(t *testing.T) { str := fmt.Sprintf(GetFirstEcosystemScript(SqlData{Wallet: -1744264011260937456})) path, _ := os.Getwd() os.WriteFile(path+"/eco.sql", []byte(str), 0777) } ================================================ FILE: packages/migration/first_delayed_contracts.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var firstDelayedContractsDataSQL = `INSERT INTO "1_delayed_contracts" ("id", "contract", "key_id", "block_id", "every_block", "high_rate", "conditions") VALUES (next_id('1_delayed_contracts'), '@1CheckNodesBan', '{{.Wallet}}', '10', '10', '4','ContractConditions("@1MainCondition")'); ` ================================================ FILE: packages/migration/first_ecosys_contracts_data.go ================================================ // Code generated by go generate; DO NOT EDIT. package migration var firstEcosystemContractsSQL = ` INSERT INTO "1_contracts" (id, name, value, token_id, conditions, app_id, ecosystem) VALUES (next_id('1_contracts'), 'AccessControlMode', 'contract AccessControlMode { data { VotingId int "optional" } func decentralizedAutonomous(){ if !DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":2}).Row(){ warning "control mode DAO error" } var prev string prev = $stack[0] if Len($stack) > 3{ prev = $stack[Len($stack) - 3] } if prev != "@1VotingDecisionCheck" { warning LangRes("@1contract_start_votingdecisioncheck_only") } $voting = DBFind("@1votings").Where({"ecosystem": $ecosystem_id, "id": $VotingId,"voting->name":{"$begin":"voting_for_control_mode_template"}}).Columns("voting->type_decision,flags->success,voting->type").Row() if Int($voting["voting.type"]) != 2 { warning LangRes("@1voting_type_invalid") } if Int($voting["voting.type_decision"]) != 4 { warning LangRes("@1voting_error_decision") } if Int($voting["flags.success"]) != 1 { warning LangRes("@1voting_error_success") } } func chooseControl(){ $control = DBFind("@1ecosystems").Where({"id":$ecosystem_id,"control_mode":{"$in":["1","2"]}}).Row() if !$control{ warning "control mode error" } if $control["control_mode"] == 2 && $VotingId{ decentralizedAutonomous() return } DeveloperCondition() } conditions { $VotingId = Int($VotingId) chooseControl() $result = $control["control_mode"] } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'AccessVoteTempRun', 'contract AccessVoteTempRun { data { ContractAccept string "optional" ContractAcceptParams map "optional" } func votingCheck(){ var app_id int app_id = Int(DBFind("@1applications").Where({"ecosystem": $ecosystem_id, "name": "Basic"}).One("id")) $templateId = Int(DBFind("@1app_params").Where({"app_id": app_id, "name": "voting_template_control_mode", "ecosystem": $ecosystem_id}).One("value")) if $templateId == 0 { warning LangRes("@1template_id_not_found") } } action { votingCheck() var temp map temp["TemplateId"] = $templateId temp["Duration"] = 7 temp["ContractAccept"] = $ContractAccept temp["ContractAcceptParams"] = JSONEncode($ContractAcceptParams) CallContract("@1VotingTemplateRun",temp) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'BindWallet', 'contract BindWallet { data { Id int } conditions { $cur = DBRow("contracts").Columns("id,conditions,wallet_id").WhereId($Id) if !$cur { error Sprintf("Contract %d does not exist", $Id) } Eval($cur["conditions"]) if $key_id != Int($cur["wallet_id"]) { error Sprintf("Wallet %d cannot activate the contract", $key_id) } } action { BndWallet($Id, $ecosystem_id) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'CallDelayedContract', 'contract CallDelayedContract { data { Id int } conditions { HonorNodeCondition() var rows array rows = DBFind("@1delayed_contracts").Where({"id": $Id, "deleted": 0}) if !Len(rows) { warning Sprintf(LangRes("@1template_delayed_contract_not_exist"), $Id) } $cur = rows[0] $limit = Int($cur["limit"]) $counter = Int($cur["counter"]) if $block < Int($cur["block_id"]) { warning Sprintf(LangRes("@1template_delayed_contract_error"), $Id, $cur["block_id"], $block) } if $limit > 0 && $counter >= $limit { warning Sprintf(LangRes("@1template_delayed_contract_limited"), $Id) } } action { $counter = $counter + 1 var block_id int block_id = $block if $limit == 0 || $limit > $counter { block_id = block_id + Int($cur["every_block"]) } DBUpdate("@1delayed_contracts", $Id, {"counter": $counter, "block_id": block_id}) var params map CallContract($cur["contract"], params) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'CheckNodesBan', 'contract CheckNodesBan { func getPermission() { var array_permissions array result i int prevContract string array_permissions = ["@1CheckNodesBan"] prevContract = $stack[0] if Len($stack) > 2 { prevContract = $stack[Len($stack) - 2] } while i < Len(array_permissions) { var contract_name string contract_name = array_permissions[i] if contract_name == prevContract { result = 1 } i = i + 1 } if result == 0 { warning LangRes("@1contract_chain_distorted") } } conditions { getPermission() HonorNodeCondition() var rows array rows = DBFind("@1delayed_contracts").Where({"contract": "@1CheckNodesBan", "deleted": 0}) if !Len(rows) { warning Sprintf(LangRes("@1template_delayed_contract_not_exist"), $Id) } $cur = rows[0] $counter = Int($cur["counter"]) + 1 $Id = Int($cur["id"]) } action { DBUpdateExt("@1delayed_contracts", {"id":$Id}, {"counter": $counter}) UpdateNodesBan($block_time) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditAppParam', 'contract EditAppParam { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("app_params", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("app_params", $Id, pars) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditApplication', 'contract EditApplication { data { ApplicationId int Conditions string "optional" } func onlyConditions() bool { return $Conditions && false } conditions { RowConditions("applications", $ApplicationId, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("applications", $ApplicationId, pars) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditColumn', 'contract EditColumn { data { TableName string Name string Permissions string } conditions { ColumnCondition($TableName, $Name, "", $Permissions) } action { PermColumn($TableName, $Name, $Permissions) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditContract', 'contract EditContract { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("contracts", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } $cur = DBFind("contracts").Columns("id,value,conditions,wallet_id,token_id").WhereId($Id).Row() if !$cur { error Sprintf("Contract %d does not exist", $Id) } if $Value { ValidateEditContractNewValue($Value, $cur["value"]) } $recipient = Int($cur["wallet_id"]) } action { UpdateContract($Id, $Value, $Conditions, $recipient, $cur["token_id"]) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditLang', 'contract EditLang { data { Id int Trans string } conditions { EvalCondition("parameters", "changing_language", "value") $lang = DBFind("languages").Where({id: $Id}).Row() } action { EditLanguage($Id, $lang["name"], $Trans) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditMenu', 'contract EditMenu { data { Id int Value string "optional" Title string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value && !$Title } conditions { RowConditions("menu", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Title { pars["title"] = $Title } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("menu", $Id, pars) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditPage', 'contract EditPage { data { Id int Value string "optional" Menu string "optional" Conditions string "optional" ValidateCount int "optional" ValidateMode string "optional" } func onlyConditions() bool { return $Conditions && !$Value && !$Menu && !$ValidateCount } func preparePageValidateCount(count int) int { var min, max int min = Int(EcosysParam("min_page_validate_count")) max = Int(EcosysParam("max_page_validate_count")) if count < min { count = min } else { if count > max { count = max } } return count } conditions { RowConditions("pages", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } $ValidateCount = preparePageValidateCount($ValidateCount) } action { var pars map if $Value { pars["value"] = $Value } if $Menu { pars["menu"] = $Menu } if $Conditions { pars["conditions"] = $Conditions } if $ValidateCount { pars["validate_count"] = $ValidateCount } if $ValidateMode { if $ValidateMode != "1" { $ValidateMode = "0" } pars["validate_mode"] = $ValidateMode } if pars { DBUpdate("pages", $Id, pars) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditParameter', 'contract EditParameter { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { DeveloperCondition() RowConditions("@1parameters", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } if $Value { $Name = DBFind("@1parameters").Where({"id": $Id, "ecosystem": $ecosystem_id}).One("name") if $Name == "founder_account" { var account string account = IdToAddress(Int($Value)) if !DBFind("@1keys").Where({"account": account, "ecosystem": $ecosystem_id, "deleted": 0}).One("id") { warning Sprintf(LangRes("@1template_user_not_found"), $Value) } } if $Name == "max_block_user_tx" || $Name == "money_digit" || $Name == "max_sum" || $Name == "min_page_validate_count" || $Name == "max_page_validate_count" { if Size($Value) == 0 { warning LangRes("@1value_not_received") } if Int($Value) <= 0 { warning LangRes("@1value_must_greater_zero") } } } } action { var pars map if $Value { if $Value == ` + "`" + `""` + "`" + ` { pars["value"] = "" } else { pars["value"] = $Value } } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("@1parameters", $Id, pars) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditSnippet', 'contract EditSnippet { data { Id int Value string "optional" Conditions string "optional" } func onlyConditions() bool { return $Conditions && !$Value } conditions { RowConditions("snippets", $Id, onlyConditions()) if $Conditions { ValidateCondition($Conditions, $ecosystem_id) } } action { var pars map if $Value { pars["value"] = $Value } if $Conditions { pars["conditions"] = $Conditions } if pars { DBUpdate("snippets", $Id, pars) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'EditTable', 'contract EditTable { data { Name string InsertPerm string UpdatePerm string NewColumnPerm string ReadPerm string "optional" } conditions { if !$InsertPerm { info("Insert condition is empty") } if !$UpdatePerm { info("Update condition is empty") } if !$NewColumnPerm { info("New column condition is empty") } var permissions map permissions["insert"] = $InsertPerm permissions["update"] = $UpdatePerm permissions["new_column"] = $NewColumnPerm if $ReadPerm { permissions["read"] = $ReadPerm } $Permissions = permissions TableConditions($Name, "", JSONEncode($Permissions)) } action { PermTable($Name, JSONEncode($Permissions)) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'HonorNodeCondition', 'contract HonorNodeCondition { conditions { var account_key int account_key = AddressToId($account_id) if IsHonorNodeKey(account_key) { return } warning "HonorNodeCondition: Sorry, you do not have access to this action" } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'Import', 'contract Import { data { Data string } conditions { $ApplicationId = 0 var app_map map app_map = DBFind("@1buffer_data").Columns("value->app_name").Where({"key": "import_info", "account": $account_id, "ecosystem": $ecosystem_id}).Row() if app_map { var app_id int ival string ival = Str(app_map["value.app_name"]) app_id = Int(DBFind("@1applications").Columns("id").Where({"name": ival, "ecosystem": $ecosystem_id}).One("id")) if app_id { $ApplicationId = app_id } } } action { var editors, creators map editors["pages"] = "EditPage" editors["snippets"] = "EditSnippet" editors["menu"] = "EditMenu" editors["app_params"] = "EditAppParam" editors["languages"] = "EditLang" editors["contracts"] = "EditContract" editors["tables"] = "" // nothing creators["pages"] = "NewPage" creators["snippets"] = "NewSnippet" creators["menu"] = "NewMenu" creators["app_params"] = "NewAppParam" creators["languages"] = "NewLang" creators["contracts"] = "NewContract" creators["tables"] = "NewTable" var dataImport array dataImport = JSONDecode($Data) var i int while i < Len(dataImport) { var item cdata map type name string cdata = dataImport[i] if cdata { cdata["ApplicationId"] = $ApplicationId type = cdata["Type"] name = cdata["Name"] // Println(Sprintf("import %v: %v", type, cdata["Name"])) var tbl string tbl = "@1" + Str(type) if type == "app_params" { item = DBFind(tbl).Where({"name": name, "ecosystem": $ecosystem_id, "app_id": $ApplicationId}).Row() } else { item = DBFind(tbl).Where({"name": name, "ecosystem": $ecosystem_id}).Row() } var contractName string if item { contractName = editors[type] cdata["Id"] = Int(item["id"]) if type == "contracts" { if item["conditions"] == "false" { // ignore updating impossible contractName = "" } } elif type == "menu" { var menu menuItem string menu = Replace(item["value"], " ", "") menu = Replace(menu, "\n", "") menu = Replace(menu, "\r", "") menuItem = Replace(cdata["Value"], " ", "") menuItem = Replace(menuItem, "\n", "") menuItem = Replace(menuItem, "\r", "") if Contains(menu, menuItem) { // ignore repeated contractName = "" } else { cdata["Value"] = item["value"] + "\n" + cdata["Value"] } } } else { contractName = creators[type] } if contractName != "" { CallContract(contractName, cdata) } } i = i + 1 } // Println(Sprintf("> time: %v", $time)) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'ImportUpload', 'contract ImportUpload { data { Data file } conditions { $Body = BytesToString($Data["Body"]) $limit = 10 // data piece size of import } action { // init buffer_data, cleaning old buffer var initJson map $import_id = Int(DBFind("@1buffer_data").Where({"account": $account_id, "key": "import", "ecosystem": $ecosystem_id}).One("id")) if $import_id { DBUpdate("@1buffer_data", $import_id, {"value": initJson}) } else { $import_id = DBInsert("@1buffer_data", {"account": $account_id, "key": "import", "value": initJson, "ecosystem": $ecosystem_id}) } $info_id = Int(DBFind("@1buffer_data").Where({"account": $account_id, "key": "import_info", "ecosystem": $ecosystem_id}).One("id")) if $info_id { DBUpdate("@1buffer_data", $info_id, {"value": initJson}) } else { $info_id = DBInsert("@1buffer_data", {"account": $account_id, "key": "import_info", "value": initJson, "ecosystem": $ecosystem_id}) } var input map arrData array input = JSONDecode($Body) arrData = input["data"] var pages_arr blocks_arr menu_arr parameters_arr languages_arr contracts_arr tables_arr array // IMPORT INFO var i lenArrData int item map lenArrData = Len(arrData) while i < lenArrData { item = arrData[i] if item["Type"] == "pages" { pages_arr = Append(pages_arr, item["Name"]) } elif item["Type"] == "snippets" { blocks_arr = Append(blocks_arr, item["Name"]) } elif item["Type"] == "menu" { menu_arr = Append(menu_arr, item["Name"]) } elif item["Type"] == "app_params" { parameters_arr = Append(parameters_arr, item["Name"]) } elif item["Type"] == "languages" { languages_arr = Append(languages_arr, item["Name"]) } elif item["Type"] == "contracts" { contracts_arr = Append(contracts_arr, item["Name"]) } elif item["Type"] == "tables" { tables_arr = Append(tables_arr, item["Name"]) } i = i + 1 } var inf map inf["app_name"] = input["name"] inf["pages"] = Join(pages_arr, ", ") inf["pages_count"] = Len(pages_arr) inf["snippets"] = Join(blocks_arr, ", ") inf["blocks_count"] = Len(blocks_arr) inf["menu"] = Join(menu_arr, ", ") inf["menu_count"] = Len(menu_arr) inf["parameters"] = Join(parameters_arr, ", ") inf["parameters_count"] = Len(parameters_arr) inf["languages"] = Join(languages_arr, ", ") inf["languages_count"] = Len(languages_arr) inf["contracts"] = Join(contracts_arr, ", ") inf["contracts_count"] = Len(contracts_arr) inf["tables"] = Join(tables_arr, ", ") inf["tables_count"] = Len(tables_arr) if 0 == inf["pages_count"] + inf["blocks_count"] + inf["menu_count"] + inf["parameters_count"] + inf["languages_count"] + inf["contracts_count"] + inf["tables_count"] { warning "Invalid or empty import file" } // IMPORT DATA // the contracts is imported in one piece, the rest is cut under the $limit var sliced contracts array i = 0 while i < lenArrData { var items array l int item map while l < $limit && (i + l < lenArrData) { item = arrData[i + l] if item["Type"] == "contracts" { contracts = Append(contracts, item) } else { items = Append(items, item) } l = l + 1 } var batch map batch["Data"] = JSONEncode(items) sliced = Append(sliced, batch) i = i + $limit } if Len(contracts) > 0 { var batch map batch["Data"] = JSONEncode(contracts) sliced = Append(sliced, batch) } input["data"] = sliced // storing DBUpdate("@1buffer_data", $import_id, {"value": input}) DBUpdate("@1buffer_data", $info_id, {"value": inf}) var name string name = Str(input["name"]) var cndns string cndns = Str(input["conditions"]) if !DBFind("@1applications").Columns("id").Where({"name": name, "ecosystem": $ecosystem_id}).One("id") { DBInsert("@1applications", {"name": name, "conditions": cndns, "ecosystem": $ecosystem_id}) } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewAppParam', 'contract NewAppParam { data { ApplicationId int Name string Value string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("app_params").Columns("id").Where({"name":$Name}).One("id") { warning Sprintf( "Application parameter %s already exists", $Name) } } action { DBInsert("app_params", {app_id: $ApplicationId, name: $Name, value: $Value, conditions: $Conditions}) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewApplication', 'contract NewApplication { data { Name string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if Size($Name) == 0 { warning "Application name missing" } if DBFind("applications").Columns("id").Where({name:$Name}).One("id") { warning Sprintf( "Application %s already exists", $Name) } } action { $result = DBInsert("applications", {name: $Name,conditions: $Conditions}) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewBadBlock', 'contract NewBadBlock { data { ProducerNodeID int ConsumerNodeID int BlockID int Timestamp int Reason string } action { DBInsert("@1bad_blocks", {producer_node_id: $ProducerNodeID,consumer_node_id: $ConsumerNodeID, block_id: $BlockID, "timestamp block_time": $Timestamp, reason: $Reason}) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewContract', 'contract NewContract { data { ApplicationId int Value string Conditions string TokenEcosystem int "optional" } conditions { ValidateCondition($Conditions,$ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } $contract_name = ContractName($Value) if !$contract_name { error "must be the name" } if !$TokenEcosystem { $TokenEcosystem = 1 } else { if !SysFuel($TokenEcosystem) { warning Sprintf("Ecosystem %d is not system", $TokenEcosystem) } } } action { $result = CreateContract($contract_name, $Value, $Conditions, $TokenEcosystem, $ApplicationId) } func price() int { return SysParamInt("contract_price") } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewEcosystem', 'contract NewEcosystem { data { Name string } action { $result = CreateEcosystem($key_id, $Name) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewLang', 'contract NewLang { data { ApplicationId int Name string Trans string } conditions { if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("languages").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Language resource %s already exists", $Name) } EvalCondition("parameters", "changing_language", "value") } action { CreateLanguage($Name, $Trans) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewMenu', 'contract NewMenu { data { Name string Value string Title string "optional" Conditions string } conditions { ValidateCondition($Conditions,$ecosystem_id) if DBFind("menu").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Menu %s already exists", $Name) } } action { DBInsert("menu", {name:$Name,value: $Value, title: $Title, conditions: $Conditions}) } func price() int { return SysParamInt("menu_price") } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewPage', 'contract NewPage { data { ApplicationId int Name string Value string Menu string Conditions string ValidateCount int "optional" ValidateMode string "optional" } func preparePageValidateCount(count int) int { var min, max int min = Int(EcosysParam("min_page_validate_count")) max = Int(EcosysParam("max_page_validate_count")) if count < min { count = min } else { if count > max { count = max } } return count } conditions { ValidateCondition($Conditions,$ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("pages").Columns("id").Where({name: $Name}).One("id") { warning Sprintf( "Page %s already exists", $Name) } $ValidateCount = preparePageValidateCount($ValidateCount) if $ValidateMode { if $ValidateMode != "1" { $ValidateMode = "0" } } } action { DBInsert("pages", {name: $Name,value: $Value, menu: $Menu, validate_count:$ValidateCount,validate_mode: $ValidateMode, conditions: $Conditions,app_id: $ApplicationId}) } func price() int { return SysParamInt("page_price") } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewParameter', 'contract NewParameter { data { Name string Value string Conditions string } func warnEmpty(name value string) { if Size(value) == 0 { warning Sprintf(LangRes("@1x_parameter_empty"),name) } } conditions { DeveloperCondition() ValidateCondition($Conditions, $ecosystem_id) $Name = TrimSpace($Name) warnEmpty("Name",$Name) if DBFind("@1parameters").Where({"name": $Name, "ecosystem": $ecosystem_id}).One("id") { warning Sprintf(LangRes("@1template_parameter_exists"), $Name) } } action { DBInsert("@1parameters", {"name": $Name, "value": $Value, "conditions": $Conditions, "ecosystem": $ecosystem_id}) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewSnippet', 'contract NewSnippet { data { ApplicationId int Name string Value string Conditions string } conditions { ValidateCondition($Conditions, $ecosystem_id) if $ApplicationId == 0 { warning "Application id cannot equal 0" } if DBFind("snippets").Columns("id").Where({name:$Name}).One("id") { warning Sprintf( "Block %s already exists", $Name) } } action { DBInsert("snippets", {name: $Name, value: $Value, conditions: $Conditions, app_id: $ApplicationId}) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewTable', 'contract NewTable { data { ApplicationId int Name string Columns string Permissions string } conditions { if $ApplicationId == 0 { warning "Application id cannot equal 0" } TableConditions($Name, $Columns, $Permissions) } action { CreateTable($Name, $Columns, $Permissions, $ApplicationId) } func price() int { return SysParamInt("table_price") } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'NewUser', 'contract NewUser { data { NewPubkey string "optional" Ecosystem int "optional" } func getEcosystem() { $e_id = Int($Ecosystem) if $e_id == 0 { $e_id = $ecosystem_id } $eco = DBFind("@1ecosystems").Where({"id": $e_id}).Row() if !$eco { warning Sprintf(LangRes("@1ecosystem_not_found"), $e_id) } } func canOpt() bool { return $free_membership == 1 || $e_id == 1 } conditions { getEcosystem() $newId = PubToID($NewPubkey) if $newId == 0 { warning LangRes("@1wrong_pub") } if Size($NewPubkey) == 0 { warning "You did not enter the public key" } $pub = HexToPub($NewPubkey) $account = IdToAddress($newId) $k = DBFind("@1keys").Where({"id": $newId, "account": $account, "ecosystem": $e_id}).Row() $free_membership = Int(DBFind("@1parameters").Where({"ecosystem": $e_id, "name": "free_membership"}).One("value")) } action { var iscan bool iscan = canOpt() if !iscan { warning Sprintf(LangRes("@1eco_no_open_new_user"), $eco["name"], $e_id) } if $k { var kid int kpub string kid = Int($k["id"]) kpub = $k["pub"] if Size(kpub) != 0 { warning Sprintf(LangRes("@1template_user_exists"), IdToAddress($newId)) } DBUpdateExt("@1keys", {"id": kid, "ecosystem": $e_id}, {"pub": $pub}) $result = $account }else{ DBInsert("@1keys", {"id": $newId, "account": $account, "pub": $pub, "ecosystem": $e_id}) if !DBFind("@1keys").Where({"ecosystem": 1, "account": $account}).Row() { DBInsert("@1keys", {"id": $newId, "account": $account, "pub": $pub, "ecosystem": 1}) var h map $whiteHoleBalance = DBFind("@1keys").Where({"account": $white_hole_account,"ecosystem":1}).Columns("amount").One("amount") h["sender_id"] =$white_hole_key h["sender_balance"] = $whiteHoleBalance h["recipient_id"] = $newId h["comment"] = "New User" h["block_id"] = $block h["txhash"] = $txhash h["ecosystem"] = 1 h["type"] = 4 h["created_at"] = $time DBInsert("@1history", h) } $result = $account } var h map $whiteHoleBalance = DBFind("@1keys").Where({"account": $white_hole_account,"ecosystem":$ecosystem_id}).Columns("amount").One("amount") h["sender_id"] =$white_hole_key h["sender_balance"] = $whiteHoleBalance h["recipient_id"] = $newId h["comment"] = "New User" h["block_id"] = $block h["txhash"] = $txhash h["ecosystem"] = $ecosystem_id h["type"] = 4 h["created_at"] = $time DBInsert("@1history", h) } } ', '1', 'ContractConditions("NodeOwnerCondition")', '1', '1'), (next_id('1_contracts'), 'NodeOwnerCondition', 'contract NodeOwnerCondition { conditions { $raw_honor_nodes = SysParamString("honor_nodes") if Size($raw_honor_nodes) == 0 { ContractConditions("MainCondition") } else { $honor_nodes = JSONDecode($raw_honor_nodes) var i int while i < Len($honor_nodes) { $fn = $honor_nodes[i] if $fn["key_id"] == $key_id { return true } i = i + 1 } warning "NodeOwnerCondition: Sorry, you do not have access to this action." } } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'UnbindWallet', 'contract UnbindWallet { data { Id int } conditions { $cur = DBRow("contracts").Columns("id,conditions,wallet_id").WhereId($Id) if !$cur { error Sprintf("Contract %d does not exist", $Id) } Eval($cur["conditions"]) if $key_id != Int($cur["wallet_id"]) { error Sprintf("Wallet %d cannot deactivate the contract", $key_id) } } action { UnbndWallet($Id, $ecosystem_id) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'UpdatePlatformParam', 'contract UpdatePlatformParam { data { Name string Value string Conditions string "optional" } conditions { if !GetContractByName($Name){ warning "System parameter not found" } } action { var params map params["Value"] = $Value CallContract($Name, params) DBUpdatePlatformParam($Name, $Value, $Conditions) } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'UploadBinary', 'contract UploadBinary { data { ApplicationId int Name string Data bytes DataMimeType string "optional" MemberAccount string "optional" } conditions { if Size($MemberAccount) > 0 { $UserID = $MemberAccount } else { $UserID = $account_id } $Id = Int(DBFind("@1binaries").Columns("id").Where({"app_id": $ApplicationId, "account": $UserID, "name": $Name, "ecosystem": $ecosystem_id}).One("id")) if $Id == 0 { if $ApplicationId == 0 { warning LangRes("@1aid_cannot_zero") } } } action { var hash string hash = Hash($Data) if $DataMimeType == "" { $DataMimeType = "application/octet-stream" } if $Id != 0 { DBUpdate("@1binaries", $Id, {"data": $Data, "hash": hash, "mime_type": $DataMimeType}) } else { $Id = DBInsert("@1binaries", {"app_id": $ApplicationId, "account": $UserID, "name": $Name, "data": $Data, "hash": hash, "mime_type": $DataMimeType, "ecosystem": $ecosystem_id}) } $result = $Id } } ', '1', 'ContractConditions("MainCondition")', '1', '1'), (next_id('1_contracts'), 'UploadFile', 'contract UploadFile { data { ApplicationId int Data file Name string "optional" } conditions { if $Name == "" { $Name = $Data["Name"] } $Body = $Data["Body"] $DataMimeType = $Data["MimeType"] } action { $Id = @1UploadBinary("ApplicationId,Name,Data,DataMimeType", $ApplicationId, $Name, $Body, $DataMimeType) $result = $Id } } ', '1', 'ContractConditions("MainCondition")', '1', '1'); ` ================================================ FILE: packages/migration/first_ecosys_pages_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var firstEcosystemPagesDataSQL = `INSERT INTO "1_pages" (id, name, value, menu, conditions, app_id, ecosystem) VALUES (next_id('1_pages'), 'notifications', '', 'default_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'), (next_id('1_pages'), 'import_app', 'Div(content-wrapper){ DBFind(@1buffer_data).Columns("id,value->name,value->data").Where({"key": import, "account": #account_id#, "ecosystem": #ecosystem_id#}).Vars(import) DBFind(@1buffer_data).Columns("value->app_name,value->pages,value->pages_count,value->blocks,value->blocks_count,value->menu,value->menu_count,value->parameters,value->parameters_count,value->languages,value->languages_count,value->contracts,value->contracts_count,value->tables,value->tables_count").Where({"key": import_info, "account": #account_id#, "ecosystem": #ecosystem_id#}).Vars(info) SetTitle("Import - #info_value_app_name#") Data(data_info, "DataName,DataCount,DataInfo"){ Pages,"#info_value_pages_count#","#info_value_pages#" Blocks,"#info_value_blocks_count#","#info_value_blocks#" Menu,"#info_value_menu_count#","#info_value_menu#" Parameters,"#info_value_parameters_count#","#info_value_parameters#" Language resources,"#info_value_languages_count#","#info_value_languages#" Contracts,"#info_value_contracts_count#","#info_value_contracts#" Tables,"#info_value_tables_count#","#info_value_tables#" } Div(breadcrumb){ Span(Class: text-muted, Body: "Your data that you can import") } Div(panel panel-primary){ ForList(data_info){ Div(list-group-item){ Div(row){ Div(col-md-10 mc-sm text-left){ Span(Class: text-bold, Body: "#DataName#") } Div(col-md-2 mc-sm text-right){ If(#DataCount# > 0){ Span(Class: text-bold, Body: "(#DataCount#)") }.Else{ Span(Class: text-muted, Body: "(0)") } } } Div(row){ Div(col-md-12 mc-sm text-left){ If(#DataCount# > 0){ Span(Class: h6, Body: "#DataInfo#") }.Else{ Span(Class: text-muted h6, Body: "Nothing selected") } } } } } If(#import_id# > 0){ Div(list-group-item text-right){ VarAsIs(imp_data, "#import_value_data#") Button(Body: "Import", Class: btn btn-primary, Page: @1apps_list).CompositeContract(@1Import, "#imp_data#") } } } }', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'), (next_id('1_pages'), 'import_upload', 'Div(content-wrapper){ SetTitle("Import") Div(breadcrumb){ Span(Class: text-muted, Body: "Select payload that you want to import") } Form(panel panel-primary){ Div(list-group-item){ Input(Name: Data, Type: file) } Div(list-group-item text-right){ Button(Body: "Load", Class: btn btn-primary, Contract: @1ImportUpload, Page: @1import_app) } } }', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '1', '1'); ` ================================================ FILE: packages/migration/first_ecosys_snippets_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var firstEcosystemBlocksDataSQL = `INSERT INTO "1_snippets" (id, name, value, conditions, app_id, ecosystem) VALUES (next_id('1_snippets'), 'pager_header', '', 'ContractConditions("@1DeveloperCondition")', '1', '1'); ` ================================================ FILE: packages/migration/first_ecosystem_schema.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration // sqlFirstEcosystemSchema contains SQL queries for creating first ecosystem var sqlFirstEcosystemSchema = ` {{head "1_ecosystems"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":255}) t.Column("info", "jsonb", {"null": true}) t.Column("fee_mode_info", "jsonb", {"null": true}) t.Column("is_valued", "bigint", {"default": "0"}) t.Column("emission_amount", "jsonb", {"null": true}) t.Column("token_symbol", "string", {"null": true, "size":255}) t.Column("token_name", "string", {"null": true, "size":255}) t.Column("type_emission", "bigint", {"default": "0"}) t.Column("type_withdraw", "bigint", {"default": "0"}) t.Column("control_mode", "bigint", {"default": "1"}) t.Column("digits", "bigint", {"default": "0"}) {{footer "primary"}} {{head "1_platform_parameters"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":255}) t.Column("value", "text", {"default": ""}) t.Column("conditions", "text", {"default": ""}) {{footer "primary" "index(name)"}} {{head "1_delayed_contracts"}} t.Column("id", "bigint", {"default": "0"}) t.Column("contract", "string", {"default": "", "size":255}) t.Column("key_id", "bigint", {"default": "0"}) t.Column("block_id", "bigint", {"default": "0"}) t.Column("every_block", "bigint", {"default": "0"}) t.Column("counter", "bigint", {"default": "0"}) t.Column("high_rate", "bigint", {"default": "0"}) t.Column("limit", "bigint", {"default": "0"}) t.Column("deleted", "bigint", {"default": "0"}) t.Column("conditions", "text", {"default": ""}) {{footer "primary" "index(block_id)"}} {{head "1_bad_blocks"}} t.Column("id", "bigint", {"default": "0"}) t.Column("producer_node_id", "bigint", {"default": "0"}) t.Column("block_id", "bigint", {"default": "0"}) t.Column("consumer_node_id", "bigint", {"default": "0"}) t.Column("block_time", "timestamp", {}) t.Column("reason", "text", {"default": ""}) t.Column("deleted", "bigint", {"default": "0"}) {{footer "primary" }} {{head "1_node_ban_logs"}} t.Column("id", "bigint", {"default": "0"}) t.Column("node_id", "bigint", {"default": "0"}) t.Column("banned_at", "timestamp", {}) t.Column("ban_time", "bigint", {"default": "0"}) t.Column("reason", "text", {"default": ""}) {{footer "primary" }} ` var sqlFirstEcosystemCommon = ` {{head "1_keys"}} t.Column("id", "bigint", {"default": "0"}) t.Column("pub", "bytea", {"default": ""}) t.Column("amount", "decimal(30)", {"default_raw": "'0' CHECK (amount >= 0)"}) t.Column("maxpay", "decimal(30)", {"default_raw": "'0' CHECK (maxpay >= 0)"}) t.Column("multi", "bigint", {"default": "0"}) t.Column("deleted", "bigint", {"default": "0"}) t.Column("blocked", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) t.Column("account", "char(24)", {}) t.PrimaryKey("ecosystem", "id") {{footer "index(account)" "unique(ecosystem, account)"}} {{head "1_menu"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":255}) t.Column("title", "string", {"default": "", "size":255}) t.Column("value", "text", {"default": ""}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem, name)"}} {{head "1_pages"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":255}) t.Column("value", "text", {"default": ""}) t.Column("menu", "string", {"default": "", "size":255}) t.Column("validate_count", "bigint", {"default": "1"}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("app_id", "bigint", {"default": "1"}) t.Column("validate_mode", "character(1)", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem, name)"}} {{head "1_snippets"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":255}) t.Column("value", "text", {"default": ""}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("app_id", "bigint", {"default": "1"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem, name)"}} {{head "1_languages"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":100}) t.Column("res", "text", {"default": ""}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem, name)"}} {{head "1_contracts"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "text", {"default": ""}) t.Column("value", "text", {"default": ""}) t.Column("wallet_id", "bigint", {"default": "0"}) t.Column("token_id", "bigint", {"default": "1"}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("app_id", "bigint", {"default": "1"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem)"}} {{head "1_tables"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size": 100}) t.Column("permissions", "jsonb", {"null": true}) t.Column("columns", "jsonb", {"null": true}) t.Column("conditions", "text", {"default": ""}) t.Column("app_id", "bigint", {"default": "1"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem, name)"}} {{head "1_views"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size": 100}) t.Column("columns", "jsonb", {"null": true}) t.Column("wheres", "jsonb", {"null": true}) t.Column("permissions", "jsonb", {"null": true}) t.Column("conditions", "text", {"default": ""}) t.Column("app_id", "bigint", {"default": "1"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem, name)"}} {{head "1_parameters"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size": 255}) t.Column("value", "text", {"default": ""}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, name)" "index(ecosystem, name)"}} {{head "1_history"}} t.Column("id", "bigint", {"default": "0"}) t.Column("sender_id", "bigint", {"default": "0"}) t.Column("recipient_id", "bigint", {"default": "0"}) t.Column("sender_balance", "decimal(30)", {"default": "0"}) t.Column("recipient_balance", "decimal(30)", {"default": "0"}) t.Column("amount", "decimal(30)", {"default": "0"}) t.Column("value_detail", "jsonb", {"null": true}) t.Column("comment", "text", {"default": ""}) t.Column("status", "bigint", {"default": "0"}) t.Column("block_id", "bigint", {"default": "0"}) t.Column("txhash", "bytea", {"default": ""}) t.Column("created_at", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) t.Column("type", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem, sender_id)"}} add_index("1_history", ["ecosystem", "recipient_id"], {}) add_index("1_history", ["block_id", "txhash"], {}) {{head "1_sections"}} t.Column("id", "bigint", {"default": "0"}) t.Column("title", "string", {"default": "", "size": 255}) t.Column("urlname", "string", {"default": "", "size": 255}) t.Column("page", "string", {"default": "", "size": 255}) t.Column("roles_access", "jsonb", {"null": true}) t.Column("status", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem)"}} {{head "1_members"}} t.Column("id", "bigint", {"default": "0"}) t.Column("member_name", "string", {"default": "", "size": 255}) t.Column("image_id", "bigint", {"default": "0"}) t.Column("member_info", "jsonb", {"null": true}) t.Column("ecosystem", "bigint", {"default": "1"}) t.Column("account", "char(24)", {}) {{footer "primary" "index(ecosystem)"}} add_index("1_members", ["account", "ecosystem"], {"unique": true}) {{head "1_roles"}} t.Column("id", "bigint", {"default": "0"}) t.Column("default_page", "string", {"default": "", "size": 255}) t.Column("role_name", "string", {"default": "", "size": 255}) t.Column("deleted", "bigint", {"default": "0"}) t.Column("role_type", "bigint", {"default": "0"}) t.Column("creator", "jsonb", {"default": "{}"}) t.Column("date_created", "bigint", {"default": "0"}) t.Column("date_deleted", "bigint", {"default": "0"}) t.Column("company_id", "bigint", {"default": "0"}) t.Column("roles_access", "jsonb", {"null": true}) t.Column("image_id", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem, deleted)"}} add_index("1_roles", ["ecosystem", "role_type"], {}) {{head "1_roles_participants"}} t.Column("id", "bigint", {"default": "0"}) t.Column("role", "jsonb", {"null": true}) t.Column("member", "jsonb", {"null": true}) t.Column("appointed", "jsonb", {"null": true}) t.Column("date_created", "bigint", {"default": "0"}) t.Column("date_deleted", "bigint", {"default": "0"}) t.Column("deleted", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem)"}} {{head "1_notifications"}} t.Column("id", "bigint", {"default": "0"}) t.Column("recipient", "jsonb", {"null": true}) t.Column("sender", "jsonb", {"null": true}) t.Column("notification", "jsonb", {"null": true}) t.Column("page_params", "jsonb", {"null": true}) t.Column("processing_info", "jsonb", {"null": true}) t.Column("page_name", "string", {"default": "", "size": 255}) t.Column("date_created", "bigint", {"default": "0"}) t.Column("date_start_processing", "bigint", {"default": "0"}) t.Column("date_closed", "bigint", {"default": "0"}) t.Column("closed", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem)"}} {{head "1_applications"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size": 255}) t.Column("uuid", "uuid", {"default": "00000000-0000-0000-0000-000000000000"}) t.Column("conditions", "text", {"default": ""}) t.Column("deleted", "bigint", {"default": "0"}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "index(ecosystem)"}} {{head "1_binaries"}} t.Column("id", "bigint", {"default": "0"}) t.Column("app_id", "bigint", {"default": "1"}) t.Column("name", "string", {"default": "", "size": 255}) t.Column("data", "bytea", {"default": ""}) t.Column("hash", "string", {"default": "", "size": 64}) t.Column("mime_type", "string", {"default": "", "size": 255}) t.Column("ecosystem", "bigint", {"default": "1"}) t.Column("account", "char(24)", {}) {{footer "primary"}} add_index("1_binaries", ["account", "ecosystem", "app_id", "name"], {"unique": true}) {{head "1_app_params"}} t.Column("id", "bigint", {"default": "0"}) t.Column("app_id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size": 255}) t.Column("value", "text", {"default": ""}) t.Column("conditions", "text", {"default": ""}) t.Column("permissions", "jsonb", {"null": true}) t.Column("ecosystem", "bigint", {"default": "1"}) {{footer "primary" "unique(ecosystem, app_id, name)" "index(ecosystem,app_id,name)"}} {{head "1_buffer_data"}} t.Column("id", "bigint", {"default": "0"}) t.Column("key", "string", {"default": "", "size": 255}) t.Column("value", "jsonb", {"null": true}) t.Column("ecosystem", "bigint", {"default": "1"}) t.Column("account", "char(24)", {}) {{footer "primary" "index(ecosystem)"}} ` ================================================ FILE: packages/migration/first_ecosystems_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var firstEcosystemDataSQL = ` INSERT INTO "1_ecosystems" ("id", "name", "is_valued", "digits", "token_symbol", "token_name") VALUES (next_id('1_ecosystems'), 'platform ecosystem', '1', '{{.Digits}}', '{{.TokenSymbol}}', '{{.TokenName}}') ; INSERT INTO "1_applications" (id, name, conditions, ecosystem) VALUES (next_id('1_applications'), 'System', 'ContractConditions("MainCondition")', '1'); ` ================================================ FILE: packages/migration/first_tables_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var firstTablesDataSQL = ` INSERT INTO "1_tables" ("id", "name", "permissions","columns", "conditions") VALUES (next_id('1_tables'), 'delayed_contracts', '{ "insert": "ContractAccess(\"@1NewDelayedContract\")", "update": "ContractAccess(\"@1CallDelayedContract\",\"@1EditDelayedContract\",\"@1CheckNodesBan\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "contract": "ContractAccess(\"@1EditDelayedContract\")", "key_id": "ContractAccess(\"@1EditDelayedContract\")", "block_id": "ContractAccess(\"@1CallDelayedContract\",\"@1EditDelayedContract\")", "every_block": "ContractAccess(\"@1EditDelayedContract\")", "counter": "ContractAccess(\"@1CallDelayedContract\",\"@1EditDelayedContract\",\"@1CheckNodesBan\")", "high_rate": "ContractAccess(\"@1EditDelayedContract\")", "limit": "ContractAccess(\"@1EditDelayedContract\")", "deleted": "ContractAccess(\"@1EditDelayedContract\")", "conditions": "ContractAccess(\"@1EditDelayedContract\")" }', 'ContractConditions("@1MainCondition")' ), (next_id('1_tables'), 'ecosystems', '{ "insert": "ContractAccess(\"@1NewEcosystem\")", "update": "ContractAccess(\"@1EditEcosystemName\",\"@1VotingVesAccept\",\"@1EcManageInfo\",\"@1EcoFeeModeManage\",\"@1EditControlMode\",\"@1NewToken\",\"@1TeChange\",\"@1TeEmission\",\"@1TeBurn\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "ContractAccess(\"@1EditEcosystemName\")", "info": "ContractAccess(\"@1EcManageInfo\")", "digits": "ContractAccess(\"@1NewToken\")", "fee_mode_info": "ContractAccess(\"@1EcoFeeModeManage\")", "is_valued": "ContractAccess(\"@1VotingVesAccept\")", "emission_amount": "ContractAccess(\"@1NewToken\",\"@1TeBurn\",\"@1TeEmission\")", "token_symbol": "ContractAccess(\"@1NewToken\")", "token_name": "ContractAccess(\"@1NewToken\")", "type_emission": "ContractAccess(\"@1TeChange\",\"@1NewToken\")", "type_withdraw": "ContractAccess(\"@1TeChange\",\"@1NewToken\")", "control_mode": "ContractAccess(\"@1EditControlMode\")" }', 'ContractConditions("@1MainCondition")' ), (next_id('1_tables'), 'platform_parameters', '{ "insert": "false", "update": "ContractAccess(\"@1UpdatePlatformParam\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "value": "ContractAccess(\"@1UpdatePlatformParam\")", "name": "false", "conditions": "ContractAccess(\"@1UpdatePlatformParam\")" }', 'ContractConditions("@1MainCondition")' ), (next_id('1_tables'), 'bad_blocks', '{ "insert": "ContractAccess(\"@1NewBadBlock\")", "update": "ContractAccess(\"@1NewBadBlock\", \"@1CheckNodesBan\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "block_id": "ContractAccess(\"@1CheckNodesBan\")", "producer_node_id": "ContractAccess(\"@1CheckNodesBan\")", "consumer_node_id": "ContractAccess(\"@1CheckNodesBan\")", "block_time": "ContractAccess(\"@1CheckNodesBan\")", "reason": "ContractAccess(\"@1CheckNodesBan\")", "deleted": "ContractAccess(\"@1CheckNodesBan\")" }', 'ContractConditions("@1MainCondition")' ), (next_id('1_tables'), 'node_ban_logs', '{ "insert": "ContractAccess(\"@1CheckNodesBan\")", "update": "ContractAccess(\"@1CheckNodesBan\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "node_id": "ContractAccess(\"@1CheckNodesBan\")", "banned_at": "ContractAccess(\"@1CheckNodesBan\")", "ban_time": "ContractAccess(\"@1CheckNodesBan\")", "reason": "ContractAccess(\"@1CheckNodesBan\")" }', 'ContractConditions("@1MainCondition")' ), (next_id('1_tables'), 'time_zones', '{ "insert": "false", "update": "false", "new_column": "false" }', '{ "name": "false", "offset": "false" }', 'ContractConditions("@1MainCondition")' ); ` ================================================ FILE: packages/migration/gen/contracts.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package main import ( "bufio" "bytes" "html/template" "os" "path/filepath" "sort" "strings" "github.com/BurntSushi/toml" ) const ( ext = ".sim" defaultPackageName = "migration" ) var ( scenarios = []scenario{ { Source: []string{"./contracts/ecosystem"}, Dest: "./contracts_data.go", Variable: "contractsDataSQL", Ecosystem: "{{.Ecosystem}}", Owner: "{{.Owner}}", }, { Source: []string{"./contracts/first_ecosystem"}, Dest: "./first_ecosys_contracts_data.go", Variable: "firstEcosystemContractsSQL", Ecosystem: "1", Owner: "{{.Owner}}", }, { Source: []string{"./contracts/common", "./contracts/first_ecosystem", "./contracts/clb"}, Dest: "./clb/clb_data_contracts.go", Variable: "contractsDataSQL", Ecosystem: "%[1]d", }, } propPrefix = []byte("// +prop ") ) type scenario struct { Source []string Dest string Variable string Ecosystem string Owner string } type contract struct { Name string Source template.HTML Conditions template.HTML AppID string } type meta struct { AppID string Conditions string } var fns = template.FuncMap{ "add": func(a, b int) int { return a + b }, } var contractsTemplate = template.Must(template.New("").Funcs(fns).Parse(`// Code generated by go generate; DO NOT EDIT. package {{ .Package }} var {{ .Variable }} = ` + "`" + ` INSERT INTO "1_contracts" (id, name, value, token_id, conditions, app_id{{if .Owner }}, wallet_id{{end}}, ecosystem) VALUES {{- $last := add (len .Contracts) -1}} {{- range $i, $item := .Contracts}} (next_id('1_contracts'), '{{ $item.Name }}', '{{ $item.Source }}', '{{ $.Ecosystem }}', '{{ $item.Conditions }}', '{{ $item.AppID }}'{{if $.Owner }}, {{ $.Owner }}{{end}}, '{{ $.Ecosystem }}'){{if eq $last $i}};{{else}},{{end}} {{- end}} ` + "`")) func main() { for _, s := range scenarios { if err := generate(s); err != nil { panic(err) } } } func escape(data string) template.HTML { //data = strings.Replace(data, `%`, `%%`, -1) data = strings.Replace(data, `'`, `''`, -1) data = strings.Replace(data, "`", "` + \"`\" + `", -1) return template.HTML(data) } func loadSource(srcPath string) (*contract, error) { file, err := os.Open(srcPath) if err != nil { return nil, err } defer file.Close() props := make([]byte, 0) data := make([]byte, 0) scan := bufio.NewScanner(file) for scan.Scan() { line := scan.Bytes() if bytes.HasPrefix(line, propPrefix) { props = append(append(props, line[len(propPrefix):]...), '\n') } else { data = append(append(data, line...), '\n') } } m := &meta{} if err = toml.Unmarshal(props, m); err != nil { return nil, err } name := filepath.Base(srcPath) ext := filepath.Ext(srcPath) return &contract{ Name: name[0 : len(name)-len(ext)], Source: escape(string(data)), Conditions: escape(m.Conditions), AppID: m.AppID, }, nil } func loadSources(srcPaths []string) ([]*contract, error) { sources := make([]*contract, 0) for _, srcPath := range srcPaths { err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { if filepath.Ext(path) != ext { return nil } source, err := loadSource(path) if err != nil { return err } sources = append(sources, source) return nil }) if err != nil { return nil, err } } sort.Slice(sources, func(i, j int) bool { return sources[i].Name < sources[j].Name }) return sources, nil } func generate(s scenario) error { sources, err := loadSources(s.Source) if err != nil { return err } file, err := os.Create(s.Dest) if err != nil { return err } defer file.Close() pkg := filepath.Base(filepath.Dir(s.Dest)) if pkg == "." { pkg = defaultPackageName } return contractsTemplate.Execute(file, map[string]any{ "Package": pkg, "Variable": s.Variable, "Ecosystem": s.Ecosystem, "Owner": nil, "Contracts": sources, }) } ================================================ FILE: packages/migration/gen/contracts_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package main import ( "fmt" "html/template" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestEscape(t *testing.T) { var cases = []struct { Source string Expected template.HTML }{ {`'test'`, `''test''`}, {"`test`", "` + \"`\" + `test` + \"`\" + `"}, {`100%`, `100%%`}, } for _, v := range cases { assert.Equal(t, v.Expected, escape(v.Source)) } } func tempContract(appID int, conditions, value string) (string, error) { file, err := os.CreateTemp("", "contract") if err != nil { return "", err } defer file.Close() file.Write([]byte(fmt.Sprintf(`// +prop AppID = %d // +prop Conditions = '%s' %s`, appID, conditions, value))) return file.Name(), nil } func TestLoadSource(t *testing.T) { value := "contract Test {}" path, err := tempContract(5, "true", value) assert.NoError(t, err) source, err := loadSource(path) assert.NoError(t, err) assert.Equal(t, &contract{ Name: filepath.Base(path), Source: template.HTML(value + "\n"), Conditions: template.HTML("true"), AppID: 5, }, source) } ================================================ FILE: packages/migration/keys_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration import ( "strconv" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" ) var keysDataSQL = ` INSERT INTO "1_keys" (id, account, pub, blocked, ecosystem) VALUES (` + consts.GuestKey + `, '` + consts.GuestAddress + `', decode('` + consts.GuestPublic + `', 'hex'), 1, '{{.Ecosystem}}'), (` + strconv.FormatInt(converter.HoleAddrMap[converter.BlackHoleAddr].K, 10) + `, '` + converter.HoleAddrMap[converter.BlackHoleAddr].S + `', decode('', 'hex'), 1, '{{.Ecosystem}}'), (` + strconv.FormatInt(converter.HoleAddrMap[converter.WhiteHoleAddr].K, 10) + `, '` + converter.HoleAddrMap[converter.WhiteHoleAddr].S + `', decode('', 'hex'), 1, '{{.Ecosystem}}'); ` ================================================ FILE: packages/migration/members_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" ) var membersDataSQL = ` INSERT INTO "1_members" ("id", "account", "member_name", "ecosystem") VALUES (next_id('1_members'), '{{.Account}}', 'founder', '{{.Ecosystem}}'), (next_id('1_members'), '` + consts.GuestAddress + `', 'guest', '{{.Ecosystem}}'), (next_id('1_members'), '` + converter.HoleAddrMap[converter.BlackHoleAddr].S + `', 'black_hole', '{{.Ecosystem}}'), (next_id('1_members'), '` + converter.HoleAddrMap[converter.WhiteHoleAddr].S + `', 'white_hole', '{{.Ecosystem}}'); ` ================================================ FILE: packages/migration/menu_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var menuDataSQL = `INSERT INTO "1_menu" (id, name, value, conditions, ecosystem) VALUES (next_id('1_menu'), 'developer_menu', 'MenuItem(Title:"Import", Page:@1import_upload, Icon:"icon-cloud-upload")', 'ContractAccess("@1EditMenu")','{{.Ecosystem}}'); ` ================================================ FILE: packages/migration/migration.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration import ( "fmt" "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/migration/updates" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) const ( eVer = `Wrong version %s` ) var migrations = []*migration{ // Initial schema {"0.0.1", migrationInitialTables, true}, {"0.0.2", migrationInitialSchema, false}, } var updateMigrations = []*migration{ {"0.0.3", updates.MigrationUpdatePriceExec, false}, {"0.0.4", updates.MigrationUpdateAccessExec, false}, {"0.0.5", updates.MigrationUpdatePriceCreateExec, false}, } type migration struct { version string data string template bool } type database interface { CurrentVersion() (string, error) ApplyMigration(string, string) error } func compareVer(a, b string) (int, error) { var ( av, bv []string ai, bi int err error ) if av = strings.Split(a, `.`); len(av) != 3 { return 0, fmt.Errorf(eVer, a) } if bv = strings.Split(b, `.`); len(bv) != 3 { return 0, fmt.Errorf(eVer, b) } for i, v := range av { if ai, err = strconv.Atoi(v); err != nil { return 0, fmt.Errorf(eVer, a) } if bi, err = strconv.Atoi(bv[i]); err != nil { return 0, fmt.Errorf(eVer, b) } if ai < bi { return -1, nil } if ai > bi { return 1, nil } } return 0, nil } func migrate(db database, appVer string, migrations []*migration) error { dbVerString, err := db.CurrentVersion() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "err": err}).Errorf("parse version") return err } if cmp, err := compareVer(dbVerString, appVer); err != nil { log.WithFields(log.Fields{"type": consts.MigrationError, "err": err}).Errorf("parse version") return err } else if cmp >= 0 { return nil } for _, m := range migrations { if cmp, err := compareVer(dbVerString, m.version); err != nil { log.WithFields(log.Fields{"type": consts.MigrationError, "err": err}).Errorf("parse version") return err } else if cmp >= 0 { continue } if m.template { m.data, err = sqlConvert([]string{m.data}) if err != nil { return err } } err = db.ApplyMigration(m.version, m.data) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "err": err, "version": m.version}).Errorf("apply migration") return err } log.WithFields(log.Fields{"version": m.version}).Debug("apply migration") } return nil } func runMigrations(db database, migrationList []*migration) error { return migrate(db, consts.VERSION, migrationList) } // InitMigrate applies initial migrations func InitMigrate(db database) error { mig := migrations if conf.Config.IsSubNode() { //mig = append(mig, migrationsSub) } if conf.Config.IsSupportingCLB() { //mig = append(mig, migrationsCLB) } return runMigrations(db, mig) } // UpdateMigrate applies update migrations func UpdateMigrate(db database) error { return runMigrations(db, updateMigrations) } ================================================ FILE: packages/migration/migration_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration import ( "testing" ) type dbMock struct { versions []string } func (dbm *dbMock) CurrentVersion() (string, error) { return dbm.versions[len(dbm.versions)-1], nil } func (dbm *dbMock) ApplyMigration(version, query string) error { dbm.versions = append(dbm.versions, version) return nil } func createDBMock(version string) *dbMock { return &dbMock{versions: []string{version}} } func TestMockMigration(t *testing.T) { err := migrate(createDBMock("error version"), ``, nil) if err.Error() != "Wrong version error version" { t.Error(err) } appVer := "0.0.2" err = migrate(createDBMock("0"), appVer, []*migration{{version: "error version", data: ""}}) if err.Error() != "Wrong version 0" { t.Error(err) } db := createDBMock("0.0.0") err = migrate( db, appVer, []*migration{ {version: "0.0.1", data: ""}, {version: "0.0.2", data: ""}, }, ) if err != nil { t.Error(err) } if v, _ := db.CurrentVersion(); v != "0.0.2" { t.Errorf("current version expected 0.0.2 get %s", v) } db = createDBMock("0.0.2") err = migrate(db, appVer, []*migration{ {version: "0.0.3", data: ""}, }) if err != nil { t.Error(err) } if v, _ := db.CurrentVersion(); v != "0.0.2" { t.Errorf("current version expected 0.0.2 get %s", v) } } ================================================ FILE: packages/migration/pages_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var pagesDataSQL = `INSERT INTO "1_pages" (id, name, value, menu, conditions, app_id, ecosystem) VALUES (next_id('1_pages'), 'developer_index', '', 'developer_menu', 'ContractConditions("@1DeveloperCondition")', '{{.AppID}}', '{{.Ecosystem}}');` ================================================ FILE: packages/migration/parameters_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var parametersDataSQL = ` INSERT INTO "1_parameters" ("id","name", "value", "conditions", "ecosystem") VALUES (next_id('1_parameters'),'founder_account', '{{.Wallet}}', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'new_table', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_tables', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_language', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_page', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_menu', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_contracts', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_parameters', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_app_params', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'changing_snippets', 'ContractConditions("DeveloperCondition")', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'max_sum', '1000000', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'print_stylesheet', 'body { /* You can define your custom styles here or create custom CSS rules */ }', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'min_page_validate_count', '1', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'), (next_id('1_parameters'),'max_page_validate_count', '6', 'ContractConditions("DeveloperCondition")', '{{.Ecosystem}}'); ` ================================================ FILE: packages/migration/platform_parameters_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var platformParametersDataSQL = ` INSERT INTO "1_platform_parameters" ("id","name", "value", "conditions") VALUES (next_id('1_platform_parameters'),'default_ecosystem_page', 'If(#ecosystem_id# > 1){Include(@1welcome)}', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'default_ecosystem_menu', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'default_ecosystem_contract', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'gap_between_blocks', '2', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'rollback_blocks', '60', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'honor_nodes', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'number_of_nodes', '101', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_block_size', '67108864', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_tx_size', '33554432', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_tx_block', '5000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_columns', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_indexes', '5', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_tx_block_per_user', '5000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_fuel_tx', '20000000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_fuel_block', '200000000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'taxes_size', '3', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'taxes_wallet', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'fuel_rate', '[["1","1000000"]]', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'max_block_generation_time', '2000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'incorrect_blocks_per_day','10','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'node_ban_time','86400000','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'node_ban_time_local','1800000','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_tx_size', '15', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'test','false','false'), (next_id('1_platform_parameters'),'price_tx_data', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'private_blockchain', '1', 'false'), (next_id('1_platform_parameters'),'pay_free_contract', '@1CallDelayedContract,@1CheckNodesBan,@1NewUser', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'local_node_ban_time', '60', 'ContractAccess("@1UpdatePlatformParam")'); ` ================================================ FILE: packages/migration/sections_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var sectionsDataSQL = ` INSERT INTO "1_sections" ("id","title","urlname","page","roles_access", "status", "ecosystem") VALUES (next_id('1_sections'), 'Home', 'home', 'default_page', '[]', 2, '{{.Ecosystem}}'), (next_id('1_sections'), 'Developer', 'developer', 'developer_index', '[]', 1, '{{.Ecosystem}}'); ` ================================================ FILE: packages/migration/tables_data.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var tablesDataSQL = `INSERT INTO "1_tables" ("id", "name", "permissions","columns", "conditions", "ecosystem") VALUES (next_id('1_tables'), 'contracts', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "false", "value": "ContractAccess(\"@1EditContract\")", "wallet_id": "ContractAccess(\"@1BindWallet\", \"@1UnbindWallet\")", "token_id": "ContractAccess(\"@1EditContract\")", "conditions": "ContractAccess(\"@1EditContract\")", "permissions": "ContractConditions(\"@1MainCondition\")", "app_id": "ContractAccess(\"@1ItemChangeAppId\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'keys', '{ "insert": "true", "update": "ContractAccess(\"@1TokensTransfer\",\"@1TokensLockoutMember\",\"@1NewToken\",\"@1TeBurn\",\"@1ProfileEdit\",\"@1NewUser\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "pub": "ContractAccess(\"@1NewUser\")", "amount": "ContractAccess(\"@1TokensTransfer\",\"@1NewToken\",\"@1TeBurn\",\"@1ProfileEdit\")", "maxpay": "ContractConditions(\"@1MainCondition\")", "deleted": "ContractAccess(\"@1DeleteMember\")", "blocked": "ContractAccess(\"@1TokensLockoutMember\")", "account": "false", "ecosystem": "false", "multi": "ContractConditions(\"@1MainCondition\")" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'history', '{ "insert": "ContractAccess(\"@1TokensTransfer\",\"@1NewUser\",\"@1NewToken\",\"@1TeBurn\",\"@1ProfileEdit\",\"@1MembershipRequest\",\"@1MembershipDecide\",\"@1MembershipAdd\",\"@1DeleteMember\")", "update": "ContractConditions(\"@1MainCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "sender_id": "false", "recipient_id": "false", "sender_balance": "false", "recipient_balance": "false", "amount": "false", "value_detail": "false", "comment": "false", "status": "false", "block_id": "false", "txhash": "false", "ecosystem": "false", "type": "false", "created_at": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'languages', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "ContractAccess(\"@1EditLang\")", "res": "ContractAccess(\"@1EditLang\")", "conditions": "ContractAccess(\"@1EditLang\")", "permissions": "ContractConditions(\"@1MainCondition\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'menu', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "false", "value": "ContractAccess(\"@1EditMenu\",\"@1AppendMenu\")", "title": "ContractAccess(\"@1EditMenu\")", "conditions": "ContractAccess(\"@1EditMenu\")", "permissions": "ContractConditions(\"@1MainCondition\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'pages', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "false", "value": "ContractAccess(\"@1EditPage\",\"@1AppendPage\")", "menu": "ContractAccess(\"@1EditPage\")", "validate_count": "ContractAccess(\"@1EditPage\")", "validate_mode": "ContractAccess(\"@1EditPage\")", "app_id": "ContractAccess(\"@1ItemChangeAppId\")", "conditions": "ContractAccess(\"@1EditPage\")", "permissions": "ContractConditions(\"@1MainCondition\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'snippets', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "false", "value": "ContractAccess(\"@1EditSnippet\")", "conditions": "ContractAccess(\"@1EditSnippet\")", "permissions": "ContractConditions(\"@1MainCondition\")", "app_id": "ContractAccess(\"@1ItemChangeAppId\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'members', '{ "insert": "ContractAccess(\"@1ProfileEdit\")", "update": "ContractAccess(\"@1ProfileEdit\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "image_id": "ContractAccess(\"@1ProfileEdit\")", "member_info": "ContractAccess(\"@1ProfileEdit\")", "member_name": "false", "account":"false", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'roles', '{ "insert": "ContractAccess(\"@1RolesCreate\",\"@1RolesInstall\")", "update": "ContractAccess(\"@1RolesAccessManager\",\"@1RolesDelete\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "default_page": "false", "creator": "false", "deleted": "ContractAccess(\"@1RolesDelete\")", "company_id": "false", "date_deleted": "ContractAccess(\"@1RolesDelete\")", "image_id": "false", "role_name": "false", "date_created": "false", "roles_access": "ContractAccess(\"@1RolesAccessManager\")", "role_type": "false", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'roles_participants', '{ "insert": "ContractAccess(\"@1RolesAssign\",\"@1VotingDecisionCheck\",\"@1RolesInstall\")", "update": "ContractAccess(\"@1RolesUnassign\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "deleted": "ContractAccess(\"@1RolesUnassign\")", "date_deleted": "ContractAccess(\"@1RolesUnassign\")", "member": "false", "role": "false", "date_created": "false", "appointed": "false", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'notifications', '{ "insert": "ContractAccess(\"@1NotificationsSend\", \"@1CheckNodesBan\", \"@1NotificationsBroadcast\")", "update": "ContractAccess(\"@1NotificationsSend\", \"@1NotificationsClose\", \"@1NotificationsProcess\", \"@1NotificationsUpdateParams\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "date_closed": "ContractAccess(\"@1NotificationsClose\")", "sender": "false", "processing_info": "ContractAccess(\"@1NotificationsClose\",\"@1NotificationsProcess\")", "date_start_processing": "ContractAccess(\"@1NotificationsClose\",\"@1NotificationsProcess\")", "notification": "false", "page_name": "false", "page_params": "ContractAccess(\"@1NotificationsUpdateParams\")", "closed": "ContractAccess(\"@1NotificationsClose\")", "date_created": "false", "recipient": "false", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'sections', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "title": "ContractAccess(\"@1EditSection\")", "urlname": "ContractAccess(\"@1EditSection\")", "page": "ContractAccess(\"@1EditSection\")", "roles_access": "ContractAccess(\"@1SectionRoles\")", "status": "ContractAccess(\"@1EditSection\",\"@1NewSection\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'applications', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractConditions(\"DeveloperCondition\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "false", "uuid": "false", "conditions": "ContractAccess(\"@1EditApplication\")", "deleted": "ContractAccess(\"@1DelApplication\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'binaries', '{ "insert": "ContractAccess(\"@1UploadBinary\")", "update": "ContractAccess(\"@1UploadBinary\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "hash": "ContractAccess(\"@1UploadBinary\")", "account": "false", "data": "ContractAccess(\"@1UploadBinary\")", "name": "false", "app_id": "false", "ecosystem": "false", "mime_type": "ContractAccess(\"@1UploadBinary\")" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'parameters', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractAccess(\"@1EditParameter\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "name": "false", "value": "ContractAccess(\"@1EditParameter\")", "conditions": "ContractAccess(\"@1EditParameter\")", "permissions": "ContractConditions(\"@1MainCondition\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'app_params', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "ContractAccess(\"@1EditAppParam\")", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "app_id": "ContractAccess(\"@1ItemChangeAppId\")", "name": "false", "value": "ContractAccess(\"@1EditAppParam\")", "conditions": "ContractAccess(\"@1EditAppParam\")", "permissions": "ContractConditions(\"@1MainCondition\")", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'buffer_data', '{ "insert": "true", "update": "true", "new_column": "ContractConditions(\"@1MainCondition\")" }', '{ "key": "false", "value": "true", "account": "false", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ), (next_id('1_tables'), 'views', '{ "insert": "ContractConditions(\"DeveloperCondition\")", "update": "false", "new_column": "false" }', '{ "name": "false", "columns": "false", "wheres": "false", "permissions": "true", "conditions": "true", "app_id": "false", "ecosystem": "false" }', 'ContractConditions("@1MainCondition")', '{{.Ecosystem}}' ); ` ================================================ FILE: packages/migration/timezones.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package migration var sqlTimeZonesSQL = ` {{head "1_time_zones"}} t.Column("id", "bigint", {"default": "0"}) t.Column("name", "string", {"default": "", "size":255}) t.Column("offset", "string", {"default": "", "size":6}) {{footer "primary" "unique(name)"}} ` var timeZonesSQL = ` INSERT INTO "1_time_zones" VALUES (next_id('1_time_zones'), 'Africa/Abidjan', 'UTC'), (next_id('1_time_zones'), 'Africa/Accra', 'UTC'), (next_id('1_time_zones'), 'Africa/Addis_Ababa', '+03:00'), (next_id('1_time_zones'), 'Africa/Algiers', '+01:00'), (next_id('1_time_zones'), 'Africa/Asmara', '+03:00'), (next_id('1_time_zones'), 'Africa/Bamako', 'UTC'), (next_id('1_time_zones'), 'Africa/Bangui', '+01:00'), (next_id('1_time_zones'), 'Africa/Banjul', 'UTC'), (next_id('1_time_zones'), 'Africa/Bissau', 'UTC'), (next_id('1_time_zones'), 'Africa/Blantyre', '+02:00'), (next_id('1_time_zones'), 'Africa/Brazzaville', '+01:00'), (next_id('1_time_zones'), 'Africa/Bujumbura', '+02:00'), (next_id('1_time_zones'), 'Africa/Cairo', '+02:00'), (next_id('1_time_zones'), 'Africa/Casablanca', '+01:00'), (next_id('1_time_zones'), 'Africa/Ceuta', '+02:00'), (next_id('1_time_zones'), 'Africa/Conakry', 'UTC'), (next_id('1_time_zones'), 'Africa/Dakar', 'UTC'), (next_id('1_time_zones'), 'Africa/Dar_es_Salaam', '+03:00'), (next_id('1_time_zones'), 'Africa/Djibouti', '+03:00'), (next_id('1_time_zones'), 'Africa/Douala', '+01:00'), (next_id('1_time_zones'), 'Africa/El_Aaiun', '+01:00'), (next_id('1_time_zones'), 'Africa/Freetown', 'UTC'), (next_id('1_time_zones'), 'Africa/Gaborone', '+02:00'), (next_id('1_time_zones'), 'Africa/Harare', '+02:00'), (next_id('1_time_zones'), 'Africa/Johannesburg', '+02:00'), (next_id('1_time_zones'), 'Africa/Juba', '+03:00'), (next_id('1_time_zones'), 'Africa/Kampala', '+03:00'), (next_id('1_time_zones'), 'Africa/Khartoum', '+02:00'), (next_id('1_time_zones'), 'Africa/Kigali', '+02:00'), (next_id('1_time_zones'), 'Africa/Kinshasa', '+01:00'), (next_id('1_time_zones'), 'Africa/Lagos', '+01:00'), (next_id('1_time_zones'), 'Africa/Libreville', '+01:00'), (next_id('1_time_zones'), 'Africa/Lome', 'UTC'), (next_id('1_time_zones'), 'Africa/Luanda', '+01:00'), (next_id('1_time_zones'), 'Africa/Lubumbashi', '+02:00'), (next_id('1_time_zones'), 'Africa/Lusaka', '+02:00'), (next_id('1_time_zones'), 'Africa/Malabo', '+01:00'), (next_id('1_time_zones'), 'Africa/Maputo', '+02:00'), (next_id('1_time_zones'), 'Africa/Maseru', '+02:00'), (next_id('1_time_zones'), 'Africa/Mbabane', '+02:00'), (next_id('1_time_zones'), 'Africa/Mogadishu', '+03:00'), (next_id('1_time_zones'), 'Africa/Monrovia', 'UTC'), (next_id('1_time_zones'), 'Africa/Nairobi', '+03:00'), (next_id('1_time_zones'), 'Africa/Ndjamena', '+01:00'), (next_id('1_time_zones'), 'Africa/Niamey', '+01:00'), (next_id('1_time_zones'), 'Africa/Nouakchott', 'UTC'), (next_id('1_time_zones'), 'Africa/Ouagadougou', 'UTC'), (next_id('1_time_zones'), 'Africa/Porto-Novo', '+01:00'), (next_id('1_time_zones'), 'Africa/Sao_Tome', 'UTC'), (next_id('1_time_zones'), 'Africa/Tripoli', '+02:00'), (next_id('1_time_zones'), 'Africa/Tunis', '+01:00'), (next_id('1_time_zones'), 'Africa/Windhoek', '+02:00'), (next_id('1_time_zones'), 'America/Adak', '-09:00'), (next_id('1_time_zones'), 'America/Anchorage', '-08:00'), (next_id('1_time_zones'), 'America/Anguilla', '-04:00'), (next_id('1_time_zones'), 'America/Antigua', '-04:00'), (next_id('1_time_zones'), 'America/Araguaina', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Buenos_Aires', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Catamarca', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Cordoba', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Jujuy', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/La_Rioja', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Mendoza', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Rio_Gallegos', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Salta', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/San_Juan', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/San_Luis', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Tucuman', '-03:00'), (next_id('1_time_zones'), 'America/Argentina/Ushuaia', '-03:00'), (next_id('1_time_zones'), 'America/Aruba', '-04:00'), (next_id('1_time_zones'), 'America/Asuncion', '-04:00'), (next_id('1_time_zones'), 'America/Atikokan', '-05:00'), (next_id('1_time_zones'), 'America/Bahia', '-03:00'), (next_id('1_time_zones'), 'America/Bahia_Banderas', '-05:00'), (next_id('1_time_zones'), 'America/Barbados', '-04:00'), (next_id('1_time_zones'), 'America/Belem', '-03:00'), (next_id('1_time_zones'), 'America/Belize', '-06:00'), (next_id('1_time_zones'), 'America/Blanc-Sablon', '-04:00'), (next_id('1_time_zones'), 'America/Boa_Vista', '-04:00'), (next_id('1_time_zones'), 'America/Bogota', '-05:00'), (next_id('1_time_zones'), 'America/Boise', '-06:00'), (next_id('1_time_zones'), 'America/Cambridge_Bay', '-06:00'), (next_id('1_time_zones'), 'America/Campo_Grande', '-04:00'), (next_id('1_time_zones'), 'America/Cancun', '-05:00'), (next_id('1_time_zones'), 'America/Caracas', '-04:00'), (next_id('1_time_zones'), 'America/Cayenne', '-03:00'), (next_id('1_time_zones'), 'America/Cayman', '-05:00'), (next_id('1_time_zones'), 'America/Chicago', '-05:00'), (next_id('1_time_zones'), 'America/Chihuahua', '-06:00'), (next_id('1_time_zones'), 'America/Costa_Rica', '-06:00'), (next_id('1_time_zones'), 'America/Creston', '-07:00'), (next_id('1_time_zones'), 'America/Cuiaba', '-04:00'), (next_id('1_time_zones'), 'America/Curacao', '-04:00'), (next_id('1_time_zones'), 'America/Danmarkshavn', 'UTC'), (next_id('1_time_zones'), 'America/Dawson', '-07:00'), (next_id('1_time_zones'), 'America/Dawson_Creek', '-07:00'), (next_id('1_time_zones'), 'America/Denver', '-06:00'), (next_id('1_time_zones'), 'America/Detroit', '-04:00'), (next_id('1_time_zones'), 'America/Dominica', '-04:00'), (next_id('1_time_zones'), 'America/Edmonton', '-06:00'), (next_id('1_time_zones'), 'America/Eirunepe', '-05:00'), (next_id('1_time_zones'), 'America/El_Salvador', '-06:00'), (next_id('1_time_zones'), 'America/Fort_Nelson', '-07:00'), (next_id('1_time_zones'), 'America/Fortaleza', '-03:00'), (next_id('1_time_zones'), 'America/Glace_Bay', '-03:00'), (next_id('1_time_zones'), 'America/Godthab', '-02:00'), (next_id('1_time_zones'), 'America/Goose_Bay', '-03:00'), (next_id('1_time_zones'), 'America/Grand_Turk', '-04:00'), (next_id('1_time_zones'), 'America/Grenada', '-04:00'), (next_id('1_time_zones'), 'America/Guadeloupe', '-04:00'), (next_id('1_time_zones'), 'America/Guatemala', '-06:00'), (next_id('1_time_zones'), 'America/Guayaquil', '-05:00'), (next_id('1_time_zones'), 'America/Guyana', '-04:00'), (next_id('1_time_zones'), 'America/Halifax', '-03:00'), (next_id('1_time_zones'), 'America/Havana', '-04:00'), (next_id('1_time_zones'), 'America/Hermosillo', '-07:00'), (next_id('1_time_zones'), 'America/Indiana/Indianapolis', '-04:00'), (next_id('1_time_zones'), 'America/Indiana/Knox', '-05:00'), (next_id('1_time_zones'), 'America/Indiana/Marengo', '-04:00'), (next_id('1_time_zones'), 'America/Indiana/Petersburg', '-04:00'), (next_id('1_time_zones'), 'America/Indiana/Tell_City', '-05:00'), (next_id('1_time_zones'), 'America/Indiana/Vevay', '-04:00'), (next_id('1_time_zones'), 'America/Indiana/Vincennes', '-04:00'), (next_id('1_time_zones'), 'America/Indiana/Winamac', '-04:00'), (next_id('1_time_zones'), 'America/Inuvik', '-06:00'), (next_id('1_time_zones'), 'America/Iqaluit', '-04:00'), (next_id('1_time_zones'), 'America/Jamaica', '-05:00'), (next_id('1_time_zones'), 'America/Juneau', '-08:00'), (next_id('1_time_zones'), 'America/Kentucky/Louisville', '-04:00'), (next_id('1_time_zones'), 'America/Kentucky/Monticello', '-04:00'), (next_id('1_time_zones'), 'America/Kralendijk', '-04:00'), (next_id('1_time_zones'), 'America/La_Paz', '-04:00'), (next_id('1_time_zones'), 'America/Lima', '-05:00'), (next_id('1_time_zones'), 'America/Los_Angeles', '-07:00'), (next_id('1_time_zones'), 'America/Lower_Princes', '-04:00'), (next_id('1_time_zones'), 'America/Maceio', '-03:00'), (next_id('1_time_zones'), 'America/Managua', '-06:00'), (next_id('1_time_zones'), 'America/Manaus', '-04:00'), (next_id('1_time_zones'), 'America/Marigot', '-04:00'), (next_id('1_time_zones'), 'America/Martinique', '-04:00'), (next_id('1_time_zones'), 'America/Matamoros', '-05:00'), (next_id('1_time_zones'), 'America/Mazatlan', '-06:00'), (next_id('1_time_zones'), 'America/Menominee', '-05:00'), (next_id('1_time_zones'), 'America/Merida', '-05:00'), (next_id('1_time_zones'), 'America/Metlakatla', '-08:00'), (next_id('1_time_zones'), 'America/Mexico_City', '-05:00'), (next_id('1_time_zones'), 'America/Miquelon', '-02:00'), (next_id('1_time_zones'), 'America/Moncton', '-03:00'), (next_id('1_time_zones'), 'America/Monterrey', '-05:00'), (next_id('1_time_zones'), 'America/Montevideo', '-03:00'), (next_id('1_time_zones'), 'America/Montserrat', '-04:00'), (next_id('1_time_zones'), 'America/Nassau', '-04:00'), (next_id('1_time_zones'), 'America/New_York', '-04:00'), (next_id('1_time_zones'), 'America/Nipigon', '-04:00'), (next_id('1_time_zones'), 'America/Nome', '-08:00'), (next_id('1_time_zones'), 'America/Noronha', '-02:00'), (next_id('1_time_zones'), 'America/North_Dakota/Beulah', '-05:00'), (next_id('1_time_zones'), 'America/North_Dakota/Center', '-05:00'), (next_id('1_time_zones'), 'America/North_Dakota/New_Salem', '-05:00'), (next_id('1_time_zones'), 'America/Ojinaga', '-06:00'), (next_id('1_time_zones'), 'America/Panama', '-05:00'), (next_id('1_time_zones'), 'America/Pangnirtung', '-04:00'), (next_id('1_time_zones'), 'America/Paramaribo', '-03:00'), (next_id('1_time_zones'), 'America/Phoenix', '-07:00'), (next_id('1_time_zones'), 'America/Port-au-Prince', '-04:00'), (next_id('1_time_zones'), 'America/Port_of_Spain', '-04:00'), (next_id('1_time_zones'), 'America/Porto_Velho', '-04:00'), (next_id('1_time_zones'), 'America/Puerto_Rico', '-04:00'), (next_id('1_time_zones'), 'America/Punta_Arenas', '-03:00'), (next_id('1_time_zones'), 'America/Rainy_River', '-05:00'), (next_id('1_time_zones'), 'America/Rankin_Inlet', '-05:00'), (next_id('1_time_zones'), 'America/Recife', '-03:00'), (next_id('1_time_zones'), 'America/Regina', '-06:00'), (next_id('1_time_zones'), 'America/Resolute', '-05:00'), (next_id('1_time_zones'), 'America/Rio_Branco', '-05:00'), (next_id('1_time_zones'), 'America/Santarem', '-03:00'), (next_id('1_time_zones'), 'America/Santiago', '-04:00'), (next_id('1_time_zones'), 'America/Santo_Domingo', '-04:00'), (next_id('1_time_zones'), 'America/Sao_Paulo', '-03:00'), (next_id('1_time_zones'), 'America/Scoresbysund', 'UTC'), (next_id('1_time_zones'), 'America/Sitka', '-08:00'), (next_id('1_time_zones'), 'America/St_Barthelemy', '-04:00'), (next_id('1_time_zones'), 'America/St_Johns', '-02:30'), (next_id('1_time_zones'), 'America/St_Kitts', '-04:00'), (next_id('1_time_zones'), 'America/St_Lucia', '-04:00'), (next_id('1_time_zones'), 'America/St_Thomas', '-04:00'), (next_id('1_time_zones'), 'America/St_Vincent', '-04:00'), (next_id('1_time_zones'), 'America/Swift_Current', '-06:00'), (next_id('1_time_zones'), 'America/Tegucigalpa', '-06:00'), (next_id('1_time_zones'), 'America/Thule', '-03:00'), (next_id('1_time_zones'), 'America/Thunder_Bay', '-04:00'), (next_id('1_time_zones'), 'America/Tijuana', '-07:00'), (next_id('1_time_zones'), 'America/Toronto', '-04:00'), (next_id('1_time_zones'), 'America/Tortola', '-04:00'), (next_id('1_time_zones'), 'America/Vancouver', '-07:00'), (next_id('1_time_zones'), 'America/Whitehorse', '-07:00'), (next_id('1_time_zones'), 'America/Winnipeg', '-05:00'), (next_id('1_time_zones'), 'America/Yakutat', '-08:00'), (next_id('1_time_zones'), 'America/Yellowknife', '-06:00'), (next_id('1_time_zones'), 'Antarctica/Casey', '+08:00'), (next_id('1_time_zones'), 'Antarctica/Davis', '+07:00'), (next_id('1_time_zones'), 'Antarctica/DumontDUrville', '+10:00'), (next_id('1_time_zones'), 'Antarctica/Macquarie', '+11:00'), (next_id('1_time_zones'), 'Antarctica/Mawson', '+05:00'), (next_id('1_time_zones'), 'Antarctica/McMurdo', '+12:00'), (next_id('1_time_zones'), 'Antarctica/Palmer', '-03:00'), (next_id('1_time_zones'), 'Antarctica/Rothera', '-03:00'), (next_id('1_time_zones'), 'Antarctica/Syowa', '+03:00'), (next_id('1_time_zones'), 'Antarctica/Troll', '+02:00'), (next_id('1_time_zones'), 'Antarctica/Vostok', '+06:00'), (next_id('1_time_zones'), 'Arctic/Longyearbyen', '+02:00'), (next_id('1_time_zones'), 'Asia/Aden', '+03:00'), (next_id('1_time_zones'), 'Asia/Almaty', '+06:00'), (next_id('1_time_zones'), 'Asia/Amman', '+03:00'), (next_id('1_time_zones'), 'Asia/Anadyr', '+12:00'), (next_id('1_time_zones'), 'Asia/Aqtau', '+05:00'), (next_id('1_time_zones'), 'Asia/Aqtobe', '+05:00'), (next_id('1_time_zones'), 'Asia/Ashgabat', '+05:00'), (next_id('1_time_zones'), 'Asia/Atyrau', '+05:00'), (next_id('1_time_zones'), 'Asia/Baghdad', '+03:00'), (next_id('1_time_zones'), 'Asia/Bahrain', '+03:00'), (next_id('1_time_zones'), 'Asia/Baku', '+04:00'), (next_id('1_time_zones'), 'Asia/Bangkok', '+07:00'), (next_id('1_time_zones'), 'Asia/Barnaul', '+07:00'), (next_id('1_time_zones'), 'Asia/Beirut', '+03:00'), (next_id('1_time_zones'), 'Asia/Bishkek', '+06:00'), (next_id('1_time_zones'), 'Asia/Brunei', '+08:00'), (next_id('1_time_zones'), 'Asia/Chita', '+09:00'), (next_id('1_time_zones'), 'Asia/Choibalsan', '+08:00'), (next_id('1_time_zones'), 'Asia/Chongqing', '+08:00'), (next_id('1_time_zones'), 'Asia/Colombo', '+05:30'), (next_id('1_time_zones'), 'Asia/Damascus', '+03:00'), (next_id('1_time_zones'), 'Asia/Dhaka', '+06:00'), (next_id('1_time_zones'), 'Asia/Dili', '+09:00'), (next_id('1_time_zones'), 'Asia/Dubai', '+04:00'), (next_id('1_time_zones'), 'Asia/Dushanbe', '+05:00'), (next_id('1_time_zones'), 'Asia/Famagusta', '+03:00'), (next_id('1_time_zones'), 'Asia/Gaza', '+03:00'), (next_id('1_time_zones'), 'Asia/Hebron', '+03:00'), (next_id('1_time_zones'), 'Asia/Ho_Chi_Minh', '+07:00'), (next_id('1_time_zones'), 'Asia/Hong_Kong', '+08:00'), (next_id('1_time_zones'), 'Asia/Hovd', '+07:00'), (next_id('1_time_zones'), 'Asia/Irkutsk', '+08:00'), (next_id('1_time_zones'), 'Asia/Jakarta', '+07:00'), (next_id('1_time_zones'), 'Asia/Jayapura', '+09:00'), (next_id('1_time_zones'), 'Asia/Jerusalem', '+03:00'), (next_id('1_time_zones'), 'Asia/Kabul', '+04:30'), (next_id('1_time_zones'), 'Asia/Kamchatka', '+12:00'), (next_id('1_time_zones'), 'Asia/Karachi', '+05:00'), (next_id('1_time_zones'), 'Asia/Kathmandu', '+05:45'), (next_id('1_time_zones'), 'Asia/Khandyga', '+09:00'), (next_id('1_time_zones'), 'Asia/Kolkata', '+05:30'), (next_id('1_time_zones'), 'Asia/Krasnoyarsk', '+07:00'), (next_id('1_time_zones'), 'Asia/Kuala_Lumpur', '+08:00'), (next_id('1_time_zones'), 'Asia/Kuching', '+08:00'), (next_id('1_time_zones'), 'Asia/Kuwait', '+03:00'), (next_id('1_time_zones'), 'Asia/Macau', '+08:00'), (next_id('1_time_zones'), 'Asia/Magadan', '+11:00'), (next_id('1_time_zones'), 'Asia/Makassar', '+08:00'), (next_id('1_time_zones'), 'Asia/Manila', '+08:00'), (next_id('1_time_zones'), 'Asia/Muscat', '+04:00'), (next_id('1_time_zones'), 'Asia/Nicosia', '+03:00'), (next_id('1_time_zones'), 'Asia/Novokuznetsk', '+07:00'), (next_id('1_time_zones'), 'Asia/Novosibirsk', '+07:00'), (next_id('1_time_zones'), 'Asia/Omsk', '+06:00'), (next_id('1_time_zones'), 'Asia/Oral', '+05:00'), (next_id('1_time_zones'), 'Asia/Phnom_Penh', '+07:00'), (next_id('1_time_zones'), 'Asia/Pontianak', '+07:00'), (next_id('1_time_zones'), 'Asia/Pyongyang', '+09:00'), (next_id('1_time_zones'), 'Asia/Qatar', '+03:00'), (next_id('1_time_zones'), 'Asia/Qostanay', '+06:00'), (next_id('1_time_zones'), 'Asia/Qyzylorda', '+05:00'), (next_id('1_time_zones'), 'Asia/Riyadh', '+03:00'), (next_id('1_time_zones'), 'Asia/Sakhalin', '+11:00'), (next_id('1_time_zones'), 'Asia/Samarkand', '+05:00'), (next_id('1_time_zones'), 'Asia/Seoul', '+09:00'), (next_id('1_time_zones'), 'Asia/Shanghai', '+08:00'), (next_id('1_time_zones'), 'Asia/Singapore', '+08:00'), (next_id('1_time_zones'), 'Asia/Srednekolymsk', '+11:00'), (next_id('1_time_zones'), 'Asia/Taipei', '+08:00'), (next_id('1_time_zones'), 'Asia/Tashkent', '+05:00'), (next_id('1_time_zones'), 'Asia/Tbilisi', '+04:00'), (next_id('1_time_zones'), 'Asia/Tehran', '+04:30'), (next_id('1_time_zones'), 'Asia/Thimphu', '+06:00'), (next_id('1_time_zones'), 'Asia/Tokyo', '+09:00'), (next_id('1_time_zones'), 'Asia/Tomsk', '+07:00'), (next_id('1_time_zones'), 'Asia/Ulaanbaatar', '+08:00'), (next_id('1_time_zones'), 'Asia/Urumqi', '+06:00'), (next_id('1_time_zones'), 'Asia/Ust-Nera', '+10:00'), (next_id('1_time_zones'), 'Asia/Vientiane', '+07:00'), (next_id('1_time_zones'), 'Asia/Vladivostok', '+10:00'), (next_id('1_time_zones'), 'Asia/Yakutsk', '+09:00'), (next_id('1_time_zones'), 'Asia/Yangon', '+06:30'), (next_id('1_time_zones'), 'Asia/Yekaterinburg', '+05:00'), (next_id('1_time_zones'), 'Asia/Yerevan', '+04:00'), (next_id('1_time_zones'), 'Atlantic/Azores', 'UTC'), (next_id('1_time_zones'), 'Atlantic/Bermuda', '-03:00'), (next_id('1_time_zones'), 'Atlantic/Canary', '+01:00'), (next_id('1_time_zones'), 'Atlantic/Cape_Verde', '-01:00'), (next_id('1_time_zones'), 'Atlantic/Faroe', '+01:00'), (next_id('1_time_zones'), 'Atlantic/Madeira', '+01:00'), (next_id('1_time_zones'), 'Atlantic/Reykjavik', 'UTC'), (next_id('1_time_zones'), 'Atlantic/South_Georgia', '-02:00'), (next_id('1_time_zones'), 'Atlantic/St_Helena', 'UTC'), (next_id('1_time_zones'), 'Atlantic/Stanley', '-03:00'), (next_id('1_time_zones'), 'Australia/Adelaide', '+09:30'), (next_id('1_time_zones'), 'Australia/Brisbane', '+10:00'), (next_id('1_time_zones'), 'Australia/Broken_Hill', '+09:30'), (next_id('1_time_zones'), 'Australia/Currie', '+10:00'), (next_id('1_time_zones'), 'Australia/Darwin', '+09:30'), (next_id('1_time_zones'), 'Australia/Eucla', '+08:45'), (next_id('1_time_zones'), 'Australia/Hobart', '+10:00'), (next_id('1_time_zones'), 'Australia/Lindeman', '+10:00'), (next_id('1_time_zones'), 'Australia/Lord_Howe', '+10:30'), (next_id('1_time_zones'), 'Australia/Melbourne', '+10:00'), (next_id('1_time_zones'), 'Australia/Perth', '+08:00'), (next_id('1_time_zones'), 'Australia/Sydney', '+10:00'), (next_id('1_time_zones'), 'Europe/Amsterdam', '+02:00'), (next_id('1_time_zones'), 'Europe/Andorra', '+02:00'), (next_id('1_time_zones'), 'Europe/Astrakhan', '+04:00'), (next_id('1_time_zones'), 'Europe/Athens', '+03:00'), (next_id('1_time_zones'), 'Europe/Belgrade', '+02:00'), (next_id('1_time_zones'), 'Europe/Berlin', '+02:00'), (next_id('1_time_zones'), 'Europe/Bratislava', '+02:00'), (next_id('1_time_zones'), 'Europe/Brussels', '+02:00'), (next_id('1_time_zones'), 'Europe/Bucharest', '+03:00'), (next_id('1_time_zones'), 'Europe/Budapest', '+02:00'), (next_id('1_time_zones'), 'Europe/Busingen', '+02:00'), (next_id('1_time_zones'), 'Europe/Chisinau', '+03:00'), (next_id('1_time_zones'), 'Europe/Copenhagen', '+02:00'), (next_id('1_time_zones'), 'Europe/Dublin', '+01:00'), (next_id('1_time_zones'), 'Europe/Gibraltar', '+02:00'), (next_id('1_time_zones'), 'Europe/Guernsey', '+01:00'), (next_id('1_time_zones'), 'Europe/Helsinki', '+03:00'), (next_id('1_time_zones'), 'Europe/Isle_of_Man', '+01:00'), (next_id('1_time_zones'), 'Europe/Istanbul', '+03:00'), (next_id('1_time_zones'), 'Europe/Jersey', '+01:00'), (next_id('1_time_zones'), 'Europe/Kaliningrad', '+02:00'), (next_id('1_time_zones'), 'Europe/Kiev', '+03:00'), (next_id('1_time_zones'), 'Europe/Kirov', '+03:00'), (next_id('1_time_zones'), 'Europe/Lisbon', '+01:00'), (next_id('1_time_zones'), 'Europe/Ljubljana', '+02:00'), (next_id('1_time_zones'), 'Europe/London', '+01:00'), (next_id('1_time_zones'), 'Europe/Luxembourg', '+02:00'), (next_id('1_time_zones'), 'Europe/Madrid', '+02:00'), (next_id('1_time_zones'), 'Europe/Malta', '+02:00'), (next_id('1_time_zones'), 'Europe/Mariehamn', '+03:00'), (next_id('1_time_zones'), 'Europe/Minsk', '+03:00'), (next_id('1_time_zones'), 'Europe/Monaco', '+02:00'), (next_id('1_time_zones'), 'Europe/Moscow', '+03:00'), (next_id('1_time_zones'), 'Europe/Oslo', '+02:00'), (next_id('1_time_zones'), 'Europe/Paris', '+02:00'), (next_id('1_time_zones'), 'Europe/Podgorica', '+02:00'), (next_id('1_time_zones'), 'Europe/Prague', '+02:00'), (next_id('1_time_zones'), 'Europe/Riga', '+03:00'), (next_id('1_time_zones'), 'Europe/Rome', '+02:00'), (next_id('1_time_zones'), 'Europe/Samara', '+04:00'), (next_id('1_time_zones'), 'Europe/San_Marino', '+02:00'), (next_id('1_time_zones'), 'Europe/Sarajevo', '+02:00'), (next_id('1_time_zones'), 'Europe/Saratov', '+04:00'), (next_id('1_time_zones'), 'Europe/Simferopol', '+03:00'), (next_id('1_time_zones'), 'Europe/Skopje', '+02:00'), (next_id('1_time_zones'), 'Europe/Sofia', '+03:00'), (next_id('1_time_zones'), 'Europe/Stockholm', '+02:00'), (next_id('1_time_zones'), 'Europe/Tallinn', '+03:00'), (next_id('1_time_zones'), 'Europe/Tirane', '+02:00'), (next_id('1_time_zones'), 'Europe/Ulyanovsk', '+04:00'), (next_id('1_time_zones'), 'Europe/Uzhgorod', '+03:00'), (next_id('1_time_zones'), 'Europe/Vaduz', '+02:00'), (next_id('1_time_zones'), 'Europe/Vatican', '+02:00'), (next_id('1_time_zones'), 'Europe/Vienna', '+02:00'), (next_id('1_time_zones'), 'Europe/Vilnius', '+03:00'), (next_id('1_time_zones'), 'Europe/Volgograd', '+04:00'), (next_id('1_time_zones'), 'Europe/Warsaw', '+02:00'), (next_id('1_time_zones'), 'Europe/Zagreb', '+02:00'), (next_id('1_time_zones'), 'Europe/Zaporozhye', '+03:00'), (next_id('1_time_zones'), 'Europe/Zurich', '+02:00'), (next_id('1_time_zones'), 'Indian/Antananarivo', '+03:00'), (next_id('1_time_zones'), 'Indian/Chagos', '+06:00'), (next_id('1_time_zones'), 'Indian/Christmas', '+07:00'), (next_id('1_time_zones'), 'Indian/Cocos', '+06:30'), (next_id('1_time_zones'), 'Indian/Comoro', '+03:00'), (next_id('1_time_zones'), 'Indian/Kerguelen', '+05:00'), (next_id('1_time_zones'), 'Indian/Mahe', '+04:00'), (next_id('1_time_zones'), 'Indian/Maldives', '+05:00'), (next_id('1_time_zones'), 'Indian/Mauritius', '+04:00'), (next_id('1_time_zones'), 'Indian/Mayotte', '+03:00'), (next_id('1_time_zones'), 'Indian/Reunion', '+04:00'), (next_id('1_time_zones'), 'Pacific/Apia', '+13:00'), (next_id('1_time_zones'), 'Pacific/Auckland', '+12:00'), (next_id('1_time_zones'), 'Pacific/Bougainville', '+11:00'), (next_id('1_time_zones'), 'Pacific/Chatham', '+12:45'), (next_id('1_time_zones'), 'Pacific/Chuuk', '+10:00'), (next_id('1_time_zones'), 'Pacific/Easter', '-06:00'), (next_id('1_time_zones'), 'Pacific/Efate', '+11:00'), (next_id('1_time_zones'), 'Pacific/Enderbury', '+13:00'), (next_id('1_time_zones'), 'Pacific/Fakaofo', '+13:00'), (next_id('1_time_zones'), 'Pacific/Fiji', '+12:00'), (next_id('1_time_zones'), 'Pacific/Funafuti', '+12:00'), (next_id('1_time_zones'), 'Pacific/Galapagos', '-06:00'), (next_id('1_time_zones'), 'Pacific/Gambier', '-09:00'), (next_id('1_time_zones'), 'Pacific/Guadalcanal', '+11:00'), (next_id('1_time_zones'), 'Pacific/Guam', '+10:00'), (next_id('1_time_zones'), 'Pacific/Honolulu', '-10:00'), (next_id('1_time_zones'), 'Pacific/Kiritimati', '+14:00'), (next_id('1_time_zones'), 'Pacific/Kosrae', '+11:00'), (next_id('1_time_zones'), 'Pacific/Kwajalein', '+12:00'), (next_id('1_time_zones'), 'Pacific/Majuro', '+12:00'), (next_id('1_time_zones'), 'Pacific/Marquesas', '-09:30'), (next_id('1_time_zones'), 'Pacific/Midway', '-11:00'), (next_id('1_time_zones'), 'Pacific/Nauru', '+12:00'), (next_id('1_time_zones'), 'Pacific/Niue', '-11:00'), (next_id('1_time_zones'), 'Pacific/Norfolk', '+11:00'), (next_id('1_time_zones'), 'Pacific/Noumea', '+11:00'), (next_id('1_time_zones'), 'Pacific/Pago_Pago', '-11:00'), (next_id('1_time_zones'), 'Pacific/Palau', '+09:00'), (next_id('1_time_zones'), 'Pacific/Pitcairn', '-08:00'), (next_id('1_time_zones'), 'Pacific/Pohnpei', '+11:00'), (next_id('1_time_zones'), 'Pacific/Port_Moresby', '+10:00'), (next_id('1_time_zones'), 'Pacific/Rarotonga', '-10:00'), (next_id('1_time_zones'), 'Pacific/Saipan', '+10:00'), (next_id('1_time_zones'), 'Pacific/Tahiti', '-10:00'), (next_id('1_time_zones'), 'Pacific/Tarawa', '+12:00'), (next_id('1_time_zones'), 'Pacific/Tongatapu', '+13:00'), (next_id('1_time_zones'), 'Pacific/Wake', '+12:00'), (next_id('1_time_zones'), 'Pacific/Wallis', '+12:00'); ` ================================================ FILE: packages/migration/updates/migration_update_exec.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package updates var MigrationUpdatePriceCreateExec = ` INSERT INTO "1_platform_parameters" (id, name, value, conditions) VALUES (next_id('1_platform_parameters'),'price_create_rate', '1000000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_ecosystem', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_table', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_column', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_contract', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_menu', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_page', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_snippet', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_view', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_application', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_token', '5000', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_lang', '1', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'),'price_create_exec_@1_new_section', '1', 'ContractAccess("@1UpdatePlatformParam")'); ` var MigrationUpdatePriceExec = ` INSERT INTO "1_platform_parameters" (id, name, value, conditions) VALUES (next_id('1_platform_parameters'), 'price_exec_get_block', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_int', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_map_keys', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_pub_to_hex', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_sqrt', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_json_encode_indent', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_encode_base64', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_sorted_keys', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_hex_to_pub', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_throw', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_create_contract', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_edit_language', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_date_time', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_edit_ecosys_name', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_update_notifications', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_update_roles_notifications', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_contract_name', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_bnd_wallet', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_check_signature', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_row_conditions', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_append', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_round', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_lang_res', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_to_upper', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_allow_change_condition', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_bytes_to_string', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_app_param', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_float', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_money', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_del_table', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_string_to_bytes', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_date_time_location', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_h_mac', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_hex_to_bytes', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_split', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_column_type', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_unix_date_time_location', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_contract_conditions', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_random', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_type', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_del_column', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_update_nodes_ban', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_log10', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_validate_edit_contract_new_value', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_format_money', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_create_language', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_role_access', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_decode_base64', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_unix_date_time', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_history', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_floor', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_json_decode', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_update_contract', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_log', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_json_encode', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_to_lower', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_unbnd_wallet', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_history_row', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_block_time', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_contract_access', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_transaction_info', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_pow', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_hash', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_check_condition', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_str', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_trim_space', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_address_to_id', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_id_to_address', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_pub_to_id', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_ecosys_param', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_sys_param_string', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_sys_param_int', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_sys_fuel', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_validate_condition', '30', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_eval_condition', '20', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_has_prefix', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_contains', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_replace', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_join', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_update_lang', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_size', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_substr', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_contracts_list', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_is_object', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_compile_contract', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_flush_contract', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_eval', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_len', '5', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_bind_wallet', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_unbind_wallet', '10', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_create_ecosystem', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_table_conditions', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_create_table', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_perm_table', '100', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_column_condition', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_create_column', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_perm_column', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_contract_by_name', '20', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_get_contract_by_id', '20', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_money_div', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_check_sign', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_date_format', '50', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_is_honor_node_key', '10', 'ContractAccess("@1UpdatePlatformParam")'); ` var MigrationUpdateAccessExec = ` INSERT INTO "1_platform_parameters" (id, name, value, conditions) VALUES (next_id('1_platform_parameters'), 'access_exec_compile_contract', 'ContractAccess("@1NewContract", "@1EditContract", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_update_contract', 'ContractAccess("@1EditContract", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_create_contract', 'ContractAccess("@1NewContract", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_create_table', 'ContractAccess("@1NewTable", "@1NewTableJoint", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_flush_contract', 'ContractAccess("@1NewContract", "@1EditContract", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_perm_table', 'ContractAccess("@1EditTable")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_table_conditions', 'ContractAccess("@1NewTable", "@1Import", "@1NewTableJoint", "@1EditTable")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_column_condition', 'ContractAccess("@1NewColumn", "@1EditColumn")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_create_column', 'ContractAccess("@1NewColumn")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_perm_column', 'ContractAccess("@1EditColumn")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_create_language', 'ContractAccess("@1NewLang", "@1NewLangJoint", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_edit_language', 'ContractAccess("@1EditLang", "@1EditLangJoint", "@1Import")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_create_ecosystem', 'ContractAccess("@1NewEcosystem")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_edit_ecosys_name', 'ContractAccess("@1EditEcosystemName")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_bind_wallet', 'ContractAccess("@1BindWallet")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_unbind_wallet', 'ContractAccess("@1UnbindWallet")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_set_contract_wallet', 'ContractAccess("@1BindWallet", "@1UnbindWallet")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_update_nodes_ban', 'ContractAccess("@1CheckNodesBan")', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'access_exec_create_view', 'ContractAccess("@1NewView")', 'ContractAccess("@1UpdatePlatformParam")'); ` var tentative = ` INSERT INTO "1_platform_parameters" (id, name, value, conditions) VALUES (next_id('1_platform_parameters'), 'block_reward','10','ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'external_blockchain', '', 'ContractAccess("@1UpdatePlatformParam")'), (next_id('1_platform_parameters'), 'price_exec_send_external_transaction', '50', 'ContractAccess("@1UpdatePlatformParam")'); ` ================================================ FILE: packages/modes/api.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package modes import ( "github.com/IBAX-io/go-ibax/packages/service/jsonrpc" "net/http" "github.com/IBAX-io/go-ibax/packages/api" "github.com/IBAX-io/go-ibax/packages/conf" ) func RegisterRoutes() http.Handler { m := api.Mode{ EcosystemGetter: GetEcosystemGetter(), ContractRunner: GetSmartContractRunner(), ClientTxProcessor: GetClientTxPreprocessor(), } r := api.NewRouter(m) if !conf.Config.IsSupportingCLB() { m.SetBlockchainRoutes(r) } if conf.Config.IsSubNode() { m.SetSubNodeRoutes(r) } if conf.Config.IsSupportingCLB() { } return r.GetAPI() } type JsonRpcRoutes struct { s *rpcServer next http.Handler } func (s *JsonRpcRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/" { s.s.ServeHTTP(w, r) return } s.next.ServeHTTP(w, r) } func RegisterJsonRPCRoutes(next http.Handler) http.Handler { m := jsonrpc.Mode{ EcosystemGetter: GetEcosystemGetter(), ContractRunner: GetSmartContractRunner(), ClientTxProcessor: GetClientTxPreprocessor(), } rpc := newRpcServer(m) rpc.lo.Lock() defer rpc.lo.Unlock() err := rpc.enableRpc(conf.Config.JsonRPC.Namespace) if err != nil { panic(err) } return &JsonRpcRoutes{rpc, next} } ================================================ FILE: packages/modes/client_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package modes import ( "bytes" "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) var ErrDiffKey = errors.New("Different keys") type blockchainTxPreprocessor struct{} func (p blockchainTxPreprocessor) ProcessClientTxBatches(txDatas [][]byte, key int64, le *log.Entry) (retTx []string, err error) { var rtxs []*sqldb.RawTx for _, txData := range txDatas { rtx := &transaction.Transaction{} if err = rtx.Unmarshall(bytes.NewBuffer(txData), true); err != nil { return nil, err } rtxs = append(rtxs, rtx.SetRawTx()) retTx = append(retTx, fmt.Sprintf("%x", rtx.Hash())) } err = sqldb.SendTxBatches(rtxs) return } type ClbTxPreprocessor struct{} /* func (p ClbTxPreprocessor) ProcessClientTranstaction(txData []byte, key int64, le *log.Entry) (string, error) { tx, err := transaction.UnmarshallTransaction(bytes.NewBuffer(txData), true) if err != nil { le.WithFields(log.Fields{"type": consts.ParseError, "error": err}).Error("on unmarshaling user tx") return "", err } ts := &sqldb.TransactionStatus{ BlockId: 1, Hash: tx.TxHash, Timestamp: time.Now().Unix(), WalletID: key, Type: tx.Rtx.Type(), } if err := ts.Create(); err != nil { le.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on creating tx status") return "", err } res, _, err := tx.CallCLBContract() if err != nil { le.WithFields(log.Fields{"type": consts.ParseError, "error": err}).Error("on execution contract") return "", err } if err := sqldb.SetTransactionStatusBlockMsg(nil, 1, res, tx.TxHash); err != nil { le.WithFields(log.Fields{"type": consts.DBError, "error": err, "tx_hash": tx.TxHash}).Error("updating transaction status block id") return "", err } return string(converter.BinToHex(tx.TxHash)), nil }*/ func (p ClbTxPreprocessor) ProcessClientTxBatches(txData [][]byte, key int64, le *log.Entry) ([]string, error) { return nil, nil } func GetClientTxPreprocessor() types.ClientTxPreprocessor { if conf.Config.IsSupportingCLB() { return ClbTxPreprocessor{} } return blockchainTxPreprocessor{} } // BlockchainSCRunner implementls SmartContractRunner for blockchain mode type BlockchainSCRunner struct{} // RunContract runs smart contract on blockchain mode func (runner BlockchainSCRunner) RunContract(data, hash []byte, keyID, tnow int64, le *log.Entry) error { if err := transaction.CreateTransaction(data, hash, keyID, tnow); err != nil { le.WithFields(log.Fields{"type": consts.ContractError, "error": err}).Error("Executing contract") return err } return nil } // CLBSCRunner implementls SmartContractRunner for clb mode type CLBSCRunner struct{} // RunContract runs smart contract on clb mode func (runner CLBSCRunner) RunContract(data, hash []byte, keyID, tnow int64, le *log.Entry) error { proc := GetClientTxPreprocessor() _, err := proc.ProcessClientTxBatches([][]byte{data}, keyID, le) if err != nil { le.WithFields(log.Fields{"error": consts.ContractError}).Error("on run internal NewUser") return err } return nil } // GetSmartContractRunner returns mode boundede implementation of SmartContractRunner func GetSmartContractRunner() types.SmartContractRunner { if !conf.Config.IsSupportingCLB() { return BlockchainSCRunner{} } return CLBSCRunner{} } ================================================ FILE: packages/modes/daemons.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package modes ================================================ FILE: packages/modes/ecosystem_getter.go ================================================ package modes import ( "github.com/IBAX-io/go-ibax/packages/api" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) func GetEcosystemGetter() types.EcosystemGetter { if conf.Config.IsSupportingCLB() { return CLBEcosystemGetter{} } return BCEcosystemGetter{} } type BCEcosystemGetter struct { logger *log.Entry } func (ng BCEcosystemGetter) GetEcosystemName(id int64) (string, error) { ecosystem := &sqldb.Ecosystem{} found, err := ecosystem.Get(nil, id) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting ecosystem from db") return "", err } if !found { log.WithFields(log.Fields{"type": consts.NotFound, "id": id, "error": api.ErrEcosystemNotFound}).Error("ecosystem not found") return "", err } return ecosystem.Name, nil } func (g BCEcosystemGetter) GetEcosystemLookup() ([]int64, []string, error) { return sqldb.GetAllSystemStatesIDs() } func (v BCEcosystemGetter) ValidateId(formEcosysID, clientEcosysID int64, le *log.Entry) (int64, error) { if formEcosysID <= 0 { return clientEcosysID, nil } count, err := sqldb.NewDbTransaction(nil).GetNextID("1_ecosystems") if err != nil { le.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting next id of ecosystems") return 0, err } if formEcosysID >= count { le.WithFields(log.Fields{"state_id": formEcosysID, "count": count, "type": consts.ParameterExceeded}).Error("ecosystem is larger then max count") return 0, api.ErrEcosystemNotFound } return formEcosysID, nil } type CLBEcosystemGetter struct{} func (g CLBEcosystemGetter) GetEcosystemLookup() ([]int64, []string, error) { return []int64{1}, []string{"Platform ecosystem"}, nil } func (CLBEcosystemGetter) ValidateId(id, clientID int64, le *log.Entry) (int64, error) { return consts.DefaultCLB, nil } func (ng CLBEcosystemGetter) GetEcosystemName(id int64) (string, error) { return "Platform ecosystem", nil } ================================================ FILE: packages/modes/mode_fabrics.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package modes import ( "context" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/clbmanager" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/daemons" "github.com/IBAX-io/go-ibax/packages/network/tcpserver" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) func GetDaemonLoader() types.DaemonFactory { if conf.Config.IsSupportingCLB() { return CLBDaemonFactory{ logger: log.WithFields(log.Fields{"loader": "clb_daemon_loader"}), } } if conf.Config.IsSubNode() { return SNDaemonFactory{ logger: log.WithFields(log.Fields{"loader": "subnode_daemon_loader"}), } } return BCDaemonFactory{ logger: log.WithFields(log.Fields{"loader": "blockchain_daemon_loader"}), } } // BCDaemonFactory allow load blockchain daemons type BCDaemonFactory struct { logger *log.Entry } // Load loads blockchain daemons func (l BCDaemonFactory) Load(ctx context.Context) error { if err := daemons.InitialLoad(l.logger); err != nil { return err } if err := syspar.SysUpdate(nil); err != nil { log.Errorf("can't read platform parameters: %s", utils.ErrInfo(err)) return err } if err := syspar.SysTableColType(nil); err != nil { log.Errorf("can't table col type: %s", utils.ErrInfo(err)) return err } if data, ok := block.GetDataFromFirstBlock(); ok { syspar.SetFirstBlockData(data) } mode := "Public blockchain" if syspar.IsPrivateBlockchain() { mode = "Private Blockchain" } logMode(l.logger, mode) l.logger.Info("load contracts") if err := smart.LoadContracts(); err != nil { log.Errorf("Load Contracts error: %s", err) return err } l.logger.Info("start daemons") daemons.StartDaemons(ctx, l.GetDaemonsList()) if err := tcpserver.TcpListener(conf.Config.TCPServer.Str()); err != nil { log.Errorf("can't start tcp servers, stop") return err } na := node.NewNodeRelevanceService() na.Run(ctx) if err := node.InitNodesBanService(); err != nil { l.logger.WithError(err).Error("Can't init ban service") return err } return nil } func (BCDaemonFactory) GetDaemonsList() []string { return []string{ "BlocksCollection", "BlockGenerator", "QueueParserTx", "QueueParserBlocks", "Disseminator", "Confirmations", "Scheduler", "CandidateNodeVoting", //"ExternalNetwork", } } // SNDaemonFactory allows load subnode daemons type SNDaemonFactory struct { logger *log.Entry } // Load loads subnode daemons func (l SNDaemonFactory) Load(ctx context.Context) error { daemons.InitialLoad(l.logger) if err := syspar.SysUpdate(nil); err != nil { log.Errorf("can't read platform parameters: %s", utils.ErrInfo(err)) return err } if err := syspar.SysTableColType(nil); err != nil { log.Errorf("can't table col type: %s", utils.ErrInfo(err)) return err } if data, ok := block.GetDataFromFirstBlock(); ok { syspar.SetFirstBlockData(data) } mode := "Public blockchain" if syspar.IsPrivateBlockchain() { mode = "Private Blockchain" } logMode(l.logger, mode) l.logger.Info("load contracts") if err := smart.LoadContracts(); err != nil { log.Errorf("Load Contracts error: %s", err) return err } l.logger.Info("start subnode daemons") daemons.StartDaemons(ctx, l.GetDaemonsList()) if err := tcpserver.TcpListener(conf.Config.TCPServer.Str()); err != nil { log.Errorf("can't start tcp servers, stop") return err } node.NewNodeRelevanceService().Run(ctx) if err := node.InitNodesBanService(); err != nil { l.logger.WithError(err).Error("Can't init ban service") return err } return nil } func (SNDaemonFactory) GetDaemonsList() []string { return []string{ "Scheduler", } } // CLBDaemonFactory allows load clb daemons type CLBDaemonFactory struct { logger *log.Entry } // Load loads clb daemons func (l CLBDaemonFactory) Load(ctx context.Context) error { if err := syspar.SysUpdate(nil); err != nil { l.logger.Errorf("can't read platform parameters: %s", utils.ErrInfo(err)) return err } if err := syspar.SysTableColType(nil); err != nil { log.Errorf("can't table col type: %s", utils.ErrInfo(err)) return err } logMode(l.logger, conf.Config.LocalConf.RunNodeMode) l.logger.Info("load contracts") if err := smart.LoadContracts(); err != nil { l.logger.Errorf("Load Contracts error: %s", err) return err } l.logger.Info("start daemons") daemons.StartDaemons(ctx, l.GetDaemonsList()) // if err := tcpserver.TcpListener(conf.Config.TCPServer.Str()); err != nil { log.Errorf("can't start tcp servers, stop") return err } clbmanager.InitCLBManager() return nil } func (CLBDaemonFactory) GetDaemonsList() []string { return []string{ "Scheduler", } } func logMode(logger *log.Entry, mode string) { logLevel := log.GetLevel() log.SetLevel(log.InfoLevel) logger.WithFields(log.Fields{"mode": mode}).Info("Node running mode") log.SetLevel(logLevel) } ================================================ FILE: packages/modes/rpc.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package modes import ( "fmt" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/service/jsonrpc" "net/http" "strings" "sync" "sync/atomic" "time" ) const stopTimeout = 5 * time.Second type rpcServer struct { lo sync.Mutex httpHandler atomic.Value mode jsonrpc.Mode } type rpcHandler struct { http.Handler server *jsonrpc.Server } type adminAPI struct { svr *serverApi } func (a *adminAPI) GetApis() []any { var apis []any if a == nil { return nil } if a.svr != nil { apis = append(apis, a.svr) } return apis } func newAdminApi(r *rpcServer) *adminAPI { return &adminAPI{ svr: newServerApi(r), } } type serverApi struct { rs *rpcServer } func newServerApi(r *rpcServer) *serverApi { return &serverApi{r} } func (s *serverApi) StartJsonRpc(ctx jsonrpc.RequestContext, namespace *string) (bool, *jsonrpc.Error) { if namespace != nil { conf.Config.JsonRPC.Namespace = *namespace } s.rs.httpHandler.Store((*rpcHandler)(nil)) err := s.rs.enableRpc(conf.Config.JsonRPC.Namespace) if err != nil { return false, jsonrpc.DefaultError("enable rpc failed") } return true, nil } func (s *serverApi) StopJsonRpc() (bool, *jsonrpc.Error) { s.rs.lo.Lock() defer s.rs.lo.Unlock() s.rs.disableRPC() return true, nil } func (r *rpcServer) rpcIsEnable() bool { return r.httpHandler.Load().(*rpcHandler) != nil } func (r *rpcServer) getApis(namespace string) []any { var apis []any switch namespace { case jsonrpc.GetNamespace(jsonrpc.NamespaceAdmin): adminApi := newAdminApi(r) apis = append(apis, adminApi.GetApis()...) case jsonrpc.GetNamespace(jsonrpc.NamespaceIBAX): ibaxApi := jsonrpc.NewIbaxApi(r.mode) apis = append(apis, ibaxApi.GetApis()...) case jsonrpc.GetNamespace(jsonrpc.NamespaceNet): netApi := jsonrpc.NewNetApi() apis = append(apis, netApi.GetApis()...) case jsonrpc.GetNamespace(jsonrpc.NamespaceDebug): debugApi := jsonrpc.NewDebugApi() apis = append(apis, debugApi.GetApis()...) } return apis } func (r *rpcServer) enableRpc(namespaces string) error { if r.rpcIsEnable() { return fmt.Errorf("RPC Server is already enabled") } srv := jsonrpc.NewServer(r.mode) for _, m := range strings.Split(namespaces, ",") { name := strings.TrimSpace(m) funcs := r.getApis(name) for _, f := range funcs { err := srv.RegisterName(name, f) if err != nil { return err } } } r.httpHandler.Store(&rpcHandler{ Handler: jsonrpc.NewMiddlewares(srv, r.mode), server: srv, }) return nil } func (s *rpcServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { handler := s.httpHandler.Load().(*rpcHandler) if handler != nil { handler.ServeHTTP(w, r) return } w.WriteHeader(http.StatusNotFound) } func newRpcServer(m jsonrpc.Mode) *rpcServer { s := &rpcServer{ mode: m, } s.httpHandler.Store((*rpcHandler)(nil)) return s } func (r *rpcServer) disableRPC() { handler := r.httpHandler.Load().(*rpcHandler) if handler != nil { r.httpHandler.Store((*rpcHandler)(nil)) handler.server.Stop() } } ================================================ FILE: packages/network/httpserver/max_body.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package httpserver import "net/http" type MaxBodyReader struct { h http.Handler n int64 } func (h *MaxBodyReader) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, h.n) h.h.ServeHTTP(w, r) } func NewMaxBodyReader(h http.Handler, n int64) http.Handler { return &MaxBodyReader{h, n} } ================================================ FILE: packages/network/machineState.go ================================================ package network import "fmt" type VoteMsg struct { CurrentBlockHeight int64 `json:"currentBlockHeight"` LocalAddress string `json:"localAddress"` TcpAddress string `json:"tcpAddress"` EcosystemID int64 `json:"ecosystemID"` Hash []byte `json:"hash"` Agree bool `json:"agree"` Msg string `json:"msg"` Time int64 `json:"time"` Sign []byte `json:"sign"` } func (voteMsg *VoteMsg) VoteForSign() string { return fmt.Sprintf("%v,%v,%v,%v,%x,%v", voteMsg.LocalAddress, voteMsg.TcpAddress, voteMsg.CurrentBlockHeight, voteMsg.EcosystemID, voteMsg.Hash, voteMsg.Time) } func (voteMsg *VoteMsg) VerifyVoteForSign() string { return fmt.Sprintf("%v,%v,%v,%v,%v,%v,%x,%v", voteMsg.LocalAddress, voteMsg.TcpAddress, voteMsg.CurrentBlockHeight, voteMsg.EcosystemID, voteMsg.Agree, voteMsg.Msg, voteMsg.Hash, voteMsg.Time) } ================================================ FILE: packages/network/protocol.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package network import ( "encoding/binary" "errors" "fmt" "io" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" log "github.com/sirupsen/logrus" ) type ReqTypesFlag uint16 // Types of requests const ( RequestTypeHonorNode ReqTypesFlag = iota + 1 RequestTypeNotHonorNode RequestTypeStopNetwork RequestTypeConfirmation RequestTypeBlockCollection RequestTypeMaxBlock RequestTypeVoting RequestSyncMatchineState // BlocksPerRequest contains count of blocks per request BlocksPerRequest int = 10 ) var ErrNotAccepted = errors.New("Not accepted") var ErrMaxSize = errors.New("Size greater than max size") // SelfReaderWriter read from Reader to himself and write to io.Writer from himself type SelfReaderWriter interface { Read(io.Reader) error Write(io.Writer) error } // RequestType is type of request type RequestType struct { Type ReqTypesFlag } // Read read first 2 bytes to uint16 func (rt *RequestType) Read(r io.Reader) error { return binary.Read(r, binary.LittleEndian, &rt.Type) } func (rt *RequestType) Write(w io.Writer) error { return binary.Write(w, binary.LittleEndian, rt.Type) } // MaxBlockResponse is max block response type MaxBlockResponse struct { BlockID int64 } func (resp *MaxBlockResponse) Read(r io.Reader) error { return binary.Read(r, binary.LittleEndian, &resp.BlockID) } func (resp *MaxBlockResponse) Write(w io.Writer) error { return binary.Write(w, binary.LittleEndian, resp.BlockID) } // GetBodiesRequest contains BlockID type GetBodiesRequest struct { BlockID uint32 ReverseOrder bool } func (req *GetBodiesRequest) Read(r io.Reader) error { if err := binary.Read(r, binary.LittleEndian, &req.BlockID); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading getBodiesRequest blockID") return err } order, err := readBool(r) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading GetBodiesRequest reverse order") } req.ReverseOrder = order return nil } func (req *GetBodiesRequest) Write(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, req.BlockID); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on sending GetBodiesRequest blockID") return err } if err := writeBool(w, req.ReverseOrder); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on sending GetBodiesRequest reverse order") return err } return nil } // GetBodyResponse is Data []bytes type GetBodyResponse struct { Data []byte } func (resp *GetBodyResponse) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { log.WithError(err).Error("on reading GetBodyResponse") return err } resp.Data = slice return nil } func (resp *GetBodyResponse) Write(w io.Writer) error { return writeSlice(w, resp.Data) } // ConfirmRequest contains request data type ConfirmRequest struct { BlockID uint32 } func (req *ConfirmRequest) Read(r io.Reader) error { return binary.Read(r, binary.LittleEndian, &req.BlockID) } func (req *ConfirmRequest) Write(w io.Writer) error { return binary.Write(w, binary.LittleEndian, req.BlockID) } // ConfirmResponse contains response data type ConfirmResponse struct { // ConfType uint8 Hash []byte `size:"32"` } func (resp *ConfirmResponse) Read(r io.Reader) error { h, err := readSliceWithSize(r, consts.HashSize) if err == io.EOF { } else if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading ConfirmResponse reverse order") return err } resp.Hash = h return nil } func (resp *ConfirmResponse) Write(w io.Writer) error { if err := writeSliceWithSize(w, resp.Hash, consts.HashSize); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on sending ConfiremResponse hash") return err } return nil } // DisRequest contains request data type DisRequest struct { Data []byte } func (req *DisRequest) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { log.WithError(err).Error("on reading disseminator request") return err } req.Data = slice return nil } func (req *DisRequest) Write(w io.Writer) error { err := writeSlice(w, req.Data) if err != nil { log.WithError(err).Error("on sending disseminator request") } return err } // DisHashResponse contains response data type DisHashResponse struct { Data []byte } func (resp *DisHashResponse) Read(r io.Reader) error { slice, err := ReadSliceWithMaxSize(r, uint64(syspar.GetMaxTxSize())) if err != nil { return err } resp.Data = slice return nil } func (resp *DisHashResponse) Write(w io.Writer) error { return writeSlice(w, resp.Data) } type StopNetworkRequest struct { Data []byte } func (req *StopNetworkRequest) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { return err } req.Data = slice return nil } func (req *StopNetworkRequest) Write(w io.Writer) error { return writeSlice(w, req.Data) } type StopNetworkResponse struct { Hash []byte } func (resp *StopNetworkResponse) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { return err } resp.Hash = slice return nil } func (resp *StopNetworkResponse) Write(w io.Writer) error { return writeSlice(w, resp.Hash) } func readBool(r io.Reader) (bool, error) { var val uint8 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return false, err } return val > 0, nil } func writeBool(w io.Writer, val bool) error { var intVal int8 if val { intVal = 1 } return binary.Write(w, binary.LittleEndian, intVal) } func ReadSlice(r io.Reader) ([]byte, error) { sizeBuf := make([]byte, 4) if _, err := io.ReadFull(r, sizeBuf); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading bytes slice size") return nil, err } size, errInt := binary.Uvarint(sizeBuf) if errInt <= 0 { log.WithFields(log.Fields{"type": consts.ConversionError, "errInt": errInt}).Error("on convert sizeBuf to value") return nil, fmt.Errorf("wrong sizebuf") } data := make([]byte, size) if _, err := io.ReadFull(r, data); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading block body") return nil, err } return data, nil } func ReadSliceWithMaxSize(r io.Reader, maxSize uint64) ([]byte, error) { sizeBuf := make([]byte, 4) if _, err := io.ReadFull(r, sizeBuf); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading bytes slice size") return nil, err } size, errInt := binary.Uvarint(sizeBuf) if errInt <= 0 { log.WithFields(log.Fields{"type": consts.ConversionError, "errInt": errInt}).Error("on convert sizeBuf to value") return nil, fmt.Errorf("wrong sizebuf") } if size > maxSize { return nil, ErrMaxSize } data := make([]byte, size) if _, err := io.ReadFull(r, data); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading block body") return nil, err } return data, nil } func readSliceToBuf(r io.Reader, buf []byte) ([]byte, error) { sizeBuf := make([]byte, 4) if _, err := io.ReadFull(r, sizeBuf); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading bytes slice size") return nil, err } size, errInt := binary.Uvarint(sizeBuf) if errInt <= 0 { log.WithFields(log.Fields{"type": consts.ConversionError, "errInt": errInt}).Error("on convirt sizeBuf to value") return nil, fmt.Errorf("wrong sizebuf") } if cap(buf) < int(size) { buf = make([]byte, size) } _, err := io.ReadFull(r, buf[:size]) return buf, err } func writeSlice(w io.Writer, slice []byte) error { byteSize := make([]byte, 4) binary.PutUvarint(byteSize, uint64(len(slice))) w.Write(byteSize) _, err := w.Write(slice) return err } // if bytesLen < 0 then slice length reads before reading slice body func readSliceWithSize(r io.Reader, size int) ([]byte, error) { var value int32 slice := make([]byte, size) if err := binary.Read(r, binary.LittleEndian, &value); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading integer from network") return slice, err } _, err := io.ReadFull(r, slice) return slice, err } func writeSliceWithSize(w io.Writer, value []byte, size int32) error { if err := binary.Write(w, binary.LittleEndian, size); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on writing size") return err } _, err := w.Write(value) return err } func SendRequestType(reqType int64, w io.Writer) error { _, err := w.Write(converter.DecToBin(reqType, 2)) return err } func ReadInt(r io.Reader) (int64, error) { var value int64 err := binary.Read(r, binary.LittleEndian, &value) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading integer from network") return 0, err } return value, nil } func WriteInt(value int64, w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, value); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on sending integer to network") return err } return nil } type CandidateNodeVotingRequest struct { Data []byte } func (req *CandidateNodeVotingRequest) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { log.WithError(err).Error("on reading disseminator request") return err } req.Data = slice return nil } func (req *CandidateNodeVotingRequest) Write(w io.Writer) error { err := writeSlice(w, req.Data) if err != nil { log.WithError(err).Error("on sending disseminator request") } return err } type CandidateNodeVotingResponse struct { Data []byte } func (resp *CandidateNodeVotingResponse) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { log.WithError(err).Error("on reading CandidateNodeVotingResponse") return err } resp.Data = slice return nil } func (resp *CandidateNodeVotingResponse) Write(w io.Writer) error { return writeSlice(w, resp.Data) } type BroadcastNodeConnInfoRequest struct { Data []byte } func (req *BroadcastNodeConnInfoRequest) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { log.WithError(err).Error("on reading disseminator request") return err } req.Data = slice return nil } func (req *BroadcastNodeConnInfoRequest) Write(w io.Writer) error { err := writeSlice(w, req.Data) if err != nil { log.WithError(err).Error("on sending disseminator request") } return err } type BroadcastNodeConnInfoResponse struct { Data []byte } func (resp *BroadcastNodeConnInfoResponse) Read(r io.Reader) error { slice, err := ReadSlice(r) if err != nil { log.WithError(err).Error("on reading CandidateNodeVotingResponse") return err } resp.Data = slice return nil } func (resp *BroadcastNodeConnInfoResponse) Write(w io.Writer) error { return writeSlice(w, resp.Data) } ================================================ FILE: packages/network/protocol_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package network import ( "bytes" "encoding/binary" "fmt" "strings" "testing" "github.com/stretchr/testify/require" ) func TestEmptyGetBodyResponse(t *testing.T) { buf := []byte{} w := bytes.NewBuffer(buf) empty := &GetBodyResponse{} require.NoError(t, empty.Write(w)) r := bytes.NewReader(w.Bytes()) emptyRes := &GetBodyResponse{} require.NoError(t, emptyRes.Read(r)) } func TestWriteReadInts(t *testing.T) { buf := []byte{} b := bytes.NewBuffer(buf) st := uint16(2) require.NoError(t, binary.Write(b, binary.LittleEndian, st)) var val uint16 err := binary.Read(b, binary.LittleEndian, &val) require.NoError(t, err) require.Equal(t, val, st) fmt.Println(val) } func TestRequestType(t *testing.T) { rt := RequestType{1} buf := []byte{} b := bytes.NewBuffer(buf) result := RequestType{} require.NoError(t, rt.Write(b)) require.NoError(t, result.Read(b)) require.Equal(t, rt, result) fmt.Println(rt, result) } func TestGetBodyResponse(t *testing.T) { rt := GetBodyResponse{Data: make([]byte, 4, 4)} buf := []byte{} b := bytes.NewBuffer(buf) result := GetBodyResponse{} require.NoError(t, rt.Write(b)) require.NoError(t, result.Read(b)) require.Equal(t, rt, result) fmt.Println(rt, result) } func TestBodyResponse(t *testing.T) { rt := GetBodyResponse{Data: []byte(strings.Repeat("A", 32))} buf := []byte{} b := bytes.NewBuffer(buf) result := &GetBodyResponse{} require.NoError(t, rt.Write(b)) require.NoError(t, result.Read(b)) require.Equal(t, rt.Data, result.Data) fmt.Println(rt, result) } ================================================ FILE: packages/network/tcpclient/blocks_collection.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "context" "encoding/binary" "errors" "fmt" "io" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" log "github.com/sirupsen/logrus" ) var ErrorEmptyBlockBody = errors.New("block is empty") var ErrorWrongSizeBytes = errors.New("wrong size bytes") const hasVal = "has value" const hasntVal = "has not value" const sizeBytesLength = 4 // GetBlocksBodies send GetBodiesRequest returns channel of binary blocks data func GetBlocksBodies(ctx context.Context, host string, blockID int64, reverseOrder bool) (<-chan []byte, error) { conn, err := newConnection(host) if err != nil { return nil, err } // send the type of data rt := &network.RequestType{Type: network.RequestTypeBlockCollection} if err = rt.Write(conn); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("writing data type block body to connection") return nil, err } req := &network.GetBodiesRequest{ BlockID: uint32(blockID), ReverseOrder: reverseOrder, } if err = req.Write(conn); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on sending blocks bodies request") return nil, err } blocksCount, err := network.ReadInt(conn) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("on getting blocks count") return nil, err } if blocksCount == 0 { return nil, fmt.Errorf("host: %s does'nt contains blocks", host) } blocksChan, errChan := GetBlockBodiesChan(ctx, conn, blocksCount) go func() { for err := range errChan { if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("on reading block bodies") } } }() return blocksChan, nil } func GetBlockBodiesChan(ctx context.Context, src io.ReadCloser, blocksCount int64) (<-chan []byte, <-chan error) { rawBlocksCh := make(chan []byte, blocksCount) errChan := make(chan error, 1) sizeBuf := make([]byte, sizeBytesLength) var bodyBuf []byte afterBodyProcessed := func(done <-chan struct{}) { <-done BytesPool.Put(bodyBuf) } go func() { defer func() { close(rawBlocksCh) close(errChan) src.Close() go afterBodyProcessed(ctx.Done()) }() dataSize, err := network.ReadInt(src) if err != nil { errChan <- err return } bodyBuf = BytesPool.Get(dataSize) var bodyStartIndx int64 for i := 0; i < int(blocksCount); i++ { _, err := io.ReadFull(src, sizeBuf) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on reading size of block data") errChan <- err return } size, intErr := binary.Uvarint(sizeBuf) if intErr < 0 { log.WithFields(log.Fields{"type": consts.ConversionError, "error": ErrorWrongSizeBytes}).Error("on convert size body") errChan <- ErrorWrongSizeBytes return } bodyEndIndx := bodyStartIndx + int64(size) body := bodyBuf[bodyStartIndx:bodyEndIndx] if readed, err := io.ReadFull(src, body); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "size": size, "readed": readed, "error": err}).Error("on reading block body") errChan <- err return } bodyStartIndx = bodyEndIndx rawBlocksCh <- body errChan <- nil } }() return rawBlocksCh, errChan } ================================================ FILE: packages/network/tcpclient/candidate_node_voting.go ================================================ package tcpclient import ( "encoding/json" "errors" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" "time" ) func UpdateMachineStatus(localAddress, tcpAddress string, logger *log.Entry) ([]byte, error) { conn, err := newConnection(tcpAddress) if err != nil { logger.WithFields(log.Fields{"type": consts.ConnectionError, "error": err, "tcpAddress": tcpAddress}).Error("dialing to host") return nil, err } defer conn.Close() rt := &network.RequestType{Type: network.RequestTypeVoting} if err = rt.Write(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "tcpAddress": tcpAddress}).Error("sending request type") return nil, err } prevBlock := &sqldb.InfoBlock{} _, err = prevBlock.Get() NodePrivateKey, NodePublicKey := utils.GetNodeKeys() if len(NodePrivateKey) < 1 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node private key is empty") return nil, errors.New(`node private key is empty`) } if len(NodePublicKey) < 1 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node public key is empty") return nil, errors.New(`node public key is empty`) } voteMsg := &network.VoteMsg{ CurrentBlockHeight: prevBlock.BlockID, LocalAddress: localAddress, TcpAddress: tcpAddress, EcosystemID: 0, Hash: prevBlock.Hash, Time: time.Now().UnixMilli(), } signStr := voteMsg.VoteForSign() signed, err := crypto.SignString(NodePrivateKey, signStr) if err != nil { logger.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("signing voteMsg") return nil, err } voteMsg.Sign = signed data, err := json.Marshal(voteMsg) if err != nil { log.Fatalf("VoteMsg JSON marshaling failed: %s", err) } req := &network.CandidateNodeVotingRequest{ Data: data, } if err = req.Write(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "tcpAddress": tcpAddress}).Error("sending voting request") return nil, err } resp := &network.CandidateNodeVotingResponse{} if err := resp.Read(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "tcpAddress": tcpAddress}).Error("receiving voting response") return nil, err } return resp.Data, nil } func BroadcastNodeConnInfo(tcpAddress string, data []byte, logger *log.Entry) error { conn, err := newConnection(tcpAddress) if err != nil { logger.WithFields(log.Fields{"type": consts.ConnectionError, "error": err, "tcpAddress": tcpAddress}).Error("dialing to host") return err } defer conn.Close() rt := &network.RequestType{Type: network.RequestSyncMatchineState} if err = rt.Write(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "tcpAddress": tcpAddress}).Error("sending request type") return err } req := &network.BroadcastNodeConnInfoRequest{ Data: data, } if err = req.Write(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "tcpAddress": tcpAddress}).Error("sending voting request") return err } return nil } ================================================ FILE: packages/network/tcpclient/client.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "errors" "fmt" "net" "strings" "time" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) var wrongAddressError = errors.New("Wrong address") // NormalizeHostAddress get address. if port not defined returns combined string with ip and defaultPort func NormalizeHostAddress(address string, defaultPort int64) (string, error) { _, _, err := net.SplitHostPort(address) if err != nil { if strings.HasSuffix(err.Error(), "missing port in address") { return fmt.Sprintf("%s:%d", address, defaultPort), nil } return "", err } return address, nil } func newConnection(addr string) (net.Conn, error) { if len(addr) == 0 { return nil, wrongAddressError } host, err := NormalizeHostAddress(addr, consts.DefaultTcpPort) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "host": addr, "error": err}).Error("on normalize host address") return nil, err } conn, err := net.DialTimeout("tcp", host, consts.TCPConnTimeout) if err != nil { log.WithFields(log.Fields{"type": consts.ConnectionError, "error": err, "address": host}).Debug("dialing tcp") return nil, err } conn.SetReadDeadline(time.Now().Add(consts.ReadTimeout * time.Second)) conn.SetWriteDeadline(time.Now().Add(consts.WriteTimeout * time.Second)) return conn, nil } ================================================ FILE: packages/network/tcpclient/client_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "bytes" "context" "fmt" "math/rand" _ "net/http/pprof" "strings" "testing" "github.com/IBAX-io/go-ibax/packages/network" ) var inputs = make([][]byte, 0, 100) func init() { for i := 0; i < 100; i++ { inputs = append(inputs, []byte(strings.Repeat("B", rand.Intn(194334)))) } } type BufCloser struct { *bytes.Buffer } func (bc BufCloser) Close() error { bc.Reset() return nil } func BenchmarkGetBlockBodiesWithChanReadAll(t *testing.B) { var bts []byte r := BufCloser{bytes.NewBuffer(bts)} // dataLen := 4 t.ResetTimer() for j := 0; j < t.N; j++ { t.StopTimer() for i := 0; i < 100; i++ { resp := network.GetBodyResponse{ Data: inputs[i], } resp.Write(r) } //ctxDone, cancel := context.WithCancel(context.Background()) t.StartTimer() //blocksC, errC := GetBlockBodiesChanReadAll(ctxDone, r, 100) // blocksC, errC := GetBlockBodiesChan(ctxDone, r, 100) //go func() { // err := <-errC // if err != nil { // fmt.Println(err) // } //}() // //for item := range blocksC { // item = item[:0] //} //cancel() } } //===================================================GetBlockBodiesChanByBlock // 500 2264475 ns/op 109001 B/op 108 allocs/op with pool size 12832256 func BenchmarkGetBlockBodiesChanByBlockWithSyncPool(t *testing.B) { var bts []byte r := BufCloser{bytes.NewBuffer(bts)} t.ResetTimer() for j := 0; j < t.N; j++ { t.StopTimer() for i := 0; i < 100; i++ { resp := network.GetBodyResponse{ Data: inputs[i], } // fmt.Println("lenData", len(inputs[i])) resp.Write(r) } //ctxDone, cancel := context.WithCancel(context.Background()) // //t.StartTimer() //blocksC, errC := GetBlockBodiesChanByBlock(ctxDone, r, 100) // //go func() { // err := <-errC // if err != nil { // fmt.Println(err) // } //}() // //for item := range blocksC { // item = item[:0] //} //cancel() } } func BenchmarkGetBlockBodiesChanByBlockWithBytePool(t *testing.B) { var bts []byte r := BufCloser{bytes.NewBuffer(bts)} t.ResetTimer() for j := 0; j < t.N; j++ { t.StopTimer() var dataSize int64 for i := 0; i < 100; i++ { dataSize += int64(len(inputs[i])) } network.WriteInt(dataSize, r) // fmt.Println("sending data size", dataSize) for i := 0; i < 100; i++ { resp := network.GetBodyResponse{ Data: inputs[i], } // fmt.Println("lenData", len(inputs[i])) resp.Write(r) } //ctxDone, cancel := context.WithCancel(context.Background()) // //t.StartTimer() //blocksC, errC := GetBlockBodiesChanByBlockWithBytePool(ctxDone, r, 100) // //go func() { // err := <-errC // if err != nil { // fmt.Println(err) // } //}() // //for item := range blocksC { // // fmt.Println(len(item)) // item = item[:0] //} //cancel() } } func BenchmarkGetBlockBodiesWithChanReadToStruct(t *testing.B) { var bts []byte r := BufCloser{bytes.NewBuffer(bts)} t.ResetTimer() for j := 0; j < t.N; j++ { t.StopTimer() for i := 0; i < 100; i++ { resp := network.GetBodyResponse{ Data: inputs[i], } // fmt.Println("lenData", len(inputs[i])) resp.Write(r) } ctx := context.Background() t.StartTimer() blocksC, errC := GetBlockBodiesChan(ctx, r, 100) go func() { err := <-errC if err != nil { fmt.Println(err) } }() for item := range blocksC { item = item[:0] } } } func BenchmarkGetBlockBodiesAsSlice(t *testing.B) { var bts []byte r := BufCloser{bytes.NewBuffer(bts)} // dataLen := 4 t.ResetTimer() for j := 0; j < t.N; j++ { t.StopTimer() for i := 0; i < 100; i++ { resp := network.GetBodyResponse{ Data: inputs[i], } resp.Write(r) } //ctxDone, cancel := context.WithCancel(context.Background()) // //t.StartTimer() //blocks, err := GetBlockBodiesReadAll(ctxDone, r, 100) //if err != nil { // fmt.Println(err) //} // //for i := 0; i < len(blocks); i++ { // blocks[i] = blocks[i][:0] //} //cancel() } } //============================================== // func TestReadSize(t *testing.T) { // bts := []byte{} // buf := bytes.NewBuffer(bts) // resp := network.GetBodyResponse{ // Data: []byte(strings.Repeat("B", 152627)), // } // resp.Write(buf) // val, err := binary.ReadUvarint(buf) // require.NoError(t, err) // fmt.Println(val) // // data := buf.Bytes() // // size, intErr := binary.Uvarint(data[:4]) // // fmt.Println(size, intErr) // } // func TestBinary(t *testing.T) { // buf := []byte{} // bb := bytes.NewBuffer(buf) // for _, x := range []uint64{1, 2, 127, 128, 255, 152627} { // mb := make([]byte, 4) // _ = binary.PutUvarint(mb, x) // bb.Write(mb) // // fmt.Printf("%x\n", buf[:n]) // } // resBts := bb.Bytes() // fmt.Println(resBts) // var pos int // for i := 0; i < 6; i++ { // valBts := resBts[pos : pos+4] // pos += 4 // fmt.Println("valBts", valBts) // value, re := binary.Uvarint(valBts) // fmt.Println(value, "readed", re) // } // } // //100000 17333 ns/op 576 B/op 19 allocs/op // func BenchmarkGetBlockBodiesWithBuffer(t *testing.B) { // ctx := context.Background() // var bts []byte // r := BufCloser{ // Buffer: bytes.NewBuffer(bts), // } // byteString := []byte(strings.Repeat("A", 32)) // t.ResetTimer() // for j := 0; j < t.N; j++ { // t.StopTimer() // for i := 0; i < 5; i++ { // resp := network.GetBodyResponse{ // Data: byteString, // } // resp.Write(r) // } // fmt.Println("[", j, "]") // t.StartTimer() // blocksC, errC := GetBlockBodiesChanWithPool(ctx, r, 5) // go func() { // err := <-errC // if err != nil { // fmt.Println(err) // } // }() // for item := range blocksC { // fmt.Println(string(item)) // BlockBodyPool.putBytes(item) // } // } // } ================================================ FILE: packages/network/tcpclient/confirmation.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/network" log "github.com/sirupsen/logrus" ) func CheckConfirmation(host string, blockID int64, logger *log.Entry) (hash string) { conn, err := newConnection(host) if err != nil { logger.WithFields(log.Fields{"type": consts.ConnectionError, "error": err, "host": host, "block_id": blockID}).Debug("dialing to host") return "0" } defer conn.Close() rt := &network.RequestType{Type: network.RequestTypeConfirmation} if err = rt.Write(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "host": host, "block_id": blockID}).Error("sending request type") return "0" } req := &network.ConfirmRequest{ BlockID: uint32(blockID), } if err = req.Write(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "host": host, "block_id": blockID}).Error("sending confirmation request") return "0" } resp := &network.ConfirmResponse{} if err := resp.Read(conn); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err, "host": host, "block_id": blockID}).Error("receiving confirmation response") return "0" } return string(converter.BinToHex(resp.Hash)) } ================================================ FILE: packages/network/tcpclient/disseminator.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "bytes" "context" "encoding/json" "errors" "io" "net" "sync" "sync/atomic" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" log "github.com/sirupsen/logrus" ) var ( ErrNodesUnavailable = errors.New("All nodes unvailabale") ) func sendRawTransacitionsToHost(host string, packet []byte) error { con, err := newConnection(host) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "host": host}).Error("on creating tcp connection") return err } defer con.Close() if err := sendDisseminatorRequest(con, network.RequestTypeNotHonorNode, packet); err != nil { log.WithFields(log.Fields{"type": consts.TCPClientError, "error": err, "host": host}).Error("on sending disseminator request") return err } return nil } func SendTransacitionsToAll(ctx context.Context, hosts []string, txes []sqldb.Transaction) error { if len(hosts) == 0 || len(txes) == 0 { return nil } packet, err := MarshalTxPacket(txes) if err != nil { return err } var wg sync.WaitGroup var errCount int32 for _, h := range hosts { if err := ctx.Err(); err != nil { log.Debug("exit by context error") return err } wg.Add(1) go func(host string, pak []byte) { defer wg.Done() if err := sendRawTransacitionsToHost(host, pak); err != nil { atomic.AddInt32(&errCount, 1) } }(h, packet) } wg.Wait() if int(errCount) == len(hosts) { return ErrNodesUnavailable } return nil } func SendFullBlockToAll(ctx context.Context, hosts []string, block *sqldb.InfoBlock, txes []sqldb.Transaction, nodeID int64) error { if len(hosts) == 0 { return nil } req := prepareFullBlockRequest(block, txes, nodeID) txDataMap := make(map[string][]byte, len(txes)) for _, tx := range txes { txDataMap[string(tx.Hash)] = tx.Data } var errCount int32 increaseErrCount := func() { atomic.AddInt32(&errCount, 1) } var wg sync.WaitGroup for _, host := range hosts { wg.Add(1) go func(h string) { defer wg.Done() con, err := newConnection(h) if err != nil { increaseErrCount() log.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "host": h}).Error("on creating tcp connection") return } defer con.Close() response, err := sendFullBlockRequest(con, req) if err != nil { increaseErrCount() log.WithFields(log.Fields{"type": consts.NetworkError, "error": err, "host": h}).Error("on sending full block request") return } if len(response) == 0 || len(response) < consts.HashSize { return } var buf bytes.Buffer requestedHashes := parseTxHashesFromResponse(response) for _, txhash := range requestedHashes { if data, ok := txDataMap[string(txhash)]; ok && len(data) > 0 { log.WithFields(log.Fields{"len_of_tx": len(data)}).Debug("on prepare full tx package") if _, err := buf.Write(converter.EncodeLengthPlusData(data)); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Warn("on write tx hash to response buffer") } } } if _, err = con.Write(converter.DecToBin(buf.Len(), 4)); err != nil { increaseErrCount() log.WithFields(log.Fields{"type": consts.IOError, "error": err, "host": h}).Error("on writing requested transactions buf length") return } if _, err = con.Write(buf.Bytes()); err != nil { increaseErrCount() log.WithFields(log.Fields{"type": consts.IOError, "error": err, "host": h}).Error("on writing requested transactions") return } }(host) } wg.Wait() if int(errCount) == len(hosts) { log.WithFields(log.Fields{"type": consts.NetworkError, "err_count": errCount}).Error(ErrNodesUnavailable) return ErrNodesUnavailable } return nil } func sendFullBlockRequest(con net.Conn, data []byte) (response []byte, err error) { if err := sendDisseminatorRequest(con, network.RequestTypeHonorNode, data); err != nil { log.WithFields(log.Fields{"type": consts.TCPClientError, "error": err}).Error("on sending disseminator request") return nil, err } //response return resieveRequiredTransactions(con) } func MarshalTxPacket(txes []sqldb.Transaction) ([]byte, error) { dat := make([][]byte, 0) for _, tr := range txes { dat = append(dat, tr.Data) } return json.Marshal(dat) } //func prepareTxPacket(txes []sqldb.Transaction) []byte { // // form packet to send // var buf bytes.Buffer // for _, tr := range txes { // buf.Write(tr.Data) // } // // return buf.Bytes() //} func prepareFullBlockRequest(block *sqldb.InfoBlock, trs []sqldb.Transaction, nodeID int64) []byte { var noBlockFlag byte if block == nil { noBlockFlag = 1 } var buf bytes.Buffer buf.Write(converter.DecToBin(nodeID, 8)) buf.WriteByte(noBlockFlag) if noBlockFlag == 0 { buf.Write(block.Marshall()) } if trs != nil { for _, tr := range trs { buf.Write(tr.Hash) } } return buf.Bytes() } func resieveRequiredTransactions(con net.Conn) (response []byte, err error) { needTxResp := network.DisHashResponse{} if err := needTxResp.Read(con); err != nil { if err == io.EOF { return nil, nil } if err == network.ErrMaxSize { log.WithFields(log.Fields{"max_size": syspar.GetMaxTxSize(), "type": consts.ParameterExceeded}).Warning("response size is larger than max tx size") return nil, nil } log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading data") return nil, err } return needTxResp.Data, err } func parseTxHashesFromResponse(resp []byte) (hashes [][]byte) { hashes = make([][]byte, 0, len(resp)/consts.HashSize) for len(resp) >= consts.HashSize { hashes = append(hashes, converter.BytesShift(&resp, consts.HashSize)) } return } func sendDisseminatorRequest(con net.Conn, requestType network.ReqTypesFlag, packet []byte) (err error) { /* Packet format: type 2 bytes len 4 bytes data len bytes */ // type rt := network.RequestType{ Type: requestType, } err = rt.Write(con) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("writing request type to host") return err } // data size // size := converter.DecToBin(len(packet), 4) // _, err = con.Write(size) // if err != nil { // log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("writing data size to host") // return err // } // // data // _, err = con.Write(packet) // if err != nil { // log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("writing data to host") // return err // } req := network.DisRequest{ Data: packet, } return req.Write(con) } ================================================ FILE: packages/network/tcpclient/max_block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "context" "io" "sync" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) func HostWithMaxBlock(ctx context.Context, hosts []string) (bestHost string, maxBlockID int64, err error) { if len(hosts) == 0 { return "", -1, nil } return hostWithMaxBlock(ctx, hosts) } func GetMaxBlockID(host string) (blockID int64, err error) { return getMaxBlock(host) } func getMaxBlock(host string) (blockID int64, err error) { con, err := newConnection(host) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.ConnectionError, "host": host}).Debug("error connecting to host") return -1, err } defer con.Close() // send max block request rt := &network.RequestType{ Type: network.RequestTypeMaxBlock, } if err := rt.Write(con); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.ConnectionError, "host": host}).Error("on sending Max block request type") return -1, err } // response resp := network.MaxBlockResponse{} err = resp.Read(con) if err == io.EOF { } else if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.ConnectionError, "host": host}).Error("reading max block id from host") return -1, err } return resp.BlockID, nil } func hostWithMaxBlock(ctx context.Context, hosts []string) (bestHost string, maxBlockID int64, err error) { maxBlockID = -1 type blockAndHost struct { host string blockID int64 err error } resultChan := make(chan blockAndHost, len(hosts)) /* rand.Shuffle(len(hosts), func(i, j int) { hosts[i], hosts[j] = hosts[j], hosts[i] }) this implementation available only in Golang 1.10 */ utils.ShuffleSlice(hosts) var wg sync.WaitGroup for _, h := range hosts { if ctx.Err() != nil { log.WithFields(log.Fields{"error": ctx.Err(), "type": consts.ContextError}).Error("context error") return "", maxBlockID, ctx.Err() } wg.Add(1) go func(host string) { blockID, err := getMaxBlock(host) defer wg.Done() resultChan <- blockAndHost{ host: host, blockID: blockID, err: err, } }(h) } wg.Wait() var errCount int for i := 0; i < len(hosts); i++ { bl := <-resultChan if bl.err != nil { errCount++ continue } // If blockID is maximal then the current host is the best if bl.blockID > maxBlockID { maxBlockID = bl.blockID bestHost = bl.host } } if errCount == len(hosts) { return "", 0, ErrNodesUnavailable } return bestHost, maxBlockID, nil } ================================================ FILE: packages/network/tcpclient/pools.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "sync" ) // return nearest power of 2 that bigest than v func powerOfTwo(v int) int64 { v-- v |= v >> 1 v |= v >> 2 v |= v >> 4 v |= v >> 8 v |= v >> 16 v++ return int64(v) } var BytesPool *bytePool func init() { BytesPool = &bytePool{ pools: make(map[int64]*sync.Pool), } } type bytePool struct { pools map[int64]*sync.Pool } func (p *bytePool) Get(size int64) []byte { power := powerOfTwo(int(size)) if pool, ok := p.pools[power]; ok { return pool.Get().([]byte) } pool := &sync.Pool{ New: func() any { return make([]byte, power) }, } p.pools[power] = pool return pool.Get().([]byte) } func (p *bytePool) Put(buf []byte) { if len(buf) == 0 { return } if pool, ok := p.pools[int64(len(buf))]; ok { pool.Put(buf) } } ================================================ FILE: packages/network/tcpclient/pools_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "fmt" "strings" "testing" "github.com/stretchr/testify/require" ) func TestBytesPoolGet(t *testing.T) { buf := BytesPool.Get(12832256) require.Equal(t, 16777216, len(buf)) } func TestBytesPoolPut(t *testing.T) { short := []byte(strings.Repeat("A", 5)) buf := BytesPool.Get(12832256) copy(buf[:5], short) BytesPool.Put(buf) newBuf := BytesPool.Get(12832256) require.Equal(t, 16777216, len(newBuf)) require.Equal(t, newBuf[:5], short) fmt.Println(newBuf[:6]) } func TestBytesPoolCicle(t *testing.T) { short := []byte(strings.Repeat("A", 5)) buf := BytesPool.Get(int64(len(short))) copy(buf[:5], short) BytesPool.Put(buf) power := powerOfTwo(5) fmt.Println("power", power) newBuf := BytesPool.Get(5) require.Equal(t, power, int64(len(newBuf))) require.Equal(t, newBuf[:5], short) fmt.Println(newBuf) } ================================================ FILE: packages/network/tcpclient/stop_network.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpclient import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" ) func SendStopNetwork(addr string, req *network.StopNetworkRequest) error { conn, err := newConnection(addr) if err != nil { return err } defer conn.Close() rt := &network.RequestType{ Type: network.RequestTypeStopNetwork, } if err = rt.Write(conn); err != nil { return err } if err = req.Write(conn); err != nil { return err } res := &network.StopNetworkResponse{} if err = res.Read(conn); err != nil { return err } if len(res.Hash) != consts.HashSize { return network.ErrNotAccepted } return nil } ================================================ FILE: packages/network/tcpserver/blocks_collection.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "net" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // BlockCollection writes the body of the specified block // blocksCollection and queue_parser_blocks daemons send the request through p.GetBlocks() func BlockCollection(request *network.GetBodiesRequest, w net.Conn) error { block := &sqldb.BlockChain{} var blocks []sqldb.BlockChain var err error if request.ReverseOrder { blocks, err = block.GetReverseBlockchain(int64(request.BlockID), network.BlocksPerRequest) } else { blocks, err = block.GetBlocksFrom(int64(request.BlockID-1), "ASC", network.BlocksPerRequest) } if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "block_id": request.BlockID}).Error("Error getting 1000 blocks from block_id") if err := network.WriteInt(0, w); err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("on sending 0 requested blocks") } return err } if err := network.WriteInt(int64(len(blocks)), w); err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("on sending requested blocks count") return err } if err := network.WriteInt(lenOfBlockData(blocks), w); err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("on sending requested blocks data length") return err } for _, b := range blocks { br := &network.GetBodyResponse{Data: b.Data} if err := br.Write(w); err != nil { return err } } return nil } func lenOfBlockData(blocks []sqldb.BlockChain) int64 { var length int64 for i := 0; i < len(blocks); i++ { length += int64(len(blocks[i].Data)) } return length } ================================================ FILE: packages/network/tcpserver/candidate_node_voting.go ================================================ package tcpserver import ( "encoding/hex" "encoding/json" "errors" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" "time" ) func CandidateNodeVoting(r *network.CandidateNodeVotingRequest) (*network.CandidateNodeVotingResponse, error) { resp := &network.CandidateNodeVotingResponse{} voteMsg := &network.VoteMsg{} err := json.Unmarshal(r.Data, voteMsg) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("JSONUnmarshallError") return nil, err } voteMsg, err = checkClientVote(r, voteMsg) if err != nil { log.WithFields(log.Fields{"type": "CheckVote", "error": err}).Error("check vote error") return nil, err } data, err := json.Marshal(voteMsg) if err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("JSONMarshallError") return nil, err } resp.Data = data return resp, nil } type VotingRes struct { VoteMsgInfo network.VoteMsg `json:"voteMsgInfo"` Err string `json:"err"` } type VotingTotal struct { Data map[string]VotingRes `json:"data"` AgreeQuantity int64 `json:"agreeQuantity"` LocalAddress string `json:"localAddress"` St int64 `json:"st"` } func SyncMatchineStateRes(request *network.BroadcastNodeConnInfoRequest) (*network.BroadcastNodeConnInfoResponse, error) { resp := &network.BroadcastNodeConnInfoResponse{} var votingTotal VotingTotal err := json.Unmarshal(request.Data, &votingTotal) if err != nil { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err}).Error("unmarshal voting total") return nil, err } if votingTotal.AgreeQuantity > 0 { candidateNode := &sqldb.CandidateNode{ TcpAddress: votingTotal.LocalAddress, ReplyCount: votingTotal.AgreeQuantity, DateReply: votingTotal.St, CandidateNodes: request.Data, } err = candidateNode.UpdateCandidateNodeInfo() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("update candidate honor voting") return nil, err } } return resp, nil } func checkClientVote(r *network.CandidateNodeVotingRequest, voteMsgParam *network.VoteMsg) (*network.VoteMsg, error) { var ( prevBlock = &sqldb.InfoBlock{} st = time.Now() candidateNodeSql = &sqldb.CandidateNode{} ) _, err := prevBlock.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting previous block") return nil, err } voteMsg := &network.VoteMsg{ CurrentBlockHeight: prevBlock.BlockID, LocalAddress: voteMsgParam.LocalAddress, TcpAddress: voteMsgParam.TcpAddress, EcosystemID: 0, Hash: prevBlock.Hash, Time: time.Now().UnixMilli(), } if voteMsgParam.CurrentBlockHeight < prevBlock.BlockID { voteMsg.Msg = "Not synced to latest block" voteMsg.Agree = false signed, err := sign(voteMsg) if err != nil { return nil, err } voteMsg.Sign = signed return voteMsg, nil } timeVerification := st.After(time.Unix(voteMsgParam.Time, 0)) if timeVerification { voteMsg.Msg = "Time verification failed" voteMsg.Agree = false signed, err := sign(voteMsg) if err != nil { return nil, err } voteMsg.Sign = signed return voteMsg, nil } err = candidateNodeSql.GetCandidateNodeByAddress(voteMsgParam.LocalAddress) if err != nil { return nil, err } pk, err := hex.DecodeString(candidateNodeSql.NodePubKey) pk = crypto.CutPub(pk) _, err = crypto.Verify(pk, []byte(voteMsgParam.VoteForSign()), voteMsgParam.Sign) if err != nil { voteMsg.Msg = "Signature verification failed" voteMsg.Agree = false return voteMsg, nil } voteMsg.Msg = "Passed the verification" voteMsg.Agree = true signed, err := sign(voteMsg) if err != nil { return nil, err } voteMsg.Sign = signed return voteMsg, nil } func sign(voteMsg *network.VoteMsg) ([]byte, error) { NodePrivateKey, _ := utils.GetNodeKeys() if len(NodePrivateKey) < 1 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node private key is empty") return nil, errors.New(`node private key is empty`) } signStr := voteMsg.VerifyVoteForSign() signed, err := crypto.SignString(NodePrivateKey, signStr) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("verify voting signature") return nil, err } return signed, nil } ================================================ FILE: packages/network/tcpserver/confirmation.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // Confirmation writes the hash of the specified block // The request is sent by 'confirmations' daemon func Confirmation(r *network.ConfirmRequest) (*network.ConfirmResponse, error) { resp := &network.ConfirmResponse{} block := &sqldb.BlockChain{} found, err := block.Get(int64(r.BlockID)) if err != nil || !found { hash := [32]byte{} resp.Hash = hash[:] } else { resp.Hash = block.Hash // can we send binary data ? } if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "block_id": r.BlockID}).Error("Getting block") } else if len(block.Hash) == 0 { log.WithFields(log.Fields{"type": consts.DBError, "block_id": r.BlockID}).Warning("Block not found") } return resp, nil } ================================================ FILE: packages/network/tcpserver/disseminate_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "bytes" crand "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "io" "github.com/IBAX-io/go-ibax/packages/common/crypto/symalgo/aes" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) // DisseminateTxs serves requests from disseminator for tx func DisseminateTxs(rw io.ReadWriter) error { r := &network.DisRequest{} if err := r.Read(rw); err != nil { return err } txs, err := UnmarshalTxPacket(r.Data) if err != nil { return err } var rtxs []*sqldb.RawTx for _, tran := range txs { if int64(len(tran)) > syspar.GetMaxTxSize() { log.WithFields(log.Fields{"type": consts.ParameterExceeded, "max_tx_size": syspar.GetMaxTxSize(), "current_size": len(tran)}).Error("transaction size exceeds max size") return utils.ErrInfo("len(txBinData) > max_tx_size") } if tran == nil { log.WithFields(log.Fields{"type": consts.ParameterExceeded, "mx_tx_size": syspar.GetMaxTxSize(), "info": "tran nil", "current_size": len(tran)}).Error("transaction size nil") continue } rtx := &transaction.Transaction{} if err = rtx.Unmarshall(bytes.NewBuffer(tran), true); err != nil { return err } rtxs = append(rtxs, rtx.SetRawTx()) } err = sqldb.SendTxBatches(rtxs) if err != nil { return err } return nil } //// Type2 serves requests from disseminator //func Type2(rw io.ReadWriter) (*network.DisTrResponse, error) { // r := &network.DisRequest{} // if err := r.Read(rw); err != nil { // return nil, err // } // // binaryData := r.Data // // take the transactions from usual users but not nodes. // _, _, decryptedBinData, err := DecryptData(&binaryData) // if err != nil { // return nil, utils.ErrInfo(err) // } // // if int64(len(binaryData)) > syspar.GetMaxTxSize() { // log.WithFields(log.Fields{"type": consts.ParameterExceeded, "max_size": syspar.GetMaxTxSize(), "size": len(binaryData)}).Error("transaction size exceeds max size") // return nil, utils.ErrInfo("len(txBinData) > max_tx_size") // } // // if len(binaryData) < 5 { // log.WithFields(log.Fields{"type": consts.ProtocolError, "len": len(binaryData), "should_be_equal": 5}).Error("binary data slice has incorrect length") // return nil, utils.ErrInfo("len(binaryData) < 5") // } // // tx := transaction.Transaction{} // if err = tx.Unmarshall(bytes.NewBuffer(decryptedBinData)); err != nil { // log.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err}).Error("unmarshalling transaction") // return nil, err // } // // _, err = sqldb.DeleteQueueTxByHash(nil, tx.Hash()) // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err, "hash": tx.Hash()}).Error("Deleting queue_tx with hash") // return nil, utils.ErrInfo(err) // } // // queueTx := &sqldb.QueueTx{Hash: tx.Hash(), Data: decryptedBinData, FromGate: 0} // err = queueTx.Create() // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Creating queue_tx") // return nil, utils.ErrInfo(err) // } // // return &network.DisTrResponse{}, nil //} // DecryptData is decrypting data func DecryptData(binaryTx *[]byte) ([]byte, []byte, []byte, error) { if len(*binaryTx) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("binary tx is empty") return nil, nil, nil, utils.ErrInfo("len(binaryTx) == 0") } myUserID := converter.BinToDecBytesShift(&*binaryTx, 5) log.WithFields(log.Fields{"user_id": myUserID}).Debug("decrypted userID is") // remove the encrypted key, and all that stay in $binary_tx will be encrypted keys of the transactions/blocks length, err := converter.DecodeLength(&*binaryTx) if err != nil { log.WithFields(log.Fields{"type": consts.ProtocolError, "error": err}).Error("Decoding binary tx length") return nil, nil, nil, err } encryptedKey := converter.BytesShift(&*binaryTx, length) iv := converter.BytesShift(&*binaryTx, 16) log.WithFields(log.Fields{"encryptedKey": encryptedKey, "iv": iv}).Debug("binary tx encryptedKey and iv is") if len(encryptedKey) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("binary tx encrypted key is empty") return nil, nil, nil, utils.ErrInfo("len(encryptedKey) == 0") } if len(*binaryTx) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("binary tx is empty") return nil, nil, nil, utils.ErrInfo("len(*binaryTx) == 0") } nodeKeyPrivate, _ := utils.GetNodeKeys() if len(nodeKeyPrivate) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node private key is empty") return nil, nil, nil, utils.ErrInfo("len(nodePrivateKey) == 0") } block, _ := pem.Decode([]byte(nodeKeyPrivate)) if block == nil || block.Type != "RSA PRIVATE KEY" { log.WithFields(log.Fields{"type": consts.CryptoError}).Error("No valid PEM data found") return nil, nil, nil, utils.ErrInfo("No valid PEM data found") } privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("Parse PKCS1PrivateKey") return nil, nil, nil, utils.ErrInfo(err) } decKey, err := rsa.DecryptPKCS1v15(crand.Reader, privateKey, encryptedKey) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("rsa Decrypt") return nil, nil, nil, utils.ErrInfo(err) } log.WithFields(log.Fields{"key": decKey}).Debug("decrypted key") if len(decKey) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("decrypted key is empty") return nil, nil, nil, utils.ErrInfo("len(decKey)") } log.WithFields(log.Fields{"binaryTx": *binaryTx, "iv": iv}).Debug("binaryTx and iv is") decrypted, err := aes.Decrypt(iv, *binaryTx, decKey) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("Decryption binary tx") return nil, nil, nil, utils.ErrInfo(err) } return decKey, iv, decrypted, nil } func UnmarshalTxPacket(dat []byte) ([][]byte, error) { var txes [][]byte err := json.Unmarshal(dat, &txes) return txes, err } ================================================ FILE: packages/network/tcpserver/disseminator.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "bytes" "errors" "io" "gorm.io/gorm/clause" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/IBAX-io/go-ibax/packages/conf/syspar" log "github.com/sirupsen/logrus" ) // Disseminator get the list of transactions which belong to the sender from 'disseminator' daemon // do not load the blocks here because here could be the chain of blocks that are loaded for a long time // download the transactions here, because they are small and definitely will be downloaded in 60 sec func Disseminator(rw io.ReadWriter) error { r := &network.DisRequest{} if err := r.Read(rw); err != nil { return err } buf := bytes.NewBuffer(r.Data) /* * data structure * type - 1 byte. 0 - block, 1 - list of transactions * {if type==1}: * * tx_hash - 32 bytes * * {if type==0}: * block_id - 3 bytes * hash - 32 bytes * * tx_hash - 32 bytes * * */ // honor_node_id of the sender to know where to take a data when it will be downloaded by another daemon honorNodeID := converter.BinToDec(buf.Next(8)) log.Debug("honorNodeID", honorNodeID) n, err := syspar.GetNodeByPosition(honorNodeID) if err != nil { log.WithError(err).Error("on getting node by position") return err } // get data type (0 - block and transactions, 1 - only transactions) newDataType := converter.BinToDec(buf.Next(1)) log.Debug("newDataType", newDataType) if newDataType == 0 { banned := n != nil && node.GetNodesBanService().IsBanned(*n) if banned { buf.Next(3) buf.Next(consts.HashSize) } else { err := processBlock(buf, honorNodeID) if err != nil { log.WithError(err).Error("on process block") return err } } } // get unknown transactions from received packet needTx, err := getUnknownTransactions(buf) if err != nil { log.WithError(err).Error("on getting unknown txes") return err } // send the list of transactions which we want to get err = (&network.DisHashResponse{Data: needTx}).Write(rw) if err != nil { log.WithError(err).Error("on sending neeeded tx list") return err } if len(needTx) == 0 { return nil } // get this new transactions txBodies, err := resieveTxBodies(rw) if err != nil { log.WithError(err).Error("on reading needed txes from disseminator") return err } // and save them return saveNewTransactions(txBodies) } func resieveTxBodies(con io.Reader) ([]byte, error) { sizeBuf := make([]byte, 4) if _, err := io.ReadFull(con, sizeBuf); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on getting size of tx bodies") return nil, err } size := converter.BinToDec(sizeBuf) txBodies := make([]byte, size) if _, err := io.ReadFull(con, txBodies); err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("on getting tx bodies") return nil, err } return txBodies, nil } func processBlock(buf *bytes.Buffer, honorNodeID int64) error { infoBlock := &sqldb.InfoBlock{} found, err := infoBlock.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting cur block ID") return utils.ErrInfo(err) } if !found { log.WithFields(log.Fields{"type": consts.NotFound}).Error("cant find info block") return errors.New("can't find info block") } // get block ID newBlockID := converter.BinToDec(buf.Next(3)) log.WithFields(log.Fields{"new_block_id": newBlockID}).Debug("Generated new block id") // get block hash blockHash := buf.Next(consts.HashSize) log.Debugf("blockHash %x", blockHash) qb := &sqldb.QueueBlock{} found, err = qb.GetQueueBlockByHash(blockHash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting QueueBlock") return utils.ErrInfo(err) } // we accept only new blocks if !found && newBlockID >= infoBlock.BlockID { queueBlock := &sqldb.QueueBlock{Hash: blockHash, HonorNodeID: honorNodeID, BlockID: newBlockID} err = queueBlock.Create() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Creating QueueBlock") return nil } } return nil } func getUnknownTransactions(buf *bytes.Buffer) ([]byte, error) { hashes, err := readHashes(buf) if err != nil { log.WithFields(log.Fields{"type": consts.ProtocolError, "error": err}).Error("on reading hashes") return nil, err } var needTx []byte // TODO: remove cycle, select miltiple txes throw in(?) for _, hash := range hashes { // check if we have such a transaction // check log_transaction exists, err := sqldb.GetLogTransactionsCount(hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "txHash": hash}).Error("Getting log tx count") return nil, utils.ErrInfo(err) } if exists > 0 { log.WithFields(log.Fields{"txHash": hash, "type": consts.DuplicateObject}).Warning("tx with this hash already exists in log_tx") continue } exists, err = sqldb.GetTransactionsCount(hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "txHash": hash}).Error("Getting tx count") return nil, utils.ErrInfo(err) } if exists > 0 { log.WithFields(log.Fields{"txHash": hash, "type": consts.DuplicateObject}).Warning("tx with this hash already exists in tx") continue } // check transaction queue exists, err = sqldb.GetQueuedTransactionsCount(hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting queue_tx count") return nil, utils.ErrInfo(err) } if exists > 0 { log.WithFields(log.Fields{"txHash": hash, "type": consts.DuplicateObject}).Warning("tx with this hash already exists in queue_tx") continue } needTx = append(needTx, hash...) } return needTx, nil } func readHashes(buf *bytes.Buffer) ([][]byte, error) { if buf.Len()%consts.HashSize != 0 { log.WithFields(log.Fields{"hashes_slice_size": buf.Len(), "tx_size": consts.HashSize, "type": consts.ProtocolError}).Error("incorrect hashes length") return nil, errors.New("wrong transactions hashes size") } hashes := make([][]byte, 0, buf.Len()/consts.HashSize) for buf.Len() > 0 { hashes = append(hashes, buf.Next(consts.HashSize)) } return hashes, nil } func saveNewTransactions(binaryTxs []byte) error { var queueTxs []*sqldb.QueueTx log.WithFields(log.Fields{"binaryTxs": binaryTxs}).Debug("trying to save binary txs") for len(binaryTxs) > 0 { txSize, err := converter.DecodeLength(&binaryTxs) if err != nil { log.WithFields(log.Fields{"type": consts.ProtocolError, "err": err}).Error("decoding binary txs length") return err } if int64(len(binaryTxs)) < txSize { log.WithFields(log.Fields{"type": consts.ProtocolError, "size": txSize, "len": len(binaryTxs)}).Error("incorrect binary txs len") return utils.ErrInfo(errors.New("bad transactions packet")) } txBinData := converter.BytesShift(&binaryTxs, txSize) if len(txBinData) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("binaryTxs is empty") return utils.ErrInfo(errors.New("len(txBinData) == 0")) } if int64(len(txBinData)) > syspar.GetMaxTxSize() { log.WithFields(log.Fields{"type": consts.ParameterExceeded, "len": len(txBinData), "size": syspar.GetMaxTxSize()}).Error("len of tx data exceeds max size") return utils.ErrInfo("len(txBinData) > max_tx_size") } rtx := transaction.Transaction{} if err = rtx.Unmarshall(bytes.NewBuffer(txBinData), true); err != nil { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err}).Error("unmarshalling transaction") return err } queueTxs = append(queueTxs, &sqldb.QueueTx{Hash: rtx.Hash(), Data: txBinData, Expedite: rtx.Expedite(), Time: rtx.Timestamp(), FromGate: 1}) } if err := sqldb.GetDB(nil).Clauses(clause.OnConflict{DoNothing: true}).Create(&queueTxs).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("error creating QueueTx") return err } return nil } ================================================ FILE: packages/network/tcpserver/max_block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) // MaxBlock sends the last block ID // blocksCollection daemon sends this request func MaxBlock() (*network.MaxBlockResponse, error) { infoBlock := &sqldb.InfoBlock{} found, err := infoBlock.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting cur blockID") return nil, utils.ErrInfo(err) } if !found { log.WithFields(log.Fields{"type": consts.NotFound}).Debug("Can't found info block") } return &network.MaxBlockResponse{ BlockID: infoBlock.BlockID, }, nil } ================================================ FILE: packages/network/tcpserver/stop_network.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "errors" "time" "github.com/IBAX-io/go-ibax/packages/transaction" "net" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/common/crypto/x509" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) var errStopCertAlreadyUsed = errors.New("Stop certificate is already used") // StopNetwork is stop network tx type func StopNetwork(req *network.StopNetworkRequest, w net.Conn) error { hash, err := processStopNetwork(req.Data) if err != nil { return err } res := &network.StopNetworkResponse{hash} if err = res.Write(w); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.NetworkError}).Error("sending response") return err } return nil } func processStopNetwork(b []byte) ([]byte, error) { cert, err := x509.ParseCert(b) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.ParseError}).Error("parsing cert") return nil, err } if cert.EqualBytes(consts.UsedStopNetworkCerts...) { log.WithFields(log.Fields{"error": errStopCertAlreadyUsed, "type": consts.InvalidObject}).Error("checking cert") return nil, errStopCertAlreadyUsed } fbdata, err := syspar.GetFirstBlockData() if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.ConfigError}).Error("getting data of first block") return nil, err } if err = cert.Validate(fbdata.StopNetworkCertBundle); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.InvalidObject}).Error("validating cert") return nil, err } var data []byte snp := new(transaction.StopNetworkParser) data, err = snp.BinMarshal(&types.StopNetwork{ KeyID: conf.Config.KeyID, Time: time.Now().Unix(), StopNetworkCert: b, }) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.MarshallingError}).Error("binary marshaling") return nil, err } hash := crypto.DoubleHash(data) tx := &sqldb.Transaction{ Hash: hash, Data: data, Type: types.StopNetworkTxType, KeyID: conf.Config.KeyID, HighRate: sqldb.TransactionRateStopNetwork, Time: snp.Timestamp, } if err = tx.Create(nil); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("inserting tx to database") return nil, err } return hash, nil } ================================================ FILE: packages/network/tcpserver/tcpserver.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package tcpserver import ( "net" "strings" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network" "github.com/IBAX-io/go-ibax/packages/service/node" log "github.com/sirupsen/logrus" ) // HandleTCPRequest proceed TCP requests func HandleTCPRequest(rw net.Conn) { dType := &network.RequestType{} err := dType.Read(rw) if err != nil { log.Errorf("read request type failed: %s", err) return } log.WithFields(log.Fields{"request_type": dType.Type}).Debug("tcpserver got request type") var response network.SelfReaderWriter switch dType.Type { case network.RequestTypeHonorNode: if node.IsNodePaused() { return } err = Disseminator(rw) case network.RequestTypeNotHonorNode: if node.IsNodePaused() { return } err = DisseminateTxs(rw) case network.RequestTypeStopNetwork: req := &network.StopNetworkRequest{} if err = req.Read(rw); err == nil { err = StopNetwork(req, rw) } case network.RequestTypeConfirmation: //if node.IsNodePaused() { // return //} req := &network.ConfirmRequest{} if err = req.Read(rw); err == nil { response, err = Confirmation(req) } case network.RequestTypeBlockCollection: req := &network.GetBodiesRequest{} if err = req.Read(rw); err == nil { err = BlockCollection(req, rw) } case network.RequestTypeMaxBlock: response, err = MaxBlock() case network.RequestTypeVoting: req := &network.CandidateNodeVotingRequest{} if err = req.Read(rw); err == nil { response, err = CandidateNodeVoting(req) } case network.RequestSyncMatchineState: req := &network.BroadcastNodeConnInfoRequest{} if err = req.Read(rw); err == nil { _, err = SyncMatchineStateRes(req) if err != nil { log.WithFields(log.Fields{"type": "SyncMatchineStateRes", "error": err}).Error("SyncMatchineStateRes hour candidate voting") } return } } if err != nil || response == nil { return } log.WithFields(log.Fields{"response": response, "request_type": dType.Type}).Debug("tcpserver responded") if err = response.Write(rw); err != nil { // err = SendRequest(response, rw) log.Errorf("tcpserver handle error: %s", err) } } // TcpListener is listening tcp address func TcpListener(laddr string) error { if strings.HasPrefix(laddr, "127.") { log.Warn("Listening at local address: ", laddr) } l, err := net.Listen("tcp", laddr) if err != nil { log.WithFields(log.Fields{"type": consts.ConnectionError, "error": err, "host": laddr}).Error("Error listening") return err } go func() { defer l.Close() for { conn, err := l.Accept() if err != nil { log.WithFields(log.Fields{"type": consts.ConnectionError, "error": err, "host": laddr}).Error("Error accepting") time.Sleep(time.Second) } else { go func(conn net.Conn) { HandleTCPRequest(conn) conn.Close() }(conn) } } }() return nil } ================================================ FILE: packages/notificator/notificator.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package notificator import ( "encoding/json" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/publisher" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) type notificationRecord struct { EcosystemID string `json:"ecosystem"` RoleID string `json:"role_id"` RecordsCount int64 `json:"count"` } // UpdateNotifications send stats about unreaded messages to centrifugo for ecosystem func UpdateNotifications(ecosystemID int64, accounts []string) { notificationsStats, err := getEcosystemNotificationStats(ecosystemID, accounts) if err != nil { return } for account, n := range notificationsStats { sendUserStats(account, *n) } } // UpdateRolesNotifications send stats about unreaded messages to centrifugo for ecosystem func UpdateRolesNotifications(ecosystemID int64, roles []int64) { members, _ := sqldb.GetRoleMembers(nil, ecosystemID, roles) UpdateNotifications(ecosystemID, members) } func getEcosystemNotificationStats(ecosystemID int64, users []string) (map[string]*[]notificationRecord, error) { result, err := sqldb.GetNotificationsCount(ecosystemID, users) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting notification count") return nil, err } return parseRecipientNotification(result, ecosystemID), nil } func parseRecipientNotification(rows []sqldb.NotificationsCount, systemID int64) map[string]*[]notificationRecord { recipientNotifications := make(map[string]*[]notificationRecord) for _, r := range rows { if r.RecipientID == 0 { continue } roleNotifications := notificationRecord{ EcosystemID: converter.Int64ToStr(systemID), RoleID: converter.Int64ToStr(r.RoleID), RecordsCount: r.Count, } nr, ok := recipientNotifications[r.Account] if ok { *nr = append(*nr, roleNotifications) continue } records := []notificationRecord{ roleNotifications, } recipientNotifications[r.Account] = &records } return recipientNotifications } func sendUserStats(account string, stats []notificationRecord) { rawStats, err := json.Marshal(stats) if err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("notification statistic") } err = publisher.Write(account, string(rawStats)) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Debug("writing to centrifugo") } } ================================================ FILE: packages/notificator/queue.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package notificator import ( "github.com/IBAX-io/go-ibax/packages/types" ) type Queue struct { Accounts []*Accounts Roles []*Roles } type Accounts struct { Ecosystem int64 List []string } type Roles struct { Ecosystem int64 List []int64 } func (q *Queue) Size() int { return len(q.Accounts) + len(q.Roles) } func (q *Queue) AddAccounts(ecosystem int64, list ...string) { q.Accounts = append(q.Accounts, &Accounts{ Ecosystem: ecosystem, List: list, }) } func (q *Queue) AddRoles(ecosystem int64, list ...int64) { q.Roles = append(q.Roles, &Roles{ Ecosystem: ecosystem, List: list, }) } func (q *Queue) Send() { for _, a := range q.Accounts { UpdateNotifications(a.Ecosystem, a.List) } for _, r := range q.Roles { UpdateRolesNotifications(r.Ecosystem, r.List) } } func NewQueue() types.Notifications { return &Queue{ Accounts: make([]*Accounts, 0), Roles: make([]*Roles, 0), } } ================================================ FILE: packages/notificator/token_movements.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package notificator import ( "fmt" "strings" "time" "github.com/shopspring/decimal" "net/smtp" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) const ( networkPerDayLimit = 100000000 networkPerDayMsgTemplate = "day chain movement volume = %s" fromToDayLimitMsgTemplate = "from %d to %d sended volume = %s" perBlockTokenMovementTemplate = "from wallet %d token movement count = %d in block: %d" networkPerDayEvent = 1 fromToDayLimitEvent = 2 perBlockTokenMovementEvent = 3 ) var lastLimitEvents map[uint8]time.Time func init() { lastLimitEvents = make(map[uint8]time.Time, 0) } func sendEmail(conf conf.TokenMovementConfig, message string) error { auth := smtp.PlainAuth("", conf.Username, conf.Password, conf.Host) to := []string{conf.To} msg := []byte(fmt.Sprintf("From: %s\r\n", conf.From) + fmt.Sprintf("To: %s\r\n", conf.To) + fmt.Sprintf("Subject: %s\r\n", conf.Subject) + "\r\n" + fmt.Sprintf("%s\r\n", message)) err := smtp.SendMail(fmt.Sprintf("%s:%d", conf.Host, conf.Port), auth, conf.From, to, msg) if err != nil { log.WithError(err).Error("sending email") } return err } // CheckTokenMovementLimits check all limits func CheckTokenMovementLimits(tx *sqldb.DbTransaction, conf conf.TokenMovementConfig, blockID int64) { var messages []string if needCheck(networkPerDayEvent) { amount, err := sqldb.GetExcessCommonTokenMovementPerDay(tx) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("check common token movement") } else if amount.GreaterThanOrEqual(decimal.NewFromFloat(networkPerDayLimit)) { messages = append(messages, fmt.Sprintf(networkPerDayMsgTemplate, amount.String())) lastLimitEvents[networkPerDayEvent] = time.Now() } } if needCheck(fromToDayLimitEvent) { transfers, err := sqldb.GetExcessFromToTokenMovementPerDay(tx) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("check from to token movement") } else { for _, transfer := range transfers { messages = append(messages, fmt.Sprintf(fromToDayLimitMsgTemplate, transfer.SenderID, transfer.RecipientID, transfer.Amount)) } if len(transfers) > 0 { lastLimitEvents[fromToDayLimitEvent] = time.Now() } } } excesses, err := sqldb.GetExcessTokenMovementQtyPerBlock(tx, blockID) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("check token movement per block") } else { for _, excess := range excesses { messages = append(messages, fmt.Sprintf(perBlockTokenMovementTemplate, excess.SenderID, excess.TxCount, blockID)) } } if len(messages) > 0 { sendEmail(conf, strings.Join(messages, "\n")) } } // checks needed only if we have'nt prevent events or if event older then 1 day func needCheck(event uint8) bool { t, ok := lastLimitEvents[event] if !ok { return true } return time.Now().Sub(t) >= 24*time.Hour } ================================================ FILE: packages/pb/Makefile ================================================ # install tools pb-install: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install github.com/golang/protobuf/protoc-gen-go@latest go get -u google.golang.org/grpc go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@latest go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@latest go install github.com/gogo/protobuf/protoc-gen-gogofaster@latest # generate *.pb.go file pb-generate: protoc -I=. --gogofaster_out=:../script --gogofaster_opt=paths=source_relative ./vm.proto protoc -I=. --gogofaster_out=:../smart --gogofaster_opt=paths=source_relative ./gas.proto protoc -I=. --gogofaster_out=:../types --gogofaster_opt=paths=source_relative ./block.proto protoc -I=. --gogofaster_out=:../pbgo --gogofaster_opt=paths=source_relative ./tx.proto protoc -I=. --gogofaster_out=:../common/crypto --gogofaster_opt=paths=source_relative ./crypto.proto protoc -I=. --gogofaster_out=:../types --gogofaster_opt=paths=source_relative ./play.proto ================================================ FILE: packages/pb/block.proto ================================================ syntax = "proto3"; option go_package = "github.com/IBAX-io/go-ibax/packages/types"; package types; import "play.proto"; // BlockSyncMethod define block sync method. enum BlockSyncMethod{ CONTRACTVM = 0; SQLDML = 1; } //BlockHeader is a structure of the block's header message BlockHeader{ int64 block_id = 1; int64 timestamp = 2; int64 ecosystem_id = 3; int64 key_id = 4; int64 node_position = 5; bytes sign = 6; bytes block_hash = 7; //differences with before and after in tx modification table bytes rollbacks_hash = 8; int32 version = 9; int32 consensus_mode = 10; bytes candidate_nodes = 11; int64 network_id = 12; } // BlockData is a structure of the block's message BlockData { BlockHeader header = 1; BlockHeader prev_header = 2; bytes merkle_root =3; bytes bin_data =4; repeated bytes tx_full_data =5; AfterTxs after_txs =6; bool sys_update = 7; } ================================================ FILE: packages/pb/crypto.proto ================================================ syntax = "proto3"; option go_package = "github.com/IBAX-io/go-ibax/packages/common/crypto"; package crypto; // AsymAlgo is asymmetric algorithms enum AsymAlgo{ ECC_P256 = 0; ECC_Secp256k1 = 1; SM2 = 2; ECC_P512 = 3; } // SymAlgo is symmetric algorithms enum SymAlgo{ AES = 0; SM4 = 1; } // HashAlgo is hash algorithms enum HashAlgo{ SHA256 = 0; KECCAK256 = 1; SM3 = 2; SHA3_256 = 3; } ================================================ FILE: packages/pb/gas.proto ================================================ syntax = "proto3"; option go_package = "github.com/IBAX-io/go-ibax/packages/smart"; package smart; enum PaymentType { INVALID = 0; ContractCaller = 1; ContractBinder = 2; EcosystemAddress = 3; } enum GasScenesType { Unknown = 0; Reward = 1; Taxes = 2; Direct = 15; Combustion = 16; TransferSelf = 24; } enum GasPayAbleType { Invalid = 0; Unable = 1; Capable = 2; } enum FuelType { UNKNOWN = 0; vmCost_fee = 1; storage_fee = 2; expedite_fee = 3; } enum Arithmetic { NATIVE = 0; MUL = 3; DIV = 4; } ================================================ FILE: packages/pb/play.proto ================================================ syntax = "proto3"; option go_package = "github.com/IBAX-io/go-ibax/packages/types"; package types; import "tx.proto"; //AfterTxs defined block batch process tx for sql DML message AfterTxs { repeated AfterTx txs =1; //TxBinLogSql defined contract exec sql for tx DML // repeated bytes tx_bin_log_sql = 2; repeated RollbackTx rts = 3; } message AfterTx { bytes used_tx = 1; LogTransaction lts = 2; pbgo.TxResult upd_tx_status = 3; } message RollbackTx { int64 id = 1; int64 block_id = 2; bytes tx_hash = 3; string name_table = 4; string table_id = 5; string data = 6; bytes data_hash = 7; } message LogTransaction { bytes hash = 1; int64 block = 2; // bytes tx_data = 3; int64 timestamp = 4; int64 address = 5; int64 ecosystem_id = 6; string contract_name = 7; pbgo.TxInvokeStatusCode invoke_status = 8; } ================================================ FILE: packages/pb/tx.proto ================================================ syntax = "proto3"; option go_package = "github.com/IBAX-io/go-ibax/packages/pbgo"; package pbgo; // Transaction types. enum TransactionTypes{ SMARTCONTRACT = 0; FIRSTBLOCK = 1; STOPNETWORK = 2; } // Transaction invoke status code. enum TxInvokeStatusCode{ SUCCESS = 0; PENALTY = 1; FAILED = 2; PENDING = 3; } message FirstBlock { int64 key_id = 1; int64 timestamp = 2; bytes public_key = 3; bytes node_public_key = 4; bytes stop_network_cert_bundle = 5; int64 test = 6; uint64 private_blockchain = 7; } message StopNetwork { int64 key_id = 1; int64 timestamp = 2; bytes stop_network_cert = 3; } message TxResult{ bytes hash = 1; int64 block_id = 2; TxInvokeStatusCode code = 3; string result = 4; string error = 5; } ================================================ FILE: packages/pb/vm.proto ================================================ syntax = "proto3"; option go_package = "github.com/IBAX-io/go-ibax/packages/script"; package script; // VMType is virtual machine type enum VMType { // VMType_INVALID is invalid type INVALID = 0; // VMType_Smart is smart vm type Smart = 1; // VMType_CLB is clb vm type CLB = 2; // VMType_CLBMaster is CLBMaster type CLBMaster = 3; } // ObjectType Types of the compiled objects enum ObjectType{ // ObjUnknown is an unknown object. Unknown = 0; // ObjectType_Contract is a contract object. Contract = 1; // ObjectType_Func is a function object. myfunc() Func = 2; // ObjectType_ExtFunc is an extended build in function object. $myfunc() ExtFunc = 3; // ObjectType_Var is a variable. myvar Var = 4; // ObjectType_ExtVar is an extended build in variable. $myvar ExtVar = 5; } ================================================ FILE: packages/pbgo/tx.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: tx.proto package pbgo import ( fmt "fmt" proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Transaction types. type TransactionTypes int32 const ( TransactionTypes_SMARTCONTRACT TransactionTypes = 0 TransactionTypes_FIRSTBLOCK TransactionTypes = 1 TransactionTypes_STOPNETWORK TransactionTypes = 2 ) var TransactionTypes_name = map[int32]string{ 0: "SMARTCONTRACT", 1: "FIRSTBLOCK", 2: "STOPNETWORK", } var TransactionTypes_value = map[string]int32{ "SMARTCONTRACT": 0, "FIRSTBLOCK": 1, "STOPNETWORK": 2, } func (x TransactionTypes) String() string { return proto.EnumName(TransactionTypes_name, int32(x)) } func (TransactionTypes) EnumDescriptor() ([]byte, []int) { return fileDescriptor_0fd2153dc07d3b5c, []int{0} } // Transaction invoke status code. type TxInvokeStatusCode int32 const ( TxInvokeStatusCode_SUCCESS TxInvokeStatusCode = 0 TxInvokeStatusCode_PENALTY TxInvokeStatusCode = 1 TxInvokeStatusCode_FAILED TxInvokeStatusCode = 2 TxInvokeStatusCode_PENDING TxInvokeStatusCode = 3 ) var TxInvokeStatusCode_name = map[int32]string{ 0: "SUCCESS", 1: "PENALTY", 2: "FAILED", 3: "PENDING", } var TxInvokeStatusCode_value = map[string]int32{ "SUCCESS": 0, "PENALTY": 1, "FAILED": 2, "PENDING": 3, } func (x TxInvokeStatusCode) String() string { return proto.EnumName(TxInvokeStatusCode_name, int32(x)) } func (TxInvokeStatusCode) EnumDescriptor() ([]byte, []int) { return fileDescriptor_0fd2153dc07d3b5c, []int{1} } type FirstBlock struct { KeyId int64 `protobuf:"varint,1,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` NodePublicKey []byte `protobuf:"bytes,4,opt,name=node_public_key,json=nodePublicKey,proto3" json:"node_public_key,omitempty"` StopNetworkCertBundle []byte `protobuf:"bytes,5,opt,name=stop_network_cert_bundle,json=stopNetworkCertBundle,proto3" json:"stop_network_cert_bundle,omitempty"` Test int64 `protobuf:"varint,6,opt,name=test,proto3" json:"test,omitempty"` PrivateBlockchain uint64 `protobuf:"varint,7,opt,name=private_blockchain,json=privateBlockchain,proto3" json:"private_blockchain,omitempty"` } func (m *FirstBlock) Reset() { *m = FirstBlock{} } func (m *FirstBlock) String() string { return proto.CompactTextString(m) } func (*FirstBlock) ProtoMessage() {} func (*FirstBlock) Descriptor() ([]byte, []int) { return fileDescriptor_0fd2153dc07d3b5c, []int{0} } func (m *FirstBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *FirstBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_FirstBlock.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *FirstBlock) XXX_Merge(src proto.Message) { xxx_messageInfo_FirstBlock.Merge(m, src) } func (m *FirstBlock) XXX_Size() int { return m.Size() } func (m *FirstBlock) XXX_DiscardUnknown() { xxx_messageInfo_FirstBlock.DiscardUnknown(m) } var xxx_messageInfo_FirstBlock proto.InternalMessageInfo func (m *FirstBlock) GetKeyId() int64 { if m != nil { return m.KeyId } return 0 } func (m *FirstBlock) GetTimestamp() int64 { if m != nil { return m.Timestamp } return 0 } func (m *FirstBlock) GetPublicKey() []byte { if m != nil { return m.PublicKey } return nil } func (m *FirstBlock) GetNodePublicKey() []byte { if m != nil { return m.NodePublicKey } return nil } func (m *FirstBlock) GetStopNetworkCertBundle() []byte { if m != nil { return m.StopNetworkCertBundle } return nil } func (m *FirstBlock) GetTest() int64 { if m != nil { return m.Test } return 0 } func (m *FirstBlock) GetPrivateBlockchain() uint64 { if m != nil { return m.PrivateBlockchain } return 0 } type StopNetwork struct { KeyId int64 `protobuf:"varint,1,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` StopNetworkCert []byte `protobuf:"bytes,3,opt,name=stop_network_cert,json=stopNetworkCert,proto3" json:"stop_network_cert,omitempty"` } func (m *StopNetwork) Reset() { *m = StopNetwork{} } func (m *StopNetwork) String() string { return proto.CompactTextString(m) } func (*StopNetwork) ProtoMessage() {} func (*StopNetwork) Descriptor() ([]byte, []int) { return fileDescriptor_0fd2153dc07d3b5c, []int{1} } func (m *StopNetwork) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *StopNetwork) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_StopNetwork.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *StopNetwork) XXX_Merge(src proto.Message) { xxx_messageInfo_StopNetwork.Merge(m, src) } func (m *StopNetwork) XXX_Size() int { return m.Size() } func (m *StopNetwork) XXX_DiscardUnknown() { xxx_messageInfo_StopNetwork.DiscardUnknown(m) } var xxx_messageInfo_StopNetwork proto.InternalMessageInfo func (m *StopNetwork) GetKeyId() int64 { if m != nil { return m.KeyId } return 0 } func (m *StopNetwork) GetTimestamp() int64 { if m != nil { return m.Timestamp } return 0 } func (m *StopNetwork) GetStopNetworkCert() []byte { if m != nil { return m.StopNetworkCert } return nil } type TxResult struct { Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` BlockId int64 `protobuf:"varint,2,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` Code TxInvokeStatusCode `protobuf:"varint,3,opt,name=code,proto3,enum=pbgo.TxInvokeStatusCode" json:"code,omitempty"` Result string `protobuf:"bytes,4,opt,name=result,proto3" json:"result,omitempty"` Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"` } func (m *TxResult) Reset() { *m = TxResult{} } func (m *TxResult) String() string { return proto.CompactTextString(m) } func (*TxResult) ProtoMessage() {} func (*TxResult) Descriptor() ([]byte, []int) { return fileDescriptor_0fd2153dc07d3b5c, []int{2} } func (m *TxResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *TxResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_TxResult.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *TxResult) XXX_Merge(src proto.Message) { xxx_messageInfo_TxResult.Merge(m, src) } func (m *TxResult) XXX_Size() int { return m.Size() } func (m *TxResult) XXX_DiscardUnknown() { xxx_messageInfo_TxResult.DiscardUnknown(m) } var xxx_messageInfo_TxResult proto.InternalMessageInfo func (m *TxResult) GetHash() []byte { if m != nil { return m.Hash } return nil } func (m *TxResult) GetBlockId() int64 { if m != nil { return m.BlockId } return 0 } func (m *TxResult) GetCode() TxInvokeStatusCode { if m != nil { return m.Code } return TxInvokeStatusCode_SUCCESS } func (m *TxResult) GetResult() string { if m != nil { return m.Result } return "" } func (m *TxResult) GetError() string { if m != nil { return m.Error } return "" } func init() { proto.RegisterEnum("pbgo.TransactionTypes", TransactionTypes_name, TransactionTypes_value) proto.RegisterEnum("pbgo.TxInvokeStatusCode", TxInvokeStatusCode_name, TxInvokeStatusCode_value) proto.RegisterType((*FirstBlock)(nil), "pbgo.FirstBlock") proto.RegisterType((*StopNetwork)(nil), "pbgo.StopNetwork") proto.RegisterType((*TxResult)(nil), "pbgo.TxResult") } func init() { proto.RegisterFile("tx.proto", fileDescriptor_0fd2153dc07d3b5c) } var fileDescriptor_0fd2153dc07d3b5c = []byte{ // 516 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0xcd, 0x8e, 0xda, 0x3c, 0x14, 0x25, 0xfc, 0x73, 0x99, 0x19, 0x82, 0xf5, 0xcd, 0xa7, 0x54, 0x6a, 0x23, 0xc4, 0xa2, 0x42, 0xa8, 0x80, 0xd4, 0x2e, 0xba, 0x26, 0x19, 0x18, 0x45, 0x50, 0x40, 0x8e, 0xab, 0xfe, 0x6c, 0xa2, 0xfc, 0x58, 0x10, 0x05, 0xe2, 0xc8, 0x31, 0x53, 0xd8, 0xf7, 0x01, 0xba, 0xe9, 0x3b, 0x75, 0x39, 0xcb, 0x2e, 0x2b, 0x78, 0x91, 0x2a, 0x06, 0xcd, 0x54, 0x9d, 0x5d, 0x77, 0xf7, 0xdc, 0x73, 0x7c, 0xef, 0xf1, 0xb1, 0xa1, 0x2a, 0x76, 0xfd, 0x84, 0x33, 0xc1, 0x50, 0x31, 0xf1, 0x96, 0xac, 0xfd, 0x35, 0x0f, 0x30, 0x0e, 0x79, 0x2a, 0x8c, 0x35, 0xf3, 0x23, 0x74, 0x0d, 0xe5, 0x88, 0xee, 0x9d, 0x30, 0xd0, 0x94, 0x96, 0xd2, 0x29, 0xe0, 0x52, 0x44, 0xf7, 0x56, 0x80, 0x9e, 0x43, 0x4d, 0x84, 0x1b, 0x9a, 0x0a, 0x77, 0x93, 0x68, 0x79, 0xc9, 0x3c, 0x36, 0xd0, 0x0b, 0x80, 0x64, 0xeb, 0xad, 0x43, 0xdf, 0x89, 0xe8, 0x5e, 0x2b, 0xb4, 0x94, 0xce, 0x05, 0xae, 0x9d, 0x3a, 0x13, 0xba, 0x47, 0x2f, 0xa1, 0x11, 0xb3, 0x80, 0x3a, 0x7f, 0x68, 0x8a, 0x52, 0x73, 0x99, 0xb5, 0x17, 0x0f, 0xba, 0xb7, 0xa0, 0xa5, 0x82, 0x25, 0x4e, 0x4c, 0xc5, 0x17, 0xc6, 0x23, 0xc7, 0xa7, 0x5c, 0x38, 0xde, 0x36, 0x0e, 0xd6, 0x54, 0x2b, 0xc9, 0x03, 0xd7, 0x19, 0x3f, 0x3b, 0xd1, 0x26, 0xe5, 0xc2, 0x90, 0x24, 0x42, 0x50, 0x14, 0x34, 0x15, 0x5a, 0x59, 0x1a, 0x93, 0x35, 0xea, 0x01, 0x4a, 0x78, 0x78, 0xe7, 0x0a, 0xea, 0x78, 0xd9, 0xcd, 0xfc, 0x95, 0x1b, 0xc6, 0x5a, 0xa5, 0xa5, 0x74, 0x8a, 0xb8, 0x79, 0x66, 0x8c, 0x07, 0xa2, 0x1d, 0x43, 0xdd, 0x7e, 0x9c, 0xfd, 0x6f, 0x31, 0x74, 0xa1, 0xf9, 0xc4, 0xff, 0x39, 0x8d, 0xc6, 0x5f, 0xc6, 0xdb, 0xdf, 0x15, 0xa8, 0x92, 0x1d, 0xa6, 0xe9, 0x76, 0x2d, 0x32, 0xff, 0x2b, 0x37, 0x5d, 0xc9, 0x5d, 0x17, 0x58, 0xd6, 0xe8, 0x19, 0x54, 0xa5, 0xef, 0xcc, 0xc3, 0x69, 0x53, 0x45, 0x62, 0x2b, 0x40, 0xaf, 0xa0, 0xe8, 0xb3, 0x80, 0xca, 0xd1, 0x57, 0xaf, 0xb5, 0x7e, 0xf6, 0x8e, 0x7d, 0xb2, 0xb3, 0xe2, 0x3b, 0x16, 0x51, 0x5b, 0xb8, 0x62, 0x9b, 0x9a, 0x2c, 0xa0, 0x58, 0xaa, 0xd0, 0xff, 0x50, 0xe6, 0x72, 0x8d, 0x0c, 0xbd, 0x86, 0xcf, 0x08, 0xfd, 0x07, 0x25, 0xca, 0x39, 0xe3, 0x32, 0xda, 0x1a, 0x3e, 0x81, 0xee, 0x18, 0x54, 0xc2, 0xdd, 0x38, 0x75, 0x7d, 0x11, 0xb2, 0x98, 0xec, 0x13, 0x9a, 0xa2, 0x26, 0x5c, 0xda, 0xef, 0x86, 0x98, 0x98, 0xf3, 0x19, 0xc1, 0x43, 0x93, 0xa8, 0x39, 0x74, 0x05, 0x30, 0xb6, 0xb0, 0x4d, 0x8c, 0xe9, 0xdc, 0x9c, 0xa8, 0x0a, 0x6a, 0x40, 0xdd, 0x26, 0xf3, 0xc5, 0x6c, 0x44, 0x3e, 0xcc, 0xf1, 0x44, 0xcd, 0x77, 0x6f, 0x01, 0x3d, 0x75, 0x84, 0xea, 0x50, 0xb1, 0xdf, 0x9b, 0xe6, 0xc8, 0xb6, 0xd5, 0x5c, 0x06, 0x16, 0xa3, 0xd9, 0x70, 0x4a, 0x3e, 0xa9, 0x0a, 0x02, 0x28, 0x8f, 0x87, 0xd6, 0x74, 0x74, 0xa3, 0xe6, 0xcf, 0xc4, 0x8d, 0x35, 0xbb, 0x55, 0x0b, 0x86, 0xf1, 0xe3, 0xa0, 0x2b, 0xf7, 0x07, 0x5d, 0xf9, 0x75, 0xd0, 0x95, 0x6f, 0x47, 0x3d, 0x77, 0x7f, 0xd4, 0x73, 0x3f, 0x8f, 0x7a, 0xee, 0x73, 0x67, 0x19, 0x8a, 0xd5, 0xd6, 0xeb, 0xfb, 0x6c, 0x33, 0xb0, 0x8c, 0xe1, 0xc7, 0x5e, 0xc8, 0x06, 0x4b, 0xd6, 0x0b, 0x3d, 0x77, 0x37, 0x48, 0x5c, 0x3f, 0x72, 0x97, 0x34, 0x1d, 0x64, 0xd9, 0x78, 0x65, 0xf9, 0xe1, 0xdf, 0xfc, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x00, 0xe8, 0x53, 0x93, 0xfc, 0x02, 0x00, 0x00, } func (m *FirstBlock) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *FirstBlock) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *FirstBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.PrivateBlockchain != 0 { i = encodeVarintTx(dAtA, i, uint64(m.PrivateBlockchain)) i-- dAtA[i] = 0x38 } if m.Test != 0 { i = encodeVarintTx(dAtA, i, uint64(m.Test)) i-- dAtA[i] = 0x30 } if len(m.StopNetworkCertBundle) > 0 { i -= len(m.StopNetworkCertBundle) copy(dAtA[i:], m.StopNetworkCertBundle) i = encodeVarintTx(dAtA, i, uint64(len(m.StopNetworkCertBundle))) i-- dAtA[i] = 0x2a } if len(m.NodePublicKey) > 0 { i -= len(m.NodePublicKey) copy(dAtA[i:], m.NodePublicKey) i = encodeVarintTx(dAtA, i, uint64(len(m.NodePublicKey))) i-- dAtA[i] = 0x22 } if len(m.PublicKey) > 0 { i -= len(m.PublicKey) copy(dAtA[i:], m.PublicKey) i = encodeVarintTx(dAtA, i, uint64(len(m.PublicKey))) i-- dAtA[i] = 0x1a } if m.Timestamp != 0 { i = encodeVarintTx(dAtA, i, uint64(m.Timestamp)) i-- dAtA[i] = 0x10 } if m.KeyId != 0 { i = encodeVarintTx(dAtA, i, uint64(m.KeyId)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *StopNetwork) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *StopNetwork) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *StopNetwork) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.StopNetworkCert) > 0 { i -= len(m.StopNetworkCert) copy(dAtA[i:], m.StopNetworkCert) i = encodeVarintTx(dAtA, i, uint64(len(m.StopNetworkCert))) i-- dAtA[i] = 0x1a } if m.Timestamp != 0 { i = encodeVarintTx(dAtA, i, uint64(m.Timestamp)) i-- dAtA[i] = 0x10 } if m.KeyId != 0 { i = encodeVarintTx(dAtA, i, uint64(m.KeyId)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *TxResult) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *TxResult) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *TxResult) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Error) > 0 { i -= len(m.Error) copy(dAtA[i:], m.Error) i = encodeVarintTx(dAtA, i, uint64(len(m.Error))) i-- dAtA[i] = 0x2a } if len(m.Result) > 0 { i -= len(m.Result) copy(dAtA[i:], m.Result) i = encodeVarintTx(dAtA, i, uint64(len(m.Result))) i-- dAtA[i] = 0x22 } if m.Code != 0 { i = encodeVarintTx(dAtA, i, uint64(m.Code)) i-- dAtA[i] = 0x18 } if m.BlockId != 0 { i = encodeVarintTx(dAtA, i, uint64(m.BlockId)) i-- dAtA[i] = 0x10 } if len(m.Hash) > 0 { i -= len(m.Hash) copy(dAtA[i:], m.Hash) i = encodeVarintTx(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *FirstBlock) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.KeyId != 0 { n += 1 + sovTx(uint64(m.KeyId)) } if m.Timestamp != 0 { n += 1 + sovTx(uint64(m.Timestamp)) } l = len(m.PublicKey) if l > 0 { n += 1 + l + sovTx(uint64(l)) } l = len(m.NodePublicKey) if l > 0 { n += 1 + l + sovTx(uint64(l)) } l = len(m.StopNetworkCertBundle) if l > 0 { n += 1 + l + sovTx(uint64(l)) } if m.Test != 0 { n += 1 + sovTx(uint64(m.Test)) } if m.PrivateBlockchain != 0 { n += 1 + sovTx(uint64(m.PrivateBlockchain)) } return n } func (m *StopNetwork) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.KeyId != 0 { n += 1 + sovTx(uint64(m.KeyId)) } if m.Timestamp != 0 { n += 1 + sovTx(uint64(m.Timestamp)) } l = len(m.StopNetworkCert) if l > 0 { n += 1 + l + sovTx(uint64(l)) } return n } func (m *TxResult) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Hash) if l > 0 { n += 1 + l + sovTx(uint64(l)) } if m.BlockId != 0 { n += 1 + sovTx(uint64(m.BlockId)) } if m.Code != 0 { n += 1 + sovTx(uint64(m.Code)) } l = len(m.Result) if l > 0 { n += 1 + l + sovTx(uint64(l)) } l = len(m.Error) if l > 0 { n += 1 + l + sovTx(uint64(l)) } return n } func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *FirstBlock) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: FirstBlock: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: FirstBlock: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field KeyId", wireType) } m.KeyId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.KeyId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) } m.Timestamp = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Timestamp |= int64(b&0x7F) << shift if b < 0x80 { break } } case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.PublicKey = append(m.PublicKey[:0], dAtA[iNdEx:postIndex]...) if m.PublicKey == nil { m.PublicKey = []byte{} } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NodePublicKey", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.NodePublicKey = append(m.NodePublicKey[:0], dAtA[iNdEx:postIndex]...) if m.NodePublicKey == nil { m.NodePublicKey = []byte{} } iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StopNetworkCertBundle", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.StopNetworkCertBundle = append(m.StopNetworkCertBundle[:0], dAtA[iNdEx:postIndex]...) if m.StopNetworkCertBundle == nil { m.StopNetworkCertBundle = []byte{} } iNdEx = postIndex case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Test", wireType) } m.Test = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Test |= int64(b&0x7F) << shift if b < 0x80 { break } } case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field PrivateBlockchain", wireType) } m.PrivateBlockchain = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.PrivateBlockchain |= uint64(b&0x7F) << shift if b < 0x80 { break } } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthTx } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *StopNetwork) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: StopNetwork: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: StopNetwork: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field KeyId", wireType) } m.KeyId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.KeyId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) } m.Timestamp = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Timestamp |= int64(b&0x7F) << shift if b < 0x80 { break } } case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StopNetworkCert", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.StopNetworkCert = append(m.StopNetworkCert[:0], dAtA[iNdEx:postIndex]...) if m.StopNetworkCert == nil { m.StopNetworkCert = []byte{} } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthTx } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *TxResult) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: TxResult: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: TxResult: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) if m.Hash == nil { m.Hash = []byte{} } iNdEx = postIndex case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field BlockId", wireType) } m.BlockId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.BlockId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) } m.Code = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Code |= TxInvokeStatusCode(b&0x7F) << shift if b < 0x80 { break } } case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.Result = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthTx } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } m.Error = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthTx } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowTx } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowTx } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowTx } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthTx } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupTx } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthTx } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") ) ================================================ FILE: packages/protocols/block_counter.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package protocols import "github.com/IBAX-io/go-ibax/packages/storage/sqldb" type intervalBlocksCounter interface { count(state blockGenerationState) (int, error) } type blocksCounter struct { } func (bc *blocksCounter) count(state blockGenerationState) (int, error) { blockchain := &sqldb.BlockChain{} blocks, err := blockchain.GetNodeBlocksAtTime(state.start, state.start.Add(state.duration), state.nodePosition) if err != nil { return 0, err } return len(blocks), nil } ================================================ FILE: packages/protocols/block_counter_mock.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package protocols import "github.com/stretchr/testify/mock" // mockIntervalBlocksCounter is an autogenerated mock type for the intervalBlocksCounter type type mockIntervalBlocksCounter struct { mock.Mock } // count provides a mock function with given fields: state func (_m *mockIntervalBlocksCounter) count(state blockGenerationState) (int, error) { ret := _m.Called(state) var r0 int if rf, ok := ret.Get(0).(func(blockGenerationState) int); ok { r0 = rf(state) } else { r0 = ret.Get(0).(int) } var r1 error if rf, ok := ret.Get(1).(func(blockGenerationState) error); ok { r1 = rf(state) } else { r1 = ret.Error(1) } return r0, r1 } ================================================ FILE: packages/protocols/block_time_calculator.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package protocols import ( "time" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) // BlockTimeCalculator calculating block generation time type BlockTimeCalculator struct { clock utils.Clock blocksCounter intervalBlocksCounter firstBlockTime time.Time blockGenerationTime time.Duration blocksGap time.Duration nodesCount int64 } type blockGenerationState struct { start time.Time duration time.Duration nodePosition int64 } func NewBlockTimeCalculator(firstBlockTime time.Time, generationTime, blocksGap time.Duration, nodesCount int64) BlockTimeCalculator { return BlockTimeCalculator{ clock: &utils.ClockWrapper{}, blocksCounter: &blocksCounter{}, firstBlockTime: firstBlockTime, blockGenerationTime: generationTime, blocksGap: blocksGap, nodesCount: nodesCount, } } func (btc *BlockTimeCalculator) TimeToGenerate(nodePosition int64) (bool, error) { bgs, err := btc.countBlockTime(btc.clock.Now()) if err != nil { return false, err } blocks, err := btc.blocksCounter.count(bgs) if err != nil { return false, err } if blocks != 0 { return false, DuplicateBlockError } return bgs.nodePosition == nodePosition, nil } func (btc *BlockTimeCalculator) ValidateBlock(nodePosition int64, at time.Time) (bool, error) { bgs, err := btc.countBlockTime(at) if err != nil { return false, err } blocks, err := btc.blocksCounter.count(bgs) if err != nil { return false, err } if blocks != 0 { return false, DuplicateBlockError } return bgs.nodePosition == nodePosition, nil } func (btc *BlockTimeCalculator) SetClock(clock utils.Clock) *BlockTimeCalculator { btc.clock = clock return btc } func (btc *BlockTimeCalculator) setBlockCounter(counter intervalBlocksCounter) *BlockTimeCalculator { btc.blocksCounter = counter return btc } func (btc *BlockTimeCalculator) countBlockTime(blockTime time.Time) (blockGenerationState, error) { bgs := blockGenerationState{} nextBlockStart := btc.firstBlockTime var curNodeIndex int64 if blockTime.Before(nextBlockStart) { return blockGenerationState{}, TimeError } for { curBlockStart := nextBlockStart curBlockEnd := curBlockStart.Add(btc.blocksGap + btc.blockGenerationTime) nextBlockStart = curBlockEnd.Add(time.Second) if blockTime.Equal(curBlockStart) || blockTime.After(curBlockStart) && blockTime.Before(nextBlockStart) { bgs.start = curBlockStart bgs.duration = btc.blocksGap + btc.blockGenerationTime bgs.nodePosition = curNodeIndex return bgs, nil } if btc.nodesCount > 0 { curNodeIndex = (curNodeIndex + 1) % btc.nodesCount } } } func BuildBlockTimeCalculator(transaction *sqldb.DbTransaction) (BlockTimeCalculator, error) { var btc BlockTimeCalculator firstBlock := sqldb.BlockChain{} found, err := firstBlock.Get(1) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting first block") return btc, err } if !found { log.WithFields(log.Fields{"type": consts.NotFound, "error": err}).Error("first block not found") return btc, err } blockGenerationDuration := time.Millisecond * time.Duration(syspar.GetMaxBlockGenerationTime()) blocksGapDuration := time.Second * time.Duration(syspar.GetGapsBetweenBlocks()) btc = NewBlockTimeCalculator(time.Unix(firstBlock.Time, 0), blockGenerationDuration, blocksGapDuration, syspar.GetNumberOfNodesFromDB(transaction), ) return btc, nil } ================================================ FILE: packages/protocols/block_time_calculator_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package protocols import ( "testing" "time" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBlockTimeCalculator_TimeToGenerate(t *testing.T) { cases := []struct { firstBlockTime time.Time blockGenTime time.Duration blocksGap time.Duration nodesCount int64 clock utils.Clock blocksCounter intervalBlocksCounter nodePosition int64 result bool err error }{ { firstBlockTime: time.Unix(1, 0), clock: func() utils.Clock { mc := &utils.MockClock{} mc.On("Now").Return(time.Unix(0, 0)) return mc }(), err: TimeError, }, { firstBlockTime: time.Unix(1, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 3, nodePosition: 2, clock: func() utils.Clock { mc := &utils.MockClock{} mc.On("Now").Return(time.Unix(16, 0)) return mc }(), blocksCounter: func() intervalBlocksCounter { ibc := &mockIntervalBlocksCounter{} ibc.On("count", blockGenerationState{ start: time.Unix(13, 0), duration: time.Second * 5, nodePosition: 2, }).Return(1, nil) return ibc }(), result: false, err: DuplicateBlockError, }, { firstBlockTime: time.Unix(1, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 3, nodePosition: 2, clock: func() utils.Clock { mc := &utils.MockClock{} mc.On("Now").Return(time.Unix(16, 0)) return mc }(), blocksCounter: func() intervalBlocksCounter { ibc := &mockIntervalBlocksCounter{} ibc.On("count", blockGenerationState{ start: time.Unix(13, 0), duration: time.Second * 5, nodePosition: 2, }).Return(0, nil) return ibc }(), result: true, }, } for _, c := range cases { btc := NewBlockTimeCalculator(c.firstBlockTime, c.blockGenTime, c.blocksGap, c.nodesCount, ) execResult, execErr := btc. SetClock(c.clock). setBlockCounter(c.blocksCounter). TimeToGenerate(c.nodePosition) require.Equal(t, c.err, execErr) assert.Equal(t, c.result, execResult) } } func TestBlockTimeCalculator_ValidateBlock(t *testing.T) { cases := []struct { firstBlockTime time.Time blockGenTime time.Duration blocksGap time.Duration nodesCount int64 time time.Time blocksCounter intervalBlocksCounter nodePosition int64 result bool err error }{ { firstBlockTime: time.Unix(1, 0), time: time.Unix(0, 0), err: TimeError, }, { firstBlockTime: time.Unix(1, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 3, nodePosition: 2, time: time.Unix(16, 0), blocksCounter: func() intervalBlocksCounter { ibc := &mockIntervalBlocksCounter{} ibc.On("count", blockGenerationState{ start: time.Unix(13, 0), duration: time.Second * 5, nodePosition: 2, }).Return(1, nil) return ibc }(), result: false, err: DuplicateBlockError, }, { firstBlockTime: time.Unix(1, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 3, nodePosition: 2, time: time.Unix(16, 0), blocksCounter: func() intervalBlocksCounter { ibc := &mockIntervalBlocksCounter{} ibc.On("count", blockGenerationState{ start: time.Unix(13, 0), duration: time.Second * 5, nodePosition: 2, }).Return(0, nil) return ibc }(), result: true, }, } for _, c := range cases { btc := NewBlockTimeCalculator(c.firstBlockTime, c.blockGenTime, c.blocksGap, c.nodesCount, ) execResult, execErr := btc. setBlockCounter(c.blocksCounter). ValidateBlock(c.nodePosition, c.time) require.Equal(t, c.err, execErr) assert.Equal(t, c.result, execResult) } } func TestBlockTImeCalculator_countBlockTime(t *testing.T) { cases := []struct { firstBlockTime time.Time blockGenTime time.Duration blocksGap time.Duration nodesCount int64 clock time.Time result blockGenerationState err error }{ // Current time before first block case { firstBlockTime: time.Unix(1, 0), clock: time.Unix(0, 0), err: TimeError, }, // Zero duration case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 0, blocksGap: time.Second * 0, nodesCount: 5, clock: time.Unix(0, 0), result: blockGenerationState{ start: time.Unix(0, 0), duration: time.Second * 0, nodePosition: 0, }, }, // Duration testing case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 1, blocksGap: time.Second * 0, nodesCount: 5, clock: time.Unix(0, 0), result: blockGenerationState{ start: time.Unix(0, 0), duration: time.Second * 1, nodePosition: 0, }, }, // Duration testing case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 0, blocksGap: time.Second * 1, nodesCount: 5, clock: time.Unix(0, 0), result: blockGenerationState{ start: time.Unix(0, 0), duration: time.Second * 1, nodePosition: 0, }, }, // Duration testing case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 4, blocksGap: time.Second * 6, nodesCount: 5, clock: time.Unix(0, 0), result: blockGenerationState{ start: time.Unix(0, 0), duration: time.Second * 10, nodePosition: 0, }, }, // Block lowest time boundary case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 1, blocksGap: time.Second * 1, nodesCount: 10, clock: time.Unix(0, 0), result: blockGenerationState{ start: time.Unix(0, 0), duration: time.Second * 2, nodePosition: 0, }, }, // Block highest time boundary case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 10, clock: time.Unix(5, 999999999), result: blockGenerationState{ start: time.Unix(0, 0), duration: time.Second * 5, nodePosition: 0, }, }, // Last nodePosition case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 0, blocksGap: time.Second * 1, nodesCount: 3, clock: time.Unix(6, 0), result: blockGenerationState{ start: time.Unix(6, 0), duration: time.Second * 1, nodePosition: 0, }, }, // One node case { firstBlockTime: time.Unix(0, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 2, nodesCount: 1, clock: time.Unix(6, 0), result: blockGenerationState{ start: time.Unix(5, 0), duration: time.Second * 4, nodePosition: 0, }, }, // Custom firstBlockTime case { firstBlockTime: time.Unix(1, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 3, clock: time.Unix(13, 0), result: blockGenerationState{ start: time.Unix(13, 0), duration: time.Second * 5, nodePosition: 2, }, }, // Current time is in middle of interval case { firstBlockTime: time.Unix(1, 0), blockGenTime: time.Second * 2, blocksGap: time.Second * 3, nodesCount: 3, clock: time.Unix(16, 0), result: blockGenerationState{ start: time.Unix(13, 0), duration: time.Second * 5, nodePosition: 2, }, }, // Real life case { firstBlockTime: time.Unix(1519240000, 0), blockGenTime: time.Second * 4, blocksGap: time.Second * 5, nodesCount: 101, clock: time.Unix(1519241010, 1234), result: blockGenerationState{ start: time.Unix(1519241010, 0), duration: time.Second * 9, nodePosition: 0, }, }, } for _, c := range cases { btc := NewBlockTimeCalculator(c.firstBlockTime, c.blockGenTime, c.blocksGap, c.nodesCount, ) execResult, execErr := btc.countBlockTime(c.clock) require.Equal(t, c.err, execErr) assert.Equal(t, c.result, execResult) } } ================================================ FILE: packages/protocols/block_time_counter_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package protocols import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBlockTimeCounter(t *testing.T) { btc := BlockTimeCounter{ start: time.Unix(0, 0), duration: 5 * time.Second, numberNodes: 3, } at := time.Unix(13, 0) queue, err := btc.queue(at) assert.NoError(t, err) assert.Equal(t, 2, queue) np, err := btc.nodePosition(at) assert.NoError(t, err) assert.Equal(t, 2, np) nextTime, err := btc.nextTime(at, 2) assert.NoError(t, err) assert.Equal(t, time.Unix(25, 0).Add(1*time.Millisecond), nextTime) start, end, err := btc.RangeByTime(at) assert.NoError(t, err) assert.Equal(t, time.Unix(10, 0).Add(1*time.Millisecond), start) assert.Equal(t, time.Unix(15, 0), end) fmt.Println("ranges:", start.Unix(), end.Unix()) } func TestRangeByTime(t *testing.T) { btc := BlockTimeCounter{ start: time.Unix(1532977623, 0), duration: 4 * time.Second, numberNodes: 1, } st, end, err := btc.RangeByTime(time.Unix(1533062723, 0)) require.NoError(t, err) fmt.Println(st.Unix(), end.Unix()) st, end, err = btc.RangeByTime(time.Unix(1533062724, 0)) require.NoError(t, err) fmt.Println(st.Unix(), end.Unix()) // 1532977623 st, end, err = btc.RangeByTime(time.Unix(1532977624, 0)) require.NoError(t, err) fmt.Println(st.Unix(), end.Unix()) // 1533062719 1533062723 // 1533062723 1533062727 // 1532977623 1532977627 } func TestBlockOnlineTime(t *testing.T) { btc := BlockTimeCounter{ start: time.Unix(1607311077, 0), duration: 4000000000, numberNodes: 3, } //node23 1607336686 node22 1607392437 1607392568 node21 1607408766 1607393213 exists, err := btc.NodeTimeExists(time.Unix(1607393213, 0), int(0)) if err != nil { fmt.Println(err.Error()) } if exists { fmt.Println("exist") } } ================================================ FILE: packages/protocols/generation-queue.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package protocols import ( "errors" "time" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/conf/syspar" ) // BlockTimeChecker allow check queue to generate current block type BlockTimeChecker interface { TimeToGenerate(position int64) (bool, error) BlockForTimeExists(t time.Time, nodePosition int) (bool, error) RangeByTime(at time.Time) (start, end time.Time, err error) } var ( WrongNodePositionError = errors.New("wrong node position") TimeError = errors.New("current time before first block") DuplicateBlockError = errors.New("block for this time interval exists") ) type BlockTimeCounter struct { start time.Time duration time.Duration numberNodes int } // Queue returns serial queue number for time func (btc *BlockTimeCounter) queue(t time.Time) (int, error) { ut := t.Unix() t = time.Unix(ut, 0) if t.Before(btc.start) { return -1, TimeError } return int((t.Sub(btc.start) - 1) / btc.duration), nil } // NodePosition returns generating node position for time func (btc *BlockTimeCounter) nodePosition(t time.Time) (int, error) { queue, err := btc.queue(t) if err != nil { return -1, err } return queue % btc.numberNodes, nil } func (btc *BlockTimeCounter) NodeTimeExists(t time.Time, nodePosition int) (bool, error) { ps, err := btc.nodePosition(t) if err != nil { return false, err } if ps == nodePosition { return true, nil } //startInterval, endInterval, err := btc.RangeByTime(t) //if err != nil { // return false, err //} return false, nil } // BlockForTimeExists checks conformity between time and nodePosition // changes functionality of ValidateBlock prevent blockTimeCalculator func (btc *BlockTimeCounter) BlockForTimeExists(t time.Time, nodePosition int) (bool, error) { startInterval, endInterval, err := btc.RangeByTime(t) if err != nil { return false, err } b := &sqldb.BlockChain{} blocks, err := b.GetNodeBlocksAtTime(startInterval, endInterval, int64(nodePosition)) if err != nil { return false, err } return len(blocks) > 0, nil } // NextTime returns next generation time for node position at time func (btc *BlockTimeCounter) nextTime(t time.Time, nodePosition int) (time.Time, error) { if nodePosition >= btc.numberNodes { return time.Unix(0, 0), WrongNodePositionError } queue, err := btc.queue(t) if err != nil { return time.Unix(0, 0), err } curNodePosition := queue % btc.numberNodes d := nodePosition - curNodePosition if curNodePosition >= nodePosition { d += btc.numberNodes } return btc.start.Add(btc.duration*time.Duration(queue+d) + time.Millisecond), nil } // RangeByTime returns start and end of interval by time func (btc *BlockTimeCounter) RangeByTime(t time.Time) (start, end time.Time, err error) { queue, err := btc.queue(t) if err != nil { st := time.Unix(0, 0) return st, st, err } start = btc.start.Add(btc.duration*time.Duration(queue) + time.Second) end = start.Add(btc.duration - time.Second) return } // TimeToGenerate returns true if the generation queue at time belongs to the specified node func (btc *BlockTimeCounter) TimeToGenerate(at time.Time, nodePosition int) (bool, error) { if nodePosition >= btc.numberNodes { return false, WrongNodePositionError } position, err := btc.nodePosition(at) return position == nodePosition, err } // NewBlockTimeCounter return initialized BlockTimeCounter func NewBlockTimeCounter() *BlockTimeCounter { btc := BlockTimeCounter{ start: time.Unix(syspar.GetFirstBlockTimestamp(), 0), duration: syspar.GetMaxBlockTimeDuration(), numberNodes: int(syspar.GetCountOfActiveNodes()), } return &btc } ================================================ FILE: packages/publisher/publisher.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package publisher import ( "context" "fmt" "strconv" "sync" "time" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/centrifugal/gocent" "github.com/golang-jwt/jwt/v4" log "github.com/sirupsen/logrus" ) type ClientsChannels struct { storage map[int64]string sync.RWMutex } func (cn *ClientsChannels) Set(id int64, s string) { cn.Lock() defer cn.Unlock() cn.storage[id] = s } func (cn *ClientsChannels) Get(id int64) string { cn.RLock() defer cn.RUnlock() return cn.storage[id] } var ( clientsChannels = ClientsChannels{storage: make(map[int64]string)} centrifugoTimeout = time.Second * 5 publisher *gocent.Client config conf.CentrifugoConfig ) type CentJWT struct { Sub string jwt.RegisteredClaims } // InitCentrifugo client func InitCentrifugo(cfg conf.CentrifugoConfig) { config = cfg publisher = gocent.New(gocent.Config{ Addr: cfg.URL, Key: cfg.Key, }) } func GetJWTCent(userID, expire int64) (string, string, error) { timestamp := strconv.FormatInt(time.Now().Unix(), 10) centJWT := CentJWT{ Sub: strconv.FormatInt(userID, 10), RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Second * time.Duration(expire))}, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, centJWT) result, err := token.SignedString([]byte(config.Secret)) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("JWT centrifugo error") return "", "", err } clientsChannels.Set(userID, result) return result, timestamp, nil } // Write is publishing data to server func Write(account string, data string) error { ctx, cancel := context.WithTimeout(context.Background(), centrifugoTimeout) defer cancel() return publisher.Publish(ctx, "client"+account, []byte(data)) } // GetStats returns Stats func GetStats() (gocent.InfoResult, error) { if publisher == nil { return gocent.InfoResult{}, fmt.Errorf("publisher not initialized") } ctx, cancel := context.WithTimeout(context.Background(), centrifugoTimeout) defer cancel() return publisher.Info(ctx) } ================================================ FILE: packages/rollback/block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package rollback import ( "bytes" "errors" "fmt" "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" log "github.com/sirupsen/logrus" ) var ( ErrLastBlock = errors.New("block is not the last") ) // RollbackBlock is blocking rollback func RollbackBlock(data []byte) error { bl, err := block.UnmarshallBlock(bytes.NewBuffer(data), true) if err != nil { return err } b := &sqldb.BlockChain{} if _, err = b.GetMaxBlock(); err != nil { return err } if b.ID != bl.Header.BlockId { return ErrLastBlock } dbTx, err := sqldb.StartTransaction() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("starting transaction") return err } err = rollbackBlock(dbTx, bl) if err != nil { dbTx.Rollback() return err } if err = b.DeleteById(dbTx, bl.Header.BlockId); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting block by id") dbTx.Rollback() return err } b = &sqldb.BlockChain{} if _, err = b.Get(bl.Header.BlockId - 1); err != nil { dbTx.Rollback() return err } bl, err = block.UnmarshallBlock(bytes.NewBuffer(b.Data), false) if err != nil { dbTx.Rollback() return err } ib := &sqldb.InfoBlock{ Hash: b.Hash, RollbacksHash: b.RollbacksHash, BlockID: b.ID, NodePosition: strconv.Itoa(int(b.NodePosition)), KeyID: b.KeyID, Time: b.Time, CurrentVersion: strconv.Itoa(int(bl.Header.Version)), ConsensusMode: b.ConsensusMode, CandidateNodes: b.CandidateNodes, } err = ib.Update(dbTx) if err != nil { dbTx.Rollback() return err } return dbTx.Commit() } func rollbackBlock(dbTx *sqldb.DbTransaction, block *block.Block) error { // rollback transactions in reverse order logger := block.GetLogger() var transferSelfHashes = make([]string, 0) for i := len(block.Transactions) - 1; i >= 0; i-- { t := block.Transactions[i] t.DbTransaction = dbTx _, err := sqldb.MarkTransactionUnusedAndUnverified(dbTx, t.Hash()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("starting transaction") return err } _, err = sqldb.DeleteLogTransactionsByHash(dbTx, t.Hash()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting log transactions by hash") return err } ts := &sqldb.TransactionStatus{} err = ts.UpdateBlockID(dbTx, 0, t.Hash()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating block id in transaction status") return err } _, err = sqldb.DeleteQueueTxByHash(dbTx, t.Hash()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting transacion from queue by hash") return err } switch t.Inner.(type) { case *transaction.SmartTransactionParser: t.Inner.(*transaction.SmartTransactionParser).DbTransaction = t.DbTransaction if err = rollbackTransaction(t.Hash(), t.DbTransaction, logger); err != nil { return err } transferSelf := t.Inner.(*transaction.SmartTransactionParser).TxSmart.TransferSelf if transferSelf != nil { if strings.EqualFold("UTXO", transferSelf.Source) && strings.EqualFold("Account", transferSelf.Target) { transferSelfHashes = append(transferSelfHashes, fmt.Sprintf("%x", t.Hash())) } } } err = t.Inner.TxRollback() if err != nil { return err } } err := sqldb.RollbackOutputs(block.Header.BlockId, dbTx, transferSelfHashes, logger) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating outputs by block id") return err } return nil } ================================================ FILE: packages/rollback/rollback.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package rollback import ( "bytes" "strconv" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/pkg/errors" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // ToBlockID rollbacks blocks till blockID func ToBlockID(blockID int64, dbTx *sqldb.DbTransaction, logger *log.Entry) error { _, err := sqldb.MarkVerifiedAndNotUsedTransactionsUnverified() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("marking verified and not used transactions unverified") return err } // roll back our blocks for { block := &sqldb.BlockChain{} blocks, err := block.GetBlocks(blockID, syspar.GetMaxTxCount()) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting blocks") return err } if len(blocks) == 0 { break } for _, block := range blocks { // roll back our blocks to the block blockID err = RollbackBlock(block.Data) if err != nil { return errors.WithMessagef(err, "block_id: %d", block.ID) } logger.WithFields(log.Fields{"rollback_tx": block.Tx}).Infof("rollback %d successful", block.ID) } blocks = blocks[:0] } block := &sqldb.BlockChain{} _, err = block.Get(blockID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting block") return err } header, err := types.ParseBlockHeader(bytes.NewBuffer(block.Data), syspar.GetMaxBlockSize()) if err != nil { return err } ib := &sqldb.InfoBlock{ Hash: block.Hash, BlockID: header.BlockId, Time: header.Timestamp, EcosystemID: header.EcosystemId, KeyID: header.KeyId, NodePosition: converter.Int64ToStr(header.NodePosition), CurrentVersion: strconv.Itoa(int(header.Version)), RollbacksHash: block.RollbacksHash, ConsensusMode: block.ConsensusMode, CandidateNodes: block.CandidateNodes, } err = ib.Update(dbTx) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating info block") return err } return nil } ================================================ FILE: packages/rollback/transaction.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package rollback import ( "encoding/json" "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) func rollbackUpdatedRow(tx map[string]string, where string, dbTx *sqldb.DbTransaction, logger *log.Entry) error { var rollbackInfo map[string]string if err := json.Unmarshal([]byte(tx["data"]), &rollbackInfo); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling rollback.Data from json") return err } addSQLUpdate := "" for k, v := range rollbackInfo { k = `"` + strings.Trim(k, `"`) + `"` if v == "NULL" { addSQLUpdate += k + `=NULL,` } else if syspar.IsByteColumn(tx["table_name"], k) && len(v) != 0 { addSQLUpdate += k + `=decode('` + v + `','HEX'),` } else { addSQLUpdate += k + `='` + strings.Replace(v, `'`, `''`, -1) + `',` } } addSQLUpdate = addSQLUpdate[0 : len(addSQLUpdate)-1] if err := dbTx.Update(tx["table_name"], addSQLUpdate, where); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err, "rollback_id": tx["id"], "block_id": tx["block_id"], "where": where}).Error("updating table for rollback ") return err } return nil } func rollbackInsertedRow(tx map[string]string, where string, dbTx *sqldb.DbTransaction, logger *log.Entry) error { if err := dbTx.Delete(tx["table_name"], where); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "rollback_id": tx["id"], "table": tx["table_name"], "where": where}).Error("deleting from table for rollback") return err } return nil } func rollbackTransaction(txHash []byte, dbTx *sqldb.DbTransaction, logger *log.Entry) error { rollbackTx := &sqldb.RollbackTx{} txs, err := rollbackTx.GetRollbackTransactions(dbTx, txHash) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting rollback transactions") return err } for _, tx := range txs { if tx["table_name"] == smart.SysName { var sysData smart.SysRollData err := json.Unmarshal([]byte(tx["data"]), &sysData) if err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling rollback.Data from json") return err } switch sysData.Type { case "NewTable": err = smart.SysRollbackTable(dbTx, sysData) case "NewView": err = smart.SysRollbackView(dbTx, sysData) case "NewColumn": err = smart.SysRollbackColumn(dbTx, sysData) case "NewContract": err = smart.SysRollbackNewContract(sysData, tx["table_id"]) case "EditContract": err = smart.SysRollbackEditContract(dbTx, sysData, tx["table_id"]) case "NewEcosystem": err = smart.SysRollbackEcosystem(dbTx, sysData) case "ActivateContract": err = smart.SysRollbackActivate(sysData) case "DeactivateContract": err = smart.SysRollbackDeactivate(sysData) case "DeleteColumn": err = smart.SysRollbackDeleteColumn(dbTx, sysData) case "DeleteTable": err = smart.SysRollbackDeleteTable(dbTx, sysData) } if err != nil { return err } continue } table := tx[`table_name`] var ( rollbackInfo map[string]string ecoID, keyName string isFirstTable bool ) if len(tx["data"]) > 0 { if err := json.Unmarshal([]byte(tx["data"]), &rollbackInfo); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling rollback.Data from json") return err } if len(rollbackInfo) > 0 { if v, ok := rollbackInfo["ecosystem"]; ok { ecoID = v } } } if under := strings.IndexByte(table, '_'); under > 0 { keyName = table[under+1:] if v, ok := converter.FirstEcosystemTables[keyName]; ok && v { isFirstTable = true } } where := ` WHERE "id"='` if len(tx["data"]) <= 0 { if isFirstTable { var a []string if strings.Contains(tx["table_id"], ",") { a = strings.Split(tx["table_id"], ",") ecoID = a[1] where += a[0] + `'` } } } if len(tx["data"]) > 0 && isFirstTable { where += tx["table_id"] + `'` } if isFirstTable { where += fmt.Sprintf(` AND "ecosystem"='%d'`, converter.StrToInt64(ecoID)) tx[`table_name`] = `1_` + keyName } else { where += tx["table_id"] + `'` } if len(tx["data"]) > 0 { if err := rollbackUpdatedRow(tx, where, dbTx, logger); err != nil { return err } } else { if err := rollbackInsertedRow(tx, where, dbTx, logger); err != nil { return err } } } txForDelete := &sqldb.RollbackTx{TxHash: txHash} err = txForDelete.DeleteByHash(dbTx) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting rollback transaction by hash") return err } return nil } ================================================ FILE: packages/scheduler/contract/request.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package contract import ( "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strings" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) const ( headerAuthPrefix = "Bearer " ) type authResult struct { UID string `json:"uid,omitempty"` Token string `json:"token,omitempty"` } type contractResult struct { Hash string `json:"hash"` // These fields are used for CLB Message struct { Type string `json:"type,omitempty"` Error string `json:"error,omitempty"` } `json:"errmsg,omitempty"` Result string `json:"result,omitempty"` } // NodeContract creates a transaction to execute the contract. // The transaction is signed with a node key. func NodeContract(Name string) (result contractResult, err error) { var ( sign []byte ret authResult NodePrivateKey, NodePublicKey string ) err = sendAPIRequest(`GET`, `getuid`, nil, &ret, ``) if err != nil { return } auth := ret.Token if len(ret.UID) == 0 { err = fmt.Errorf(`getuid has returned empty uid`) return } NodePrivateKey, NodePublicKey = utils.GetNodeKeys() if len(NodePrivateKey) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("node private key is empty") err = errors.New(`empty node private key`) return } sign, err = crypto.SignString(NodePrivateKey, ret.UID) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("signing node uid") return } form := url.Values{"pubkey": {NodePublicKey}, "signature": {hex.EncodeToString(sign)}, `ecosystem`: {converter.Int64ToStr(1)}} var logret authResult err = sendAPIRequest(`POST`, `login`, &form, &logret, auth) if err != nil { return } auth = logret.Token form = url.Values{`clb`: {`true`}} err = sendAPIRequest(`POST`, `node/`+Name, &form, &result, auth) if err != nil { return } return } func sendAPIRequest(rtype, url string, form *url.Values, v any, auth string) error { client := &http.Client{} var ioform io.Reader if form != nil { ioform = strings.NewReader(form.Encode()) } req, err := http.NewRequest(rtype, fmt.Sprintf(`http://%s:%d%s%s`, conf.Config.HTTP.Host, conf.Config.HTTP.Port, consts.ApiPath, url), ioform) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("new api request") return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if len(auth) > 0 { req.Header.Set("Authorization", headerAuthPrefix+auth) } resp, err := client.Do(req) if err != nil { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("api request") return err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading api answer") return err } if resp.StatusCode != http.StatusOK { log.WithFields(log.Fields{"type": consts.NetworkError, "error": err}).Error("api status code") return fmt.Errorf(`%d %s`, resp.StatusCode, strings.TrimSpace(string(data))) } if err = json.Unmarshal(data, v); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling api answer") return err } return nil } ================================================ FILE: packages/scheduler/contract/task.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package contract import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/scheduler" log "github.com/sirupsen/logrus" ) // ContractHandler represents contract handler type ContractHandler struct { Contract string } // Run executes task func (ch *ContractHandler) Run(t *scheduler.Task) { _, err := NodeContract(ch.Contract) if err != nil { log.WithFields(log.Fields{"type": consts.ContractError, "error": err, "task": t.String(), "contract": ch.Contract}).Error("run contract task") return } log.WithFields(log.Fields{"task": t.String(), "contract": ch.Contract}).Info("run contract task") } ================================================ FILE: packages/scheduler/scheduler.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package scheduler import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/robfig/cron/v3" log "github.com/sirupsen/logrus" ) var scheduler *Scheduler func init() { scheduler = NewScheduler() } // Scheduler represents wrapper over the cron library type Scheduler struct { cron *cron.Cron } // AddTask adds task to cron func (s *Scheduler) AddTask(t *Task) error { err := t.ParseCron() if err != nil { return err } s.cron.Schedule(t, t) log.WithFields(log.Fields{"task": t.String()}).Info("task added") return nil } // UpdateTask updates task func (s *Scheduler) UpdateTask(t *Task) error { err := t.ParseCron() if err != nil { log.WithFields(log.Fields{"type": consts.ParseError, "error": err}).Error("parse cron format") return err } s.cron.Stop() defer s.cron.Start() entries := s.cron.Entries() for _, entry := range entries { task := entry.Schedule.(*Task) if task.ID == t.ID { *task = *t log.WithFields(log.Fields{"task": t.String()}).Info("task updated") return nil } continue } s.cron.Schedule(t, t) log.WithFields(log.Fields{"task": t.String()}).Info("task added") return nil } // NewScheduler creates a new scheduler func NewScheduler() *Scheduler { s := &Scheduler{cron: cron.New()} s.cron.Start() return s } // AddTask adds task to global scheduler func AddTask(t *Task) error { return scheduler.AddTask(t) } // UpdateTask updates task in global scheduler func UpdateTask(t *Task) error { return scheduler.UpdateTask(t) } // Parse parses cron format func Parse(cronSpec string) (cron.Schedule, error) { sch, err := cron.ParseStandard(cronSpec) if err != nil { log.WithFields(log.Fields{"type": consts.ParseError, "error": err}).Error("parse cron format") return nil, err } return sch, nil } ================================================ FILE: packages/scheduler/scheduler_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package scheduler import ( "testing" "time" ) func TestParse(t *testing.T) { cases := map[string]string{ "60 * * * *": "End of range (60) above maximum (59): 60", "0-59 0-23 1-31 1-12 0-6": "", "*/2 */2 */2 */2 */2": "", "* * * * *": "", } for cronSpec, expectedErr := range cases { _, err := Parse(cronSpec) if err != nil { if errStr := err.Error(); errStr != expectedErr { t.Errorf("cron: %s, expected: %s, got: %s\n", cronSpec, expectedErr, errStr) } continue } if expectedErr != "" { t.Errorf("cron: %s, error: %s\n", cronSpec, err) } } } type mockHandler struct { count int } func (mh *mockHandler) Run(t *Task) { mh.count++ } // This test required timeout 60s // go test -timeout 60s func TestTask(t *testing.T) { var taskID = "task1" sch := NewScheduler() task := &Task{ID: taskID} nextTime := task.Next(time.Now()) if nextTime != zeroTime { t.Error("error") } task = &Task{CronSpec: "60 * * * *"} err := sch.AddTask(task) if errStr := err.Error(); errStr != "End of range (60) above maximum (59): 60" { t.Error(err) } err = sch.UpdateTask(task) if errStr := err.Error(); errStr != "End of range (60) above maximum (59): 60" { t.Error(err) } err = sch.UpdateTask(&Task{ID: "task2"}) if err != nil { t.Error(err) } handler := &mockHandler{} task = &Task{ID: taskID, CronSpec: "* * * * *", Handler: handler} sch.UpdateTask(task) now := time.Now() time.Sleep(task.Next(now).Sub(now) + time.Second) if handler.count == 0 { t.Error("task not running") } } ================================================ FILE: packages/scheduler/task.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package scheduler import ( "fmt" "time" "github.com/robfig/cron/v3" ) var zeroTime time.Time // Handler represents interface of task handler type Handler interface { Run(*Task) } // Task represents task type Task struct { ID string CronSpec string Handler Handler schedule cron.Schedule } // String returns description of task func (t *Task) String() string { return fmt.Sprintf("%s %s", t.ID, t.CronSpec) } // ParseCron parsed cron format func (t *Task) ParseCron() error { if len(t.CronSpec) == 0 { return nil } var err error t.schedule, err = Parse(t.CronSpec) return err } // Next returns time for next task func (t *Task) Next(tm time.Time) time.Time { if len(t.CronSpec) == 0 { return zeroTime } return t.schedule.Next(tm) } // Run executes task func (t *Task) Run() { t.Handler.Run(t) } ================================================ FILE: packages/script/cmds_list.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script const ( // cmdUnknown = iota // error // here are described the commands of bytecode cmdPush = iota + 1 // Push value to stack cmdVar // Push variable to stack cmdExtend // Push extend variable to stack cmdCallExtend // Call extend function cmdPushStr // Push ident as string cmdCall // call a function cmdCallVariadic // call a variadic function cmdReturn // return from function cmdIf // run block if Value is true cmdElse // run block if Value is false cmdAssignVar // list of assigned var cmdAssign // assign cmdLabel // label for continue cmdContinue // continue from label cmdWhile // while cmdBreak // break cmdIndex // get index [] cmdSetIndex // set index [] cmdFuncName // set func name Func(...).Name(...) cmdUnwrapArr // unwrap array to stack cmdMapInit // map initialization cmdArrayInit // array initialization cmdError // error command ) // the commands for operations in expressions are listed below const ( cmdNot = iota | 0x0100 cmdSign ) const ( cmdAdd = iota | 0x0200 cmdSub cmdMul cmdDiv cmdAnd cmdOr cmdEqual cmdNotEq cmdLess cmdNotLess cmdGreat cmdNotGreat cmdSys = 0xff cmdUnary uint16 = 50 ) ================================================ FILE: packages/script/code_block.go ================================================ package script import ( "reflect" "strings" ) /* Byte code could be described as a tree where functions and contracts are on the top level and nesting goes further according to nesting of bracketed. Tree nodes are structures of 'CodeBlock' type. For instance, func a { if b { while d { } } if c { } } will be compiled into CodeBlock(a) which will have two child blocks CodeBlock (b) and CodeBlock (c) that are responsible for executing bytecode inside if. CodeBlock (b) will have a child CodeBlock (d) with a cycle. */ // CodeBlock contains all information about compiled block {...} and its children type CodeBlock struct { Objects map[string]*ObjInfo Type ObjectType Owner *OwnerInfo // Types that are valid to be assigned to Info: // *FuncInfo // *ContractInfo Info isCodeBlockInfo Parent *CodeBlock Vars []reflect.Type Code ByteCodes Children CodeBlocks } type isCodeBlockInfo interface { isCodeBlockInfo() } func (*FuncInfo) isCodeBlockInfo() {} func (*ContractInfo) isCodeBlockInfo() {} func (m *CodeBlock) GetInfo() isCodeBlockInfo { if m != nil { return m.Info } return nil } func (m *CodeBlock) GetFuncInfo() *FuncInfo { if x, ok := m.GetInfo().(*FuncInfo); ok { return x } return nil } func (m *CodeBlock) GetContractInfo() *ContractInfo { if x, ok := m.GetInfo().(*ContractInfo); ok { return x } return nil } // ByteCode stores a command and an additional parameter. type ByteCode struct { Cmd uint16 Line uint16 Value any } // CodeBlocks is a slice of blocks type CodeBlocks []*CodeBlock func (bs *CodeBlocks) push(x any) { *bs = append(*bs, x.(*CodeBlock)) } func (bs *CodeBlocks) peek() *CodeBlock { bsLen := len(*bs) if bsLen == 0 { return nil } return (*bs)[bsLen-1] } func (bs *CodeBlocks) get(idx int) *CodeBlock { if idx >= 0 && len(*bs) > 0 && len(*bs) > idx { return (*bs)[idx] } return nil } // ByteCodes is the slice of ByteCode items type ByteCodes []*ByteCode func (bs *ByteCodes) push(x any) { *bs = append(*bs, x.(*ByteCode)) } func (bs *ByteCodes) peek() *ByteCode { bsLen := len(*bs) if bsLen == 0 { return nil } return (*bs)[bsLen-1] } func newByteCode(cmd uint16, line uint16, value any) *ByteCode { return &ByteCode{Cmd: cmd, Line: line, Value: value} } // OwnerInfo storing info about owner type OwnerInfo struct { StateID uint32 `json:"state"` Active bool `json:"active"` TableID int64 `json:"tableid"` WalletID int64 `json:"walletid"` TokenID int64 `json:"tokenid"` } // ObjInfo is the common object type type ObjInfo struct { Type ObjectType // Types that are valid to be assigned to Value: // *CodeBlock // *ExtFuncInfo // *ObjInfo_Variable // *ObjInfo_ExtendVariable Value isObjInfoValue } type isObjInfoValue interface { isObjInfoValue() } type ObjInfo_Variable struct { Name string Index int } type ObjInfo_ExtendVariable struct { //object extend variable name Name string } func (*CodeBlock) isObjInfoValue() {} func (*ExtFuncInfo) isObjInfoValue() {} func (*ObjInfo_Variable) isObjInfoValue() {} func (*ObjInfo_ExtendVariable) isObjInfoValue() {} func (m *ObjInfo) GetValue() isObjInfoValue { if m != nil { return m.Value } return nil } func (m *ObjInfo) GetCodeBlock() *CodeBlock { if x, ok := m.GetValue().(*CodeBlock); ok { return x } return nil } func (m *ObjInfo) GetExtFuncInfo() *ExtFuncInfo { if x, ok := m.GetValue().(*ExtFuncInfo); ok { return x } return nil } func (m *ObjInfo) GetVariable() *ObjInfo_Variable { if x, ok := m.GetValue().(*ObjInfo_Variable); ok { return x } return nil } func (m *ObjInfo) GetExtendVariable() *ObjInfo_ExtendVariable { if x, ok := m.GetValue().(*ObjInfo_ExtendVariable); ok { return x } return nil } func NewCodeBlock() *CodeBlock { b := &CodeBlock{ Objects: make(map[string]*ObjInfo), // Reserved 256 indexes for system purposes Children: make(CodeBlocks, 256, 1024), } b.Extend(NewExtendData()) return b } // Extend sets the extended variables and functions func (b *CodeBlock) Extend(ext *ExtendData) { for key, item := range ext.Objects { fobj := reflect.ValueOf(item).Type() switch fobj.Kind() { case reflect.Func: _, canWrite := ext.WriteFuncs[key] data := &ExtFuncInfo{ Name: key, Params: make([]reflect.Type, fobj.NumIn()), Results: make([]reflect.Type, fobj.NumOut()), Auto: make([]string, fobj.NumIn()), Variadic: fobj.IsVariadic(), Func: item, CanWrite: canWrite} for i := 0; i < fobj.NumIn(); i++ { if isauto, ok := ext.AutoPars[fobj.In(i).String()]; ok { data.Auto[i] = isauto } data.Params[i] = fobj.In(i) } for i := 0; i < fobj.NumOut(); i++ { data.Results[i] = fobj.Out(i) } b.Objects[key] = &ObjInfo{Type: ObjectType_ExtFunc, Value: data} } } } func (b *CodeBlock) getObjByNameExt(name string, state uint32) (ret *ObjInfo) { sname := StateName(state, name) if ret = b.getObjByName(name); ret == nil && len(sname) > 0 { ret = b.getObjByName(sname) } return } func (block *CodeBlock) getObjByName(name string) (ret *ObjInfo) { var ok bool names := strings.Split(name, `.`) for i, name := range names { ret, ok = block.Objects[name] if !ok { return nil } if i == len(names)-1 { return } if ret.Type != ObjectType_Contract && ret.Type != ObjectType_Func { return nil } block = ret.GetCodeBlock() } return } func (cb *CodeBlock) contractBaseCost() int64 { var cost int64 c := cb.GetContractInfo() if c != nil { cost += int64(len(cb.Objects) * CostCall) cost += int64(len(c.Settings) * CostCall) if c.Tx != nil { cost += int64(len(*c.Tx) * CostExtend) } } return cost } func (block *CodeBlock) isParentContract() bool { if block.Parent != nil && block.Parent.Type == ObjectType_Contract { return true } return false } func setWritable(block *CodeBlocks) { for i := len(*block) - 1; i >= 0; i-- { blockItem := (*block)[i] if blockItem.Type == ObjectType_Func { blockItem.GetFuncInfo().CanWrite = true } if blockItem.Type == ObjectType_Contract { blockItem.GetContractInfo().CanWrite = true } } } func (ret *ObjInfo) getInParams() int { if ret.Type == ObjectType_ExtFunc { return len(ret.GetExtFuncInfo().Params) } return len(ret.GetCodeBlock().GetFuncInfo().Params) } ================================================ FILE: packages/script/compile.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) // operPrior contains command and its priority type operPrior struct { Cmd uint16 // identifier of the command Priority uint16 // priority of the command } // State contains a new state and a handle function type compileState struct { NewState stateTypes // a new state FuncFlag int // a handle flag FuncHandle compileFunc // a handle function } func newCompileState(newState stateTypes, funcFlag int) compileState { return compileState{NewState: newState, FuncFlag: funcFlag, FuncHandle: funcHandles[funcFlag]} } const ( mapConst = iota mapVar mapMap mapExtend mapArray mustKey mustColon mustComma mustValue ) type mapItem struct { Type int Value any } // The compiler converts the sequence of lexemes into the bytecodes using a finite state machine the same as // it was implemented in lexical analysis. The difference lays in that we do not convert the list of // states and transitions to the intermediate array. const ( // Errors of compilation // errNoError = iota errUnknownCmd = iota + 1 // unknown command errMustName // must be the name errMustLCurly // must be '{' errMustRCurly // must be '}' errParams // wrong parameters errVars // wrong variables errVarType // must be type errAssign // must be '=' errStrNum // must be number or string ) var ( // Array of operations and their priority opers = map[uint32]operPrior{ isOr: {Cmd: cmdOr, Priority: 10}, isAnd: {Cmd: cmdAnd, Priority: 15}, isEqEq: {Cmd: cmdEqual, Priority: 20}, isNotEq: {Cmd: cmdNotEq, Priority: 20}, isLess: {Cmd: cmdLess, Priority: 22}, isGrEq: {Cmd: cmdNotLess, Priority: 22}, isGreat: {Cmd: cmdGreat, Priority: 22}, isLessEq: {Cmd: cmdNotGreat, Priority: 22}, isPlus: {Cmd: cmdAdd, Priority: 25}, isMinus: {Cmd: cmdSub, Priority: 25}, isAsterisk: {Cmd: cmdMul, Priority: 30}, isSolidus: {Cmd: cmdDiv, Priority: 30}, isSign: {Cmd: cmdSign, Priority: cmdUnary}, isNot: {Cmd: cmdNot, Priority: cmdUnary}, isLPar: {Cmd: cmdSys, Priority: 0xff}, isRPar: {Cmd: cmdSys, Priority: 0}, } ) // StateName checks the name of the contract and modifies it to @[state]name if it is necessary. func StateName(state uint32, name string) string { if !strings.HasPrefix(name, `@`) { return fmt.Sprintf(`@%d%s`, state, name) } else if len(name) > 1 && (name[1] < '0' || name[1] > '9') { name = `@1` + name[1:] } return name } // CompileBlock compile the source code into the CodeBlock structure with a byte-code func (vm *VM) CompileBlock(input []rune, owner *OwnerInfo) (*CodeBlock, error) { root := &CodeBlock{Owner: owner} lexemes, err := lexParser(input) if err != nil { return nil, err } if len(lexemes) == 0 { return root, nil } curState := stateRoot stack := make([]stateTypes, 0, 64) blockstack := make(CodeBlocks, 1, 64) blockstack[0] = root fork := 0 for i := 0; i < len(lexemes); i++ { var ( newState compileState ok bool ) lexeme := lexemes[i] if newState, ok = states[curState][int(lexeme.Type)]; !ok { newState = states[curState][0] } nextState := newState.NewState & 0xff if (newState.NewState & stateFork) > 0 { fork = i } if (newState.NewState & stateToFork) > 0 { i = fork fork = 0 lexeme = lexemes[i] } if (newState.NewState & stateStay) > 0 { curState = nextState i-- continue } if nextState == stateEval { if newState.NewState&stateLabel > 0 { blockstack.peek().Code.push(newByteCode(cmdLabel, lexeme.Line, 0)) } curlen := len(blockstack.peek().Code) if err := vm.compileEval(&lexemes, &i, &blockstack); err != nil { return nil, err } if (newState.NewState&stateMustEval) > 0 && curlen == len(blockstack.peek().Code) { log.WithFields(log.Fields{"type": consts.ParseError}).Error("there is not eval expression") return nil, fmt.Errorf("there is not eval expression") } nextState = curState } if (newState.NewState & statePush) > 0 { stack = append(stack, curState) top := blockstack.peek() if top.Objects == nil { top.Objects = make(map[string]*ObjInfo) } block := &CodeBlock{Parent: top} top.Children.push(block) blockstack.push(block) } if (newState.NewState & statePop) > 0 { if len(stack) == 0 { return nil, fError(&blockstack, errMustLCurly, lexeme) } nextState = stack[len(stack)-1] stack = stack[:len(stack)-1] if len(blockstack) >= 2 { prev := blockstack.get(len(blockstack) - 2) if len(prev.Code) > 0 && (*prev).Code[len((*prev).Code)-1].Cmd == cmdContinue { (*prev).Code = (*prev).Code[:len((*prev).Code)-1] prev = blockstack.peek() (*prev).Code.push(newByteCode(cmdContinue, lexeme.Line, 0)) } } blockstack = blockstack[:len(blockstack)-1] } if (newState.NewState & stateToBlock) > 0 { nextState = stateBlock } if (newState.NewState & stateToBody) > 0 { nextState = stateBody } if newState.FuncFlag > 0 { if err := funcHandles[newState.FuncFlag](&blockstack, nextState, lexeme); err != nil { lexeme.GetLogger().WithFields(log.Fields{"type": consts.ParseError, "nextState": nextState, "flag": newState.FuncFlag, "err": err, "lex_value": lexeme.Value}).Errorf("func handles") return nil, err } } curState = nextState } if len(stack) > 0 { return nil, fError(&blockstack, errMustRCurly, lexemes[len(lexemes)-1]) } for _, item := range root.Objects { if item.Type == ObjectType_Contract { if cond, ok := item.GetCodeBlock().Objects[`conditions`]; ok { if cond.Type == ObjectType_Func && cond.GetCodeBlock().GetFuncInfo().CanWrite { return nil, errCondWrite } } } } return root, nil } // FlushBlock loads the compiled CodeBlock into the virtual machine func (vm *VM) FlushBlock(root *CodeBlock) { shift := len(vm.Children) for key, item := range root.Objects { if cur, ok := vm.Objects[key]; ok { switch item.Type { case ObjectType_Contract: root.Objects[key].GetCodeBlock().GetContractInfo().ID = cur.GetCodeBlock().GetContractInfo().ID + flushMark case ObjectType_Func: root.Objects[key].GetCodeBlock().GetFuncInfo().ID = cur.GetCodeBlock().GetFuncInfo().ID + flushMark vm.Objects[key].Value = root.Objects[key].Value } } vm.Objects[key] = item } for _, item := range root.Children { switch item.Type { case ObjectType_Contract: if item.GetContractInfo().ID > flushMark { item.GetContractInfo().ID -= flushMark vm.Children[item.GetContractInfo().ID] = item shift-- continue } item.Parent = vm.CodeBlock item.GetContractInfo().ID += uint32(shift) case ObjectType_Func: if item.GetFuncInfo().ID > flushMark { item.GetFuncInfo().ID -= flushMark vm.Children[item.GetFuncInfo().ID] = item shift-- continue } item.Parent = vm.CodeBlock item.GetFuncInfo().ID += uint32(shift) } vm.Children = append(vm.Children, item) } } // FlushExtern switches off the extern mode of the compilation func (vm *VM) FlushExtern() { vm.Extern = false return } // Compile compiles a source code and loads the byte-code into the virtual machine func (vm *VM) Compile(input []rune, owner *OwnerInfo) error { root, err := vm.CompileBlock(input, owner) if err == nil { vm.FlushBlock(root) } return err } func findVar(name string, block *CodeBlocks) (ret *ObjInfo, owner *CodeBlock) { var ok bool i := len(*block) - 1 for ; i >= 0; i-- { ret, ok = (*block)[i].Objects[name] if ok { return ret, (*block)[i] } } return nil, nil } func (vm *VM) findObj(name string, block *CodeBlocks) (ret *ObjInfo, owner *CodeBlock) { sname := StateName((*block)[0].Owner.StateID, name) ret, owner = findVar(name, block) if ret != nil { return } else if len(sname) > 0 { if ret, owner = findVar(sname, block); ret != nil { return } } if ret = vm.getObjByName(name); ret == nil && len(sname) > 0 { ret = vm.getObjByName(sname) } return } func (vm *VM) getInitValue(lexemes *Lexemes, ind *int, block *CodeBlocks) (value mapItem, err error) { var ( subArr []mapItem subMap *types.Map ) i := *ind lexeme := (*lexemes)[i] switch lexeme.Type { case isLBrack: subArr, err = vm.getInitArray(lexemes, &i, block) if err == nil { value = mapItem{Type: mapArray, Value: subArr} } case isLCurly: subMap, err = vm.getInitMap(lexemes, &i, block, false) if err == nil { value = mapItem{Type: mapMap, Value: subMap} } case lexExtend: value = mapItem{Type: mapExtend, Value: lexeme.Value} case lexIdent: objInfo, tobj := vm.findObj(lexeme.Value.(string), block) if objInfo == nil { err = fmt.Errorf(eUnknownIdent, lexeme.Value) } else { value = mapItem{Type: mapVar, Value: &VarInfo{Obj: objInfo, Owner: tobj}} } case lexNumber, lexString: value = mapItem{Type: mapConst, Value: lexeme.Value} default: err = errUnexpValue } *ind = i return } func (vm *VM) getInitMap(lexemes *Lexemes, ind *int, block *CodeBlocks, oneItem bool) (*types.Map, error) { var next int if !oneItem { next = 1 } i := *ind + next key := `` ret := types.NewMap() state := mustKey main: for ; i < len(*lexemes); i++ { lexeme := (*lexemes)[i] switch lexeme.Type { case lexNewLine: continue case isRCurly: break main case isComma, isRBrack: if oneItem { *ind = i - 1 return ret, nil } } switch state { case mustComma: if lexeme.Type != isComma { return nil, errUnexpComma } state = mustKey case mustColon: if lexeme.Type != isColon { return nil, errUnexpColon } state = mustValue case mustKey: switch lexeme.Type & 0xff { case lexIdent, lexString: key = lexeme.Value.(string) case lexExtend: key = `$` + lexeme.Value.(string) case lexKeyword: for ikey, v := range keywords { if fmt.Sprint(v) == fmt.Sprint(lexeme.Value) { key = ikey if v == keyFunc && i < len(*lexemes)-1 && (*lexemes)[i+1].Type&0xff == lexIdent { continue main } break } } default: return nil, errUnexpKey } state = mustColon case mustValue: mapi, err := vm.getInitValue(lexemes, &i, block) if err != nil { return nil, err } ret.Set(key, mapi) state = mustComma } } if ret.IsEmpty() && state == mustKey { return nil, errUnexpKey } if i == len(*lexemes) { return nil, errUnclosedMap } *ind = i return ret, nil } func (vm *VM) getInitArray(lexemes *Lexemes, ind *int, block *CodeBlocks) ([]mapItem, error) { i := *ind + 1 ret := make([]mapItem, 0) state := mustValue main: for ; i < len(*lexemes); i++ { lexeme := (*lexemes)[i] switch lexeme.Type { case lexNewLine: continue case isRBrack: break main } switch state { case mustComma: if lexeme.Type != isComma { return nil, errUnexpComma } state = mustValue case mustValue: if i+1 < len(*lexemes) && (*lexemes)[i+1].Type == isColon { subMap, err := vm.getInitMap(lexemes, &i, block, true) if err != nil { return nil, err } ret = append(ret, mapItem{Type: mapMap, Value: subMap}) } else { arri, err := vm.getInitValue(lexemes, &i, block) if err != nil { return nil, err } ret = append(ret, arri) } state = mustComma } } if len(ret) > 0 && state == mustValue { return nil, errUnexpValue } if i == len(*lexemes) { return nil, errUnclosedArray } *ind = i return ret, nil } // This function is responsible for the compilation of expressions func (vm *VM) compileEval(lexemes *Lexemes, ind *int, block *CodeBlocks) error { var indexInfo *IndexInfo i := *ind curBlock := (*block)[len(*block)-1] buffer := make(ByteCodes, 0, 20) bytecode := make(ByteCodes, 0, 100) parcount := make([]int, 0, 20) setIndex := false noMap := false prevLex := uint32(0) main: for ; i < len(*lexemes); i++ { var cmd *ByteCode var call bool lexeme := (*lexemes)[i] logger := lexeme.GetLogger() if !noMap { if lexeme.Type == isLCurly { pMap, err := vm.getInitMap(lexemes, &i, block, false) if err != nil { return err } bytecode.push(newByteCode(cmdMapInit, lexeme.Line, pMap)) continue } if lexeme.Type == isLBrack { pArray, err := vm.getInitArray(lexemes, &i, block) if err != nil { return err } bytecode.push(newByteCode(cmdArrayInit, lexeme.Line, pArray)) continue } } noMap = false switch lexeme.Type { case isRCurly, isLCurly: i-- if prevLex == isComma || prevLex == lexOper { return errEndExp } break main case lexNewLine: if i > 0 && ((*lexemes)[i-1].Type == isComma || (*lexemes)[i-1].Type == lexOper) { continue main } for k := len(buffer) - 1; k >= 0; k-- { if buffer[k].Cmd == cmdSys { continue main } } break main case isLPar: buffer.push(newByteCode(cmdSys, lexeme.Line, uint16(0xff))) case isLBrack: buffer.push(newByteCode(cmdSys, lexeme.Line, uint16(0xff))) case isComma: if len(parcount) > 0 { parcount[len(parcount)-1]++ } for len(buffer) > 0 { prev := buffer[len(buffer)-1] if prev.Cmd == cmdSys && prev.Value.(uint16) == 0xff { break } else { bytecode.push(prev) buffer = buffer[:len(buffer)-1] } } case isRPar: noMap = true for { if len(buffer) == 0 { logger.WithFields(log.Fields{"lex_value": lexeme.Value, "type": consts.ParseError}).Error("there is not pair") return fmt.Errorf(`there is not pair`) } prev := buffer[len(buffer)-1] buffer = buffer[:len(buffer)-1] if prev.Value.(uint16) == 0xff { break } else { bytecode.push(prev) } } if len(buffer) > 0 { if prev := buffer[len(buffer)-1]; prev.Cmd == cmdFuncName { buffer = buffer[:len(buffer)-1] (*prev).Value = FuncNameCmd{Name: prev.Value.(FuncNameCmd).Name, Count: parcount[len(parcount)-1]} parcount = parcount[:len(parcount)-1] bytecode.push(prev) } var tail *ByteCode if prev := buffer[len(buffer)-1]; prev.Cmd == cmdCall || prev.Cmd == cmdCallVariadic { objInfo := prev.Value.(*ObjInfo) if (objInfo.Type == ObjectType_Func && objInfo.GetCodeBlock().GetFuncInfo().CanWrite) || (objInfo.Type == ObjectType_ExtFunc && objInfo.GetExtFuncInfo().CanWrite) { setWritable(block) } if objInfo.Type == ObjectType_Func && objInfo.GetCodeBlock().GetFuncInfo().Names != nil { if len(bytecode) == 0 || bytecode[len(bytecode)-1].Cmd != cmdFuncName { bytecode.push(newByteCode(cmdPush, lexeme.Line, nil)) } if i < len(*lexemes)-4 && (*lexemes)[i+1].Type == isDot { if (*lexemes)[i+2].Type != lexIdent { log.WithFields(log.Fields{"type": consts.ParseError}).Error("must be the name of the tail") return fmt.Errorf(`must be the name of the tail`) } names := prev.Value.(*ObjInfo).GetCodeBlock().GetFuncInfo().Names if _, ok := (*names)[(*lexemes)[i+2].Value.(string)]; !ok { if i < len(*lexemes)-5 && (*lexemes)[i+3].Type == isLPar { objInfo, _ := vm.findObj((*lexemes)[i+2].Value.(string), block) if objInfo != nil && (objInfo.Type == ObjectType_Func || objInfo.Type == ObjectType_ExtFunc) { tail = newByteCode(uint16(cmdCall), lexeme.Line, objInfo) } } if tail == nil { log.WithFields(log.Fields{"type": consts.ParseError, "tail": (*lexemes)[i+2].Value.(string)}).Error("unknown function tail") return fmt.Errorf(`unknown function tail '%s'`, (*lexemes)[i+2].Value.(string)) } } if tail == nil { buffer.push(newByteCode(cmdFuncName, lexeme.Line, FuncNameCmd{Name: (*lexemes)[i+2].Value.(string)})) count := 0 if (*lexemes)[i+3].Type != isRPar { count++ } parcount = append(parcount, count) i += 2 break } } } count := parcount[len(parcount)-1] parcount = parcount[:len(parcount)-1] if prev.Value.(*ObjInfo).Type == ObjectType_ExtFunc { var errtext string extinfo := prev.Value.(*ObjInfo).GetExtFuncInfo() wantlen := len(extinfo.Params) for _, v := range extinfo.Auto { if len(v) > 0 { wantlen-- } } if count != wantlen && (!extinfo.Variadic || count < wantlen) { errtext = fmt.Sprintf(eWrongParams, extinfo.Name, wantlen) logger.WithFields(log.Fields{"error": errtext, "type": consts.ParseError}).Error(errtext) return fmt.Errorf(errtext) } } if prev.Cmd == cmdCallVariadic { bytecode.push(newByteCode(cmdPush, lexeme.Line, count)) } buffer = buffer[:len(buffer)-1] bytecode.push(prev) if tail != nil { buffer.push(tail) parcount = append(parcount, 1) i += 2 } } } case isRBrack: noMap = true for { if len(buffer) == 0 { logger.WithFields(log.Fields{"lex_value": lexeme.Value, "type": consts.ParseError}).Error("there is not pair") return fmt.Errorf(`there is not pair`) } prev := buffer[len(buffer)-1] buffer = buffer[:len(buffer)-1] if prev.Value.(uint16) == 0xff { break } else { bytecode.push(prev) } } if len(buffer) > 0 { if prev := buffer[len(buffer)-1]; prev.Cmd == cmdIndex { buffer = buffer[:len(buffer)-1] if i < len(*lexemes)-1 && (*lexemes)[i+1].Type == isEq { i++ setIndex = true indexInfo = prev.Value.(*IndexInfo) noMap = false continue } bytecode.push(prev) } } if (*lexemes)[i+1].Type == isLBrack { return errMultiIndex } case lexOper: if oper, ok := opers[lexeme.Value.(uint32)]; ok { var prevType uint32 if i > 0 { prevType = (*lexemes)[i-1].Type } if oper.Cmd == cmdSub && (i == 0 || (prevType != lexNumber && prevType != lexIdent && prevType != lexExtend && prevType != lexString && prevType != isRCurly && prevType != isRBrack && prevType != isRPar)) { oper.Cmd = cmdSign oper.Priority = cmdUnary } else if prevLex == lexOper && oper.Priority != cmdUnary { return errOper } byteOper := newByteCode(oper.Cmd, lexeme.Line, oper.Priority) for { if len(buffer) == 0 { buffer.push(byteOper) break } else { prev := buffer[len(buffer)-1] if prev.Value.(uint16) >= oper.Priority && oper.Priority != cmdUnary && prev.Cmd != cmdSys { if prev.Value.(uint16) == cmdUnary { // Right to left unar := len(buffer) - 1 for ; unar > 0 && buffer[unar-1].Value.(uint16) == cmdUnary; unar-- { } bytecode = append(bytecode, buffer[unar:]...) buffer = buffer[:unar] } else { bytecode.push(prev) buffer = buffer[:len(buffer)-1] } } else { buffer.push(byteOper) break } } } } else { logger.WithFields(log.Fields{"lex_value": lexeme.Value, "type": consts.ParseError}).Error("unknown operator") return fmt.Errorf(`unknown operator %d`, lexeme.Value.(uint32)) } case lexNumber, lexString: noMap = true cmd = newByteCode(cmdPush, lexeme.Line, lexeme.Value) case lexExtend: noMap = true if i < len(*lexemes)-2 { if (*lexemes)[i+1].Type == isLPar { count := 0 if (*lexemes)[i+2].Type != isRPar { count++ } parcount = append(parcount, count) buffer.push(newByteCode(cmdCallExtend, lexeme.Line, lexeme.Value.(string))) call = true } } if !call { cmd = newByteCode(cmdExtend, lexeme.Line, lexeme.Value.(string)) if i < len(*lexemes)-1 && (*lexemes)[i+1].Type == isLBrack { buffer.push(newByteCode(cmdIndex, lexeme.Line, &IndexInfo{Extend: lexeme.Value.(string)})) } } case lexIdent: noMap = true objInfo, tobj := vm.findObj(lexeme.Value.(string), block) if objInfo == nil && (!vm.Extern || i >= len(*lexemes)-2 || (*lexemes)[i+1].Type != isLPar) { logger.WithFields(log.Fields{"lex_value": lexeme.Value, "type": consts.ParseError}).Error("unknown identifier") return fmt.Errorf(eUnknownIdent, lexeme.Value) } if i < len(*lexemes)-2 { if (*lexemes)[i+1].Type == isLPar { var ( isContract bool objContract *CodeBlock ) if vm.Extern && objInfo == nil { objInfo = &ObjInfo{Type: ObjectType_Contract} } if objInfo == nil || (objInfo.Type != ObjectType_ExtFunc && objInfo.Type != ObjectType_Func && objInfo.Type != ObjectType_Contract) { logger.WithFields(log.Fields{"lex_value": lexeme.Value, "type": consts.ParseError}).Error("unknown function") return fmt.Errorf(`unknown function %s`, lexeme.Value.(string)) } if objInfo.Type == ObjectType_Contract { if objInfo.Value != nil { objContract = objInfo.GetCodeBlock() } objInfo, tobj = vm.findObj(`ExecContract`, block) isContract = true } cmdCall := uint16(cmdCall) if (objInfo.Type == ObjectType_ExtFunc && objInfo.GetExtFuncInfo().Variadic) || (objInfo.Type == ObjectType_Func && objInfo.GetCodeBlock().GetFuncInfo().Variadic) { cmdCall = cmdCallVariadic } count := 0 if (*lexemes)[i+2].Type != isRPar { count++ } buffer.push(newByteCode(cmdCall, lexeme.Line, objInfo)) if isContract { name := StateName((*block)[0].Owner.StateID, lexeme.Value.(string)) for j := len(*block) - 1; j >= 0; j-- { topblock := (*block)[j] if topblock.Type == ObjectType_Contract { if name == topblock.GetContractInfo().Name { return errRecursion } if topblock.GetContractInfo().Used == nil { topblock.GetContractInfo().Used = make(map[string]bool) } topblock.GetContractInfo().Used[name] = true } } if objContract != nil && objContract.GetContractInfo().CanWrite { setWritable(block) } bytecode.push(newByteCode(cmdPush, lexeme.Line, name)) if count == 0 { count = 2 bytecode.push(newByteCode(cmdPush, lexeme.Line, "")) bytecode.push(newByteCode(cmdPush, lexeme.Line, "")) } count++ } if lexeme.Value.(string) == `CallContract` { count++ bytecode.push(newByteCode(cmdPush, lexeme.Line, (*block)[0].Owner.StateID)) } parcount = append(parcount, count) call = true } if (*lexemes)[i+1].Type == isLBrack { if objInfo == nil || objInfo.Type != ObjectType_Var { logger.WithFields(log.Fields{"lex_value": lexeme.Value, "type": consts.ParseError}).Error("unknown variable") return fmt.Errorf(`unknown variable %s`, lexeme.Value.(string)) } buffer.push(newByteCode(cmdIndex, lexeme.Line, &IndexInfo{VarOffset: objInfo.GetVariable().Index, Owner: tobj})) } } if !call { if objInfo.Type != ObjectType_Var { return fmt.Errorf(`unknown variable %s`, lexeme.Value.(string)) } cmd = newByteCode(cmdVar, lexeme.Line, &VarInfo{Obj: objInfo, Owner: tobj}) } } if lexeme.Type != lexNewLine { prevLex = lexeme.Type } if lexeme.Type&0xff == lexKeyword { if lexeme.Value.(uint32) == keyTail { cmd = newByteCode(cmdUnwrapArr, lexeme.Line, 0) } } if cmd != nil { bytecode.push(cmd) } } *ind = i if prevLex == lexOper { return errEndExp } for i := len(buffer) - 1; i >= 0; i-- { if buffer[i].Cmd == cmdSys { log.WithFields(log.Fields{"type": consts.ParseError}).Error("there is not pair") return fmt.Errorf(`there is not pair`) } bytecode.push(buffer[i]) } if setIndex { bytecode.push(newByteCode(cmdSetIndex, 0, indexInfo)) } curBlock.Code = append(curBlock.Code, bytecode...) return nil } // ContractsList returns list of contracts names from source of code func ContractsList(value string) ([]string, error) { names := make([]string, 0) lexemes, err := lexParser([]rune(value)) if err != nil { log.WithFields(log.Fields{"type": consts.ParseError, "error": err}).Error("getting contract list") return names, err } var level int for i, lexeme := range lexemes { switch lexeme.Type { case isLCurly: level++ case isRCurly: level-- case lexKeyword | (keyContract << 8), lexKeyword | (keyFunc << 8): if level == 0 && i+1 < len(lexemes) && lexemes[i+1].Type == lexIdent { names = append(names, lexemes[i+1].Value.(string)) } } } return names, nil } ================================================ FILE: packages/script/compile_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "fmt" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" ) type TestVM struct { Input string Func string Output any } func (block *CodeBlock) String() (ret string) { if (*block).Objects != nil { ret = fmt.Sprintf("Objects: %v\n", (*block).Objects) } ret += fmt.Sprintf("Type: %v \n", (*block).Type) if (*block).Children != nil { ret += fmt.Sprintf("Blocks: [\n") for i, item := range (*block).Children { ret += fmt.Sprintf("{%d: %v}\n", i, item.String()) } ret += fmt.Sprintf("]") } return } func getMap() *types.Map { myMap := types.NewMap() myMap.Set(`par0`, `Parameter 0`) myMap.Set(`par1`, `Parameter 1`) return myMap } func getArray() []any { myMap := types.NewMap() myMap.Set(`par0`, `Parameter 0`) myMap.Set(`par1`, `Parameter 1`) return []any{myMap, "The second string", int64(2000)} } // Str converts the value to a string func str(v any) (ret string) { return fmt.Sprint(v) } func lenArray(par []any) int64 { return int64(len(par)) } func Money(v any) (ret decimal.Decimal) { ret, _ = ValueToDecimal(v) return ret } func outMap(v *types.Map) string { return fmt.Sprint(v) } func TestVMCompile(t *testing.T) { test := []TestVM{ {`contract sets { settings { val = 1.56 rate = 100000000000 name="Name parameter" } action { $result = Settings("@22sets","name") } } func result() string { var par map return CallContract("@22sets", par) + "=" + sets() } `, `result`, `Name parameter=Name parameter`}, {`func proc(par string) string { return par + "proc" } func forarray string { var my map var ret array var myret array ret = GetArray() myret[1] = "Another " my = ret[0] my["par3"] = 3456 ret[2] = "Test" return Sprintf("result=%s+%s+%d+%s", ret[1], my["par0"], my["par3"], myret[1] + ret[2]) }`, `forarray`, `result=The second string+Parameter 0+3456+Another Test`}, {`func proc(par string) string { return par + "proc" } func formap string { var my map var ret map ret = GetMap() // Println(ret) //Println("Ooops", ret["par0"], ret["par1"]) my["par1"] = "my value" + proc(" space ") my["par2"] = 203 * (100-86) return Sprintf("result=%s+%d+%s+%s+%d", ret["par1"], my["par2"] + 32, my["par1"], proc($glob["test"] ), $glob["number"] ) }`, `formap`, `result=Parameter 1+2874+my value space proc+String valueproc+1001`}, {`func runtime string { var i int i = 50 return Sprintf("val=%d", i 0) }`, `runtime`, `runtime panic error,reflect: CallSlice using int64 as type string`}, {`func nop { return } func loop string { var i int while true {//i < 10 { i=i+1 if i==5 { continue } if i == 121 { i = i+ 4 break } } nop() return Sprintf("val=%d", i) }`, `loop`, `val=125`}, {`contract my { data { Par1 int Par2 string } func conditions { var q int Println("Front", $Par1, $parent) // my("Par1,Par2,ext", 123, "Parameter 2", "extended" ) } func action { Println("Main", $Par2, $ext) } } contract mytest { func init string { empty() my("Par1,Par2,ext", 123, "Parameter 2", "extended" ) //my("Par1,Par2,ext", 33123, "Parameter 332", "33extended" ) //@26empty("test",10) empty("toempty", 10) Println( "mytest", $parent) return "OK INIT" } } contract empty { conditions {Println("EmptyCond") } action { Println("Empty", $parent) if 1 { my("Par1,Par2,ext", 123, "Parameter 2", "extended" ) } } } `, `mytest.init`, `OK INIT`}, {`func line_test string { return "Start " + Sprintf( "My String %s %d %d", "Param 1", 24, 345 + 789) }`, `line_test`, `Start My String Param 1 24 1134`}, {`func err_test string { if 1001.02 { error "Error message err_test" } return "OK" }`, `err_test`, `{"type":"error","error":"Error message err_test"}`}, {`contract my { data { PublicKey bytes FirstName string MiddleName string "optional" LastName string } func init string { return "OK" } }`, `my.init`, `OK`}, {`func temp3 string { var i1 i2 int, s1 string, s2 string i2, i1 = 348, 7 if i1 > 5 { var i5 int, s3 string i5 = 26788 s1 = "s1 string" i2 = (i1+2)*i5+i2 s2 = Sprintf("temp 3 function %s %d", Sprintf("%s + %d", s1, i2), -1 ) } return s2 }`, `temp3`, `temp 3 function s1 string + 241440 -1`}, {`func params2(myval int, mystr string ) string { if 101>myval { if myval == 90 { } else { return Sprintf("myval=%d + %s", myval, mystr ) } } return "OOPs" } func temp2 string { if true { return params2(51, "Params 2 test") } } `, `temp2`, `myval=51 + Params 2 test`}, {`func params(myval int, mystr string ) string { return Sprintf("Params function %d %s", 33 + myval + $test1, mystr + " end" ) } func temp string { return "Prefix " + params(20, "Test string " + $test2) + $test3( 202 ) } `, `temp`, `Prefix Params function 154 Test string test 2 endtest=202=test`}, {`func my_test string { return Sprintf("Called my_test %s %d", "Ooops", 777) } contract my { func initf string { return Sprintf("%d %s %s %s", 65123 + (1001-500)*11, my_test(), "Test message", Sprintf("> %s %d <","OK", 999 )) } }`, `my.initf`, `70634 Called my_test Ooops 777 Test message > OK 999 <`}, {`contract vars { func cond() string {return "vars"} func actions() { var test int} }`, `vars.cond`, `vars`}, {`func mytail(name string, tail ...) string { if lenArray(tail) == 0 { return name } if lenArray(tail) == 1 { return Sprintf("%s=%v ", name, tail[0]) } return Sprintf("%s=%v+%v ", name, tail[1], tail[0]) } func emptytail(tail ...) string { return Sprintf("%d ", lenArray(tail)) } func sum(out string, values ...) string { var i, res int while i < lenArray(values) { res = res + values[i] i = i+1 } return Sprintf(out, res) } func calltail() string { var out string out = emptytail() + emptytail(10) + emptytail("name1", "name2") out = out + mytail("OK") + mytail("1=", 11) + mytail("2=", "name", 11) return out + sum("Sum: %d", 10, 20, 30, 40) } `, `calltail`, `0 1 2 OK1==11 2==11+name Sum: 100`}, {`func DBFind( table string).Columns(columns string) . Where(format string, tail ...). Limit(limit int). Offset(offset int) string { Println("DBFind", table, tail) return Sprintf("%s %s %s %d %d=", table, columns, format, limit, offset) } func names() string { var out, cols string cols = "name,value" out = DBFind( "mytable") + DBFind( "keys" ).Columns(cols)+ DBFind( "keys" ).Offset(199).Columns("qq"+"my") out = out + DBFind( "table").Columns("name").Where("id=?", 100).Limit(10) + DBFind( "table").Where("request") return out }`, `names`, `mytable 0 0=keys name,value 0 0=keys qqmy 0 199=table name id=? 10 0=table request 0 0=`}, {`contract seterr { func getset string { var i int i = MyFunc("qqq", 10) return "OK" } }`, `seterr.getset`, `unknown identifier MyFunc`}, {`func one() int { return 9 } func signfunc string { var myarr array myarr[0] = 0 myarr[1] = 1 var i, k, j int k = one()-2 j = /*comment*/-3 i = lenArray(myarr) - 1 return Sprintf("%s %d %d %d %d %d", "ok", lenArray(myarr)-1, i, k, j, -4) }`, `signfunc`, `ok 1 1 7 -3 -4`}, {`func exttest() string { return Replace("text", "t") } `, `exttest`, `function Replace must have 4 parameters`}, {`func mytest(first string, second int) string { return Sprintf("%s %d", first, second) } func test() { return mytest("one", "two") } `, `test`, `parameter 2 has wrong type [:5]`}, {`func mytest(first string, second int) string { return Sprintf("%s %d", first, second) } func test() string { return mytest("one") } `, `test`, `wrong count of parameters [:5]`}, { `func ifMap string { var m map if m { return "empty" } m["test"]=1 if m { return "not empty" } return error "error" }`, "ifMap", "not empty", }, {`func One(list array, name string) string { if list { var row map row = list[0] return row[name] } return nil } func Row(list array) map { var ret map if list { ret = list[0] } return ret } func GetData().WhereId(id int) array { var par array var item map item["id"] = str(id) item["name"] = "Test value " + str(id) par[0] = item return par } func GetEmpty().WhereId(id int) array { var par array return par } func result() string { var m map var s string m = GetData().WhereId(123).Row() s = GetEmpty().WhereId(1).One("name") if s != nil { return "problem" } return m["id"] + "=" + GetData().WhereId(100).One("name") }`, `result`, `123=Test value 100`}, {`func mapbug() string { $data[10] = "extend ok" return $data[10] }`, `mapbug`, `extend ok`}, {`func result() string { var myarr array myarr[0] = "string" myarr[1] = 7 myarr[2] = "9th item" return Sprintf("RESULT=%s %d %v", myarr...) }`, `result`, `RESULT=string 7 9th item`}, {`func find().Where(pattern string, params ...) string { return Sprintf(pattern, params ...) } func row().Where(pattern string, params ...) string { return find().Where(pattern, params ...) } func result() string { return row().Where("%d %d", 10, 20) } `, `result`, `10 20`}, {`func result string { var arr array var mymap map arr[100000] = 0 var i int while i < 100 { mymap[str(i)] = 10 i = i + 1 } i = i + "2" i = (i - "10")/"2"*"3" return Sprintf("%T %[1]v", .21 + i) }`, `result`, `float64 138.21`}, {`func money_test string { var my2, m1 money my2 = 100 m1 = 1.2 return Sprintf( "Account %v %v %v", my2/Money(3), my2 - Money(5.6), m1*Money(5) + Money(my2)) }`, `money_test`, `Account 33 95 105`}, {`func long() int { return 99999999999999999999 } func result() string { return Sprintf("ok=%d", long()) }`, `result`, `strconv.ParseInt: parsing "99999999999999999999": value out of range 99999999999999999999 [Ln:2 Col:34]`}, {`func result() string { var i, result int if true { if false { result = 99 } else { result = 5 } } if i == 1 { result = 20 } elif i> 0 { result = 30 } elif i == 0 { result = result + 50 if true { i=10 } } elif i==10 { Println("3") result = 0 i=33 } elif false { Println("4") result = 1 } else { Println("5") result = 2 } if i == 4 { result = result } elif i == 20 { result = 22 } else { result = result + 23 i = 11 } if i == 11 { result = result + 7 } else { result = 0 } if result == 85 { if false { result = 1 } elif 0 { result = 5 } elif 1 { result = result + 10 } } if result == 10 { result = 11 } elif result == 95 { result = result + 1 if false { result = 0 } elif true { result = result + 4 } } return Sprintf("%d", result) } `, `result`, `100`}, {`func initerr string { var my map return {qqq `, `initerr`, `unclosed map initialization`}, {`func initmap string { var my, sub map var list array var i int i = 256 var s string $ext = "Ooops" s = "Spain" my = {conditions: "$Conditions"} list = [0, i, {"item": i}, [$ext]] sub = {"name": "John", "lastname": "Smith", myarr: []} my = {qqq: 10, "22": "MY STRING", /* comment*/ "float": 1.2, "ext": $ext, "in": true, "var": i, sub: sub, "Company": {"Name": "Ltd", Country: s, Arr: [s, 20, "finish"]}} return outMap(my) + Sprintf("%v", list) }`, `initmap`, `map[qqq:10 22:MY STRING float:1.2 ext:Ooops in:true var:256 sub:map[name:John lastname:Smith myarr:[]] Company:map[Name:Ltd Country:Spain Arr:[Spain 20 finish]]][0 256 map[item:256] [Ooops]]`}, {`func test() string { var where map where["name"] = {"$in": "menus_names"} return Sprintf("%v", where) }`, `test`, `map[name:map[$in:menus_names]]`}, {`contract TestCyr { data {} conditions { } action { //test var a map a["test"] = "test" $result = a["test"] } } func result() string { var par map return CallContract("TestCyr", par) }`, `result`, `test`}, {`contract MainCond { conditions { error $test } action { $result = "OK" } } func result() bool { return MainCond } `, `result`, `unknown variable MainCond`}, {`func myFunc(my string) string { return Sprintf("writable: %s", my) } contract mySet { conditions { myFunc("test") } action { myFunc("test") } } contract myExec { conditions { mySet() } action { mySet() $result = "OK" } } func result() string { myExec() return "COND" }`, `result`, `'conditions' cannot call contracts or functions which can modify the blockchain database.`}, {`func test string { var s string var m map m = {f: 5, b: 2, a: 1, d: 3, c: 0, e: 4} var i int while i<3{ s = s + Sprintf("%v", m) i = i + 1 } return s } `, `test`, `map[f:5 b:2 a:1 d:3 c:0 e:4]map[f:5 b:2 a:1 d:3 c:0 e:4]map[f:5 b:2 a:1 d:3 c:0 e:4]`}, {`contract qqq3 { data { Name string "aaq" Temp } action { $result = $Name } } `, `qqq3.action`, `expecting type of the data field [Ln:5 Col:1]`}, {`contract qqq2 { data { Name string "aaq" "awede" } action { $result = $Name } } `, `qqq2.action`, `unexpected tag [Ln:4 Col:6]`}, {`contract qqq1 { data { string Name qwerty } action { $result = $Name } } `, `qqq1.action`, `expecting name of the data field [Ln:3 Col:6]`}, {`contract qqq { data { Name qwerty } action { $result = $Name } } `, `qqq.action`, `expecting type of the data field [Ln:3 Col:11]`}, {`contract qq3 { data { Id uint } action { $result = "OK" } } `, `qq3.action`, `expecting type of the data field [Ln:3 Col:9]`}, {`contract qq2 { data { Id, ID2 int } action { $result = str($Id) + str($ID2) } } func getqq() string { return qq2("Id,ID2", 10,20) }`, `getqq`, `1020`}, {`func IND() string { var a,b,d array a[0] = 100 a[1] = 555 b[0] = 200 d[0] = a d[1] = b d[0][0] = 777 }`, `IND`, `multi-index is not supported`}, {`func result() { /* aa /*bb*/ error "test"*/ }`, `result`, `unexpected operator; expecting operand`}, {`func result() { error "test"* }`, `result`, `unexpected end of the expression`}, {`func bool_test string { var i bool var k bool var out string i = true if i == true { out = "OK" } if i != k { out = out + "ok" } if i { out = out + "I" } return out }`, `bool_test`, `OKokI`}, } vm := NewVM() vm.Extern = true vm.Extend(&ExtendData{map[string]any{"Println": fmt.Println, "Sprintf": fmt.Sprintf, "GetMap": getMap, "GetArray": getArray, "lenArray": lenArray, "outMap": outMap, "str": str, "Money": Money, "Replace": strings.Replace}, nil, map[string]struct{}{"Sprintf": {}}}) for ikey, item := range test { if ikey > 100 { break } source := []rune(item.Input) if err := vm.Compile(source, &OwnerInfo{StateID: uint32(ikey) + 22, Active: true, TableID: 1}); err != nil { if err.Error() != item.Output { t.Errorf(`%s != %s`, err, item.Output) break } } else { glob := types.NewMap() glob.Set(`test`, `String value`) glob.Set(`number`, 1001) if out, err := vm.Call(item.Func, nil, map[string]any{ `rt_state`: uint32(ikey) + 22, `data`: make([]any, 0), `test1`: 101, `test2`: `test 2`, "glob": glob, `test3`: func(param int64) string { return fmt.Sprintf("test=%d=test", param) }, }); err == nil { if out[0].(string) != item.Output { t.Error(fmt.Errorf("err want to %v, but out %v\n", item.Output, out[0])) break } } else if err.Error() != item.Output { t.Error(err) break } } } } func TestContractList(t *testing.T) { test := []TestLexeme{{`contract NewContract { conditions { ValidateCondition($Conditions,$ecosystem_id) while i < Len(list) { if IsObject(list[i], $ecosystem_id) { warning Sprintf("Contract or function %s exists", list[i] ) } } } action { } func price() int { return SysParamInt("price_create_contract") } }func MyFunc {}`, `NewContract,MyFunc`}, {`contract demo_contract { data { contract_txt str } func test() { } conditions { if $contract_txt="" { warning "Sorry, you do not have contract access to this action." } } } contract another_contract {} func main { func subfunc(){}}`, `demo_contract,another_contract,main`}, } for _, item := range test { list, _ := ContractsList(item.Input) if strings.Join(list, `,`) != item.Output { t.Error(`wrong names`, strings.Join(list, `,`)) break } } } func TestVM_CompileBlock(t *testing.T) { r := []rune(` contract aaa { data { key_id int //time string } func bbb(account_id int){ //$key_id = 2 account_id = 3 } conditions { //$key_id = 3 } action { } } `) type args struct { input []rune owner *OwnerInfo } vm := NewVM() tests := []struct { name string args args want *CodeBlock wantErr assert.ErrorAssertionFunc }{ {name: "collides", args: args{ r, &OwnerInfo{StateID: 1}, }, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { if err != nil { return true } return false }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := vm.CompileBlock(tt.args.input, tt.args.owner) if !tt.wantErr(t, err, fmt.Sprintf("CompileBlock(%v, %v)", tt.args.input, tt.args.owner)) { return } assert.Equalf(t, tt.want, got, "CompileBlock(%v, %v)", tt.args.input, tt.args.owner) }) } } ================================================ FILE: packages/script/errors.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import "errors" const ( eContractLoop = `there is loop in %s contract` eSysVar = `system variable $%s cannot be changed` eSysFunc = `system function '%s' cannot be changed` eDataParamVarCollides = `param variable '%s' in the data section of the contract '%s' collides with the 'builtin' variable` eTypeParam = `parameter %d has wrong type` eUndefinedParam = `%s is not defined` eUnknownContract = `unknown contract %s` eWrongParams = `function %s must have %d parameters` eArrIndex = `index of array cannot be type %s` eMapIndex = `index of map cannot be type %s` eUnknownIdent = `unknown identifier %s` eWrongVar = `wrong var %v` eDataType = `expecting type of the data field [Ln:%d Col:%d]` eDataName = `expecting name of the data field [Ln:%d Col:%d]` eDataTag = `unexpected tag [Ln:%d Col:%d]` eConditionNotAllowed = `condition %s is not allowed` ) var ( errContractPars = errors.New(`wrong contract parameters`) errWrongCountPars = errors.New(`wrong count of parameters`) errDivZero = errors.New(`divided by zero`) errUnsupportedType = errors.New(`unsupported combination of types in the operator`) errMaxArrayIndex = errors.New(`the index is out of range`) errMaxMapCount = errors.New(`the maxumim length of map`) errRecursion = errors.New(`the contract can't call itself recursively`) errUnclosedArray = errors.New(`unclosed array initialization`) errUnclosedMap = errors.New(`unclosed map initialization`) errUnexpKey = errors.New(`unexpected lexeme; expecting string key`) errUnexpColon = errors.New(`unexpected lexeme; expecting colon`) errUnexpComma = errors.New(`unexpected lexeme; expecting comma`) errUnexpValue = errors.New(`unexpected lexeme; expecting string, int value or variable`) errCondWrite = errors.New(`'conditions' cannot call contracts or functions which can modify the blockchain database`) errMultiIndex = errors.New(`multi-index is not supported`) errSelfAssignment = errors.New(`self assignment`) errEndExp = errors.New(`unexpected end of the expression`) errOper = errors.New(`unexpected operator; expecting operand`) errIncorrectParameter = errors.New(`incorrect parameter of the condition function`) ) ================================================ FILE: packages/script/eval.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) type evalCode struct { Source string Code *CodeBlock } var ( evals = make(map[uint64]*evalCode) ) // CompileEval compiles conditional expression func (vm *VM) CompileEval(input string, state uint32) error { source := `func eval bool { return ` + input + `}` if input == `1` || input == `0` { source = ` func eval bool { if ` + input + ` == 1 { return true } else { return false } }` } block, err := vm.CompileBlock([]rune(source), &OwnerInfo{StateID: state}) if err != nil { return err } crc := crypto.CalcChecksum([]byte(input)) evals[crc] = &evalCode{Source: input, Code: block} return nil } // EvalIf runs the conditional expression. It compiles the source code before that if that's necessary. func (vm *VM) EvalIf(input string, state uint32, vars map[string]any) (bool, error) { if len(input) == 0 { return true, nil } crc := crypto.CalcChecksum([]byte(input)) if eval, ok := evals[crc]; !ok || eval.Source != input { if err := vm.CompileEval(input, state); err != nil { log.WithFields(log.Fields{"type": consts.EvalError, "error": err}).Error("compiling eval") return false, err } } ret, err := NewRunTime(vm, syspar.GetMaxCost()).Run(evals[crc].Code.Children[0], nil, vars) if err == nil { if len(ret) == 0 { return false, nil } return valueToBool(ret[0]), nil } return false, err } ================================================ FILE: packages/script/eval_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "fmt" "testing" ) type TestComp struct { Input string Output string } func Multi(a, b int64) (int64, error) { return a + b*2, nil } func TestEvalIf(t *testing.T) { test := []TestComp{ {`Multi(45, $citizenId")`, `there is not pair`}, {"34 + `45` < 0", `runtime panic error`}, {"Multi( (34+35)*2, Multi( $citizenId, 56))== 1 || Multi( (34+35)*2, Multi( $citizenId, 56))== 0", `false`}, {"5 + 9 > 10", `true`}, {"34 == 45", `false`}, {"1345", `true`}, {"13/13-1", `false`}, {"7665 > ($citizenId-48000)", "false"}, {"56788 + 1 >= $citizenId", "true"}, {"76 < $citizenId", "true"}, {"56789 <= $citizenId", "true"}, {"56 == 56", "true"}, {"37 != 37", "false"}, {"!!(1-1)", "false"}, {"!!$citizenId || $wallet_id", "true"}, {"!789", "false"}, {"$citizenId == 56780 + 9", `true`}, {"qwerty(45)", `unknown identifier qwerty`}, {"Multi(2, 5) > 36", "false"}, {"789 63 == 63", "true"}, {"+421", "stack is empty"}, {"1256778+223445==1480223", "true"}, {"(67-34789)*3 == -104166", "true"}, {"(5+78)*(1563-527) == 85988", "true"}, {"124 * (143-527", "there is not pair"}, {"341 * 234/0", "divided by zero"}, {"0 == ((15+82)*2 + 5)/2 - 99", "true"}, {"Multi( (34+35)*2, Multi( $citizenId, 56))== 1 || Multi( (34+35)*2, Multi( $citizenId, 56))== 0", `false`}, {"2+ Multi( (34+35)*2, Multi( $citizenId, 56)) /2 == 56972", `true`}, {"$citizenId && 0", "false"}, {"0|| ($citizenId + $wallet_id == 950240)", "true"}, } vars := map[string]any{ `citizenId`: 56789, `wallet_id`: 893451, } vm := NewVM() vm.Extend(&ExtendData{map[string]any{"Multi": Multi}, nil, nil}) for _, item := range test { out, err := vm.EvalIf(item.Input, 0, vars) if err != nil { if err.Error() != item.Output { t.Error(`error of ifeval ` + item.Input + ` ` + err.Error()) } } else { if fmt.Sprint(out) != item.Output { t.Error(`error of ifeval ` + item.Input + ` Output:` + fmt.Sprint(out)) } } } } ================================================ FILE: packages/script/extend.go ================================================ package script const ( Extend_type = `type` Extend_time = `time` Extend_ecosystem_id = `ecosystem_id` Extend_node_position = `node_position` Extend_block = `block` Extend_key_id = `key_id` Extend_account_id = `account_id` Extend_block_key_id = `block_key_id` Extend_parent = `parent` Extend_txcost = `txcost` Extend_txhash = `txhash` Extend_result = `result` Extend_sc = `sc` Extend_contract = `contract` Extend_block_time = `block_time` Extend_original_contract = `original_contract` Extend_this_contract = `this_contract` Extend_guest_key = `guest_key` Extend_guest_account = `guest_account` Extend_black_hole_key = `black_hole_key` Extend_black_hole_account = `black_hole_account` Extend_white_hole_key = `white_hole_key` Extend_white_hole_account = `white_hole_account` Extend_pre_block_data_hash = `pre_block_data_hash` Extend_gen_block = `gen_block` Extend_time_limit = `time_limit` Extend_rt_state = `rt_state` Extend_rt = `rt` Extend_stack = `stack` Extend_loop = `loop_` ) const ( // system variable cannot be changed sysVars_block = `block` sysVars_block_key_id = `block_key_id` sysVars_block_time = `block_time` sysVars_data = `data` sysVars_ecosystem_id = `ecosystem_id` sysVars_key_id = `key_id` sysVars_account_id = `account_id` sysVars_node_position = `node_position` sysVars_parent = `parent` sysVars_original_contract = `original_contract` sysVars_sc = `sc` sysVars_contract = `contract` sysVars_stack = `stack` sysVars_this_contract = `this_contract` sysVars_time = `time` sysVars_type = `type` sysVars_txcost = `txcost` sysVars_txhash = `txhash` sysVars_guest_key = `guest_key` sysVars_guest_account = `guest_account` sysVars_black_hole_key = `black_hole_key` sysVars_white_hole_key = `white_hole_key` sysVars_black_hole_account = `black_hole_account` sysVars_white_hole_account = `white_hole_account` sysVars_gen_block = `gen_block` sysVars_time_limit = `time_limit` sysVars_pre_block_data_hash = `pre_block_data_hash` ) ================================================ FILE: packages/script/func.go ================================================ package script import ( "fmt" "reflect" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) // ExtendData is used for the definition of the extended functions and variables type ExtendData struct { Objects map[string]any AutoPars map[string]string WriteFuncs map[string]struct{} } func NewExtendData() *ExtendData { return &ExtendData{ Objects: map[string]any{ "ExecContract": ExecContract, "CallContract": ExContract, "Settings": GetSettings, "MemoryUsage": MemoryUsage, "Println": fmt.Println, "Sprintf": fmt.Sprintf, }, AutoPars: map[string]string{ `*script.RunTime`: `rt`, }, WriteFuncs: map[string]struct{}{"CallContract": {}}} } // ExecContract runs the name contract where txs contains the list of parameters and // params are the values of parameters func ExecContract(rt *RunTime, name, txs string, params ...any) (any, error) { contract, ok := rt.vm.Objects[name] if !ok { log.WithFields(log.Fields{"contract_name": name, "type": consts.ContractError}).Error("unknown contract") return nil, fmt.Errorf(eUnknownContract, name) } logger := log.WithFields(log.Fields{"contract_name": name, "type": consts.ContractError}) cblock := contract.GetCodeBlock() parnames := make(map[string]bool) pars := strings.Split(txs, `,`) if len(pars) != len(params) { logger.WithFields(log.Fields{"contract_params_len": len(pars), "contract_params_len_needed": len(params), "type": consts.ContractError}).Error("wrong contract parameters pars") return ``, errContractPars } for _, ipar := range pars { parnames[ipar] = true } if _, ok := rt.extend[Extend_loop+name]; ok { logger.WithFields(log.Fields{"type": consts.ContractError, "contract_name": name}).Error("there is loop in contract") return nil, fmt.Errorf(eContractLoop, name) } rt.extend[Extend_loop+name] = true defer delete(rt.extend, Extend_loop+name) prevExtend := make(map[string]any) for key, item := range rt.extend { if isSysVar(key) { continue } prevExtend[key] = item delete(rt.extend, key) } var isSignature bool if cblock.GetContractInfo().Tx != nil { for _, tx := range *cblock.GetContractInfo().Tx { if !parnames[tx.Name] { if !strings.Contains(tx.Tags, TagOptional) { logger.WithFields(log.Fields{"transaction_name": tx.Name, "type": consts.ContractError}).Error("transaction not defined") return ``, fmt.Errorf(eUndefinedParam, tx.Name) } rt.extend[tx.Name] = reflect.New(tx.Type).Elem().Interface() } if tx.Name == `Signature` { isSignature = true } } } for i, ipar := range pars { rt.extend[ipar] = params[i] } prevthis := rt.extend[Extend_this_contract] _, nameContract := converter.ParseName(name) rt.extend[Extend_this_contract] = nameContract prevparent := rt.extend[Extend_parent] parent := `` for i := len(rt.blocks) - 1; i >= 0; i-- { if rt.blocks[i].Block.Type == ObjectType_Func && rt.blocks[i].Block.Parent != nil && rt.blocks[i].Block.Parent.Type == ObjectType_Contract { parent = rt.blocks[i].Block.Parent.GetContractInfo().Name fid, fname := converter.ParseName(parent) cid, _ := converter.ParseName(name) if len(fname) > 0 { if fid == 0 { parent = `@` + fname } else if fid == cid { parent = fname } } break } } if err := rt.SubCost(CostContract); err != nil { return nil, err } var ( stack Stacker err error ) if stack, ok = rt.extend[Extend_sc].(Stacker); ok { if err := stack.AppendStack(name); err != nil { return nil, err } } if rt.extend[Extend_sc] != nil && isSignature { obj := rt.vm.Objects[`check_signature`] finfo := obj.GetExtFuncInfo() if err := finfo.Func.(func(map[string]any, string) error)(rt.extend, name); err != nil { logger.WithFields(log.Fields{"error": err, "func_name": finfo.Name, "type": consts.ContractError}).Error("executing extended function") return nil, err } } for _, method := range []string{`conditions`, `action`} { if block, ok := (*cblock).Objects[method]; ok && block.Type == ObjectType_Func { rtemp := NewRunTime(rt.vm, rt.cost) rt.extend[Extend_parent] = parent _, err = rtemp.Run(block.GetCodeBlock(), nil, rt.extend) rt.cost = rtemp.cost if err != nil { logger.WithFields(log.Fields{"error": err, "method_name": method, "type": consts.ContractError}).Error("executing contract method") break } } } if stack != nil { stack.PopStack(name) } if err != nil { return nil, err } rt.extend[Extend_parent] = prevparent rt.extend[Extend_this_contract] = prevthis result := rt.extend[Extend_result] for key := range rt.extend { if isSysVar(key) { continue } delete(rt.extend, key) } for key, item := range prevExtend { rt.extend[key] = item } return result, nil } // ExContract executes the name contract in the state with specified parameters func ExContract(rt *RunTime, state uint32, name string, params *types.Map) (any, error) { name = StateName(state, name) contract, ok := rt.vm.Objects[name] if !ok { log.WithFields(log.Fields{"contract_name": name, "type": consts.ContractError}).Error("unknown contract") return nil, fmt.Errorf(eUnknownContract, name) } if params == nil { params = types.NewMap() } logger := log.WithFields(log.Fields{"contract_name": name, "type": consts.ContractError}) names := make([]string, 0) vals := make([]any, 0) cblock := contract.GetCodeBlock() if cblock.GetContractInfo().Tx != nil { for _, tx := range *cblock.GetContractInfo().Tx { val, ok := params.Get(tx.Name) if !ok { if !strings.Contains(tx.Tags, TagOptional) { logger.WithFields(log.Fields{"transaction_name": tx.Name, "type": consts.ContractError}).Error("transaction not defined") return nil, fmt.Errorf(eUndefinedParam, tx.Name) } val = reflect.New(tx.Type).Elem().Interface() } names = append(names, tx.Name) vals = append(vals, val) } } if len(vals) == 0 { vals = append(vals, ``) } return ExecContract(rt, name, strings.Join(names, `,`), vals...) } // GetSettings returns the value of the parameter func GetSettings(rt *RunTime, cntname, name string) (any, error) { contract, found := rt.vm.Objects[cntname] if !found || contract.GetCodeBlock() == nil { log.WithFields(log.Fields{"contract_name": cntname, "type": consts.ContractError}).Error("unknown contract") return nil, fmt.Errorf("unknown contract %s", cntname) } cblock := contract.GetCodeBlock() info := cblock.GetContractInfo() if info != nil { if val, ok := info.Settings[name]; ok { return val, nil } } return ``, nil } func MemoryUsage(rt *RunTime) int64 { return rt.mem } ================================================ FILE: packages/script/handle.go ================================================ package script import ( "fmt" "reflect" "regexp" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) type compileFunc func(*CodeBlocks, stateTypes, *Lexeme) error const ( // This is a list of identifiers for functions that will generate a bytecode for the corresponding cases. // Indexes of handle functions funcHandles = compileFunc[] cfNothing = iota cfError cfNameBlock cfFResult cfReturn cfIf cfElse cfFParam cfFType cfFTail cfFNameParam cfAssignVar cfAssign cfTX cfSettings cfConstName cfConstValue cfField cfFieldType cfFieldTag cfFields cfFieldComma cfFieldLine cfWhile cfContinue cfBreak cfCmdError // cfEval ) // VarRegexp letter { letter | unicode_digit } var VarRegexp = `^[a-zA-Z][a-zA-Z0-9_]*$` var ( // The array of functions corresponding to the constants cf... funcHandles = map[int]compileFunc{ cfNothing: nil, cfError: fError, cfNameBlock: fNameBlock, cfFResult: fFuncResult, cfReturn: fReturn, cfIf: fIf, cfElse: fElse, cfFParam: fFparam, cfFType: fFtype, cfFTail: fFtail, cfFNameParam: fFNameParam, cfAssignVar: fAssignVar, cfAssign: fAssign, cfTX: fTx, cfSettings: fSettings, cfConstName: fConstName, cfConstValue: fConstValue, cfField: fField, cfFieldType: fFieldType, cfFieldTag: fFieldTag, cfFields: fFields, cfFieldComma: fFieldComma, cfFieldLine: fFieldLine, cfWhile: fWhile, cfContinue: fContinue, cfBreak: fBreak, cfCmdError: fCmdError, } ) func fError(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { errors := []string{`no error`, `unknown command`, // errUnknownCmd `must be the name`, // errMustName `must be '{'`, // errMustLCurly `must be '}'`, // errMustRCurly `wrong parameters`, // errParams `wrong variables`, // errVars `must be type`, // errVarType `must be '='`, // errAssign `must be number or string`, // errStrNum } logger := lexeme.GetLogger() if lexeme.Type == lexNewLine { logger.WithFields(log.Fields{"error": errors[state], "lex_value": lexeme.Value, "type": consts.ParseError}).Error("unexpected new line") return fmt.Errorf(`%s (unexpected new line) [Ln:%d]`, errors[state], lexeme.Line-1) } logger.WithFields(log.Fields{"error": errors[state], "lex_value": lexeme.Value, "type": consts.ParseError}).Error("parsing error") return fmt.Errorf(`%s %x %v [Ln:%d Col:%d]`, errors[state], lexeme.Type, lexeme.Value, lexeme.Line, lexeme.Column) } func fNameBlock(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { var itype ObjectType prev := (*buf)[len(*buf)-2] fblock := buf.peek() name := lexeme.Value.(string) switch state { case stateBlock: itype = ObjectType_Contract name = StateName((*buf)[0].Owner.StateID, name) fblock.Info = &ContractInfo{ID: uint32(len(prev.Children) - 1), Name: name, Owner: (*buf)[0].Owner} default: itype = ObjectType_Func fblock.Info = &FuncInfo{Name: name} } fblock.Type = itype if _, ok := prev.Objects[name]; ok { lexeme.GetLogger().WithFields(log.Fields{"type": consts.ParseError, "contract": prev.GetContractInfo().Name, "lex_value": name}).Errorf("%s redeclared in this contract", itype) return fmt.Errorf("%s '%s' redeclared in this contract '%s'", itype, name, prev.GetContractInfo().Name) } prev.Objects[name] = &ObjInfo{Type: itype, Value: fblock} return nil } func fFuncResult(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { fblock := buf.peek().GetFuncInfo() (*fblock).Results = append((*fblock).Results, lexeme.Value.(reflect.Type)) return nil } func fReturn(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.peek().Code.push(newByteCode(cmdReturn, lexeme.Line, 0)) return nil } func fCmdError(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.peek().Code.push(newByteCode(cmdError, lexeme.Line, lexeme.Value)) return nil } func fFparam(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { block := buf.peek() if block.Type == ObjectType_Func && (state == stateFParam || state == stateFParamTYPE) { fblock := block.GetFuncInfo() if fblock.Names == nil { fblock.Params = append(fblock.Params, reflect.TypeOf(nil)) } else { for key := range *fblock.Names { if key[0] == '_' { name := key[1:] params := append((*fblock.Names)[name].Params, reflect.TypeOf(nil)) offset := append((*fblock.Names)[name].Offset, len(block.Vars)) (*fblock.Names)[name] = FuncName{Params: params, Offset: offset} break } } } } if block.Objects == nil { block.Objects = make(map[string]*ObjInfo) } if !regexp.MustCompile(VarRegexp).MatchString(lexeme.Value.(string)) { var val = lexeme.Value.(string) if len(val) > 20 { val = val[:20] + "..." } return fmt.Errorf("identifier expected, got '%s'", val) } if _, ok := block.Objects[lexeme.Value.(string)]; ok { if state == stateFParamTYPE { return fmt.Errorf("duplicate argument '%s'", lexeme.Value.(string)) } else if state == stateVarType { return fmt.Errorf("'%s' redeclared in this code block", lexeme.Value.(string)) } } block.Objects[lexeme.Value.(string)] = &ObjInfo{Type: ObjectType_Var, Value: &ObjInfo_Variable{Name: lexeme.Value.(string), Index: len(block.Vars)}} block.Vars = append(block.Vars, reflect.TypeOf(nil)) return nil } func fFtype(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { block := buf.peek() if block.Type == ObjectType_Func && state == stateFParam { fblock := block.GetFuncInfo() if fblock.Names == nil { for pkey, param := range fblock.Params { if param == reflect.TypeOf(nil) { fblock.Params[pkey] = lexeme.Value.(reflect.Type) } } } else { for key := range *fblock.Names { if key[0] == '_' { for pkey, param := range (*fblock.Names)[key[1:]].Params { if param == reflect.TypeOf(nil) { (*fblock.Names)[key[1:]].Params[pkey] = lexeme.Value.(reflect.Type) } } break } } } } for vkey, ivar := range block.Vars { if ivar == reflect.TypeOf(nil) { block.Vars[vkey] = lexeme.Value.(reflect.Type) } } return nil } func fFtail(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { var used bool block := buf.peek() fblock := block.GetFuncInfo() if fblock.Names == nil { for pkey, param := range fblock.Params { if param == reflect.TypeOf(nil) { if used { return fmt.Errorf(`... parameter must be one`) } fblock.Params[pkey] = reflect.TypeOf([]any{}) used = true } } block.GetFuncInfo().Variadic = true } else { for key := range *fblock.Names { if key[0] == '_' { name := key[1:] for pkey, param := range (*fblock.Names)[name].Params { if param == reflect.TypeOf(nil) { if used { return fmt.Errorf(`... parameter must be one`) } (*fblock.Names)[name].Params[pkey] = reflect.TypeOf([]any{}) used = true } } offset := append((*fblock.Names)[name].Offset, len(block.Vars)) (*fblock.Names)[name] = FuncName{Params: (*fblock.Names)[name].Params, Offset: offset, Variadic: true} break } } } for vkey, ivar := range block.Vars { if ivar == reflect.TypeOf(nil) { block.Vars[vkey] = reflect.TypeOf([]any{}) } } return nil } func fFNameParam(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { block := buf.peek() fblock := block.GetFuncInfo() if fblock.Names == nil { names := make(map[string]FuncName) fblock.Names = &names } for key := range *fblock.Names { if key[0] == '_' { delete(*fblock.Names, key) } } (*fblock.Names)[`_`+lexeme.Value.(string)] = FuncName{} return nil } func fIf(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.get(len(*buf) - 2).Code.push(newByteCode(cmdIf, lexeme.Line, buf.peek())) return nil } func fWhile(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.get(len(*buf) - 2).Code.push(newByteCode(cmdWhile, lexeme.Line, buf.peek())) buf.get(len(*buf) - 2).Code.push(newByteCode(cmdContinue, lexeme.Line, 0)) return nil } func fContinue(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.peek().Code.push(newByteCode(cmdContinue, lexeme.Line, 0)) return nil } func fBreak(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.peek().Code.push(newByteCode(cmdBreak, lexeme.Line, 0)) return nil } func fAssignVar(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { block := buf.peek() var ( prev []*VarInfo ivar VarInfo ) if lexeme.Type == lexExtend { if isSysVar(lexeme.Value.(string)) { lexeme.GetLogger().WithFields(log.Fields{"type": consts.ParseError, "lex_value": lexeme.Value}).Error("modifying system variable") return fmt.Errorf(eSysVar, lexeme.Value.(string)) } ivar = VarInfo{Obj: &ObjInfo{Type: ObjectType_ExtVar, Value: &ObjInfo_ExtendVariable{Name: lexeme.Value.(string)}}, Owner: nil} } else { objInfo, tobj := findVar(lexeme.Value.(string), buf) if objInfo == nil || objInfo.Type != ObjectType_Var { lexeme.GetLogger().WithFields(log.Fields{"type": consts.ParseError, "lex_value": lexeme.Value}).Error("unknown variable") return fmt.Errorf(`unknown variable '%s'`, lexeme.Value.(string)) } ivar = VarInfo{Obj: objInfo, Owner: tobj} } if len(block.Code) > 0 { if block.Code[len(block.Code)-1].Cmd == cmdAssignVar { prev = block.Code[len(block.Code)-1].Value.([]*VarInfo) } } prev = append(prev, &ivar) if len(prev) == 1 { block.Code.push(newByteCode(cmdAssignVar, lexeme.Line, prev)) } else { block.Code[len(block.Code)-1] = newByteCode(cmdAssignVar, lexeme.Line, prev) } return nil } func fAssign(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { buf.peek().Code.push(newByteCode(cmdAssign, lexeme.Line, 0)) return nil } func fTx(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { contract := buf.peek() logger := lexeme.GetLogger() if contract.Type != ObjectType_Contract { logger.WithFields(log.Fields{"type": consts.ParseError, "contract_type": contract.Type, "lex_value": lexeme.Value}).Error("data can only be in contract") return fmt.Errorf(`data can only be in contract`) } (*contract).GetContractInfo().Tx = new([]*FieldInfo) return nil } func fSettings(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { contract := buf.peek() if contract.Type != ObjectType_Contract { logger := lexeme.GetLogger() logger.WithFields(log.Fields{"type": consts.ParseError, "contract_type": contract.Type, "lex_value": lexeme.Value}).Error("data can only be in contract") return fmt.Errorf(`settings can only be in contract`) } (*contract).GetContractInfo().Settings = make(map[string]any) return nil } func fConstName(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { sets := buf.peek().GetContractInfo().Settings sets[lexeme.Value.(string)] = nil return nil } func fConstValue(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { sets := buf.peek().GetContractInfo().Settings for key, val := range sets { if val == nil { sets[key] = lexeme.Value break } } return nil } func fField(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { info := buf.peek().GetContractInfo() tx := info.Tx if len(*tx) > 0 && (*tx)[len(*tx)-1].Type == reflect.TypeOf(nil) && (*tx)[len(*tx)-1].Tags != `_` { return fmt.Errorf(eDataType, lexeme.Line, lexeme.Column) } if !regexp.MustCompile(VarRegexp).MatchString(lexeme.Value.(string)) { var val = lexeme.Value.(string) if len(val) > 20 { val = val[:20] + "..." } return fmt.Errorf("identifier expected, got '%s'", val) } if isSysVar(lexeme.Value.(string)) { lexeme.GetLogger().WithFields(log.Fields{"type": consts.ParseError, "contract": info.Name, "lex_value": lexeme.Value.(string)}).Error("param variable in the data section of the contract collides with the 'builtin' variable") return fmt.Errorf(eDataParamVarCollides, lexeme.Value.(string), info.Name) } *tx = append(*tx, &FieldInfo{Name: lexeme.Value.(string), Type: reflect.TypeOf(nil)}) return nil } func fFields(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { tx := buf.peek().GetContractInfo().Tx if len(*tx) > 0 && (*tx)[len(*tx)-1].Type == nil { return fmt.Errorf(eDataType, lexeme.Line, lexeme.Column) } return nil } func fFieldComma(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { tx := buf.peek().GetContractInfo().Tx if len(*tx) == 0 || (*tx)[len(*tx)-1].Type != nil { return fmt.Errorf(eDataName, lexeme.Line, lexeme.Column) } (*tx)[len(*tx)-1].Tags = `_` return nil } func fFieldLine(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { tx := buf.peek().GetContractInfo().Tx if len(*tx) > 0 && (*tx)[len(*tx)-1].Type == nil { return fmt.Errorf(eDataType, lexeme.Line, lexeme.Column) } for i, field := range *tx { if field.Tags == `_` { (*tx)[i].Tags = `` } } return nil } func fFieldType(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { tx := buf.peek().GetContractInfo().Tx if len(*tx) == 0 || (*tx)[len(*tx)-1].Type != nil { return fmt.Errorf(eDataName, lexeme.Line, lexeme.Column) } for i, field := range *tx { if field.Type == reflect.TypeOf(nil) { (*tx)[i].Type = lexeme.Value.(reflect.Type) (*tx)[i].Original = lexeme.Ext } } return nil } func fFieldTag(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { tx := buf.peek().GetContractInfo().Tx if len(*tx) == 0 || (*tx)[len(*tx)-1].Type == nil || len((*tx)[len(*tx)-1].Tags) != 0 { return fmt.Errorf(eDataTag, lexeme.Line, lexeme.Column) } for i := len(*tx) - 1; i >= 0; i-- { if i == len(*tx)-1 || (*tx)[i].Tags == `_` { (*tx)[i].Tags = lexeme.Value.(string) continue } break } return nil } func fElse(buf *CodeBlocks, state stateTypes, lexeme *Lexeme) error { if buf.get(len(*buf)-2).Code.peek().Cmd != cmdIf { return fmt.Errorf(`there is not if before %v [Ln:%d Col:%d]`, lexeme.Type, lexeme.Line, lexeme.Column) } buf.get(len(*buf) - 2).Code.push(newByteCode(cmdElse, lexeme.Line, buf.peek())) return nil } ================================================ FILE: packages/script/lex.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "encoding/binary" "fmt" "reflect" "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) // The lexical analysis of the incoming program is implemented in this file. It is the first phase of compilation // where the incoming text is divided into a sequence of lexemes. const ( // Here are all the created lexemes lexUnknown = iota lexSys // a system lexeme is different bracket, =, comma and so on. lexOper // Operator is +, -, *, / lexNumber // Number lexIdent // Identifier lexNewLine // Line translation lexString // String lexComment // Comment lexKeyword // Key word lexType // Name of the type lexExtend // Referring to an external variable or function - $myname lexError = 0xff // flags of lexical states lexfNext = 1 lexfPush = 2 lexfPop = 4 lexfSkip = 8 // Constants for system lexemes isLPar = 0x2801 // ( isRPar = 0x2901 // ) isComma = 0x2c01 // , isDot = 0x2e01 // . isColon = 0x3a01 // : isEq = 0x3d01 // = isLCurly = 0x7b01 // { isRCurly = 0x7d01 // } isLBrack = 0x5b01 // [ isRBrack = 0x5d01 // ] // Constants for operations isNot = 0x0021 // ! isAsterisk = 0x002a // * isPlus = 0x002b // + isMinus = 0x002d // - isSign = 0x012d // - unary isSolidus = 0x002f // / isLess = 0x003c // < isGreat = 0x003e // > isNotEq = 0x213d // != isAnd = 0x2626 // && isLessEq = 0x3c3d // <= isEqEq = 0x3d3d // == isGrEq = 0x3e3d // >= isOr = 0x7c7c // || ) const ( // The list of keyword identifiers // Constants for keywords // keyUnknown = iota keyContract = iota + 1 keyFunc keyReturn keyIf keyElif keyElse keyWhile keyTrue keyFalse keyVar keyTX keySettings keyBreak keyContinue keyWarning keyInfo keyNil keyAction keyCond keyTail keyError ) const ( msgWarning = `warning` msgError = `error` msgInfo = `info` ) const ( DtBool uint32 = iota + 1 DtBytes DtInt DtAddress DtArray DtMap DtMoney DtFloat DtString DtFile ) type typeInfo struct { Original uint32 Type reflect.Type } var ( // The list of keywords keywords = map[string]uint32{ `contract`: keyContract, `func`: keyFunc, `return`: keyReturn, `if`: keyIf, `elif`: keyElif, `else`: keyElse, msgError: keyError, msgWarning: keyWarning, msgInfo: keyInfo, `while`: keyWhile, `data`: keyTX, `settings`: keySettings, `nil`: keyNil, `action`: keyAction, `conditions`: keyCond, `true`: keyTrue, `false`: keyFalse, `break`: keyBreak, `continue`: keyContinue, `var`: keyVar, `...`: keyTail} // list of available types // The list of types which save the corresponding 'reflect' type typesMap = map[string]typeInfo{ `bool`: {Original: DtBool, Type: reflect.TypeOf(true)}, `bytes`: {Original: DtBytes, Type: reflect.TypeOf([]byte{})}, `int`: {Original: DtInt, Type: reflect.TypeOf(int64(0))}, `address`: {Original: DtAddress, Type: reflect.TypeOf(int64(0))}, `array`: {Original: DtArray, Type: reflect.TypeOf([]any{})}, `map`: {Original: DtMap, Type: reflect.TypeOf(&types.Map{})}, `money`: {Original: DtMoney, Type: reflect.TypeOf(decimal.Zero)}, `float`: {Original: DtFloat, Type: reflect.TypeOf(0.0)}, `string`: {Original: DtString, Type: reflect.TypeOf(``)}, `file`: {Original: DtFile, Type: reflect.TypeOf(&types.Map{})}, } ) func GetFieldDefaultValue(fieldType uint32) any { switch fieldType { case DtBool: return false case DtFloat: return float64(0) case DtInt, DtAddress: return int64(0) case DtMoney: return decimal.New(0, consts.MoneyDigits) case DtString: return "" case DtBytes: return []byte{} case DtArray: return []any{} case DtMap: return types.NewMap() case DtFile: return types.NewFile() } return nil } // Lexeme contains information about language item type Lexeme struct { Type uint32 // Type of the lexeme Ext uint32 Value any // Value of lexeme Line uint16 // Line of the lexeme Column uint32 // Position inside the line } func NewLexeme(t uint32, ext uint32, value any, line uint16, column uint32) *Lexeme { return &Lexeme{Type: t, Ext: ext, Value: value, Line: line, Column: column} } // GetLogger returns logger func (l *Lexeme) GetLogger() *log.Entry { return log.WithFields(log.Fields{"lex_type": l.Type, "lex_line": l.Line, "lex_column": l.Column}) } type ifBuf struct { count int pair int stop bool } // Lexemes is a slice of lexemes type Lexemes []*Lexeme // The lexical analysis is based on the finite machine which is described in the file // tools/lextable/lextable.go. lextable.go generates a representation of a finite machine as an array // and records it in the file lex_table.go. In fact, the lexTable array is a set of states and // depending on the next sign, the machine goes into a new state. // lexParser parsers the input language source code func lexParser(input []rune) (Lexemes, error) { var ( curState uint8 length, line, off, offline, flags, start, lexID uint32 ) lexemes := make(Lexemes, 0, len(input)/4) irune := len(alphabet) - 1 // This function according to the next symbol looks with help of lexTable what new state we will have, // whether we got the lexeme and what flags are displayed todo := func(r rune) { var letter uint8 if r > 127 { letter = alphabet[irune] } else { letter = alphabet[r] } val := lexTable[curState][letter] curState = uint8(val >> 16) lexID = (val >> 8) & 0xff flags = val & 0xff } length = uint32(len(input)) + 1 line = 1 skip := false ifbuf := make([]ifBuf, 0) for off < length { // Here we go through the symbols one by one if off == length-1 { todo(' ') } else { todo(input[off]) } if curState == lexError { return nil, fmt.Errorf(`unknown lexeme '%s' [Ln:%d Col:%d]`, string(input[off:off+1]), line, off-offline+1) } if (flags & lexfSkip) != 0 { off++ skip = true continue } // If machine determined the completed lexeme, we record it in the list of lexemes. if lexID > 0 { // We do not start a stack for symbols but memorize the displacement when the parse of lexeme began. // To get a string of a lexeme we take a substring from the initial displacement to the current one. // We immediately write a string as values, a number or a binary representation of operations. var ext uint32 lexOff := off if (flags & lexfPop) != 0 { lexOff = start } right := off if (flags & lexfNext) != 0 { right++ } if len(ifbuf) > 0 && ifbuf[len(ifbuf)-1].stop && lexID != lexNewLine { name := string(input[lexOff:right]) if name != `else` && name != `elif` { for i := 0; i < ifbuf[len(ifbuf)-1].count; i++ { lexemes = append(lexemes, NewLexeme(lexSys|(uint32('}')<<8), ext, uint32('}'), uint16(line), lexOff-offline+1)) } ifbuf = ifbuf[:len(ifbuf)-1] } else { ifbuf[len(ifbuf)-1].stop = false } } var value any switch lexID { case lexNewLine: if input[lexOff] == rune(0x0a) { line++ offline = off } case lexSys: ch := uint32(input[lexOff]) lexID |= ch << 8 value = ch if len(ifbuf) > 0 { if ch == '{' { ifbuf[len(ifbuf)-1].pair++ } if ch == '}' { ifbuf[len(ifbuf)-1].pair-- if ifbuf[len(ifbuf)-1].pair == 0 { ifbuf[len(ifbuf)-1].stop = true } } } case lexString, lexComment: val := string(input[lexOff+1 : right-1]) if lexID == lexString && skip { skip = false val = strings.Replace(strings.Replace(val, `\"`, `"`, -1), `\t`, "\t", -1) val = strings.Replace(strings.Replace(val, `\r`, "\r", -1), `\n`, "\n", -1) } value = val for i, ch := range val { if ch == 0xa { line++ offline = off + uint32(i) + 1 } } case lexOper: oper := []byte(string(input[lexOff:right])) value = binary.BigEndian.Uint32(append(make([]byte, 4-len(oper)), oper...)) case lexNumber: name := string(input[lexOff:right]) if strings.ContainsAny(name, `.`) { if val, err := strconv.ParseFloat(name, 64); err == nil { value = val } else { log.WithFields(log.Fields{"error": err, "value": name, "lex_line": line, "lex_col": off - offline + 1, "type": consts.ConversionError}).Error("converting lex number to float") return nil, fmt.Errorf(`%v %s [Ln:%d Col:%d]`, err, name, line, off-offline+1) } } else if val, err := strconv.ParseInt(name, 10, 64); err == nil { value = val } else { log.WithFields(log.Fields{"error": err, "value": name, "lex_line": line, "lex_col": off - offline + 1, "type": consts.ConversionError}).Error("converting lex number to int") return nil, fmt.Errorf(`%v %s [Ln:%d Col:%d]`, err, name, line, off-offline+1) } case lexIdent: name := string(input[lexOff:right]) if name[0] == '$' { lexID = lexExtend value = name[1:] } else if keyID, ok := keywords[name]; ok { switch keyID { case keyIf: ifbuf = append(ifbuf, ifBuf{}) lexID = lexKeyword | (keyID << 8) value = keyID case keyElif: if len(ifbuf) > 0 { lexemes = append(lexemes, NewLexeme(lexKeyword|(keyElse<<8), ext, uint32(keyElse), uint16(line), lexOff-offline+1), NewLexeme(lexSys|('{'<<8), ext, uint32('{'), uint16(line), lexOff-offline+1)) lexID = lexKeyword | (keyIf << 8) value = uint32(keyIf) ifbuf[len(ifbuf)-1].count++ } case keyAction, keyCond: if len(lexemes) > 0 { lexf := *lexemes[len(lexemes)-1] if lexf.Type&0xff != lexKeyword || lexf.Value.(uint32) != keyFunc { lexemes = append(lexemes, NewLexeme(lexKeyword|(keyFunc<<8), ext, uint32(keyFunc), uint16(line), lexOff-offline+1)) } } value = name case keyTrue: lexID = lexNumber value = true case keyFalse: lexID = lexNumber value = false case keyNil: lexID = lexNumber value = nil default: lexID = lexKeyword | (keyID << 8) value = keyID } } else if tInfo, ok := typesMap[name]; ok { lexID = lexType value = tInfo.Type ext = tInfo.Original } else { value = name } } if lexID != lexComment { lexemes = append(lexemes, NewLexeme(lexID, ext, value, uint16(line), lexOff-offline+1)) } } if (flags & lexfPush) != 0 { start = off } if (flags & lexfNext) != 0 { off++ } } return lexemes, nil } func OriginalToString(original uint32) string { for key, v := range typesMap { if v.Original == original { return key } } return `` } ================================================ FILE: packages/script/lex_table.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script // This file was generated with lextable.go var ( alphabet = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 4, 14, 22, 0, 12, 0, 6, 7, 21, 25, 16, 26, 15, 27, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 24, 5, 17, 19, 18, 0, 23, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 8, 28, 9, 0, 32, 3, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 10, 13, 11, 0, 0, 33, } lexTable = [][34]uint32{ {0xff0000, 0x501, 0x1, 0xe0003, 0xf0003, 0x501, 0x101, 0x101, 0x101, 0x101, 0x101, 0x101, 0x50003, 0xc0003, 0x101, 0xb0003, 0x101, 0x80003, 0x80003, 0xd0003, 0x80003, 0x201, 0x90003, 0x90003, 0x101, 0x201, 0x201, 0x70003, 0xff0000, 0x30003, 0x30003, 0x60003, 0x60003, 0x60003}, {0x10001, 0x0, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001}, {0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x405, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000}, {0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x30001, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x304, 0x30001, 0x30001, 0xff0000, 0xff0000, 0xff0000}, {0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x100001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001}, {0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x205, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000}, {0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x404, 0x60001, 0x60001, 0x60001, 0x60001, 0x60001}, {0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x40001, 0x204, 0x204, 0x204, 0x204, 0x204, 0x10005, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204}, {0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x205, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204, 0x204}, {0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x60001, 0x60001, 0x60001, 0x60001, 0x60001}, {0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001}, {0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x20001, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x30001, 0x30001, 0x104, 0x104, 0x104}, {0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x205, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000}, {0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x205, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104, 0x104}, {0xe0001, 0xe0001, 0xe0001, 0x605, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001, 0xe0001}, {0xf0001, 0xf0001, 0xf0001, 0xf0001, 0x605, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xa0008, 0xf0001, 0xf0001, 0xf0001, 0xf0001, 0xf0001}, {0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x705, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001, 0x40001}, } ) ================================================ FILE: packages/script/lex_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "fmt" "testing" ) type TestLexeme struct { Input string Output string } func (lexemes Lexemes) String(source []rune) (ret string) { for _, item := range lexemes { // slex := string(source[item.Offset:item.Right]) if item.Type == 0 { item.Value = `error` } ret += fmt.Sprintf("[%d %v]", item.Type, item.Value) } return } func TestLexParser(t *testing.T) { test := []TestLexeme{ {" my.test tail...) func 1 ...", "[4 my][11777 46][4 test][4 tail][5128 20][10497 41][520 2][3 1][5128 20]"}, {"`my string` \"another String\"" + `"test \"subtest\" test"`, "[6 my string][6 another String][6 test \"subtest\" test]"}, {"contract my { func init {}}", "[264 1][4 my][31489 123][520 2][4 init][31489 123][32001 125][32001 125]"}, {`callfunc( 1, name + 10)`, `[4 callfunc][10241 40][3 1][11265 44][4 name][2 43][3 10][10497 41]`}, {`(ab <= 24 )|| (12>67) && (56==78)`, `[10241 40][4 ab][2 15421][3 24][10497 41][2 31868][10241 40][3 12][2 62][3 67][10497 41][2 9766][10241 40][3 56][2 15677][3 78][10497 41]`}, {`!ab < !b && 12>=56 && qwe!=asd`, `[2 33][4 ab][2 60][2 33][4 b][2 9766][3 12][2 15933][3 56][2 9766][4 qwe][2 8509][4 asd]`}, {`ab || 12 && 56`, `[4 ab][2 31868][3 12][2 9766][3 56]`}, {"12 /*rue \n weweswe*/ 42", `[3 12][3 42]`}, {`true | 42`, `unknown lexeme [Ln:1 Col:7]`}, {"(\r\n)\x03 -", "unknown lexeme  [Ln:2 Col:3]"}, {` +( - ) / + // edeld lklm 3edwd`, `[2 43][10241 40][2 45][10497 41][2 47][2 43]`}, {`23+13424 * 1000.01 test`, `[3 23][2 43][3 13424][2 42][3 1000.01][4 test]`}, {` 0785/67+iname*(56-31)`, `[3 785][2 47][3 67][2 43][4 iname][2 42][10241 40][3 56][2 45][3 31][10497 41]`}, {`myvar_45 - a_qwe + t81you - 345rt`, `unknown lexeme r [Ln:1 Col:32]`}, {`10 + #mytable[id = 234].name * 20`, `[3 10][2 43][8961 35][4 mytable][23297 91][4 id][15617 61][3 234][23809 93][11777 46][4 name][2 42][3 20]`}, } for _, item := range test { source := []rune(item.Input) if out, err := lexParser(source); err != nil { if err.Error() != item.Output { fmt.Println(string(source)) t.Error(`error of lexical parser ` + err.Error()) } } else if out.String(source) != item.Output { t.Error(`error of lexical parser ` + item.Input) fmt.Println(out.String(source)) } } } ================================================ FILE: packages/script/lextable/lextable.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package main import ( "encoding/json" "fmt" "os" "strings" ) // The program creates packages/script/lex_table.go files. // Action is a map of actions type Action map[string][]string // States is a map of states type States map[string]Action const ( // AlphaSize is the length of alphabet AlphaSize = 34 ) /* Here we define the alphabet with which our language will work and describe the state machine that passes from one state to another depending on the next received character. This program converts the list of states into a numeric array and saves it as packages/script/lex_table.go */ var ( table [][AlphaSize]uint32 lexeme = map[string]uint32{ ``: 0, `sys`: 1, `oper`: 2, `number`: 3, `ident`: 4, `newline`: 5, `string`: 6, `comment`: 7, } flags = map[string]uint32{ `next`: 1, `push`: 2, `pop`: 4, `skip`: 8, } alphabet = []byte{ 0x01, //default 0x0a, //newline ' ', //space '`', //back quotes '"', //double quotes ';', '(', ')', '[', ']', '{', '}', '&', '|', '#', '.', ',', '<', '>', '=', '!', '*', '$', '@', ':', '+', '-', '/', '\\', '0', '1', 'a', '_', 128, } /* In states we have designated for d - all characters that are not specified in the state n - 0x0a, s - space, q - back quotes `, Q - double quotes, r - characters >= 128, a - A-Z and a-z, 1 - 1-9 State names are used as keys, and possible character sets are listed in the value object and then for each such set there is a new state where the transition should be made, then the name of the token, if we need to return to the initial state and the service flags are the third parameter, which indicate what to do with the current symbol. For example, we have the state main and the incoming symbol /. push says to remember it in a separate stack and next - go to the next character, while we change the state to solidus. Take the next character and look at the state of solidus If we have / or * - then we go into the comment state, so they start with // or / *. At the same time, you can see that for each comment, there are different subsequent states, since they end they are different symbols. And if we have the next character not / and not *, then we are all that we have write to the stack (/) as a token of type oper, clear the stack and return to the main state. */ states = `{ "main": { "n;": ["main", "newline", "next"], "()#[],{}:": ["main", "sys", "next"], "s": ["main", "", "next"], "q": ["string", "", "push next"], "Q": ["dstring", "", "push next"], "&": ["and", "", "push next"], "|": ["or", "", "push next"], "=": ["eq", "", "push next"], "/": ["solidus", "", "push next"], "<>!": ["oneq", "", "push next"], "*+-": ["main", "oper", "next"], "01": ["number", "", "push next"], "a_r": ["ident", "", "push next"], "@$": ["mustident", "", "push next"], ".": ["dot", "", "push next"], "d": ["error", "", ""] }, "string": { "q": ["main", "string", "pop next"], "d": ["string", "", "next"] }, "dstring": { "Q": ["main", "string", "pop next"], "\\": ["dslash", "", "skip"], "d": ["dstring", "", "next"] }, "dslash": { "d": ["dstring", "", "next"] }, "dot": { ".": ["ddot", "", "next"], "01": ["number", "", "next"], "d": ["main", "sys", "pop"] }, "ddot": { ".": ["main", "ident", "pop next"], "d": ["error", "", ""] }, "and": { "&": ["main", "oper", "pop next"], "d": ["error", "", ""] }, "or": { "|": ["main", "oper", "pop next"], "d": ["error", "", ""] }, "eq": { "=": ["main", "oper", "pop next"], "d": ["main", "sys", "pop"] }, "solidus": { "/": ["comline", "", "pop next"], "*": ["comment", "", "next"], "d": ["main", "oper", "pop"] }, "oneq": { "=": ["main", "oper", "pop next"], "d": ["main", "oper", "pop"] }, "number": { "01.": ["number", "", "next"], "a_r": ["error", "", ""], "d": ["main", "number", "pop"] }, "ident": { "01a_r": ["ident", "", "next"], "d": ["main", "ident", "pop"] }, "mustident": { "01a_r": ["ident", "", "next"], "d": ["error", "", ""] }, "comment": { "*": ["comstop", "", "next"], "d": ["comment", "", "next"] }, "comstop": { "/": ["main", "comment", "pop next"], "d": ["comment", "", "next"] }, "comline": { "n": ["main", "", ""], "d": ["comline", "", "next"] } }` ) func main() { var alpha [129]byte for ind, ch := range alphabet { i := byte(ind) switch ch { case ' ': alpha[0x09] = i alpha[0x0d] = i alpha[' '] = i case '1': for k := '1'; k <= '9'; k++ { alpha[k] = i } case 'a': for k := 'A'; k <= 'Z'; k++ { alpha[k] = i } for k := 'a'; k <= 'z'; k++ { alpha[k] = i } case 128: alpha[128] = i default: alpha[ch] = i } } out := `package script // This file was generated with lextable.go var ( alphabet = []byte{` for i, ch := range alpha { out += fmt.Sprintf(`%d,`, ch) if i > 0 && i%24 == 0 { out += "\r\n\t\t\t" } } out += "\r\n\t\t}\r\n" var ( data States ) state2int := map[string]uint{`main`: 0} if err := json.Unmarshal([]byte(states), &data); err != nil { fmt.Println(err) } for key := range data { if key != `main` { state2int[key] = uint(len(state2int)) } } table = make([][AlphaSize]uint32, len(state2int)) for key, istate := range data { curstate := state2int[key] for i := range table[curstate] { table[curstate][i] = 0xFE0000 } fmt.Println(state2int) for skey, sval := range istate { var val uint32 if sval[0] == `error` { val = 0xff0000 } else { val = uint32(state2int[sval[0]] << 16) // new state } val |= lexeme[sval[1]] << 8 // lexeme cmds := strings.Split(sval[2], ` `) var flag uint32 for _, icmd := range cmds { flag |= flags[icmd] } val |= flag for _, ch := range []byte(skey) { var ind int switch ch { case 'd': ind = 0 case 'n': ind = 1 case 's': ind = 2 case 'q': ind = 3 case 'Q': ind = 4 case 'r': ind = AlphaSize - 1 default: for k, ach := range alphabet { if ach == ch { ind = k break } } } table[curstate][ind] = val if ind == 0 { // default value for i := range table[curstate] { if table[curstate][i] == 0xFE0000 { table[curstate][i] = val } } } } } } out += "\t\tlexTable = [][" + fmt.Sprint(AlphaSize) + "]uint32{\r\n" for _, line := range table { out += "\t\t\t{" for _, ival := range line { out += fmt.Sprintf(" 0x%x,", ival) } out += "\r\n\t\t\t},\r\n" } out += "\t\t\t}\r\n)\r\n" err := os.WriteFile("../lex_table.go", []byte(out), 0644) if err != nil { fmt.Println(err.Error()) } } ================================================ FILE: packages/script/runtime.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "encoding/json" "fmt" "reflect" "strconv" "strings" "time" "unsafe" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) const ( statusNormal = iota statusReturn statusContinue statusBreak // Decimal is the constant string for decimal type Decimal = `decimal.Decimal` // Interface is the constant string for interface type Interface = `interface` File = `*types.Map` brackets = `[]` maxArrayIndex = 1000000 maxMapCount = 100000 maxCallDepth = 1000 memoryLimit = 128 << 20 // 128 MB MaxErrLen = 150 ) var sysVars = map[string]struct{}{ sysVars_block: {}, sysVars_block_key_id: {}, sysVars_block_time: {}, sysVars_data: {}, sysVars_ecosystem_id: {}, sysVars_key_id: {}, sysVars_account_id: {}, sysVars_node_position: {}, sysVars_parent: {}, sysVars_original_contract: {}, sysVars_sc: {}, sysVars_contract: {}, sysVars_stack: {}, sysVars_this_contract: {}, sysVars_time: {}, sysVars_type: {}, sysVars_txcost: {}, sysVars_txhash: {}, sysVars_guest_key: {}, sysVars_guest_account: {}, sysVars_black_hole_key: {}, sysVars_black_hole_account: {}, sysVars_white_hole_key: {}, sysVars_white_hole_account: {}, sysVars_gen_block: {}, sysVars_time_limit: {}, sysVars_pre_block_data_hash: {}, } var ( ErrMemoryLimit = errors.New("Memory limit exceeded") //ErrVMTimeLimit returns when the time limit exceeded ErrVMTimeLimit = errors.New(`time limit exceeded`) ) // VMError represents error of VM type VMError struct { Type string `json:"type"` Error string `json:"error"` } type blockStack struct { Block *CodeBlock Offset int } // ErrInfo stores info about current contract or function type ErrInfo struct { Name string Line uint16 } // RunTime is needed for the execution of the byte-code type RunTime struct { stack []any blocks []*blockStack vars []any extend map[string]any vm *VM cost int64 //cost remaining err error unwrap bool timeLimit bool callDepth uint16 mem int64 memVars map[any]int64 errInfo ErrInfo } // NewRunTime creates a new RunTime for the virtual machine func NewRunTime(vm *VM, cost int64) *RunTime { return &RunTime{ stack: make([]any, 0, 1024), vm: vm, cost: cost, memVars: make(map[any]int64), } } func isSysVar(name string) bool { if _, ok := sysVars[name]; ok || strings.HasPrefix(name, Extend_loop) { return true } return false } func (rt *RunTime) callFunc(cmd uint16, obj *ObjInfo) (err error) { var ( count, in int ) if rt.callDepth >= maxCallDepth { return fmt.Errorf("max call depth") } rt.callDepth++ defer func() { rt.callDepth-- }() size := rt.len() in = obj.getInParams() if rt.unwrap && cmd == cmdCallVariadic && size > 1 && reflect.TypeOf(rt.stack[size-2]).String() == `[]interface {}` { count = rt.getStack(size - 1).(int) arr := rt.getStack(size - 2).([]any) rt.resetByIdx(size - 2) for _, item := range arr { rt.push(item) } rt.push(count - 1 + len(arr)) size = rt.len() } rt.unwrap = false if cmd == cmdCallVariadic { count = rt.getStack(size - 1).(int) size-- } else { count = in } if obj.Type == ObjectType_Func { var imap map[string][]any finfo := obj.GetCodeBlock().GetFuncInfo() if finfo.Names != nil { if rt.getStack(size-1) != nil { imap = rt.getStack(size - 1).(map[string][]any) } rt.resetByIdx(size - 1) size = rt.len() } if cmd == cmdCallVariadic { parcount := count + 1 - in if parcount < 0 { log.WithFields(log.Fields{"type": consts.VMError}).Error(errWrongCountPars) return errWrongCountPars } pars := make([]any, parcount) shift := size - parcount for i := parcount; i > 0; i-- { pars[i-1] = rt.stack[size+i-parcount-1] } rt.resetByIdx(shift) rt.push(pars) } if rt.len() < len(finfo.Params) { log.WithFields(log.Fields{"type": consts.VMError}).Error(errWrongCountPars) return errWrongCountPars } for i, v := range finfo.Params { switch v.Kind() { case reflect.String, reflect.Int64: offset := rt.len() - in + i if v.Kind() == reflect.Int64 { rv := reflect.ValueOf(rt.stack[offset]) switch rv.Kind() { case reflect.Float64: val, _ := converter.ValueToInt(rt.stack[offset]) rt.stack[offset] = val } } if reflect.TypeOf(rt.stack[offset]) != v { log.WithFields(log.Fields{"type": consts.VMError}).Error(fmt.Sprintf(eTypeParam, i+1)) return fmt.Errorf(eTypeParam, i+1) } } } if finfo.Names != nil { rt.push(imap) } _, err = rt.RunCode(obj.GetCodeBlock()) return } var ( stack Stacker ok bool result []reflect.Value limit = 0 finfo = obj.GetExtFuncInfo() foo = reflect.ValueOf(finfo.Func) pars = make([]reflect.Value, in) ) if stack, ok = rt.extend[Extend_sc].(Stacker); ok { if err := stack.AppendStack(finfo.Name); err != nil { return err } } rt.extend[Extend_rt] = rt auto := 0 for k := 0; k < in; k++ { if len(finfo.Auto[k]) > 0 { auto++ } } shift := size - count + auto if finfo.Variadic { shift = size - count count += auto limit = count - in + 1 } i := count for ; i > limit; i-- { if len(finfo.Auto[count-i]) > 0 { pars[count-i] = reflect.ValueOf(rt.extend[finfo.Auto[count-i]]) auto-- } else { pars[count-i] = reflect.ValueOf(rt.stack[size-i+auto]) } if !pars[count-i].IsValid() { pars[count-i] = reflect.Zero(reflect.TypeOf(``)) } } if i > 0 && size-i >= 0 { pars[in-1] = reflect.ValueOf(rt.stack[size-i : size]) } else { if !pars[in-1].IsValid() { pars[in-1] = reflect.Zero(finfo.Params[in-1]) } } if finfo.Name == `ExecContract` && (pars[2].Kind() != reflect.String || !pars[3].IsValid()) { return fmt.Errorf(`unknown function %v`, pars[1]) } if finfo.Variadic { result = foo.CallSlice(pars) } else { result = foo.Call(pars) } if shift < 0 { shift = 0 } rt.resetByIdx(shift) if stack != nil { stack.PopStack(finfo.Name) } for i, ret := range result { // first return value of every extend function that makes queries to DB is cost if _, ok := rt.vm.FuncCallsDB[finfo.Name]; ok && i == 0 { if err = rt.SubCost(ret.Int()); err != nil { return } continue } if finfo.Results[i].String() == `error` { if ret.Interface() != nil { rt.errInfo = ErrInfo{Name: finfo.Name} return ret.Interface().(error) } } else { rt.push(ret.Interface()) } } return } func (rt *RunTime) extendFunc(name string) error { var ( ok bool f any ) if f, ok = rt.extend[name]; !ok || reflect.ValueOf(f).Kind() != reflect.Func { return fmt.Errorf(`unknown function %s`, name) } size := rt.len() foo := reflect.ValueOf(f) count := foo.Type().NumIn() pars := make([]reflect.Value, count) for i := count; i > 0; i-- { pars[count-i] = reflect.ValueOf(rt.stack[size-i]) } result := foo.Call(pars) rt.resetByIdx(size - count) for i, iret := range result { if foo.Type().Out(i).String() == `error` { if iret.Interface() != nil { return iret.Interface().(error) } } else { rt.push(iret.Interface()) } } return nil } func calcMem(v any) (mem int64) { rv := reflect.ValueOf(v) switch rv.Kind() { case reflect.Bool: mem = 1 case reflect.Int8, reflect.Uint8: mem = 1 case reflect.Int16, reflect.Uint16: mem = 2 case reflect.Int32, reflect.Uint32: mem = 4 case reflect.Int64, reflect.Uint64, reflect.Int, reflect.Uint: mem = 8 case reflect.Float32: mem = 4 case reflect.Float64: mem = 8 case reflect.String: mem += int64(rv.Len()) case reflect.Slice, reflect.Array: mem = 12 for i := 0; i < rv.Len(); i++ { mem += calcMem(rv.Index(i).Interface()) } case reflect.Map: mem = 4 for _, k := range rv.MapKeys() { mem += calcMem(k.Interface()) mem += calcMem(rv.MapIndex(k).Interface()) } default: mem = int64(unsafe.Sizeof(v)) } return } func (rt *RunTime) setExtendVar(k string, v any) { rt.extend[k] = v rt.recalcMemExtendVar(k) } func (rt *RunTime) recalcMemExtendVar(k string) { mem := calcMem(rt.extend[k]) rt.mem += mem - rt.memVars[k] rt.memVars[k] = mem } func (rt *RunTime) addVar(v any) { rt.vars = append(rt.vars, v) mem := calcMem(v) rt.memVars[len(rt.vars)-1] = mem rt.mem += mem } func (rt *RunTime) setVar(k int, v any) { rt.vars[k] = v rt.recalcMemVar(k) } func (rt *RunTime) recalcMemVar(k int) { mem := calcMem(rt.vars[k]) rt.mem += mem - rt.memVars[k] rt.memVars[k] = mem } func valueToBool(v any) bool { switch val := v.(type) { case int: if val != 0 { return true } case int64: if val != 0 { return true } case float64: if val != 0.0 { return true } case bool: return val case string: return len(val) > 0 case []uint8: return len(val) > 0 case []any: return val != nil && len(val) > 0 case map[string]any: return val != nil && len(val) > 0 case map[string]string: return val != nil && len(val) > 0 case *types.Map: return val != nil && val.Size() > 0 default: dec, _ := decimal.NewFromString(fmt.Sprintf(`%v`, val)) return dec.Cmp(decimal.Zero) != 0 } return false } // ValueToFloat converts interface (string, float64 or int64) to float64 func ValueToFloat(v any) (ret float64) { var err error switch val := v.(type) { case float64: ret = val case int64: ret = float64(val) case string: ret, err = strconv.ParseFloat(val, 64) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err, "value": val}).Error("converting value from string to float") } case decimal.Decimal: ret = val.InexactFloat64() } return } // ValueToDecimal converts interface (string, float64, Decimal or int64) to Decimal func ValueToDecimal(v any) (ret decimal.Decimal, err error) { switch val := v.(type) { case float64: ret = decimal.NewFromFloat(val).Floor() case string: ret, err = decimal.NewFromString(val) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err, "value": val}).Error("converting value from string to decimal") } else { ret = ret.Floor() } case int64: ret = decimal.New(val, 0) default: ret = val.(decimal.Decimal) } return } // SetCost sets the max cost of the execution. func (rt *RunTime) SetCost(cost int64) { rt.cost = cost } func (rt *RunTime) SubCost(cost int64) error { if cost > 0 { rt.cost -= cost } if rt.cost < 0 { return fmt.Errorf("runtime cost limit overflow") } return nil } // Cost return the remain cost of the execution. func (rt *RunTime) Cost() int64 { return rt.cost } // SetVMError sets error of VM func SetVMError(eType string, eText any) error { errText := fmt.Sprintf(`%v`, eText) if len(errText) > MaxErrLen { errText = errText[:MaxErrLen] + `...` } out, err := json.Marshal(&VMError{Type: eType, Error: errText}) if err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marshalling VMError") out = []byte(`{"type": "panic", "error": "marshalling VMError"}`) } return fmt.Errorf(string(out)) } func (rt *RunTime) getResultValue(item mapItem) (value any, err error) { switch item.Type { case mapConst: value = item.Value case mapExtend: var ok bool value, ok = rt.extend[item.Value.(string)] if !ok { rt.vm.logger.WithFields(log.Fields{"cmd": item.Value}).Error("unknown extend identifier") err = fmt.Errorf(`unknown extend identifier %s`, item.Value) } case mapVar: ivar := item.Value.(*VarInfo) var i int for i = len(rt.blocks) - 1; i >= 0; i-- { if ivar.Owner == rt.blocks[i].Block { value = rt.vars[rt.blocks[i].Offset+ivar.Obj.GetVariable().Index] break } } if i < 0 { err = fmt.Errorf(eWrongVar, ivar.Obj.Value) } case mapMap: value, err = rt.getResultMap(item.Value.(*types.Map)) case mapArray: value, err = rt.getResultArray(item.Value.([]mapItem)) } return } func (rt *RunTime) getResultArray(cmd []mapItem) ([]any, error) { initArr := make([]any, 0) for _, val := range cmd { value, err := rt.getResultValue(val) if err != nil { return nil, err } initArr = append(initArr, value) } return initArr, nil } func (rt *RunTime) getResultMap(cmd *types.Map) (*types.Map, error) { initMap := types.NewMap() for _, key := range cmd.Keys() { val, _ := cmd.Get(key) value, err := rt.getResultValue(val.(mapItem)) if err != nil { return nil, err } initMap.Set(key, value) } return initMap, nil } func isSelfAssignment(dest, value any) bool { if _, ok := value.([]any); !ok { if _, ok = value.(*types.Map); !ok { return false } } if reflect.ValueOf(dest).Pointer() == reflect.ValueOf(value).Pointer() { return true } switch v := value.(type) { case []any: for _, item := range v { if isSelfAssignment(dest, item) { return true } } case *types.Map: for _, item := range v.Values() { if isSelfAssignment(dest, item) { return true } } } return false } // RunCode executes CodeBlock func (rt *RunTime) RunCode(block *CodeBlock) (status int, err error) { var cmd *ByteCode defer func() { if r := recover(); r != nil { err = errors.Errorf(`runtime run code crashed: %v`, r) } if err != nil && !strings.HasPrefix(err.Error(), `{`) { var curContract, line string if block.isParentContract() { stack := block.Parent.GetContractInfo() curContract = stack.Name } if stack, ok := rt.extend[Extend_stack].([]any); ok { curContract = stack[len(stack)-1].(string) } line = "]" if cmd != nil { line = fmt.Sprintf(":%d]", cmd.Line) } if len(rt.errInfo.Name) > 0 && rt.errInfo.Name != `ExecContract` { err = fmt.Errorf("%s [%s %s%s", err, rt.errInfo.Name, curContract, line) rt.errInfo.Name = `` } else { out := err.Error() if strings.HasSuffix(out, `]`) { prev := strings.LastIndexByte(out, ' ') if strings.HasPrefix(out[prev+1:], curContract+`:`) { out = out[:prev+1] } else { out = out[:len(out)-1] + ` ` } } else { out += ` [` } err = fmt.Errorf(`%s%s%s`, out, curContract, line) } } }() top := make([]any, 8) rt.blocks = append(rt.blocks, &blockStack{Block: block, Offset: len(rt.vars)}) var namemap map[string][]any if block.Type == ObjectType_Func && block.GetFuncInfo().Names != nil { if rt.peek() != nil { namemap = rt.peek().(map[string][]any) } rt.resetByIdx(rt.len() - 1) } start := rt.len() varoff := len(rt.vars) for vkey, vpar := range block.Vars { if err = rt.SubCost(1); err != nil { break } var value any if block.Type == ObjectType_Func && vkey < len(block.GetFuncInfo().Params) { value = rt.stack[start-len(block.GetFuncInfo().Params)+vkey] } else { value = reflect.New(vpar).Elem().Interface() if vpar == reflect.TypeOf(&types.Map{}) { value = types.NewMap() } else if vpar == reflect.TypeOf([]any{}) { value = make([]any, 0, len(rt.vars)+1) } } rt.addVar(value) } if err != nil { return } if namemap != nil { for key, item := range namemap { params := (*block.GetFuncInfo().Names)[key] for i, value := range item { if params.Variadic && i >= len(params.Params)-1 { off := varoff + params.Offset[len(params.Params)-1] rt.setVar(off, append(rt.vars[off].([]any), value)) } else { rt.setVar(varoff+params.Offset[i], value) } } } } if block.Type == ObjectType_Func { start -= len(block.GetFuncInfo().Params) } var ( assign []*VarInfo tmpInt int64 tmpDec decimal.Decimal ) labels := make([]int, 0) main: for ci := 0; ci < len(block.Code); ci++ { if err = rt.SubCost(1); err != nil { break } if rt.timeLimit { err = ErrVMTimeLimit break } if rt.mem > memoryLimit { rt.vm.logger.WithFields(log.Fields{"type": consts.VMError}).Warn(ErrMemoryLimit) err = ErrMemoryLimit break } cmd = block.Code[ci] var bin any size := rt.len() if size < int(cmd.Cmd>>8) { rt.vm.logger.WithFields(log.Fields{"type": consts.VMError}).Error("stack is empty") err = fmt.Errorf(`stack is empty`) break } for i := 1; i <= int(cmd.Cmd>>8); i++ { top[i-1] = rt.stack[size-i] } switch cmd.Cmd { case cmdPush: rt.push(cmd.Value) case cmdPushStr: rt.push(cmd.Value.(string)) case cmdIf: if valueToBool(rt.peek()) { status, err = rt.RunCode(cmd.Value.(*CodeBlock)) } case cmdElse: if !valueToBool(rt.peek()) { status, err = rt.RunCode(cmd.Value.(*CodeBlock)) } case cmdWhile: val := rt.peek() rt.resetByIdx(rt.len() - 1) if valueToBool(val) { status, err = rt.RunCode(cmd.Value.(*CodeBlock)) newci := labels[len(labels)-1] labels = labels[:len(labels)-1] if status == statusContinue { ci = newci - 1 status = statusNormal continue } if status == statusBreak { status = statusNormal break } } case cmdLabel: labels = append(labels, ci) case cmdContinue: status = statusContinue case cmdBreak: status = statusBreak case cmdAssignVar: assign = cmd.Value.([]*VarInfo) case cmdAssign: count := len(assign) for ivar, item := range assign { val := rt.stack[rt.len()-count+ivar] if item.Owner == nil { if item.Obj.Type == ObjectType_ExtVar { var n = item.Obj.GetExtendVariable().Name if isSysVar(n) { err = fmt.Errorf(eSysVar, n) rt.vm.logger.WithError(err).Error("modifying system variable") break main } if v, ok := rt.extend[n]; ok && v != nil && reflect.TypeOf(v) != reflect.TypeOf(val) { err = fmt.Errorf("$%s (type %s) cannot be represented by the type %s", n, reflect.TypeOf(val), reflect.TypeOf(v)) break } rt.setExtendVar(n, val) } } else { for i := len(rt.blocks) - 1; i >= 0; i-- { if item.Owner == rt.blocks[i].Block { k := rt.blocks[i].Offset + item.Obj.GetVariable().Index switch v := rt.blocks[i].Block.Vars[item.Obj.GetVariable().Index]; v.String() { case Decimal: var v decimal.Decimal v, err = ValueToDecimal(val) if err != nil { break main } rt.setVar(k, v) default: if val != nil && v != reflect.TypeOf(val) { err = fmt.Errorf("variable '%v' (type %s) cannot be represented by the type %s", item.Obj.GetVariable().Name, reflect.TypeOf(val), v) break } rt.setVar(k, val) } break } } } } case cmdReturn: status = statusReturn case cmdError: eType := msgError if cmd.Value.(uint32) == keyWarning { eType = msgWarning } else if cmd.Value.(uint32) == keyInfo { eType = msgInfo } err = SetVMError(eType, rt.peek()) case cmdFuncName: ifunc := cmd.Value.(FuncNameCmd) mapoff := rt.len() - 1 - ifunc.Count if rt.stack[mapoff] == nil { rt.stack[mapoff] = make(map[string][]any) } params := make([]any, 0, ifunc.Count) for i := 0; i < ifunc.Count; i++ { cur := rt.stack[mapoff+1+i] if i == ifunc.Count-1 && rt.unwrap && reflect.TypeOf(cur).String() == `[]interface {}` { params = append(params, cur.([]any)...) rt.unwrap = false } else { params = append(params, cur) } } rt.stack[mapoff].(map[string][]any)[ifunc.Name] = params rt.resetByIdx(mapoff + 1) continue case cmdCallVariadic, cmdCall: var cost = int64(CostCall) if cmd.Value.(*ObjInfo).Type == ObjectType_ExtFunc { finfo := cmd.Value.(*ObjInfo).GetExtFuncInfo() if rt.vm.ExtCost != nil { cost = rt.vm.ExtCost(finfo.Name) if cost == -1 { cost = CostCall } } } if err = rt.SubCost(cost); err != nil { break } err = rt.callFunc(cmd.Cmd, cmd.Value.(*ObjInfo)) case cmdVar: ivar := cmd.Value.(*VarInfo) var i int for i = len(rt.blocks) - 1; i >= 0; i-- { if ivar.Owner == rt.blocks[i].Block { rt.push(rt.vars[rt.blocks[i].Offset+ivar.Obj.GetVariable().Index]) break } } if i < 0 { rt.vm.logger.WithFields(log.Fields{"var": ivar.Obj.Value}).Error("wrong var") err = fmt.Errorf(`wrong var %v`, ivar.Obj.Value) break main } case cmdExtend, cmdCallExtend: if err = rt.SubCost(CostExtend); err != nil { break } if val, ok := rt.extend[cmd.Value.(string)]; ok { if cmd.Cmd == cmdCallExtend { err = rt.extendFunc(cmd.Value.(string)) if err != nil { rt.vm.logger.WithFields(log.Fields{"error": err, "cmd": cmd.Value.(string)}).Error("executing extended function") err = fmt.Errorf(`extend function %s %s`, cmd.Value.(string), err) break main } } else { switch varVal := val.(type) { case int: val = int64(varVal) } rt.push(val) } } else { rt.vm.logger.WithFields(log.Fields{"cmd": cmd.Value}).Error("unknown extend identifier") err = fmt.Errorf(`unknown extend identifier %s`, cmd.Value.(string)) } case cmdIndex: rv := reflect.ValueOf(rt.stack[size-2]) itype := reflect.TypeOf(rt.stack[size-2]).String() switch { case itype == `*types.Map`: if reflect.TypeOf(rt.getStack(size-1)).String() != `string` { err = fmt.Errorf(eMapIndex, reflect.TypeOf(rt.getStack(size-1)).String()) break } v, found := rt.stack[size-2].(*types.Map).Get(rt.getStack(size - 1).(string)) if found { rt.stack[size-2] = v } else { rt.stack[size-2] = nil } rt.resetByIdx(size - 1) case itype[:2] == brackets: if reflect.TypeOf(rt.getStack(size-1)).String() != `int64` { err = fmt.Errorf(eArrIndex, reflect.TypeOf(rt.getStack(size-1)).String()) break } v := rv.Index(int(rt.getStack(size - 1).(int64))) if v.IsValid() { rt.stack[size-2] = v.Interface() } else { rt.stack[size-2] = nil } rt.resetByIdx(size - 1) default: itype := reflect.TypeOf(rt.stack[size-2]).String() rt.vm.logger.WithFields(log.Fields{"vm_type": itype}).Error("type does not support indexing") err = fmt.Errorf(`Type %s doesn't support indexing`, itype) } case cmdSetIndex: itype := reflect.TypeOf(rt.stack[size-3]).String() indexInfo := cmd.Value.(*IndexInfo) var indexKey int if indexInfo.Owner != nil { for i := len(rt.blocks) - 1; i >= 0; i-- { if indexInfo.Owner == rt.blocks[i].Block { indexKey = rt.blocks[i].Offset + indexInfo.VarOffset break } } } if isSelfAssignment(rt.stack[size-3], rt.getStack(size-1)) { err = errSelfAssignment break main } switch { case itype == `*types.Map`: if rt.stack[size-3].(*types.Map).Size() > maxMapCount { err = errMaxMapCount break } if reflect.TypeOf(rt.stack[size-2]).String() != `string` { err = fmt.Errorf(eMapIndex, reflect.TypeOf(rt.stack[size-2]).String()) break } rt.stack[size-3].(*types.Map).Set(rt.stack[size-2].(string), reflect.ValueOf(rt.getStack(size-1)).Interface()) rt.resetByIdx(size - 2) case itype[:2] == brackets: if reflect.TypeOf(rt.stack[size-2]).String() != `int64` { err = fmt.Errorf(eArrIndex, reflect.TypeOf(rt.stack[size-2]).String()) break } ind := rt.stack[size-2].(int64) if strings.Contains(itype, Interface) { slice := rt.stack[size-3].([]any) if int(ind) >= len(slice) { if ind > maxArrayIndex { err = errMaxArrayIndex break } slice = append(slice, make([]any, int(ind)-len(slice)+1)...) indexInfo := cmd.Value.(*IndexInfo) if indexInfo.Owner == nil { // Extend variable $varname rt.extend[indexInfo.Extend] = slice } else { rt.vars[indexKey] = slice } rt.stack[size-3] = slice } slice[ind] = rt.getStack(size - 1) } else { slice := rt.getStack(size - 3).([]map[string]string) slice[ind] = rt.getStack(size - 1).(map[string]string) } rt.resetByIdx(size - 2) default: rt.vm.logger.WithFields(log.Fields{"vm_type": itype}).Error("type does not support indexing") err = fmt.Errorf(`type %s doesn't support indexing`, itype) } if indexInfo.Owner == nil { rt.recalcMemExtendVar(indexInfo.Extend) } else { rt.recalcMemVar(indexKey) } case cmdUnwrapArr: if reflect.TypeOf(rt.getStack(size-1)).String() == `[]interface {}` { rt.unwrap = true } case cmdSign: switch top[0].(type) { case float64: rt.stack[size-1] = -top[0].(float64) default: rt.stack[size-1] = -top[0].(int64) } case cmdNot: rt.stack[size-1] = !valueToBool(top[0]) case cmdAdd: switch top[1].(type) { case string: switch top[0].(type) { case string: bin = top[1].(string) + top[0].(string) case int64: if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt + top[0].(int64) } case float64: bin = ValueToFloat(top[1]) + top[0].(float64) default: err = errUnsupportedType break main } case float64: switch top[0].(type) { case string, int64, float64: bin = top[1].(float64) + ValueToFloat(top[0]) default: err = errUnsupportedType break main } case int64: switch top[0].(type) { case string, int64: if tmpInt, err = converter.ValueToInt(top[0]); err == nil { bin = top[1].(int64) + tmpInt } case float64: bin = ValueToFloat(top[1]) + top[0].(float64) default: err = errUnsupportedType break main } default: if reflect.TypeOf(top[1]).String() == Decimal && reflect.TypeOf(top[0]).String() == Decimal { bin = top[1].(decimal.Decimal).Add(top[0].(decimal.Decimal)) } else { err = errUnsupportedType break main } } case cmdSub: switch top[1].(type) { case string: switch top[0].(type) { case int64: if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt - top[0].(int64) } case float64: bin = ValueToFloat(top[1]) - top[0].(float64) default: err = errUnsupportedType break main } case float64: switch top[0].(type) { case string, int64, float64: bin = top[1].(float64) - ValueToFloat(top[0]) default: err = errUnsupportedType break main } case int64: switch top[0].(type) { case int64, string: if tmpInt, err = converter.ValueToInt(top[0]); err == nil { bin = top[1].(int64) - tmpInt } case float64: bin = ValueToFloat(top[1]) - top[0].(float64) default: err = errUnsupportedType break main } default: if reflect.TypeOf(top[1]).String() == Decimal && reflect.TypeOf(top[0]).String() == Decimal { bin = top[1].(decimal.Decimal).Sub(top[0].(decimal.Decimal)) } else { err = errUnsupportedType break main } } case cmdMul: switch top[1].(type) { case string: switch top[0].(type) { case int64: if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt * top[0].(int64) } case float64: bin = ValueToFloat(top[1]) * top[0].(float64) default: err = errUnsupportedType break main } case float64: switch top[0].(type) { case string, int64, float64: bin = top[1].(float64) * ValueToFloat(top[0]) default: err = errUnsupportedType break main } case int64: switch top[0].(type) { case int64, string: if tmpInt, err = converter.ValueToInt(top[0]); err == nil { bin = top[1].(int64) * tmpInt } case float64: bin = ValueToFloat(top[1]) * top[0].(float64) default: err = errUnsupportedType break main } default: if reflect.TypeOf(top[1]).String() == Decimal && reflect.TypeOf(top[0]).String() == Decimal { bin = top[1].(decimal.Decimal).Mul(top[0].(decimal.Decimal)) } else { err = errUnsupportedType break main } } case cmdDiv: switch top[1].(type) { case string: switch v := top[0].(type) { case int64: if v == 0 { err = errDivZero break main } if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt / v } case float64: if v == 0 { err = errDivZero break main } bin = ValueToFloat(top[1]) / v default: err = errUnsupportedType break main } case float64: switch top[0].(type) { case string, int64, float64: vFloat := ValueToFloat(top[0]) if vFloat == 0 { err = errDivZero break main } bin = top[1].(float64) / vFloat default: err = errUnsupportedType break main } case int64: switch top[0].(type) { case int64, string: if tmpInt, err = converter.ValueToInt(top[0]); err == nil { if tmpInt == 0 { err = errDivZero break main } bin = top[1].(int64) / tmpInt } case float64: if top[0].(float64) == 0 { err = errDivZero break main } bin = ValueToFloat(top[1]) / top[0].(float64) default: err = errUnsupportedType break main } default: if reflect.TypeOf(top[1]).String() == Decimal && reflect.TypeOf(top[0]).String() == Decimal { if top[0].(decimal.Decimal).Cmp(decimal.Zero) == 0 { err = errDivZero break main } bin = top[1].(decimal.Decimal).Div(top[0].(decimal.Decimal)).Floor() } else { err = errUnsupportedType break main } } case cmdAnd: bin = valueToBool(top[1]) && valueToBool(top[0]) case cmdOr: bin = valueToBool(top[1]) || valueToBool(top[0]) case cmdEqual, cmdNotEq: if top[1] == nil || top[0] == nil { bin = top[0] == top[1] } else { switch top[1].(type) { case string: switch top[0].(type) { case int64: if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt == top[0].(int64) } case float64: bin = ValueToFloat(top[1]) == top[0].(float64) default: if reflect.TypeOf(top[0]).String() == Decimal { if tmpDec, err = ValueToDecimal(top[1]); err != nil { break main } bin = tmpDec.Cmp(top[0].(decimal.Decimal)) == 0 } else { bin = top[1].(string) == top[0].(string) } } case float64: bin = top[1].(float64) == ValueToFloat(top[0]) case int64: switch top[0].(type) { case int64: bin = top[1].(int64) == top[0].(int64) case float64: bin = ValueToFloat(top[1]) == top[0].(float64) default: err = errUnsupportedType break main } case bool: switch top[0].(type) { case bool: bin = top[1].(bool) == top[0].(bool) default: err = errUnsupportedType break main } default: if tmpDec, err = ValueToDecimal(top[0]); err != nil { break main } bin = top[1].(decimal.Decimal).Cmp(tmpDec) == 0 } } if cmd.Cmd == cmdNotEq { bin = !bin.(bool) } case cmdLess, cmdNotLess: switch top[1].(type) { case string: switch top[0].(type) { case int64: if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt < top[0].(int64) } case float64: bin = ValueToFloat(top[1]) < top[0].(float64) default: if reflect.TypeOf(top[0]).String() == Decimal { if tmpDec, err = ValueToDecimal(top[1]); err != nil { break main } bin = tmpDec.Cmp(top[0].(decimal.Decimal)) < 0 } else { bin = top[1].(string) < top[0].(string) } } case float64: bin = top[1].(float64) < ValueToFloat(top[0]) case int64: switch top[0].(type) { case int64: bin = top[1].(int64) < top[0].(int64) case float64: bin = ValueToFloat(top[1]) < top[0].(float64) default: err = errUnsupportedType break main } default: if tmpDec, err = ValueToDecimal(top[0]); err != nil { break main } bin = top[1].(decimal.Decimal).Cmp(tmpDec) < 0 } if cmd.Cmd == cmdNotLess { bin = !bin.(bool) } case cmdGreat, cmdNotGreat: switch top[1].(type) { case string: switch top[0].(type) { case int64: if tmpInt, err = converter.ValueToInt(top[1]); err == nil { bin = tmpInt > top[0].(int64) } case float64: bin = ValueToFloat(top[1]) > top[0].(float64) default: if reflect.TypeOf(top[0]).String() == Decimal { if tmpDec, err = ValueToDecimal(top[1]); err != nil { break main } bin = tmpDec.Cmp(top[0].(decimal.Decimal)) > 0 } else { bin = top[1].(string) > top[0].(string) } } case float64: bin = top[1].(float64) > ValueToFloat(top[0]) case int64: switch top[0].(type) { case int64: bin = top[1].(int64) > top[0].(int64) case float64: bin = ValueToFloat(top[1]) > top[0].(float64) default: err = errUnsupportedType break main } default: if tmpDec, err = ValueToDecimal(top[0]); err != nil { break main } bin = top[1].(decimal.Decimal).Cmp(tmpDec) > 0 } if cmd.Cmd == cmdNotGreat { bin = !bin.(bool) } case cmdArrayInit: var initArray []any initArray, err = rt.getResultArray(cmd.Value.([]mapItem)) if err != nil { break main } rt.push(initArray) case cmdMapInit: var initMap *types.Map initMap, err = rt.getResultMap(cmd.Value.(*types.Map)) if err != nil { break main } rt.push(initMap) default: rt.vm.logger.WithFields(log.Fields{"vm_cmd": cmd.Cmd}).Error("Unknown command") err = fmt.Errorf(`unknown command %d`, cmd.Cmd) } if err != nil { break } if status == statusReturn || status == statusContinue || status == statusBreak { break } if (cmd.Cmd >> 8) == 2 { rt.stack[size-2] = bin rt.resetByIdx(size - 1) } } if err != nil { return } last := rt.popBlock() if status == statusReturn { if last.Block.Type == ObjectType_Func { lastResults := last.Block.GetFuncInfo().Results if len(lastResults) > rt.len() { var keyNames []string for i := 0; i < len(lastResults); i++ { keyNames = append(keyNames, lastResults[i].String()) } err = fmt.Errorf("func '%s' not enough arguments to return, need [%s]", last.Block.GetFuncInfo().Name, strings.Join(keyNames, "|")) return } stackCpy := make([]any, rt.len()) copy(stackCpy, rt.stack) var index int for count := len(lastResults); count > 0; count-- { val := stackCpy[len(stackCpy)-1-index] if val != nil && lastResults[count-1] != reflect.TypeOf(val) { err = fmt.Errorf("function '%s' return index[%d] (type %s) cannot be represented by the type %s", last.Block.GetFuncInfo().Name, count-1, reflect.TypeOf(val), lastResults[count-1]) return } rt.stack[start] = rt.stack[rt.len()-count] start++ index++ } status = statusNormal } else { return } } rt.resetByIdx(start) return } // Run executes CodeBlock with the specified parameters and extended variables and functions func (rt *RunTime) Run(block *CodeBlock, params []any, extend map[string]any) (ret []any, err error) { defer func() { if r := recover(); r != nil { //rt.vm.logger.WithFields(log.Fields{"type": consts.PanicRecoveredError, "error_info": r, "stack": string(debug.Stack())}).Error("runtime panic error") err = fmt.Errorf(`runtime panic: %v`, r) } }() info := block.GetFuncInfo() rt.extend = extend var ( genBlock bool timer *time.Timer ) if gen, ok := extend[Extend_gen_block]; ok { genBlock = gen.(bool) } timeOver := func() { rt.timeLimit = true } if genBlock { timer = time.AfterFunc(time.Millisecond*time.Duration(extend[Extend_time_limit].(int64)), timeOver) } if _, err = rt.RunCode(block); err == nil { if rt.len() < len(info.Results) { var keyNames []string for i := 0; i < len(info.Results); i++ { keyNames = append(keyNames, info.Results[i].String()) } err = fmt.Errorf("not enough arguments to return, need [%s]", strings.Join(keyNames, "|")) } off := rt.len() - len(info.Results) for i := 0; i < len(info.Results) && off >= 0; i++ { ret = append(ret, rt.stack[off+i]) } } if genBlock { timer.Stop() } return } ================================================ FILE: packages/script/runtime_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "testing" "github.com/stretchr/testify/assert" ) func TestCalcMem(t *testing.T) { cases := []struct { v any mem int64 }{ {true, 1}, {int8(1), 1}, {int16(1), 2}, {int32(1), 4}, {int64(1), 8}, {int(1), 8}, {float32(1), 4}, {float64(1), 8}, {"test", 4}, {[]byte("test"), 16}, {[]string{"test", "test"}, 20}, {map[string]string{"test": "test"}, 12}, } for _, v := range cases { assert.Equal(t, v.mem, calcMem(v.v)) } } ================================================ FILE: packages/script/stack.go ================================================ package script func (rt *RunTime) Stack() []any { return rt.stack } func (rt *RunTime) push(d any) { rt.stack = append(rt.stack, d) } func (rt *RunTime) pop() (ret any) { ret = rt.peek() rt.stack = rt.stack[:rt.len()-1] return } func (rt *RunTime) len() int { return len(rt.stack) } func (rt *RunTime) swap(n int) { rt.stack[rt.len()-n], rt.stack[rt.len()-1] = rt.peek(), rt.stack[rt.len()-n] } func (rt *RunTime) dup(n int) { rt.push(&rt.stack[rt.len()-n]) } func (rt *RunTime) peek() any { return rt.stack[rt.len()-1] } func (rt *RunTime) getStack(idx int) any { if idx >= 0 && rt.len() > 0 && rt.len() > idx { return rt.stack[idx] } return nil } func (rt *RunTime) resetByIdx(idx int) { rt.stack = rt.stack[:idx] } func (rt *RunTime) popBlock() (ret *blockStack) { ret = rt.blocks[len(rt.blocks)-1] rt.blocks = rt.blocks[:len(rt.blocks)-1] return } ================================================ FILE: packages/script/state.go ================================================ package script type ( stateTypes int stateLine map[int]compileState // The list of compile states compileStates map[stateTypes]stateLine ) const ( // The list of state types stateRoot stateTypes = iota stateBody stateBlock stateContract stateFunc stateFParams stateFParam stateFParamTYPE stateFTail stateFResult stateFDot stateVar stateVarType stateAssignEval stateAssign stateTX stateSettings stateConsts stateConstsAssign stateConstsValue stateFields stateEval // The list of state flags statePush = 0x0100 statePop = 0x0200 stateStay = 0x0400 stateToBlock = 0x0800 stateToBody = 0x1000 stateFork = 0x2000 stateToFork = 0x4000 stateLabel = 0x8000 stateMustEval = 0x010000 flushMark = 0x100000 ) var ( // 'states' describes a finite machine with states on the base of which a bytecode will be generated states = compileStates{ stateRoot: { // stateRoot lexNewLine: newCompileState(stateRoot, cfNothing), lexKeyword | (keyContract << 8): newCompileState(stateContract|statePush, cfNothing), lexKeyword | (keyFunc << 8): newCompileState(stateFunc|statePush, cfNothing), lexUnknown: newCompileState(errUnknownCmd, cfError), }, stateBody: { // stateBody lexNewLine: newCompileState(stateBody, cfNothing), lexKeyword | (keyFunc << 8): newCompileState(stateFunc|statePush, cfNothing), lexKeyword | (keyReturn << 8): newCompileState(stateEval, cfReturn), lexKeyword | (keyContinue << 8): newCompileState(stateBody, cfContinue), lexKeyword | (keyBreak << 8): newCompileState(stateBody, cfBreak), lexKeyword | (keyIf << 8): newCompileState(stateEval|statePush|stateToBlock|stateMustEval, cfIf), lexKeyword | (keyWhile << 8): newCompileState(stateEval|statePush|stateToBlock|stateLabel|stateMustEval, cfWhile), lexKeyword | (keyElse << 8): newCompileState(stateBlock|statePush, cfElse), lexKeyword | (keyVar << 8): newCompileState(stateVar, cfNothing), lexKeyword | (keyTX << 8): newCompileState(stateTX, cfTX), lexKeyword | (keySettings << 8): newCompileState(stateSettings, cfSettings), lexKeyword | (keyError << 8): newCompileState(stateEval, cfCmdError), lexKeyword | (keyWarning << 8): newCompileState(stateEval, cfCmdError), lexKeyword | (keyInfo << 8): newCompileState(stateEval, cfCmdError), lexIdent: newCompileState(stateAssignEval|stateFork, cfNothing), lexExtend: newCompileState(stateAssignEval|stateFork, cfNothing), isRCurly: newCompileState(statePop, cfNothing), lexUnknown: newCompileState(errMustRCurly, cfError), }, stateBlock: { // stateBlock lexNewLine: newCompileState(stateBlock, cfNothing), isLCurly: newCompileState(stateBody, cfNothing), lexUnknown: newCompileState(errMustLCurly, cfError), }, stateContract: { // stateContract lexNewLine: newCompileState(stateContract, cfNothing), lexIdent: newCompileState(stateBlock, cfNameBlock), lexUnknown: newCompileState(errMustName, cfError), }, stateFunc: { // stateFunc lexNewLine: newCompileState(stateFunc, cfNothing), lexIdent: newCompileState(stateFParams, cfNameBlock), lexUnknown: newCompileState(errMustName, cfError), }, stateFParams: { // stateFParams lexNewLine: newCompileState(stateFParams, cfNothing), isLPar: newCompileState(stateFParam, cfNothing), lexUnknown: newCompileState(stateFResult|stateStay, cfNothing), }, stateFParam: { // stateFParam lexNewLine: newCompileState(stateFParam, cfNothing), lexIdent: newCompileState(stateFParamTYPE, cfFParam), //lexType: newCompileState(stateFParam, cfFType), isComma: newCompileState(stateFParam, cfNothing), isRPar: newCompileState(stateFResult, cfNothing), lexUnknown: newCompileState(errParams, cfError), }, stateFParamTYPE: { // stateFParamTYPE lexIdent: newCompileState(stateFParamTYPE, cfFParam), lexType: newCompileState(stateFParam, cfFType), lexKeyword | (keyTail << 8): newCompileState(stateFTail, cfFTail), isComma: newCompileState(stateFParamTYPE, cfNothing), // isRPar: newCompileState(stateFResult, Func: cfNothing), lexUnknown: newCompileState(errVarType, cfError), }, stateFTail: { // stateFTail lexNewLine: newCompileState(stateFTail, cfNothing), isRPar: newCompileState(stateFResult, cfNothing), lexUnknown: newCompileState(errParams, cfError), }, stateFResult: { // stateFResult lexNewLine: newCompileState(stateFResult, cfNothing), isDot: newCompileState(stateFDot, cfNothing), lexType: newCompileState(stateFResult, cfFResult), isComma: newCompileState(stateFResult, cfNothing), lexUnknown: newCompileState(stateBlock|stateStay, cfNothing), }, stateFDot: { // stateFDot lexNewLine: newCompileState(stateFDot, cfNothing), lexIdent: newCompileState(stateFParams, cfFNameParam), lexUnknown: newCompileState(errMustName, cfError), }, stateVar: { // stateVar lexNewLine: newCompileState(stateBody, cfNothing), lexIdent: newCompileState(stateVarType, cfFParam), isRCurly: newCompileState(stateBody|stateStay, cfNothing), isComma: newCompileState(stateVar, cfNothing), lexUnknown: newCompileState(errVars, cfError), }, stateVarType: { // stateVarType lexIdent: newCompileState(stateVarType, cfFParam), lexType: newCompileState(stateVar, cfFType), isComma: newCompileState(stateVarType, cfNothing), lexUnknown: newCompileState(errVarType, cfError), }, stateAssignEval: { // stateAssignEval isLPar: newCompileState(stateEval|stateToFork|stateToBody, cfNothing), isLBrack: newCompileState(stateEval|stateToFork|stateToBody, cfNothing), lexUnknown: newCompileState(stateAssign|stateToFork|stateStay, cfNothing), }, stateAssign: { // stateAssign isComma: newCompileState(stateAssign, cfNothing), lexIdent: newCompileState(stateAssign, cfAssignVar), lexExtend: newCompileState(stateAssign, cfAssignVar), isEq: newCompileState(stateEval|stateToBody, cfAssign), lexUnknown: newCompileState(errAssign, cfError), }, stateTX: { // stateTX lexNewLine: newCompileState(stateTX, cfNothing), isLCurly: newCompileState(stateFields, cfNothing), //lexIdent: newCompileState(stateAssign, cfTX), //todo lexExtend: newCompileState(stateAssign, cfTX), //todo lexUnknown: newCompileState(errMustLCurly, cfError), }, stateSettings: { // stateSettings lexNewLine: newCompileState(stateSettings, cfNothing), isLCurly: newCompileState(stateConsts, cfNothing), lexUnknown: newCompileState(errMustLCurly, cfError), }, stateConsts: { // stateConsts lexNewLine: newCompileState(stateConsts, cfNothing), isComma: newCompileState(stateConsts, cfNothing), lexIdent: newCompileState(stateConstsAssign, cfConstName), isRCurly: newCompileState(stateToBody, cfNothing), lexUnknown: newCompileState(errMustRCurly, cfError), }, stateConstsAssign: { // stateConstsAssign isEq: newCompileState(stateConstsValue, cfNothing), lexUnknown: newCompileState(errAssign, cfError), }, stateConstsValue: { // stateConstsValue lexString: newCompileState(stateConsts, cfConstValue), lexNumber: newCompileState(stateConsts, cfConstValue), lexUnknown: newCompileState(errStrNum, cfError), }, stateFields: { // stateFields lexNewLine: newCompileState(stateFields, cfFieldLine), isComma: newCompileState(stateFields, cfFieldComma), lexIdent: newCompileState(stateFields, cfField), lexType: newCompileState(stateFields, cfFieldType), lexString: newCompileState(stateFields, cfFieldTag), isRCurly: newCompileState(stateToBody, cfFields), lexUnknown: newCompileState(errMustRCurly, cfError), }, } ) ================================================ FILE: packages/script/vm.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "fmt" "regexp" "strings" "sync" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) type GlobalVm struct { mu sync.Mutex smartVM *VM } func init() { _vm = newVM() } var ( _vm *GlobalVm ) func newVM() *GlobalVm { vm := NewVM() return &GlobalVm{ smartVM: vm, } } // GetVM is returning smart vm func GetVM() *VM { _vm.mu.Lock() defer _vm.mu.Unlock() return _vm.smartVM } var smartObjects map[string]*ObjInfo var children uint32 func SavepointSmartVMObjects() { smartObjects = make(map[string]*ObjInfo) for k, v := range GetVM().Objects { smartObjects[k] = v } children = uint32(len(GetVM().Children)) } func RollbackSmartVMObjects() { GetVM().Objects = make(map[string]*ObjInfo) for k, v := range smartObjects { GetVM().Objects[k] = v } GetVM().Children = GetVM().Children[:children] smartObjects = nil } func ReleaseSmartVMObjects() { smartObjects = nil children = 0 } func VMCompileEval(vm *VM, src string, prefix uint32) error { var ok bool if len(src) == 0 { return nil } allowed := []string{`0`, `1`, `true`, `false`, `ContractConditions\(\s*\".*\"\s*\)`, `ContractAccess\(\s*\".*\"\s*\)`, `RoleAccess\(\s*.*\s*\)`} for _, v := range allowed { re := regexp.MustCompile(`^` + v + `$`) if re.Match([]byte(src)) { ok = true break } } if !ok { return fmt.Errorf(eConditionNotAllowed, src) } err := vm.CompileEval(src, prefix) if err != nil { return err } re := regexp.MustCompile(`^@?[\d\w_]+$`) for _, item := range getContractList(src) { if len(item) == 0 || !re.Match([]byte(item)) { return errIncorrectParameter } } return nil } func getContractList(src string) (list []string) { for _, funcCond := range []string{`ContractConditions`, `ContractAccess`} { if strings.Contains(src, funcCond) { if ret := regexp.MustCompile(funcCond + `\(\s*(.*)\s*\)`).FindStringSubmatch(src); len(ret) == 2 { for _, item := range strings.Split(ret[1], `,`) { list = append(list, strings.Trim(item, "\"` ")) } } } } return } func VMGetContractByID(vm *VM, id int32) *ContractInfo { var tableID int64 if id > consts.ShiftContractID { tableID = int64(id - consts.ShiftContractID) id = int32(tableID + vm.ShiftContract) } idcont := id if len(vm.Children) <= int(idcont) { return nil } if vm.Children[idcont] == nil || vm.Children[idcont].Type != ObjectType_Contract { return nil } if tableID > 0 && vm.Children[idcont].GetContractInfo().Owner.TableID != tableID { return nil } return vm.Children[idcont].GetContractInfo() } func RunContractById(vm *VM, id int32, methods []string, extend map[string]any, txHash []byte) error { info := VMGetContractByID(vm, id) if info == nil { return fmt.Errorf(`unknown contract id '%d'`, id) } return RunContractByName(vm, info.Name, methods, extend, txHash) } func RunContractByName(vm *VM, name string, methods []string, extend map[string]any, txHash []byte) error { obj, ok := vm.Objects[name] if !ok { return fmt.Errorf(`unknown object '%s'`, name) } if obj.Type != ObjectType_Contract { return fmt.Errorf(eUnknownContract, name) } contract := obj.GetCodeBlock() extend[Extend_txcost] = extend[Extend_txcost].(int64) - CostContract - contract.contractBaseCost() if extend[Extend_txcost].(int64) < 0 { return fmt.Errorf("runtime cost limit overflow") } var err error for i := 0; i < len(methods); i++ { method := methods[i] obj, ok := contract.Objects[method] if !ok { continue } if obj.Type == ObjectType_Func { fn := obj.GetCodeBlock() _, err = VMRun(vm, fn, nil, extend, txHash) if err != nil { break } } } return err } // VMRun executes CodeBlock in vm func VMRun(vm *VM, block *CodeBlock, params []any, extend map[string]any, hash []byte) (ret []any, err error) { if block == nil { return nil, fmt.Errorf(`code block is nil`) } var cost int64 if ecost, ok := extend[Extend_txcost]; ok { cost = ecost.(int64) } else { cost = syspar.GetMaxCost() } rt := NewRunTime(vm, cost) ret, err = rt.Run(block, params, extend) extend[Extend_txcost] = rt.Cost() if err != nil { vm.logger.WithFields(log.Fields{"type": consts.VMError, "tx_hash": fmt.Sprintf("%x", hash), "error": err, "original_contract": extend[Extend_original_contract], "this_contract": extend[Extend_this_contract], "ecosystem_id": extend[Extend_ecosystem_id]}).Error("running block in smart vm") return nil, err } return } func VMObjectExists(vm *VM, name string, state uint32) bool { name = StateName(state, name) _, ok := vm.Objects[name] return ok } // SetExtendCost sets the cost of calling extended obj in vm func (vm *VM) SetExtendCost(ext func(string) int64) { vm.ExtCost = ext } // SetFuncCallsDB Set up functions that can edit the database in vm func (vm *VM) SetFuncCallsDB(funcCallsDB map[string]struct{}) { vm.FuncCallsDB = funcCallsDB } func LoadSysFuncs(vm *VM, state int) error { code := `func DBFind(table string).Select(query string).Columns(columns string).Where(where map) .WhereId(id int).Order(order string).Limit(limit int).Offset(offset int).Group(group string).All(all bool) array { return DBSelect(table, columns, id, order, offset, limit, where, query, group, all) } func One(list array, name string) string { if list { var row map row = list[0] if Contains(name, "->") { var colfield array var val string colfield = Split(ToLower(name), "->") val = row[Join(colfield, ".")] if !val && row[colfield[0]] { var fields map var i int fields = JSONDecode(row[colfield[0]]) val = fields[colfield[1]] i = 2 while i < Len(colfield) { if GetType(val) == "map[string]interface {}" { val = val[colfield[i]] if !val { break } i= i+1 } else { break } } } if !val { return "" } return val } return Str(row[name]) } return "" } func Row(list array) map { var ret map if list { ret = list[0] } return ret } func DBRow(table string).Columns(columns string).Where(where map) .WhereId(id int).Order(order string) map { var result array result = DBFind(table).Columns(columns).Where(where).WhereId(id).Order(order) var row map if Len(result) > 0 { row = result[0] } return row } func ConditionById(table string, validate bool) { var row map row = DBRow(table).Columns("conditions").WhereId($Id) if !row["conditions"] { error Sprintf("Item %d has not been found", $Id) } Eval(row["conditions"]) if validate { ValidateCondition($Conditions,$ecosystem_id) } } func CurrentKeyFromAccount(account string) int { var row map row = DBRow("@1keys").Columns("id").Where({"account": account, "deleted": 0}) if row { return row["id"] } return 0 }` return vm.Compile([]rune(code), &OwnerInfo{StateID: uint32(state)}) } ================================================ FILE: packages/script/vm.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: vm.proto package script import ( fmt "fmt" proto "github.com/gogo/protobuf/proto" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // VMType is virtual machine type type VMType int32 const ( // VMType_INVALID is invalid type VMType_INVALID VMType = 0 // VMType_Smart is smart vm type VMType_Smart VMType = 1 // VMType_CLB is clb vm type VMType_CLB VMType = 2 // VMType_CLBMaster is CLBMaster type VMType_CLBMaster VMType = 3 ) var VMType_name = map[int32]string{ 0: "INVALID", 1: "Smart", 2: "CLB", 3: "CLBMaster", } var VMType_value = map[string]int32{ "INVALID": 0, "Smart": 1, "CLB": 2, "CLBMaster": 3, } func (x VMType) String() string { return proto.EnumName(VMType_name, int32(x)) } func (VMType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_cab246c8c7c5372d, []int{0} } // ObjectType Types of the compiled objects type ObjectType int32 const ( // ObjUnknown is an unknown object. ObjectType_Unknown ObjectType = 0 // ObjectType_Contract is a contract object. ObjectType_Contract ObjectType = 1 // ObjectType_Func is a function object. myfunc() ObjectType_Func ObjectType = 2 // ObjectType_ExtFunc is an extended build in function object. $myfunc() ObjectType_ExtFunc ObjectType = 3 // ObjectType_Var is a variable. myvar ObjectType_Var ObjectType = 4 // ObjectType_ExtVar is an extended build in variable. $myvar ObjectType_ExtVar ObjectType = 5 ) var ObjectType_name = map[int32]string{ 0: "Unknown", 1: "Contract", 2: "Func", 3: "ExtFunc", 4: "Var", 5: "ExtVar", } var ObjectType_value = map[string]int32{ "Unknown": 0, "Contract": 1, "Func": 2, "ExtFunc": 3, "Var": 4, "ExtVar": 5, } func (x ObjectType) String() string { return proto.EnumName(ObjectType_name, int32(x)) } func (ObjectType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_cab246c8c7c5372d, []int{1} } func init() { proto.RegisterEnum("script.VMType", VMType_name, VMType_value) proto.RegisterEnum("script.ObjectType", ObjectType_name, ObjectType_value) } func init() { proto.RegisterFile("vm.proto", fileDescriptor_cab246c8c7c5372d) } var fileDescriptor_cab246c8c7c5372d = []byte{ // 241 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x28, 0xcb, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0x4e, 0x2e, 0xca, 0x2c, 0x28, 0xd1, 0xb2, 0xe0, 0x62, 0x0b, 0xf3, 0x0d, 0xa9, 0x2c, 0x48, 0x15, 0xe2, 0xe6, 0x62, 0xf7, 0xf4, 0x0b, 0x73, 0xf4, 0xf1, 0x74, 0x11, 0x60, 0x10, 0xe2, 0xe4, 0x62, 0x0d, 0xce, 0x4d, 0x2c, 0x2a, 0x11, 0x60, 0x14, 0x62, 0xe7, 0x62, 0x76, 0xf6, 0x71, 0x12, 0x60, 0x12, 0xe2, 0xe5, 0xe2, 0x74, 0xf6, 0x71, 0xf2, 0x4d, 0x2c, 0x2e, 0x49, 0x2d, 0x12, 0x60, 0xd6, 0x0a, 0xe6, 0xe2, 0xf2, 0x4f, 0xca, 0x4a, 0x4d, 0x2e, 0x81, 0xe9, 0x0e, 0xcd, 0xcb, 0xce, 0xcb, 0x2f, 0xcf, 0x13, 0x60, 0x10, 0xe2, 0xe1, 0xe2, 0x70, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x06, 0x19, 0xc0, 0xc1, 0xc5, 0xe2, 0x56, 0x9a, 0x97, 0x2c, 0xc0, 0x04, 0x52, 0xe4, 0x5a, 0x51, 0x02, 0xe6, 0x30, 0x83, 0xcc, 0x0d, 0x4b, 0x2c, 0x12, 0x60, 0x11, 0xe2, 0xe2, 0x62, 0x73, 0xad, 0x28, 0x01, 0xb1, 0x59, 0x9d, 0x5c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x2b, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0xdf, 0xd3, 0xc9, 0x31, 0x42, 0x37, 0x33, 0x5f, 0x3f, 0x3d, 0x5f, 0x37, 0x33, 0x29, 0xb1, 0x42, 0xbf, 0x20, 0x31, 0x39, 0x3b, 0x31, 0x3d, 0xb5, 0x58, 0x1f, 0xe2, 0xa9, 0x24, 0x36, 0xb0, 0x1f, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x01, 0x5c, 0x7e, 0x1d, 0xef, 0x00, 0x00, 0x00, } ================================================ FILE: packages/script/vminit.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package script import ( "fmt" "reflect" "strings" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" ) const ( // CostCall is the cost of the function calling CostCall = 50 // CostContract is the cost of the contract calling CostContract = 100 // CostExtend is the cost of the extend function calling CostExtend = 10 TagFile = "file" TagAddress = "address" TagSignature = "signature" TagOptional = "optional" ) // ExtFuncInfo is the structure for the extended function type ExtFuncInfo struct { Name string Params []reflect.Type Results []reflect.Type Auto []string Variadic bool Func any CanWrite bool // If the function can update DB } // FieldInfo describes the field of the data structure type FieldInfo struct { Name string Type reflect.Type Original uint32 Tags string } // ContainsTag returns whether the tag is contained in this field func (fi *FieldInfo) ContainsTag(tag string) bool { return strings.Contains(fi.Tags, tag) } // ContractInfo contains the contract information type ContractInfo struct { ID uint32 Name string Owner *OwnerInfo Used map[string]bool // Called contracts Tx *[]*FieldInfo Settings map[string]any CanWrite bool // If the function can update DB } func (c *ContractInfo) TxMap() map[string]*FieldInfo { if c == nil { return nil } var m = make(map[string]*FieldInfo) for _, n := range *c.Tx { m[n.Name] = nil } return m } // FuncNameCmd for cmdFuncName type FuncNameCmd struct { Name string Count int } // FuncName is storing param of FuncName type FuncName struct { Params []reflect.Type Offset []int Variadic bool } // FuncInfo contains the function information type FuncInfo struct { Name string Params []reflect.Type Results []reflect.Type //tail function Names *map[string]FuncName Variadic bool ID uint32 CanWrite bool // If the function can update DB } // VarInfo contains the variable information type VarInfo struct { Obj *ObjInfo Owner *CodeBlock } // IndexInfo contains the information for SetIndex type IndexInfo struct { VarOffset int Owner *CodeBlock Extend string } // VM is the main type of the virtual machine type VM struct { *CodeBlock ExtCost func(string) int64 FuncCallsDB map[string]struct{} Extern bool // extern mode of compilation ShiftContract int64 // id of the first contract logger *log.Entry } // Stacker represents interface for working with call stack type Stacker interface { AppendStack(fn string) error PopStack(fn string) } // NewVM creates a new virtual machine func NewVM() *VM { vm := &VM{ CodeBlock: NewCodeBlock(), Extern: true, FuncCallsDB: make(map[string]struct{}), } vm.logger = log.WithFields(log.Fields{"type": consts.VMError, "extern": vm.Extern, "vm_block_type": vm.CodeBlock.Type}) return vm } func getNameByObj(obj *ObjInfo) (name string) { block := obj.GetCodeBlock() for key, val := range block.Parent.Objects { if val == obj { name = key break } } return } // Call executes the name object with the specified params and extended variables and functions func (vm *VM) Call(name string, params []any, extend map[string]any) (ret []any, err error) { var obj *ObjInfo if state, ok := extend[Extend_rt_state]; ok { obj = vm.getObjByNameExt(name, state.(uint32)) } else { obj = vm.getObjByName(name) } if obj == nil { vm.logger.WithFields(log.Fields{"type": consts.VMError, "vm_func_name": name}).Error("unknown function") return nil, fmt.Errorf(`unknown function %s`, name) } switch obj.Type { case ObjectType_Func: var cost int64 if v, ok := extend[Extend_txcost]; ok { cost = v.(int64) } else { cost = syspar.GetMaxCost() } rt := NewRunTime(vm, cost) ret, err = rt.Run(obj.GetCodeBlock(), params, extend) extend[Extend_txcost] = rt.Cost() case ObjectType_ExtFunc: finfo := obj.GetExtFuncInfo() foo := reflect.ValueOf(finfo.Func) var result []reflect.Value pars := make([]reflect.Value, len(finfo.Params)) if finfo.Variadic { for i := 0; i < len(pars)-1; i++ { pars[i] = reflect.ValueOf(params[i]) } pars[len(pars)-1] = reflect.ValueOf(params[len(pars)-1:]) result = foo.CallSlice(pars) } else { for i := 0; i < len(pars); i++ { pars[i] = reflect.ValueOf(params[i]) } result = foo.Call(pars) } for _, iret := range result { ret = append(ret, iret.Interface()) } default: vm.logger.WithFields(log.Fields{"type": consts.VMError, "vm_func_name": name}).Error("unknown function") return nil, fmt.Errorf(`unknown function %s`, name) } return ret, err } ================================================ FILE: packages/service/gateway/gateway.go ================================================ package gateway ================================================ FILE: packages/service/jsonrpc/accounts.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "encoding/json" "errors" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" "net/http" ) type accountsApi struct { Mode } func newAccountsApi(m Mode) *accountsApi { return &accountsApi{m} } func (c *accountsApi) GetKeysCount(ctx RequestContext) (*int64, *Error) { r := ctx.HTTPRequest() cnt, err := sqldb.GetKeysCount() if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting keys count") return nil, InternalError(err.Error()) } return &cnt, nil } type BalanceResult struct { Amount string `json:"amount"` Digits int64 `json:"digits"` Total string `json:"total"` Utxo string `json:"utxo"` TokenSymbol string `json:"token_symbol"` TokenName string `json:"token_name"` } type AccountOrKeyId struct { KeyId int64 `json:"key_id,omitempty"` Account string `json:"account,omitempty"` } func (bh *AccountOrKeyId) Validate(r *http.Request) error { if bh == nil { return errors.New(paramsEmpty) } if bh.KeyId == 0 { return errors.New("invalid input") } return nil } // UnmarshalJSON verify input, keyId is preferred func (bh *AccountOrKeyId) UnmarshalJSON(data []byte) error { type rename AccountOrKeyId info := rename{} err := json.Unmarshal(data, &info) if err == nil { if info.KeyId != 0 { if converter.IDToAddress(info.KeyId) == `invalid` { return errors.New("invalid key id") } bh.KeyId = info.KeyId return nil } keyId := converter.AddressToID(info.Account) if keyId == 0 { return errors.New("invalid Account") } bh.KeyId = keyId return nil } var input string err = json.Unmarshal(data, &input) if err != nil { return err } keyId := converter.AddressToID(input) if keyId == 0 { return errors.New("invalid key id or account address") } bh.KeyId = keyId return nil } func (b *accountsApi) GetBalance(ctx RequestContext, info *AccountOrKeyId, ecosystemId *int64) (*BalanceResult, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) form := &ecosystemForm{ Validator: b.EcosystemGetter, } if ecosystemId != nil { form.EcosystemID = *ecosystemId } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } if err := parameterValidator(r, info); err != nil { return nil, InvalidParamsError(err.Error()) } keyId := info.KeyId key := &sqldb.Key{} key.SetTablePrefix(form.EcosystemID) _, err := key.Get(nil, keyId) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting Key for wallet") return nil, DefaultError(err.Error()) } accountAmount, _ := decimal.NewFromString(key.Amount) sp := &sqldb.SpentInfo{} utxoAmount, err := sp.GetBalance(nil, keyId, form.EcosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting UTXO Key for wallet") return nil, DefaultError(err.Error()) } total := utxoAmount.Add(accountAmount) eco := sqldb.Ecosystem{} _, err = eco.Get(nil, form.EcosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting key balance token symbol") return nil, DefaultError(err.Error()) } return &BalanceResult{ Amount: key.Amount, Digits: eco.Digits, Total: total.String(), Utxo: utxoAmount.String(), TokenSymbol: eco.TokenSymbol, TokenName: eco.TokenName, }, nil } ================================================ FILE: packages/service/jsonrpc/api.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "encoding/hex" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" "net/http" "strings" ) const ( multipartBuf = 100000 // the buffer size for ParseMultipartForm multipartFormData = "multipart/form-data" ) type Mode struct { EcosystemGetter types.EcosystemGetter ContractRunner types.SmartContractRunner ClientTxProcessor types.ClientTxPreprocessor } type UserClient struct { KeyID int64 AccountID string EcosystemID int64 EcosystemName string RoleID int64 } func (c *UserClient) Prefix() string { return converter.Int64ToStr(c.EcosystemID) } type formValidator interface { Validate(r *http.Request) error } func parameterValidator(r *http.Request, f formValidator) (err error) { return f.Validate(r) } func isMultipartForm(r *http.Request) bool { return strings.HasPrefix(r.Header.Get("Content-Type"), multipartFormData) } type hexValue struct { value []byte } func (hv hexValue) Bytes() []byte { return hv.value } func (hv *hexValue) UnmarshalText(v []byte) (err error) { hv.value, err = hex.DecodeString(string(v)) return } ================================================ FILE: packages/service/jsonrpc/auth.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "encoding/hex" "encoding/json" "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/publisher" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" log "github.com/sirupsen/logrus" "math/rand" "net/http" "time" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" "github.com/golang-jwt/jwt/v4" ) type AuthStatusResponse struct { IsActive bool `json:"active"` ExpiresAt int64 `json:"exp,omitempty"` } type Auth struct { Mode } func authRequire(r *http.Request) *Error { client := getClient(r) if client != nil && client.KeyID != 0 { return nil } logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.EmptyObject}).Debug("wallet is empty") return UnauthorizedError() } type authApi struct { mode Mode } func newAuthApi(mode Mode) *authApi { a := &authApi{ mode: mode, } return a } func (a *authApi) GetAuthStatus(ctx RequestContext) (*AuthStatusResponse, *Error) { result := new(AuthStatusResponse) r := ctx.HTTPRequest() token := getToken(r) if token == nil { return result, nil } claims, ok := token.Claims.(*JWTClaims) if !ok { return result, nil } result.IsActive = true result.ExpiresAt = claims.ExpiresAt.Unix() return result, nil } type GetUIDResult struct { UID string `json:"uid,omitempty"` Token string `json:"token,omitempty"` Expire string `json:"expire,omitempty"` EcosystemID string `json:"ecosystem_id,omitempty"` KeyID string `json:"key_id,omitempty"` Address string `json:"address,omitempty"` NetworkID string `json:"network_id,omitempty"` Cryptoer string `json:"cryptoer"` Hasher string `json:"hasher"` } func (a *authApi) GetUid(ctx RequestContext) (*GetUIDResult, *Error) { const jwtUIDExpire = time.Second * 5 result := new(GetUIDResult) result.NetworkID = converter.Int64ToStr(conf.Config.LocalConf.NetworkID) r := ctx.HTTPRequest() token := getToken(r) result.Cryptoer, result.Hasher = conf.Config.CryptoSettings.Cryptoer, conf.Config.CryptoSettings.Hasher if token != nil { if claims, ok := token.Claims.(*JWTClaims); ok && len(claims.KeyID) > 0 { result.EcosystemID = claims.EcosystemID result.Expire = claims.ExpiresAt.Sub(time.Now()).String() result.KeyID = claims.KeyID result.Address = converter.AddressToString(converter.StrToInt64(claims.KeyID)) return result, nil } } result.UID = converter.Int64ToStr(rand.New(rand.NewSource(time.Now().Unix())).Int63()) claims := JWTClaims{ UID: result.UID, EcosystemID: "1", RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(jwtUIDExpire)}, }, } var err error if result.Token, err = generateJWTToken(claims); err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.JWTError, "error": err}).Error("generating jwt token") return nil, DefaultError(err.Error()) } return result, nil } // Special word used by frontend to sign UID generated by /getuid API command, sign is performed for contcatenated word and UID func nonceSalt() string { return fmt.Sprintf("LOGIN%d", conf.Config.LocalConf.NetworkID) } type loginForm struct { EcosystemID int64 `json:"ecosystem_id"` Expire int64 `json:"expire"` PublicKey publicKeyValue `json:"public_key"` KeyID string `json:"key_id"` Signature hexValue `json:"signature"` RoleID int64 `json:"role_id"` } type publicKeyValue struct { hexValue } func (pk *publicKeyValue) UnmarshalText(v []byte) (err error) { pk.value, err = hex.DecodeString(string(v)) pk.value = crypto.CutPub(pk.value) return } func (f *loginForm) Validate(r *http.Request) error { if f == nil { return errors.New(paramsEmpty) } if f.Expire == 0 { f.Expire = int64(jwtExpire) } return nil } type LoginResult struct { Token string `json:"token,omitempty"` EcosystemID string `json:"ecosystem_id,omitempty"` KeyID string `json:"key_id,omitempty"` Account string `json:"account,omitempty"` NotifyKey string `json:"notify_key,omitempty"` IsNode bool `json:"isnode"` IsOwner bool `json:"isowner"` IsCLB bool `json:"clb"` Timestamp string `json:"timestamp,omitempty"` Roles []rolesResult `json:"roles,omitempty"` } type rolesResult struct { RoleID int64 `json:"role_id"` RoleName string `json:"role_name"` } func (a authApi) Login(ctx RequestContext, form *loginForm) (*LoginResult, *Error) { var ( publicKey []byte wallet, founder, fm int64 isExistPub bool spfounder, spfm sqldb.StateParameter ) r := ctx.HTTPRequest() uid, err := getUID(r) if err != nil { return nil, UnUnknownUIDError() } if err := form.Validate(r); err != nil { return nil, InvalidParamsError(err.Error()) } client := getClient(r) logger := getLogger(r) if form.EcosystemID > 0 { client.EcosystemID = form.EcosystemID } else if client.EcosystemID == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Warning("state is empty, using 1 as a state") client.EcosystemID = 1 } if len(form.KeyID) > 0 { wallet = converter.StringToAddress(form.KeyID) } else if len(form.PublicKey.Bytes()) > 0 { wallet = crypto.Address(form.PublicKey.Bytes()) } account := &sqldb.Key{} account.SetTablePrefix(client.EcosystemID) isAccount, err := account.Get(nil, wallet) if err != nil { return nil, DefaultError(err.Error()) } spfm.SetTablePrefix(converter.Int64ToStr(client.EcosystemID)) if ok, err := spfm.Get(nil, "free_membership"); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting free_membership parameter") return nil, DefaultError(err.Error()) } else if ok { fm = converter.StrToInt64(spfm.Value) } publicKey = account.PublicKey isExistPub = len(publicKey) == 0 isCan := func(a, e bool) bool { return !a || (a && e) } if isCan(isAccount, isExistPub) { if !(fm == 1 || client.EcosystemID == 1) { return nil, DefaultError(fmt.Sprintf("The ecosystem (%d) is not open and cannot be registered address", client.EcosystemID)) } } if isAccount && !isExistPub { if account.Deleted == 1 { return nil, DefaultError("The key is deleted") } } else { if !allowCreateUser(client) { return nil, DefaultError("Key has not been found") } if isCan(isAccount, isExistPub) { publicKey = form.PublicKey.Bytes() if len(publicKey) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty") return nil, DefaultError("Public key is undefined") } nodePrivateKey := syspar.GetNodePrivKey() contract := smart.GetContract("NewUser", 1) sc := types.SmartTransaction{ Header: &types.Header{ ID: int(contract.Info().ID), EcosystemID: 1, Time: time.Now().Unix(), KeyID: conf.Config.KeyID, NetworkID: conf.Config.LocalConf.NetworkID, }, Params: map[string]any{ "NewPubkey": hex.EncodeToString(publicKey), "Ecosystem": client.EcosystemID, }, } stp := &transaction.SmartTransactionParser{ SmartContract: &smart.SmartContract{TxSmart: new(types.SmartTransaction)}, } txData, err := stp.BinMarshalWithPrivate(&sc, nodePrivateKey, true) if err != nil { log.WithFields(log.Fields{"type": consts.ContractError, "err": err}).Error("Building transaction") return nil, DefaultError(err.Error()) } if err := a.mode.ContractRunner.RunContract(txData, stp.Hash, sc.KeyID, stp.Timestamp, logger); err != nil { return nil, DefaultError(err.Error()) } if !conf.Config.IsSupportingCLB() { gt := 3 * syspar.GetMaxBlockGenerationTime() l := &sqldb.LogTransaction{} for i := 0; i < 2; i++ { found, err := l.GetByHash(nil, stp.Hash) if err != nil { return nil, DefaultError(err.Error()) } if found { if l.Status != 0 { return nil, DefaultError("encountered some problems when login account") } else { _, _ = account.Get(nil, wallet) break } } time.Sleep(time.Duration(gt) * time.Millisecond) } if l.Block == 0 { return nil, DefaultError("The block packing in progress, please wait") } } } else { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty, and state is not default") return nil, DefaultError(fmt.Sprintf("%d is not a membership of ecosystem %d", wallet, client.EcosystemID)) } } if len(publicKey) == 0 { if client.EcosystemID > 1 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty, and state is not default") return nil, DefaultError(fmt.Sprintf("%d is not a membership of ecosystem %d", wallet, client.EcosystemID)) } if len(form.PublicKey.Bytes()) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public key is empty") return nil, DefaultError("Public key is undefined") } } if form.RoleID != 0 && client.RoleID == 0 { checkedRole, err := checkRoleFromParam(form.RoleID, client.EcosystemID, account.AccountID) if err != nil { return nil, DefaultError(err.Error()) } if checkedRole != form.RoleID { return nil, DefaultError("Access denied") } client.RoleID = checkedRole } verify, err := crypto.Verify(publicKey, []byte(nonceSalt()+uid), form.Signature.Bytes()) if err != nil { logger.WithFields(log.Fields{"type": consts.CryptoError, "pubkey": publicKey, "uid": uid, "signature": form.Signature.Bytes()}).Info("checking signature") return nil, DefaultError(err.Error()) } if !verify { logger.WithFields(log.Fields{"type": consts.InvalidObject, "pubkey": publicKey, "uid": uid, "signature": form.Signature.Bytes()}).Error("incorrect signature") return nil, DefaultError("Signature is incorrect") } spfounder.SetTablePrefix(converter.Int64ToStr(client.EcosystemID)) if ok, err := spfounder.Get(nil, "founder_account"); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting founder_account parameter") return nil, DefaultError(err.Error()) } else if ok { founder = converter.StrToInt64(spfounder.Value) } result := &LoginResult{ Account: account.AccountID, EcosystemID: converter.Int64ToStr(client.EcosystemID), KeyID: converter.Int64ToStr(wallet), IsOwner: founder == wallet, IsNode: conf.Config.KeyID == wallet, IsCLB: conf.Config.IsSupportingCLB(), } claims := JWTClaims{ KeyID: result.KeyID, AccountID: account.AccountID, EcosystemID: result.EcosystemID, RoleID: converter.Int64ToStr(form.RoleID), RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Second * time.Duration(form.Expire))}, }, } result.Token, err = generateJWTToken(claims) if err != nil { logger.WithFields(log.Fields{"type": consts.JWTError, "error": err}).Error("generating jwt token") return nil, DefaultError(err.Error()) } result.NotifyKey, result.Timestamp, err = publisher.GetJWTCent(wallet, form.Expire) if err != nil { return nil, DefaultError(err.Error()) } ra := &sqldb.RolesParticipants{} roles, err := ra.SetTablePrefix(client.EcosystemID).GetActiveMemberRoles(account.AccountID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting roles") return nil, DefaultError(err.Error()) } for _, r := range roles { var res map[string]string if err := json.Unmarshal([]byte(r.Role), &res); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling role") return nil, DefaultError(err.Error()) } result.Roles = append(result.Roles, rolesResult{ RoleID: converter.StrToInt64(res["id"]), RoleName: res["name"], }) } return result, nil } func getUID(r *http.Request) (string, error) { var uid string token := getToken(r) if token != nil { if claims, ok := token.Claims.(*JWTClaims); ok { uid = claims.UID } } else if len(uid) == 0 { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.EmptyObject}).Warning("UID is empty") return "", errors.New("unknown uid") } return uid, nil } func checkRoleFromParam(role, ecosystemID int64, account string) (int64, error) { if role > 0 { ok, err := sqldb.MemberHasRole(nil, role, ecosystemID, account) if err != nil { log.WithFields(log.Fields{ "type": consts.DBError, "account": account, "role": role, "ecosystem": ecosystemID}).Error("check role") return 0, err } if !ok { log.WithFields(log.Fields{ "type": consts.NotFound, "account": account, "role": role, "ecosystem": ecosystemID, }).Error("member hasn't role") return 0, nil } } return role, nil } func allowCreateUser(c *UserClient) bool { if conf.Config.IsSupportingCLB() { return true } return true } ================================================ FILE: packages/service/jsonrpc/block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "bytes" "encoding/hex" "encoding/json" "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/common" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" "gorm.io/gorm" "net/http" "strconv" "strings" ) type blockChainApi struct { } func newBlockChainApi() *blockChainApi { return &blockChainApi{} } func (b *blockChainApi) MaxBlockId(ctx RequestContext) (*int64, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) bk := &sqldb.BlockChain{} found, err := bk.GetMaxBlock() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting max block") return nil, DefaultError(err.Error()) } if !found { logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("last block not found") return nil, NotFoundError() } return &bk.ID, nil } type BlockInfoResult struct { Hash string `json:"hash"` EcosystemID int64 `json:"ecosystem_id"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Tx int32 `json:"tx_count"` RollbacksHash string `json:"rollbacks_hash"` NodePosition int64 `json:"node_position"` ConsensusMode int32 `json:"consensus_mode"` } func (b *blockChainApi) GetBlockInfo(blockID int64) (*BlockInfoResult, *Error) { bk := sqldb.BlockChain{} found, err := bk.Get(blockID) if err != nil { return nil, DefaultError(err.Error()) } if !found { return nil, NotFoundError() } result := &BlockInfoResult{ Hash: hex.EncodeToString(bk.Hash), EcosystemID: bk.EcosystemID, KeyID: bk.KeyID, Time: bk.Time, Tx: bk.Tx, RollbacksHash: hex.EncodeToString(bk.RollbacksHash), NodePosition: bk.NodePosition, ConsensusMode: bk.ConsensusMode, } return result, nil } func (b *blockChainApi) HonorNodesCount() (*int64, *Error) { count := syspar.GetNumberOfNodesFromDB(nil) return &count, nil } type AppParamsForm struct { ecosystemForm paramsForm paginatorForm } func (f *AppParamsForm) Validate(r *http.Request) error { if f == nil { return errors.New(paramsEmpty) } err := f.ecosystemForm.Validate(r) if err != nil { return err } return f.paginatorForm.Validate(r) } type AppParamsResult struct { App int64 `json:"app_id"` List []ParamResult `json:"list"` } func (b *blockChainApi) AppParams(ctx RequestContext, auth Auth, appId int64, ecosystem *int64, names *string, offset, limit *int) (*AppParamsResult, *Error) { form := &AppParamsForm{ ecosystemForm: ecosystemForm{ Validator: auth.EcosystemGetter, }, } if ecosystem != nil { form.EcosystemID = *ecosystem } if names != nil { form.AcceptNames(*names) } if offset != nil { form.Offset = *offset } if limit != nil { form.Limit = *limit } r := ctx.HTTPRequest() if err := parameterValidator(r, form); err != nil { return nil, DefaultError(err.Error()) } logger := getLogger(r) ap := &sqldb.AppParam{} ap.SetTablePrefix(form.EcosystemPrefix) list, err := ap.GetAllAppParameters(appId, &form.Offset, &form.Limit, form.Names) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting all app parameters") } result := &AppParamsResult{ App: appId, List: make([]ParamResult, 0), } for _, item := range list { result.List = append(result.List, ParamResult{ ID: converter.Int64ToStr(item.ID), Name: item.Name, Value: item.Value, Conditions: item.Conditions, }) } return result, nil } type AppContentResult struct { Snippets []sqldb.Snippet `json:"snippets"` Pages []sqldb.Page `json:"pages"` Contracts []sqldb.Contract `json:"contracts"` } func (b *blockChainApi) GetAppContent(ctx RequestContext, auth Auth, appId int64) (*AppContentResult, *Error) { form := &AppParamsForm{ ecosystemForm: ecosystemForm{ Validator: auth.EcosystemGetter, }, } r := ctx.HTTPRequest() if err := parameterValidator(r, form); err != nil { return nil, ParseError(err.Error()) } logger := getLogger(r) sni := &sqldb.Snippet{} p := &sqldb.Page{} c := &sqldb.Contract{} ecosystemID := converter.StrToInt64(form.EcosystemPrefix) snippets, err := sni.GetByApp(appId, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting block interfaces by appID") return nil, DefaultError(err.Error()) } pages, err := p.GetByApp(appId, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting pages by appID") return nil, DefaultError(err.Error()) } contracts, err := c.GetByApp(appId, ecosystemID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting pages by appID") return nil, DefaultError(err.Error()) } return &AppContentResult{ Snippets: snippets, Pages: pages, Contracts: contracts, }, nil } // History Returns the change record for the entry in the specified data table in the current ecosystem func (b *blockChainApi) History(ctx RequestContext, auth Auth, tableName string, tableId uint64) (*HistoryResult, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) client := getClient(r) if tableName == "" || tableId <= 0 { return nil, InvalidParamsError("invalid params") } table := client.Prefix() + "_" + tableName rollbackTx := &sqldb.RollbackTx{} txs, err := rollbackTx.GetRollbackTxsByTableIDAndTableName(strconv.FormatUint(tableId, 10), table, rollbackHistoryLimit) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("rollback history") return nil, DefaultError(err.Error()) } if txs == nil || len(*txs) == 0 { return nil, NotFoundError() } rollbackList := make([]map[string]string, 0, len(*txs)) for _, tx := range *txs { if tx.Data == "" { continue } rollback := map[string]string{} if err := json.Unmarshal([]byte(tx.Data), &rollback); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling rollbackTx.Data from JSON") return nil, DefaultError(err.Error()) } rollbackList = append(rollbackList, rollback) } return &HistoryResult{rollbackList}, nil } type blocksTxInfoForm struct { BlockID int64 `json:"block_id"` Count int64 `json:"count"` } func (f *blocksTxInfoForm) Validate(r *http.Request) error { if f.BlockID <= 0 { return errors.New(fmt.Sprintf(invalidParams, strconv.FormatInt(f.BlockID, 10))) } if f.BlockID > 0 { f.BlockID-- } if f.Count <= 0 { f.Count = defaultPaginatorLimit } if f.Count > maxPaginatorLimit { f.Count = maxPaginatorLimit } return nil } type TxInfo struct { Hash string `json:"hash"` ContractName string `json:"contract_name"` Params map[string]any `json:"params"` KeyID int64 `json:"key_id"` } func (b *blockChainApi) GetBlocksTxInfo(ctx RequestContext, blockId, count int64) (*map[int64][]TxInfo, *Error) { r := ctx.HTTPRequest() form := &blocksTxInfoForm{ BlockID: blockId, Count: count, } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } logger := getLogger(r) blocks, err := sqldb.GetBlockchain(form.BlockID, form.BlockID+form.Count, sqldb.OrderASC) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting blocks range") return nil, DefaultError(err.Error()) } if len(blocks) == 0 { return nil, NotFoundError() } result := map[int64][]TxInfo{} for _, blockModel := range blocks { blck, err := block.UnmarshallBlock(bytes.NewBuffer(blockModel.Data), false) if err != nil { logger.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err, "bolck_id": blockModel.ID}).Error("on unmarshalling block") return nil, DefaultError(err.Error()) } txInfoCollection := make([]TxInfo, 0, len(blck.Transactions)) for _, tx := range blck.Transactions { txInfo := TxInfo{ Hash: hex.EncodeToString(tx.Hash()), } if tx.IsSmartContract() { if tx.SmartContract().TxContract != nil { txInfo.ContractName = tx.SmartContract().TxContract.Name } txInfo.Params = tx.SmartContract().TxData } if blck.IsGenesis() { txInfo.KeyID = blck.Header.KeyId } else { txInfo.KeyID = tx.KeyID() } txInfoCollection = append(txInfoCollection, txInfo) logger.WithFields(log.Fields{"block_id": blockModel.ID, "tx hash": txInfo.Hash, "contract_name": txInfo.ContractName, "key_id": txInfo.KeyID, "params": txInfoCollection}).Debug("BlockChain Transactions Information") } result[blockModel.ID] = txInfoCollection } return &result, nil } type TxDetailedInfo struct { Hash string `json:"hash"` ContractName string `json:"contract_name"` Params map[string]any `json:"params"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Type byte `json:"type"` Size string `json:"size"` } type BlockHeaderInfo struct { BlockID int64 `json:"block_id"` Time int64 `json:"time"` EcosystemID int64 `json:"-"` KeyID int64 `json:"key_id"` NodePosition int64 `json:"node_position"` Sign []byte `json:"-"` Hash string `json:"-"` Version int `json:"version"` } type BlockDetailedInfo struct { Header BlockHeaderInfo `json:"header"` Hash string `json:"hash"` EcosystemID int64 `json:"-"` NodePosition int64 `json:"node_position"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Tx int32 `json:"tx_count"` Size string `json:"size"` RollbacksHash string `json:"rollbacks_hash"` MerkleRoot string `json:"merkle_root"` BinData string `json:"bin_data"` SysUpdate bool `json:"-"` GenBlock bool `json:"-"` StopCount int `json:"stop_count"` Transactions []TxDetailedInfo `json:"transactions"` } func (b *blockChainApi) DetailedBlocks(ctx RequestContext, blockId, count int64) (*map[int64]BlockDetailedInfo, *Error) { r := ctx.HTTPRequest() form := &blocksTxInfoForm{ BlockID: blockId, Count: count, } if err := form.Validate(r); err != nil { return nil, InvalidParamsError(err.Error()) } logger := getLogger(r) blocks, err := sqldb.GetBlockchain(form.BlockID, form.BlockID+form.Count, sqldb.OrderASC) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting blocks range") return nil, DefaultError(err.Error()) } if len(blocks) == 0 { return nil, NotFoundError() } result := map[int64]BlockDetailedInfo{} for _, blockModel := range blocks { blck, err := block.UnmarshallBlock(bytes.NewBuffer(blockModel.Data), false) if err != nil { logger.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err, "block_id": blockModel.ID}).Error("on unmarshalling block") return nil, DefaultError(err.Error()) } txDetailedInfoCollection := make([]TxDetailedInfo, 0, len(blck.Transactions)) for _, tx := range blck.Transactions { txDetailedInfo := TxDetailedInfo{ Hash: hex.EncodeToString(tx.Hash()), KeyID: tx.KeyID(), Time: tx.Timestamp(), Type: tx.Type(), Size: common.StorageSize(len(tx.Payload())).TerminalString(), } if tx.IsSmartContract() { if tx.SmartContract().TxContract != nil { txDetailedInfo.ContractName = tx.SmartContract().TxContract.Name } txDetailedInfo.Params = tx.SmartContract().TxData if tx.Type() == types.TransferSelfTxType { txDetailedInfo.Params = make(map[string]any) txDetailedInfo.Params["TransferSelf"] = tx.SmartContract().TxSmart.TransferSelf } if tx.Type() == types.UtxoTxType { txDetailedInfo.Params = make(map[string]any) txDetailedInfo.Params["UTXO"] = tx.SmartContract().TxSmart.UTXO } } txDetailedInfoCollection = append(txDetailedInfoCollection, txDetailedInfo) logger.WithFields(log.Fields{"block_id": blockModel.ID, "tx hash": txDetailedInfo.Hash, "contract_name": txDetailedInfo.ContractName, "key_id": txDetailedInfo.KeyID, "time": txDetailedInfo.Time, "type": txDetailedInfo.Type, "params": txDetailedInfoCollection}).Debug("BlockChain Transactions Information") } header := BlockHeaderInfo{ BlockID: blck.Header.BlockId, Time: blck.Header.Timestamp, EcosystemID: blck.Header.EcosystemId, KeyID: blck.Header.KeyId, NodePosition: blck.Header.NodePosition, Sign: blck.Header.Sign, Hash: hex.EncodeToString(blck.Header.BlockHash), Version: int(blck.Header.Version), } bdi := BlockDetailedInfo{ Header: header, Hash: hex.EncodeToString(blockModel.Hash), EcosystemID: blockModel.EcosystemID, NodePosition: blockModel.NodePosition, KeyID: blockModel.KeyID, Time: blockModel.Time, Tx: blockModel.Tx, RollbacksHash: hex.EncodeToString(blockModel.RollbacksHash), MerkleRoot: hex.EncodeToString(blck.MerkleRoot), BinData: hex.EncodeToString(blck.BinData), Size: common.StorageSize(len(blockModel.Data)).TerminalString(), SysUpdate: blck.SysUpdate, GenBlock: blck.GenBlock, Transactions: txDetailedInfoCollection, } result[blockModel.ID] = bdi } return &result, nil } type BlockIdOrHash struct { Id int64 `json:"id,omitempty"` Hash string `json:"hash,omitempty"` isHash bool } func (bh *BlockIdOrHash) GetHash() ([]byte, bool) { if bh.Hash != "" { hash, err := hex.DecodeString(bh.Hash) if err != nil { return nil, false } return hash, true } return nil, false } func (bh *BlockIdOrHash) GetBlock() (int64, bool) { if bh.Id > 0 { return bh.Id, true } return 0, false } func (bh *BlockIdOrHash) Validate(r *http.Request) error { if bh == nil { return errors.New(paramsEmpty) } _, f1 := bh.GetHash() _, f2 := bh.GetBlock() if !f1 && !f2 { return errors.New(fmt.Sprintf(invalidParams, "block Id Or block Hash")) } if f1 { bh.isHash = f1 } return nil } func (bh *BlockIdOrHash) UnmarshalJSON(data []byte) error { type rename BlockIdOrHash info := rename{} err := json.Unmarshal(data, &info) if err == nil { if info.Id != 0 && info.Hash != "" { return fmt.Errorf("block id or block hash must be only choose one") } bh.Id = info.Id bh.Hash = info.Hash return nil } var input string err = json.Unmarshal(data, &input) if err != nil { return err } if len(input) == 64 { bh.Hash = input return nil } else { if !smart.CheckNumberChars(input) { return errors.New("invalid block id or block hash") } blockNum, err := strconv.ParseInt(input, 10, 64) if err != nil { return err } bh.Id = blockNum return nil } } func (b *blockChainApi) DetailedBlock(ctx RequestContext, bh *BlockIdOrHash) (*BlockDetailedInfo, *Error) { r := ctx.HTTPRequest() err := parameterValidator(r, bh) if err != nil { return nil, InvalidParamsError(err.Error()) } logger := getLogger(r) bk := &sqldb.BlockChain{} var f bool if bh.isHash { hash, _ := bh.GetHash() f, err = bk.GetByHash(hash) } else { f, err = bk.Get(bh.Id) } if err != nil { return nil, DefaultError(err.Error()) } if !f { return nil, NotFoundError() } blck, err := block.UnmarshallBlock(bytes.NewBuffer(bk.Data), false) if err != nil { logger.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err, "block_id": bk.ID}).Error("on unmarshalling block") return nil, DefaultError(err.Error()) } txDetailedInfoCollection := make([]TxDetailedInfo, 0, len(blck.Transactions)) for _, tx := range blck.Transactions { txDetailedInfo := TxDetailedInfo{ Hash: hex.EncodeToString(tx.Hash()), KeyID: tx.KeyID(), Time: tx.Timestamp(), Type: tx.Type(), Size: common.StorageSize(len(tx.Payload())).TerminalString(), } if tx.IsSmartContract() { if tx.SmartContract().TxContract != nil { txDetailedInfo.ContractName = tx.SmartContract().TxContract.Name } txDetailedInfo.Params = tx.SmartContract().TxData if tx.Type() == types.TransferSelfTxType { txDetailedInfo.Params = make(map[string]any) txDetailedInfo.Params["TransferSelf"] = tx.SmartContract().TxSmart.TransferSelf } if tx.Type() == types.UtxoTxType { txDetailedInfo.Params = make(map[string]any) txDetailedInfo.Params["UTXO"] = tx.SmartContract().TxSmart.UTXO } } txDetailedInfoCollection = append(txDetailedInfoCollection, txDetailedInfo) logger.WithFields(log.Fields{"block_id": bk.ID, "tx hash": txDetailedInfo.Hash, "contract_name": txDetailedInfo.ContractName, "key_id": txDetailedInfo.KeyID, "time": txDetailedInfo.Time, "type": txDetailedInfo.Type, "params": txDetailedInfoCollection}).Debug("[GetBlock]BlockChain Transactions Information") } header := BlockHeaderInfo{ BlockID: blck.Header.BlockId, Time: blck.Header.Timestamp, EcosystemID: blck.Header.EcosystemId, KeyID: blck.Header.KeyId, NodePosition: blck.Header.NodePosition, Sign: blck.Header.Sign, Hash: hex.EncodeToString(blck.Header.BlockHash), Version: int(blck.Header.Version), } result := BlockDetailedInfo{ Header: header, Hash: hex.EncodeToString(bk.Hash), EcosystemID: bk.EcosystemID, NodePosition: bk.NodePosition, KeyID: bk.KeyID, Time: bk.Time, Tx: bk.Tx, RollbacksHash: hex.EncodeToString(bk.RollbacksHash), MerkleRoot: hex.EncodeToString(blck.MerkleRoot), BinData: hex.EncodeToString(blck.BinData), Size: common.StorageSize(len(bk.Data)).TerminalString(), SysUpdate: blck.SysUpdate, GenBlock: blck.GenBlock, Transactions: txDetailedInfoCollection, } return &result, nil } func (b *blockChainApi) GetTransactionCount(ctx RequestContext, bh *BlockIdOrHash) (*int32, *Error) { r := ctx.HTTPRequest() err := parameterValidator(r, bh) if err != nil { return nil, InvalidParamsError(err.Error()) } bk := &sqldb.BlockChain{} var f bool if bh.isHash { hash, _ := bh.GetHash() f, err = bk.GetByHash(hash) } else { f, err = bk.Get(bh.Id) } if err != nil { return nil, DefaultError(err.Error()) } if !f { return nil, NotFoundError() } return &bk.Tx, nil } func (b *blockChainApi) GetEcosystemParams(ctx RequestContext, auth Auth, ecosystem *int64, names *string, offset, limit *int) (*ParamsResult, *Error) { r := ctx.HTTPRequest() form := &AppParamsForm{ ecosystemForm: ecosystemForm{ Validator: auth.EcosystemGetter, }} if ecosystem != nil { form.EcosystemID = *ecosystem } if names != nil { form.AcceptNames(*names) } if limit != nil { form.Limit = *limit } if offset != nil { form.Offset = *offset } if err := parameterValidator(r, form); err != nil { return nil, DefaultError(err.Error()) } logger := getLogger(r) sp := &sqldb.StateParameter{} sp.SetTablePrefix(form.EcosystemPrefix) list, err := sp.GetAllStateParameters(&form.Offset, &form.Limit, form.Names) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting all state parameters") } result := &ParamsResult{ List: make([]ParamResult, 0), } for _, item := range list { result.List = append(result.List, ParamResult{ ID: converter.Int64ToStr(item.ID), Name: item.Name, Value: item.Value, Conditions: item.Conditions, }) } return result, nil } type EcosystemInfo struct { Id int64 `json:"id"` Name string `json:"name"` Digits int64 `json:"digits"` TokenSymbol string `json:"token_symbol"` TokenName string `json:"token_name"` TotalAmount string `json:"total_amount"` IsWithdraw bool `json:"is_withdraw"` Withdraw string `json:"withdraw"` IsEmission bool `json:"is_emission"` Emission string `json:"emission"` Introduction string `json:"introduction"` Logo int64 `json:"logo"` Creator string `json:"creator"` } var totalSupplyToken = decimal.New(2100000000, int32(consts.MoneyDigits)) func (b *blockChainApi) EcosystemInfo(ctx RequestContext, ecosystemId int64) (*EcosystemInfo, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) para := &sqldb.StateParameter{} para.SetTablePrefix(strconv.FormatInt(ecosystemId, 10)) f, err := para.Get(nil, "founder_account") if err != nil { return nil, DefaultError(err.Error()) } if !f { return nil, NotFoundError() } eco := &sqldb.Ecosystem{} found, err := eco.Get(nil, ecosystemId) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting ecosystem name") return nil, DefaultError(err.Error()) } if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "ecosystem_id": ecosystemId}).Debug("ecosystem by id not found") return nil, NotFoundError() } info := &EcosystemInfo{} keyId, err := strconv.ParseInt(para.Value, 10, 64) if err != nil { log.WithFields(log.Fields{"error": err, "creator:": para.Value}).Debug("get Ecosystem Creator Failed") return nil, DefaultError(err.Error()) } type emsAmount struct { Val decimal.Decimal `json:"val"` Time string `json:"time"` Type string `json:"type"` } var emissionAmount []emsAmount total := decimal.New(0, 0) withdraw := decimal.New(0, 0) emission := decimal.New(0, 0) if eco.EmissionAmount != "" { info := eco.EmissionAmount if err := json.Unmarshal([]byte(info), &emissionAmount); err != nil { return nil, DefaultError(err.Error()) } for _, v := range emissionAmount { switch v.Type { case "issue": total = total.Add(v.Val) case "emission": emission = emission.Add(v.Val) case "burn": withdraw = withdraw.Add(v.Val) } } } if eco.Info != "" { minfo := make(map[string]any) err := json.Unmarshal([]byte(eco.Info), &minfo) if err != nil { logger.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err}).Error("Get Ecosystem Info Failed") return nil, DefaultError(err.Error()) } logo, ok := minfo["logo"] if ok { logoId, err := strconv.ParseInt(strings.Replace(fmt.Sprint(logo), `'`, `''`, -1), 10, 64) if err != nil { return nil, DefaultError(err.Error()) } info.Logo = logoId } for k, v := range minfo { switch k { case "description": info.Introduction = fmt.Sprint(v) } } } info.Creator = converter.AddressToString(keyId) info.Id = eco.ID info.Name = eco.Name info.TokenSymbol = eco.TokenSymbol info.TokenName = eco.TokenName info.Digits = eco.Digits if eco.TypeWithdraw == 1 { info.IsWithdraw = true } if eco.TypeEmission == 1 { info.IsEmission = true } info.Withdraw = withdraw.String() info.Emission = emission.String() if info.Id == 1 { info.TotalAmount = totalSupplyToken.String() return info, nil } info.TotalAmount = total.String() return info, nil } func (b *blockChainApi) SystemParams(ctx RequestContext, auth Auth, names *string, offset, limit *int) (*ParamsResult, *Error) { r := ctx.HTTPRequest() form := &AppParamsForm{ ecosystemForm: ecosystemForm{ Validator: auth.EcosystemGetter, }, } if names != nil { form.AcceptNames(*names) } if offset != nil { form.Offset = *offset } if limit != nil { form.Limit = *limit } if err := parameterValidator(r, form); err != nil { return nil, DefaultError(err.Error()) } logger := getLogger(r) list, err := sqldb.GetAllPlatformParameters(nil, &form.Offset, &form.Limit, form.Names) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting all platform parameters") } result := &ParamsResult{ List: make([]ParamResult, 0), } for _, item := range list { result.List = append(result.List, ParamResult{ ID: converter.Int64ToStr(item.ID), Name: item.Name, Value: item.Value, Conditions: item.Conditions, }) } if len(result.List) == 0 { return nil, NotFoundError() } return result, nil } type MemberInfo struct { ID int64 `json:"id"` MemberName string `json:"member_name"` ImageID *int64 `json:"image_id"` MemberInfo string `json:"member_info"` } func (b *blockChainApi) GetMember(ctx RequestContext, account string, ecosystemId int64) (*MemberInfo, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) keyId := converter.AddressToID(account) if keyId == 0 { return nil, InvalidParamsError(fmt.Sprintf("account[%s] address invalid", account)) } if ecosystemId <= 0 { return nil, InvalidParamsError(fmt.Sprintf("ecosystem id invalid")) } member := &sqldb.Member{} member.SetTablePrefix(converter.Int64ToStr(ecosystemId)) _, err := member.Get(account) if err != nil { logger.WithFields(log.Fields{ "type": consts.DBError, "error": err, "ecosystem": ecosystemId, "account": account, }).Error("getting member") return nil, DefaultError(err.Error()) } info := &MemberInfo{ ID: member.ID, MemberName: member.MemberName, ImageID: member.ImageID, MemberInfo: member.MemberInfo, } return info, nil } type ListWhereForm struct { ListForm Order any `json:"order"` Where any `json:"where"` } func (f *ListWhereForm) Validate(r *http.Request) error { if f == nil || f.Name == "" { return errors.New(paramsEmpty) } return f.ListForm.Validate(r) } type blockMetricByNode struct { TotalCount int64 `json:"total_count"` PartialCount int64 `json:"partial_count"` } func (b *blockChainApi) GetBlocksCountByNode(ctx RequestContext, nodePosition int64, consensusMode int32) (*blockMetricByNode, *Error) { if nodePosition < 0 || consensusMode <= 0 || (consensusMode != consts.CandidateNodeMode && consensusMode != consts.HonorNodeMode) { return nil, InvalidParamsError(paramsEmpty) } bk := &sqldb.BlockChain{} r := ctx.HTTPRequest() logger := getLogger(r) found, err := bk.GetMaxBlock() if err != nil { logger.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("on getting max block") return nil, DefaultError(err.Error()) } if !found { return nil, NotFoundError() } c, err := sqldb.GetBlockCountByNode(nodePosition, consensusMode) if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting block count by node") return nil, InternalError(err.Error()) } bm := blockMetricByNode{TotalCount: bk.ID, PartialCount: c} return &bm, nil } type openSystemInfo struct { Ecosystem int64 `json:"ecosystem"` Info *EcosystemInfo `json:"info,omitempty"` } type openSystemList struct { Count int64 `json:"count"` List []openSystemInfo `json:"list"` } // GetOpenEcosystem // verbosity Type: numeric, optional, default=1 // 1 for a ecosystem id list, and 2 for json object with ecosystem info func (b *blockChainApi) GetOpenEcosystem(ctx RequestContext, verbosity, offset, limit *int) (*openSystemList, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) form := &paginatorForm{} if offset != nil { form.Offset = *offset } if limit != nil { form.Limit = *limit } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } var bosity = 1 if verbosity != nil { bosity = *verbosity if bosity != 1 && bosity != 2 { return nil, DefaultError("Not Supported verbosity") } } var ( idList []int64 spfm sqldb.StateParameter info openSystemList sqlQuery *gorm.DB ) sqlQuery = sqldb.GetDB(nil).Table(spfm.TableName()).Where("name = 'free_membership' AND value = '1'"). Select("ecosystem") err := sqlQuery.Count(&info.Count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("get open ecosystem count") return nil, DefaultError(err.Error()) } err = sqlQuery.Offset(form.Offset).Limit(form.Limit).Order("ecosystem asc").Find(&idList).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("get open ecosystem") return nil, DefaultError(err.Error()) } info.List = make([]openSystemInfo, len(idList)) for i, ecosystem := range idList { if bosity == 2 { var er *Error info.List[i].Info, er = b.EcosystemInfo(ctx, ecosystem) if er != nil { return nil, er } } info.List[i].Ecosystem = ecosystem } return &info, nil } ================================================ FILE: packages/service/jsonrpc/callback.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "github.com/IBAX-io/go-ibax/packages/consts" log "github.com/sirupsen/logrus" "reflect" "runtime" "unicode" ) var ( errorType = reflect.TypeOf((*Error)(nil)).Elem() contextType = reflect.TypeOf((*RequestContext)(nil)).Elem() authType = reflect.TypeOf((*Auth)(nil)).Elem() notSingleType = reflect.TypeOf((*NotSingle)(nil)).Elem() ) type callback struct { fn reflect.Value // the function recv reflect.Value // receiver object of function argTypes []reflect.Type // params types hasCtx bool // method's first argument is a RequestContext (not included in argTypes) errIndex int // err return index, of 0 when method cannot return error hasAuth bool // has auth notSingle bool } func (c *callback) getArgsTypes() { fntype := c.fn.Type() // Skip receiver and context.Context parameter (if present). firstArg := 0 if c.recv.IsValid() { firstArg++ } if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { c.hasCtx = true firstArg++ } if fntype.NumIn() > firstArg && fntype.In(firstArg) == authType { c.hasAuth = true firstArg++ } if fntype.NumIn() > firstArg && fntype.In(firstArg) == notSingleType { c.notSingle = true firstArg++ } // Add all remaining parameters. c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg) for i := firstArg; i < fntype.NumIn(); i++ { c.argTypes[i-firstArg] = fntype.In(i) } } func (c *callback) call(ctx RequestContext, m Mode, args []reflect.Value) (result any, errRes *Error) { // Create the argument slice. values := make([]reflect.Value, 0, 4+len(args)) if c.recv.IsValid() { values = append(values, c.recv) } if c.hasCtx { values = append(values, reflect.ValueOf(ctx)) } if c.hasAuth { auth := Auth{m} values = append(values, reflect.ValueOf(auth)) } if c.notSingle { values = append(values, reflect.ValueOf(NotSingle{})) } values = append(values, args...) // Catch panic. defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] req := ctx.HTTPRequest() logger := getLogger(req) logger.WithFields(log.Fields{"type": consts.JsonRpcError, "error": err, "buf": string(buf)}).Error("RPC method " + req.Method + " crashed") errRes = InternalError("method handler crashed") } }() // call func. results := c.fn.Call(values) if len(results) == 0 { return nil, nil } if c.errIndex > 0 && !results[c.errIndex].IsNil() { // Method has returned non-nil error value. err := results[c.errIndex].Interface().(*Error) return reflect.Value{}, err } k := results[0].Kind() switch k { case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice: if results[0].IsNil() { return nil, nil } } return results[0].Interface(), nil } func suitableCallbacks(receiver reflect.Value) map[string]*callback { typ := receiver.Type() callbacks := make(map[string]*callback) for m := 0; m < typ.NumMethod(); m++ { method := typ.Method(m) if method.PkgPath != "" { continue // method not exported } cb := newCallback(receiver, method.Func) if cb == nil { log.WithFields(log.Fields{"err": "[json-rpv]method invalid", "method name": formatName(method.Name)}) continue // function invalid } name := formatName(method.Name) callbacks[name] = cb } return callbacks } func newCallback(receiver, fn reflect.Value) *callback { ftype := fn.Type() c := &callback{fn: fn, recv: receiver, errIndex: -1} // Determine parameter types. They must all be exported or builtin types. c.getArgsTypes() // Verify return types. The function must return at most one error // and/or one other non-error value. outs := make([]reflect.Type, ftype.NumOut()) for i := 0; i < ftype.NumOut(); i++ { outs[i] = ftype.Out(i) } if len(outs) > 2 { return nil } switch { // case len(outs) == 1 && isErrorType(outs[0]): c.errIndex = 0 // If an error is returned, it must be the last returned value. case len(outs) == 2: if isErrorType(outs[0]) || !isErrorType(outs[1]) { return nil } c.errIndex = 1 } return c } func isErrorType(t reflect.Type) bool { for t.Kind() == reflect.Ptr { t = t.Elem() } //return t.Implements(errorType) //t type is interface return t == errorType //t type is struct } func formatName(name string) string { ret := []rune(name) if len(ret) > 0 { ret[0] = unicode.ToLower(ret[0]) } return string(ret) } ================================================ FILE: packages/service/jsonrpc/common.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "encoding/hex" "encoding/json" "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/language" "github.com/IBAX-io/go-ibax/packages/publisher" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" "github.com/IBAX-io/go-ibax/packages/template" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" "gorm.io/gorm" "net/http" "strconv" "strings" "sync" "time" ) type commonApi struct { mode Mode } func newCommonApi(m Mode) *commonApi { return &commonApi{ mode: m, } } type contractField struct { Name string `json:"name"` Type string `json:"type"` Optional bool `json:"optional"` } type GetContractResult struct { ID uint32 `json:"id"` StateID uint32 `json:"state"` TableID string `json:"tableid"` WalletID string `json:"walletid"` TokenID string `json:"tokenid"` Address string `json:"address"` Fields []contractField `json:"fields"` Name string `json:"name"` AppId uint32 `json:"app_id"` Ecosystem uint32 `json:"ecosystem"` Conditions string `json:"conditions"` } func getContract(r *http.Request, name string) *smart.Contract { vm := script.GetVM() if vm == nil { return nil } client := getClient(r) contract := smart.VMGetContract(vm, name, uint32(client.EcosystemID)) if contract == nil { return nil } return contract } func getContractInfo(contract *smart.Contract) *script.ContractInfo { return contract.Info() } func (c *commonApi) GetContractInfo(ctx RequestContext, auth Auth, contractName string) (*GetContractResult, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) contract := getContract(r, contractName) if contract == nil { logger.WithFields(log.Fields{"type": consts.ContractError, "contract_name": contractName}).Debug("contract name") return nil, DefaultError(fmt.Sprintf("There is not %s contract", contractName)) } var result GetContractResult info := getContractInfo(contract) con := &sqldb.Contract{} exits, err := con.Get(info.Owner.TableID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "contract_id": info.Owner.TableID}).Error("get contract") return nil, DefaultError(fmt.Sprintf("get contract %d failed:%s", info.Owner.TableID, err.Error())) } if !exits { logger.WithFields(log.Fields{"type": consts.ContractError, "contract id": info.Owner.TableID}).Debug("get contract") return nil, DefaultError(fmt.Sprintf("There is not %d contract", info.Owner.TableID)) } fields := make([]contractField, 0) result = GetContractResult{ ID: uint32(info.Owner.TableID + consts.ShiftContractID), TableID: converter.Int64ToStr(info.Owner.TableID), Name: info.Name, StateID: info.Owner.StateID, WalletID: converter.Int64ToStr(info.Owner.WalletID), TokenID: converter.Int64ToStr(info.Owner.TokenID), Address: converter.AddressToString(info.Owner.WalletID), Ecosystem: uint32(con.EcosystemID), AppId: uint32(con.AppID), Conditions: con.Conditions, } if info.Tx != nil { for _, fitem := range *info.Tx { fields = append(fields, contractField{ Name: fitem.Name, Type: script.OriginalToString(fitem.Original), Optional: fitem.ContainsTag(script.TagOptional), }) } } result.Fields = fields return &result, nil } type ListResult struct { Count int64 `json:"count"` List []map[string]string `json:"list"` } func (c *commonApi) GetContracts(ctx RequestContext, auth Auth, offset, limit *int) (*ListResult, *Error) { r := ctx.HTTPRequest() form := &paginatorForm{} if offset != nil { form.Offset = *offset } if limit != nil { form.Limit = *limit } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } client := getClient(r) logger := getLogger(r) contract := &sqldb.Contract{} contract.EcosystemID = client.EcosystemID count, err := contract.CountByEcosystem() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting table records count") return nil, DefaultError(err.Error()) } contracts, err := contract.GetListByEcosystem(form.Offset, form.Limit) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all") return nil, DefaultError(err.Error()) } list := make([]map[string]string, len(contracts)) for i, c := range contracts { list[i] = c.ToMap() list[i]["address"] = converter.AddressToString(c.WalletID) } if len(list) == 0 { list = nil } return &ListResult{ Count: count, List: list, }, nil } type roleInfo struct { ID string `json:"id"` Name string `json:"name"` } type notifyInfo struct { RoleID string `json:"role_id"` Count int64 `json:"count"` } type KeyInfoResult struct { Account string `json:"account"` KeyId string `json:"key_id"` Ecosystems []*keyEcosystemInfo `json:"ecosystems"` } type keyEcosystemInfo struct { Ecosystem string `json:"ecosystem"` Name string `json:"name"` Digits int64 `json:"digits"` Roles []roleInfo `json:"roles,omitempty"` Notifications []notifyInfo `json:"notifications,omitempty"` } func getNotifications(ecosystemID int64, key *sqldb.Key) ([]notifyInfo, error) { notif, err := sqldb.GetNotificationsCount(ecosystemID, []string{key.AccountID}) if err != nil { return nil, err } list := make([]notifyInfo, 0) for _, n := range notif { if n.RecipientID != key.ID { continue } list = append(list, notifyInfo{ RoleID: converter.Int64ToStr(n.RoleID), Count: n.Count, }) } return list, nil } func (c *commonApi) GetKeyInfo(ctx RequestContext, accountAddress string) (*KeyInfoResult, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) keysList := make([]*keyEcosystemInfo, 0) keyID := converter.StringToAddress(accountAddress) if keyID == 0 { return nil, InvalidParamsError(fmt.Sprintf("account address %s is not valid", accountAddress)) } ids, names, err := c.mode.EcosystemGetter.GetEcosystemLookup() if err != nil { return nil, DefaultError(err.Error()) } var ( account = converter.AddressToString(keyID) found bool ) for i, ecosystemID := range ids { key := &sqldb.Key{} key.SetTablePrefix(ecosystemID) found, err = key.Get(nil, keyID) if err != nil { return nil, DefaultError(err.Error()) } if !found { continue } eco := sqldb.Ecosystem{} _, err = eco.Get(nil, ecosystemID) if err != nil { return nil, DefaultError(err.Error()) } keyRes := &keyEcosystemInfo{ Ecosystem: converter.Int64ToStr(ecosystemID), Name: names[i], Digits: eco.Digits, } ra := &sqldb.RolesParticipants{} roles, err := ra.SetTablePrefix(ecosystemID).GetActiveMemberRoles(key.AccountID) if err != nil { return nil, DefaultError(err.Error()) } for _, r := range roles { var role roleInfo if err := json.Unmarshal([]byte(r.Role), &role); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling role") return nil, DefaultError(err.Error()) } keyRes.Roles = append(keyRes.Roles, role) } keyRes.Notifications, err = getNotifications(ecosystemID, key) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting notifications") return nil, DefaultError(err.Error()) } keysList = append(keysList, keyRes) } // in test mode, registration is open in the first ecosystem if len(keysList) == 0 { notify := make([]notifyInfo, 0) notify = append(notify, notifyInfo{}) keysList = append(keysList, &keyEcosystemInfo{ Ecosystem: converter.Int64ToStr(ids[0]), Name: names[0], Notifications: notify, }) } return &KeyInfoResult{ Account: account, KeyId: strconv.FormatInt(keyID, 10), Ecosystems: keysList, }, nil } type ListForm struct { Name string `json:"name"` //table name paginatorForm rowForm } func (f *ListForm) Validate(r *http.Request) error { if f == nil || f.Name == "" { return errors.New(paramsEmpty) } if err := f.paginatorForm.Validate(r); err != nil { return err } return f.rowForm.Validate(r) } type rowForm struct { Columns string `json:"columns"` } func (f *rowForm) Validate(r *http.Request) error { if len(f.Columns) > 0 { columns := strings.Split(f.Columns, ",") list := make([]string, len(columns)) for k, column := range columns { list[k] = converter.Sanitize(column, `->`) } f.Columns = strings.Join(list, ",") } return nil } func checkAccess(tableName, columns string, client *UserClient) (table string, cols string, err error) { sc := smart.SmartContract{ CLB: conf.Config.IsSupportingCLB(), VM: script.GetVM(), TxSmart: &types.SmartTransaction{ Header: &types.Header{ EcosystemID: client.EcosystemID, KeyID: client.KeyID, NetworkID: conf.Config.LocalConf.NetworkID, }, }, } table, _, cols, err = sc.CheckAccess(tableName, columns, client.EcosystemID) return } func (c *commonApi) GetList(ctx RequestContext, auth Auth, form *ListWhereForm) (*ListResult, *Error) { r := ctx.HTTPRequest() if form == nil { return nil, InvalidParamsError(paramsEmpty) } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } client := getClient(r) logger := getLogger(r) var ( err error table, where, order string ) table, form.Columns, err = checkAccess(form.Name, form.Columns, client) if err != nil { return nil, DefaultError(err.Error()) } order, err = qb.GetOrder(table, form.Order, true) if err != nil { return nil, DefaultError(err.Error()) } var q *gorm.DB q = sqldb.GetTableListQuery(form.Name, client.EcosystemID) if len(form.Columns) > 0 { q = q.Select("id," + form.Columns) } if form.Where != nil { var inWhere any switch form.Where.(type) { case string: if len(form.Where.(string)) > 0 { inWhere, _, err = template.ParseObject([]rune(form.Where.(string))) if err != nil { return nil, DefaultError("where parse object failed") } } else { inWhere = "" } } switch v := inWhere.(type) { case string: if len(v) == 0 { where = `true` } else { return nil, DefaultError("Where has wrong format") } case map[string]any: where, err = qb.GetWhere(types.LoadMap(v)) if err != nil { return nil, DefaultError(err.Error()) } case *types.Map: where, err = qb.GetWhere(v) if err != nil { return nil, DefaultError(err.Error()) } default: return nil, DefaultError("Where has wrong format") } q = q.Where(where) } result := new(ListResult) err = q.Count(&result.Count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}). Errorf("selecting rows from table %s select %s where %s", table, smart.PrepareColumns([]string{form.Columns}), where) return nil, DefaultError(fmt.Sprintf("Table %s has not been found", table)) } if len(order) > 0 { rows, err := q.Order(order).Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") return nil, DefaultError(err.Error()) } result.List, err = sqldb.GetResult(rows) if err != nil { return nil, DefaultError(err.Error()) } } else { rows, err := q.Order("id ASC").Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") return nil, DefaultError(err.Error()) } result.List, err = sqldb.GetResult(rows) if err != nil { return nil, DefaultError(err.Error()) } } return result, nil } type HonorNodeJSON struct { TCPAddress string `json:"tcp_address"` APIAddress string `json:"api_address"` PublicKey string `json:"public_key"` UnbanTime string `json:"unban_time"` Stopped bool `json:"stopped"` } type NetworkResult struct { NetworkID string `json:"network_id"` CentrifugoURL string `json:"centrifugo_url"` Test bool `json:"test"` Private bool `json:"private"` HonorNodes []HonorNodeJSON `json:"honor_nodes"` } func getNodesJSON() []HonorNodeJSON { nodes := make([]HonorNodeJSON, 0) for _, node := range syspar.GetNodes() { nodes = append(nodes, HonorNodeJSON{ TCPAddress: node.TCPAddress, APIAddress: node.APIAddress, PublicKey: crypto.PubToHex(node.PublicKey), UnbanTime: strconv.FormatInt(node.UnbanTime.Unix(), 10), }) } return nodes } const defaultSectionsLimit = 100 type SectionsForm struct { paginatorForm Lang string `schema:"lang"` } func (f *SectionsForm) Validate(r *http.Request) error { if f == nil { return errors.New(paramsEmpty) } if err := f.paginatorForm.Validate(r); err != nil { return err } if len(f.Lang) == 0 { f.Lang = r.Header.Get("Accept-Language") } return nil } func (c *commonApi) GetSections(ctx RequestContext, auth Auth, params *SectionsForm) (*ListResult, *Error) { r := ctx.HTTPRequest() form := &SectionsForm{} if params != nil { form = params } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } client := getClient(r) logger := getLogger(r) table := "1_sections" q := sqldb.GetDB(nil).Table(table).Where("ecosystem = ? AND status > 0", client.EcosystemID).Order("id ASC") result := new(ListResult) err := q.Count(&result.Count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting table records count") return nil, DefaultError(fmt.Sprintf("Table %s has not been found", table)) } rows, err := q.Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") return nil, DefaultError(err.Error()) } result.List, err = sqldb.GetResult(rows) if err != nil { return nil, DefaultError(err.Error()) } var sections []map[string]string for _, item := range result.List { var roles []int64 if err := json.Unmarshal([]byte(item["roles_access"]), &roles); err != nil { return nil, DefaultError(err.Error()) } if len(roles) > 0 { var added bool for _, v := range roles { if v == client.RoleID { added = true break } } if !added { continue } } if item["status"] == consts.StatusMainPage { roles := &sqldb.Role{} roles.SetTablePrefix(1) role, err := roles.Get(nil, client.RoleID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Debug("Getting role by id") return nil, DefaultError(err.Error()) } if role == true && roles.DefaultPage != "" { item["default_page"] = roles.DefaultPage } } item["title"] = language.LangMacro(item["title"], int(client.EcosystemID), form.Lang) sections = append(sections, item) } result.List = sections return result, nil } type RowResult struct { Value map[string]string `json:"value"` } // GetRow // whereColumn: find whereColumn = id or Find id // columns: select colunms // example: "params":["@1history",6660819716178795186,"sender_id","created_at,ecosystem"] func (c *commonApi) GetRow(ctx RequestContext, auth Auth, tableName string, id int64, columns *string, whereColumn *string) (*RowResult, *Error) { r := ctx.HTTPRequest() form := &rowForm{} if columns != nil { form.Columns = *columns if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } } idStr := strconv.FormatInt(id, 10) if tableName == "" || idStr == "" { return nil, InvalidParamsError("tableName or id invalid") } client := getClient(r) logger := getLogger(r) q := sqldb.GetDB(nil).Limit(1) var ( err error table string ) table, form.Columns, err = checkAccess(tableName, form.Columns, client) if err != nil { return nil, DefaultError(err.Error()) } col := `id` if whereColumn != nil && len(*whereColumn) > 0 { col = converter.Sanitize(*whereColumn, `-`) } if converter.FirstEcosystemTables[tableName] { q = q.Table(table).Where(col+" = ? and ecosystem = ?", idStr, client.EcosystemID) } else { q = q.Table(table).Where(col+" = ?", idStr) } if len(form.Columns) > 0 { q = q.Select(form.Columns) } rows, err := q.Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") return nil, DefaultError("DB query is wrong") } result, err := sqldb.GetResult(rows) if err != nil { return nil, DefaultError(err.Error()) } if len(result) == 0 { return nil, NotFoundError() } return &RowResult{ Value: result[0], }, nil } type PartModel interface { SetTablePrefix(prefix string) Get(name string) (bool, error) } func getPageRowMux(ctx RequestContext, name string) (PartModel, *Error) { return getInterfaceRow(ctx, name, &sqldb.Page{}) } func getMenuRowMux(ctx RequestContext, name string) (PartModel, *Error) { return getInterfaceRow(ctx, name, &sqldb.Menu{}) } func getSnippetRowMux(ctx RequestContext, name string) (PartModel, *Error) { return getInterfaceRow(ctx, name, &sqldb.Snippet{}) } func getInterfaceRow(ctx RequestContext, name string, c PartModel) (PartModel, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) client := getClient(r) c.SetTablePrefix(client.Prefix()) if ok, err := c.Get(name); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting one row") return nil, DefaultError("DB query is wrong") } else if !ok { return nil, NotFoundError() } return c, nil } func (c *commonApi) GetPageRow(ctx RequestContext, auth Auth, name string) (PartModel, *Error) { if name == "" { return nil, InvalidParamsError(paramsEmpty) } return getPageRowMux(ctx, name) } func (c *commonApi) GetMenuRow(ctx RequestContext, auth Auth, name string) (PartModel, *Error) { if name == "" { return nil, InvalidParamsError(paramsEmpty) } return getMenuRowMux(ctx, name) } func (c *commonApi) GetSnippetRow(ctx RequestContext, auth Auth, name string) (PartModel, *Error) { if name == "" { return nil, InvalidParamsError(paramsEmpty) } return getSnippetRowMux(ctx, name) } type columnInfo struct { Name string `json:"name"` Type string `json:"type"` Perm string `json:"perm"` } type TableResult struct { Name string `json:"name"` Insert string `json:"insert"` NewColumn string `json:"new_column"` Update string `json:"update"` Read string `json:"read,omitempty"` Filter string `json:"filter,omitempty"` Conditions string `json:"conditions"` AppID string `json:"app_id"` Columns []columnInfo `json:"columns"` } func (c *commonApi) GetTable(ctx RequestContext, auth Auth, name string) (*TableResult, *Error) { if name == "" { return nil, InvalidParamsError(paramsEmpty) } r := ctx.HTTPRequest() logger := getLogger(r) client := getClient(r) prefix := client.Prefix() table := &sqldb.Table{} table.SetTablePrefix(prefix) _, err := table.Get(nil, strings.ToLower(name)) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("Getting table") return nil, DefaultError(err.Error()) } if len(table.Name) == 0 { return nil, DefaultError(fmt.Sprintf("Table %s has not been found", name)) } var columnsMap map[string]string err = json.Unmarshal([]byte(table.Columns), &columnsMap) if err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("Unmarshalling table columns to json") return nil, DefaultError(err.Error()) } columns := make([]columnInfo, 0) for key, value := range columnsMap { colType, err := sqldb.NewDbTransaction(nil).GetColumnType(prefix+`_`+name, key) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting column type from db") return nil, DefaultError(err.Error()) } columns = append(columns, columnInfo{ Name: key, Perm: value, Type: colType, }) } return &TableResult{ Name: table.Name, Insert: table.Permissions.Insert, NewColumn: table.Permissions.NewColumn, Update: table.Permissions.Update, Read: table.Permissions.Read, Filter: table.Permissions.Filter, Conditions: table.Conditions, AppID: converter.Int64ToStr(table.AppID), Columns: columns, }, nil } type tableInfo struct { Name string `json:"name"` Count string `json:"count"` } type TableCountResult struct { Count int64 `json:"count"` List []tableInfo `json:"list"` } func (c *commonApi) GetTableCount(ctx RequestContext, auth Auth, offset, limit *int) (*TableCountResult, *Error) { r := ctx.HTTPRequest() form := &paginatorForm{} if offset != nil { form.Offset = *offset } if limit != nil { form.Limit = *limit } if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } client := getClient(r) logger := getLogger(r) prefix := client.Prefix() table := &sqldb.Table{} table.SetTablePrefix(prefix) count, err := table.Count() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting records count from tables") return nil, DefaultError(err.Error()) } rows, err := sqldb.GetDB(nil).Table(table.TableName()).Where("ecosystem = ?", client.EcosystemID).Offset(form.Offset).Limit(form.Limit).Rows() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("Getting rows from table") return nil, DefaultError(err.Error()) } list, err := sqldb.GetResult(rows) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting names from tables") return nil, DefaultError(err.Error()) } result := &TableCountResult{ Count: count, List: make([]tableInfo, len(list)), } for i, item := range list { err = sqldb.GetTableQuery(item["name"], client.EcosystemID).Count(&count).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting count from table") return nil, DefaultError(err.Error()) } result.List[i].Name = item["name"] result.List[i].Count = converter.Int64ToStr(count) } return result, nil } func (c *commonApi) GetVersion() string { return consts.Version() + " " + node.NodePauseType().String() } func replaceHttpSchemeToWs(centrifugoURL string) string { if strings.HasPrefix(centrifugoURL, "http:") { return strings.Replace(centrifugoURL, "http:", "ws:", -1) } else if strings.HasPrefix(centrifugoURL, "https:") { return strings.Replace(centrifugoURL, "https:", "wss:", -1) } return centrifugoURL } func centrifugoAddressHandler(r *http.Request) (string, error) { logger := getLogger(r) if _, err := publisher.GetStats(); err != nil { logger.WithFields(log.Fields{"type": consts.CentrifugoError, "error": err}).Warn("on getting centrifugo stats") return "", err } return replaceHttpSchemeToWs(conf.Config.Centrifugo.URL), nil } func (c *commonApi) GetConfig(ctx RequestContext, option string) (map[string]any, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) if option == "" { logger.WithFields(log.Fields{"type": consts.EmptyObject, "error": "option not specified"}).Error("on getting option in config handler") return nil, NotFoundError() } rets := make(map[string]any) var err error switch option { case "centrifugo": rets[option], err = centrifugoAddressHandler(r) if err != nil { return nil, DefaultError(err.Error()) } return rets, nil } return nil, NotFoundError() } func parseEcosystem(in string) (string, string) { ecosystem, name := converter.ParseName(in) if ecosystem == 0 { return ``, name } return converter.Int64ToStr(ecosystem), name } func pageValue(r *http.Request, name string) (*sqldb.Page, string, error) { logger := getLogger(r) client := getClient(r) var ecosystem string page := &sqldb.Page{} if strings.HasPrefix(name, `@`) { ecosystem, name = parseEcosystem(name) if len(name) == 0 { logger.WithFields(log.Fields{ "type": consts.NotFound, "value": name, }).Debug("page not found") return nil, ``, errors.New(consts.NotFound) } } else { ecosystem = client.Prefix() } page.SetTablePrefix(ecosystem) found, err := page.Get(name) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting page") return nil, ``, err } if !found { logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("page not found") return nil, ``, errors.New(consts.NotFound) } return page, ecosystem, nil } func (c *commonApi) GetPageValidatorsCount(ctx RequestContext, name string) (*map[string]int64, *Error) { r := ctx.HTTPRequest() if name == "" { return nil, NotFoundError() } page, _, err := pageValue(r, name) if err != nil { return nil, DefaultError(err.Error()) } res := map[string]int64{"validate_count": page.ValidateCount} return &res, nil } func initVars(r *http.Request, vals *map[string]string) *map[string]string { client := getClient(r) vars := make(map[string]string) if vals != nil { for k, v := range *vals { vars[k] = v } } vars["_full"] = "0" vars["current_time"] = fmt.Sprintf("%d", time.Now().Unix()) vars["guest_key"] = consts.GuestKey vars["guest_account"] = consts.GuestAddress vars["black_hole_key"] = strconv.FormatInt(converter.HoleAddrMap[converter.BlackHoleAddr].K, 10) vars["black_hole_account"] = converter.HoleAddrMap[converter.BlackHoleAddr].S vars["white_hole_key"] = strconv.FormatInt(converter.HoleAddrMap[converter.WhiteHoleAddr].K, 10) vars["white_hole_account"] = converter.HoleAddrMap[converter.WhiteHoleAddr].S if client.KeyID != 0 { vars["ecosystem_id"] = converter.Int64ToStr(client.EcosystemID) vars["key_id"] = converter.Int64ToStr(client.KeyID) vars["account_id"] = client.AccountID vars["role_id"] = converter.Int64ToStr(client.RoleID) vars["ecosystem_name"] = client.EcosystemName } else { vars["ecosystem_id"] = vars["ecosystem"] delete(vars, "ecosystem") if len(vars["keyID"]) > 0 { vars["key_id"] = vars["keyID"] vars["account_id"] = converter.AddressToString(converter.StrToInt64(vars["keyID"])) } else { vars["key_id"] = "0" vars["account_id"] = "" } if len(vars["roleID"]) > 0 { vars["role_id"] = vars["roleID"] } else { vars["role_id"] = "0" } if len(vars["ecosystem_id"]) != 0 { ecosystems := sqldb.Ecosystem{} if found, _ := ecosystems.Get(nil, converter.StrToInt64(vars["ecosystem_id"])); found { vars["ecosystem_name"] = ecosystems.Name } } } if _, ok := vars["lang"]; !ok { vars["lang"] = r.Header.Get("Accept-Language") } return &vars } type ContentResult struct { Menu string `json:"menu,omitempty"` MenuTree json.RawMessage `json:"menutree,omitempty"` Title string `json:"title,omitempty"` Tree json.RawMessage `json:"tree"` NodesCount int64 `json:"nodesCount,omitempty"` } const strOne = `1` func (c *commonApi) GetSource(ctx RequestContext, auth Auth, name string, vals *map[string]string) (*ContentResult, *Error) { r := ctx.HTTPRequest() if name == "" { return nil, NotFoundError() } page, _, err := pageValue(r, name) if err != nil { return nil, DefaultError(err.Error()) } var timeout bool vars := initVars(r, vals) (*vars)["_full"] = strOne ret := template.Template2JSON(page.Value, &timeout, vars) return &ContentResult{Tree: ret}, nil } func getPage(r *http.Request, name string, vals *map[string]string) (result *ContentResult, err error) { page, _, err := pageValue(r, name) if err != nil { return nil, err } logger := getLogger(r) client := getClient(r) menu := &sqldb.Menu{} menu.SetTablePrefix(client.Prefix()) _, err = menu.Get(page.Menu) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting page menu") return nil, errors.New("Server error") } var wg sync.WaitGroup var timeout bool wg.Add(2) success := make(chan bool, 1) go func() { defer wg.Done() vars := initVars(r, vals) (*vars)["app_id"] = converter.Int64ToStr(page.AppID) ret := template.Template2JSON(page.Value, &timeout, vars) if timeout { return } retmenu := template.Template2JSON(menu.Value, &timeout, vars) if timeout { return } result = &ContentResult{ Tree: ret, Menu: page.Menu, MenuTree: retmenu, NodesCount: page.ValidateCount, } success <- true }() go func() { defer wg.Done() if conf.Config.LocalConf.MaxPageGenerationTime == 0 { return } select { case <-time.After(time.Duration(conf.Config.LocalConf.MaxPageGenerationTime) * time.Millisecond): timeout = true case <-success: } }() wg.Wait() close(success) if timeout { logger.WithFields(log.Fields{"type": consts.InvalidObject}).Error(page.Name + " is a heavy page") return nil, errors.New("this page is heavy") } return result, nil } func (c *commonApi) GetPage(ctx RequestContext, auth Auth, name string, vals *map[string]string) (*ContentResult, *Error) { r := ctx.HTTPRequest() result, err := getPage(r, name, vals) if err != nil { return nil, DefaultError(err.Error()) } return result, nil } type hashResult struct { Hash string `json:"hash"` } func (c *commonApi) GetPageHash(ctx RequestContext, name string, ecosystem *int64, vals *map[string]string) (*hashResult, *Error) { r := ctx.HTTPRequest() logger := getLogger(r) if ecosystem != nil && !strings.HasPrefix(name, "@") && *ecosystem != 0 { name = "@" + strconv.FormatInt(*ecosystem, 10) + name } result, err := getPage(r, name, vals) if err != nil { return nil, DefaultError(err.Error()) } out, err := json.Marshal(result) if err != nil { logger.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("getting string for hash") return nil, DefaultError(err.Error()) } return &hashResult{Hash: hex.EncodeToString(crypto.Hash(out))}, nil } func (c *commonApi) GetMenu(ctx RequestContext, auth Auth, name string, vals *map[string]string) (*ContentResult, *Error) { r := ctx.HTTPRequest() client := getClient(r) logger := getLogger(r) var ecosystem string menu := &sqldb.Menu{} if strings.HasPrefix(name, `@`) { ecosystem, name = parseEcosystem(name) if len(name) == 0 { logger.WithFields(log.Fields{ "type": consts.NotFound, "value": name, }).Debug("page not found") return nil, NotFoundError() } } else { ecosystem = client.Prefix() } menu.SetTablePrefix(ecosystem) found, err := menu.Get(name) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting menu") return nil, DefaultError(err.Error()) } if !found { logger.WithFields(log.Fields{"type": consts.NotFound}).Debug("menu not found") return nil, NotFoundError() } var timeout bool ret := template.Template2JSON(menu.Value, &timeout, initVars(r, vals)) return &ContentResult{Tree: ret, Title: menu.Title}, nil } type jsonContentForm struct { Template string `json:"template"` Source bool `json:"source"` } func (f *jsonContentForm) Validate(r *http.Request) error { if f == nil || len(f.Template) == 0 { return errors.New(paramsEmpty) } return nil } func (c *commonApi) GetContent(ctx RequestContext, form *jsonContentForm, vals *map[string]string) (*ContentResult, *Error) { r := ctx.HTTPRequest() if err := parameterValidator(r, form); err != nil { return nil, InvalidParamsError(err.Error()) } var timeout bool vars := initVars(r, vals) if form.Source { (*vars)["_full"] = strOne } ret := template.Template2JSON(form.Template, &timeout, vars) return &ContentResult{Tree: ret}, nil } func (c *commonApi) GetTxCount(ctx RequestContext) (*int64, *Error) { r := ctx.HTTPRequest() count, err := sqldb.GetTxCount() if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on getting tx count") return nil, InternalError(err.Error()) } return &count, nil } func (c *commonApi) GetEcosystemCount(ctx RequestContext) (*int64, *Error) { r := ctx.HTTPRequest() total, err := sqldb.GetAllSystemCount() if err != nil { logger := getLogger(r) logger.WithError(err).Error("on getting ecosystem count") return nil, InternalError(err.Error()) } return &total, nil } ================================================ FILE: packages/service/jsonrpc/common_forms.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "errors" "fmt" "net/http" "strings" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/converter" ) const ( defaultPaginatorLimit = 25 maxPaginatorLimit = 100 ) type paginatorForm struct { defaultLimit int Limit int `json:"limit"` Offset int `json:"offset"` } func (f *paginatorForm) Validate(r *http.Request) error { if f == nil { return errors.New(paramsEmpty) } f.defaultLimit = defaultPaginatorLimit if f.Limit <= 0 { f.Limit = f.defaultLimit } if f.Limit > maxPaginatorLimit { f.Limit = maxPaginatorLimit } return nil } type paramsForm struct { nopeValidator Names []string `schema:"names"` } type nopeValidator struct{} func (np nopeValidator) Validate(r *http.Request) error { return nil } func (f *paramsForm) AcceptNames(names string) { if names != "" { f.Names = strings.Split(names, ",") } } type ecosystemForm struct { EcosystemID int64 `json:"ecosystem"` EcosystemPrefix string `schema:"-"` Validator types.EcosystemGetter } func (f *ecosystemForm) Validate(r *http.Request) error { if f.Validator == nil { panic("ecosystemForm.Validator should not be empty") } client := getClient(r) logger := getLogger(r) ecosysID, err := f.Validator.ValidateId(f.EcosystemID, client.EcosystemID, logger) if err != nil { if err.Error() == "Ecosystem not found" { err = fmt.Errorf("ecosystem %d doesn't exist", f.EcosystemID) } return err } f.EcosystemID = ecosysID f.EcosystemPrefix = converter.Int64ToStr(f.EcosystemID) return nil } ================================================ FILE: packages/service/jsonrpc/context.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "context" "net/http" "github.com/golang-jwt/jwt/v4" log "github.com/sirupsen/logrus" ) type contextKey int const ( contextKeyLogger contextKey = iota contextKeyToken contextKeyClient ) func setContext(r *http.Request, key, value any) *http.Request { return r.WithContext(context.WithValue(r.Context(), key, value)) } func getContext(r *http.Request, key any) any { return r.Context().Value(key) } func setLogger(r *http.Request, log *log.Entry) *http.Request { return setContext(r, contextKeyLogger, log) } func getLogger(r *http.Request) *log.Entry { if v := getContext(r, contextKeyLogger); v != nil { return v.(*log.Entry) } return nil } func setToken(r *http.Request, token *jwt.Token) *http.Request { return setContext(r, contextKeyToken, token) } func getToken(r *http.Request) *jwt.Token { if v := getContext(r, contextKeyToken); v != nil { return v.(*jwt.Token) } return nil } func setClient(r *http.Request, client *UserClient) *http.Request { return setContext(r, contextKeyClient, client) } func getClient(r *http.Request) *UserClient { if v := getContext(r, contextKeyClient); v != nil { return v.(*UserClient) } return nil } ================================================ FILE: packages/service/jsonrpc/data.go ================================================ package jsonrpc import ( "crypto/md5" "encoding/hex" "fmt" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" "strconv" "strings" ) type NotSingle struct { } type dataApi struct { } func newDataApi() *dataApi { return &dataApi{} } func compareHash(data []byte, urlHash string) bool { urlHash = strings.ToLower(urlHash) var hash []byte switch len(urlHash) { case 32: h := md5.Sum(data) hash = h[:] case 64: hash = crypto.Hash(data) } return hex.EncodeToString(hash) == urlHash } func (d *dataApi) BinaryVerify(ctx RequestContext, notSingle NotSingle, binaryId int64, hash string) *Error { if binaryId <= 0 { return DefaultError(fmt.Sprintf(invalidParams, "binary Id")) } if hash == "" { return DefaultError(fmt.Sprintf(invalidParams, "hash")) } r := ctx.HTTPRequest() w := ctx.HTTPResponseWriter() logger := getLogger(r) bin := sqldb.Binary{} found, err := bin.GetByID(binaryId) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Errorf("getting binary by id") return DefaultError(err.Error()) } if !found { return NotFoundError() } if !compareHash(bin.Data, hash) { return DefaultError("Hash is incorrect") } w.Header().Set("Content-Type", bin.MimeType) w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, bin.Name)) w.Header().Set("Access-Control-Allow-Origin", "*") w.Write(bin.Data) return nil } func (d *dataApi) DataVerify(ctx RequestContext, notSingle NotSingle, table, column string, id int64, hash string) *Error { if table == "" || column == "" || id <= 0 || hash == "" { return InvalidParamsError("tableName or column or id or hash invalid") } r := ctx.HTTPRequest() w := ctx.HTTPResponseWriter() logger := getLogger(r) data, err := sqldb.GetColumnByID(table, column, id) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("selecting data from table") return NotFoundError() } if !compareHash([]byte(data), hash) { return DefaultError("Hash is incorrect") } w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment") w.Header().Set("Access-Control-Allow-Origin", "*") w.Write([]byte(data)) return nil } func (d *dataApi) GetAvatar(ctx RequestContext, notSingle NotSingle, account string, ecosystemId int64) *Error { if account == "" || ecosystemId <= 0 { return InvalidParamsError("account or ecosystemId invalid") } r := ctx.HTTPRequest() w := ctx.HTTPResponseWriter() logger := getLogger(r) member := &sqldb.Member{} member.SetTablePrefix(converter.Int64ToStr(ecosystemId)) found, err := member.Get(account) if err != nil { logger.WithFields(log.Fields{ "type": consts.DBError, "error": err, "ecosystem": ecosystemId, "account": account, }).Error("getting member") return DefaultError(err.Error()) } if !found { return NotFoundError() } if member.ImageID == nil { return NotFoundError() } bin := &sqldb.Binary{} bin.SetTablePrefix(converter.Int64ToStr(ecosystemId)) found, err = bin.GetByID(*member.ImageID) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "image_id": *member.ImageID}).Errorf("on getting binary by id") return DefaultError(err.Error()) } if !found { return NotFoundError() } if len(bin.Data) == 0 { logger.WithFields(log.Fields{"type": consts.EmptyObject, "error": err, "image_id": *member.ImageID}).Errorf("on check avatar size") return NotFoundError() } w.Header().Set("Content-Type", bin.MimeType) w.Header().Set("Content-Length", strconv.Itoa(len(bin.Data))) if _, err := w.Write(bin.Data); err != nil { logger.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("unable to write image") } return nil } ================================================ FILE: packages/service/jsonrpc/debug.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/service/node" "runtime" ) type debugApi struct { } func newDebugApi() *debugApi { return &debugApi{} } type memMetric struct { Alloc uint64 `json:"alloc"` Sys uint64 `json:"sys"` } func (c *debugApi) GetMemStat() (*memMetric, *Error) { var m runtime.MemStats runtime.ReadMemStats(&m) //Alloc: Number of bytes allocated and still in use //Sys: The number of bytes fetched from the system (total) return &memMetric{Alloc: m.Alloc, Sys: m.Sys}, nil } type banMetric struct { NodePosition int `json:"node_position"` Status bool `json:"status"` } func (c *debugApi) GetNodeBanStat() (*[]banMetric, *Error) { nodes := syspar.GetNodes() list := make([]banMetric, 0, len(nodes)) b := node.GetNodesBanService() for i, n := range nodes { list = append(list, banMetric{ NodePosition: i, Status: b.IsBanned(n), }) } return &list, nil } ================================================ FILE: packages/service/jsonrpc/ecosystem_params.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc type ParamResult struct { ID string `json:"id"` Name string `json:"name"` Value string `json:"value"` Conditions string `json:"conditions"` } type ParamsResult struct { List []ParamResult `json:"list"` } ================================================ FILE: packages/service/jsonrpc/errors.go ================================================ package jsonrpc import ( "fmt" "github.com/IBAX-io/go-ibax/packages/consts" ) type ErrorCode int type Error struct { Code ErrorCode `json:"code"` Message string `json:"message"` Data map[string]any `json:"data,omitempty"` } const ( ErrCodeDefault = -32000 ErrCodeInvalidInput = -32001 ErrCodeResourceNotFound = -32002 ErrCodeResourceUnavailable = -32003 ErrCodeTransactionRejected = -32004 ErrCodeMethodNotSupported = -32005 ErrCodeLimitExceeded = -32006 ErrCodeParseError = -32007 ErrCodeInvalidRequest = -32008 ErrCodeMethodNotFound = -32009 ErrCodeInvalidParams = -32010 ErrCodeInternalError = -32011 ErrCodeNotFound = -32012 ErrCodeUnknownUID = -32013 ErrCodeUnauthorized = -32014 ErrCodeParamsInvalid = -32015 ) const ( paramsEmpty = "params can not be empty" invalidParams = "params %s invalid" ) func (e *Error) Error() string { return e.Message } func NewError(code ErrorCode, message string, data ...map[string]any) *Error { e := Error{ Code: code, Message: message, } if len(data) > 0 { e.Data = data[0] } return &e } func DefaultError(message string) *Error { return &Error{ Code: ErrCodeDefault, Message: message, } } func ParseError(message string) *Error { return &Error{ Code: ErrCodeParseError, Message: message, } } func InvalidRequest(message string, data ...map[string]any) *Error { return NewError(ErrCodeInvalidRequest, message, data...) } func MethodNotFound(method string, data ...map[string]any) *Error { message := fmt.Sprintf("The method %s does not exist/is not available", method) return NewError(ErrCodeMethodNotFound, message, data...) } func InvalidParamsError(message string, data ...map[string]any) *Error { return NewError(ErrCodeInvalidParams, message, data...) } func InternalError(message string, data ...map[string]any) *Error { return NewError(ErrCodeInternalError, message, data...) } func InvalidInput(message string, data ...map[string]any) *Error { return NewError(ErrCodeInvalidInput, message, data...) } func ResourceNotFound(message string, data ...map[string]any) *Error { return NewError(ErrCodeResourceNotFound, message, data...) } func ResourceUnavailable(message string, data ...map[string]any) *Error { return NewError(ErrCodeResourceUnavailable, message, data...) } func TransactionRejected(message string, data ...map[string]any) *Error { return NewError(ErrCodeTransactionRejected, message, data...) } func MethodNotSupported(method string, data ...map[string]any) *Error { message := fmt.Sprintf("method not supported %s", method) return NewError(ErrCodeMethodNotSupported, message, data...) } func LimitExceeded(message string, data ...map[string]any) *Error { return NewError(ErrCodeLimitExceeded, message, data...) } func NotFoundError() *Error { return NewError(ErrCodeNotFound, consts.NotFound) } func UnauthorizedError() *Error { return NewError(ErrCodeUnauthorized, "Unauthorized") } func UnUnknownUIDError() *Error { return NewError(ErrCodeUnknownUID, "Unknown uid") } ================================================ FILE: packages/service/jsonrpc/handlers.go ================================================ package jsonrpc import ( "context" "encoding/json" "io" "net/http" "strconv" ) // RequestHandlerFunc is a helper for handling JSONRPC Requests over HTTP // It can be used by microservices to handle JSONRPC methods. For example: // // http.Handle("/", RequestHandlerFunc(func(ctx context.Context, r *Request) (interface{}, *Error) { // if r.Method != "eth_blockNumber" { // return nil, MethodNotSupported(fmt.Sprintf("unsupported method %s", r.Method)) // } // // return "0x123456", nil // })) func RequestHandlerFunc(fn requestHandlerFunc) *requestHandlerFunc { return &fn } type RequestContext struct { context.Context } func (rc *RequestContext) HTTPRequest() *http.Request { return rc.Value(contextKeyHTTPRequest).(*http.Request) } func (rc *RequestContext) HTTPResponseWriter() http.ResponseWriter { return rc.Value(contextKeyHTTPResponseWriter).(http.ResponseWriter) } func (rc *RequestContext) RawJSON() json.RawMessage { return rc.Value(contextKeyRawJSON).(json.RawMessage) } type ctxKey string func (c ctxKey) String() string { return "jsonrpc context key " + string(c) } var ( contextKeyHTTPRequest = ctxKey("HTTP Request") contextKeyHTTPResponseWriter = ctxKey("HTTP Response Writer") contextKeyRawJSON = ctxKey("Raw JSON") ) type requestHandlerFunc func(ctx RequestContext, request *Request) (any, *Error) func (h requestHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { buff, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if r.Header.Get("Content-Type") != "application/json" { http.Error(w, "invalid content type, only application/json is supported", http.StatusUnsupportedMediaType) return } request := Request{} err = json.Unmarshal(buff, &request) if err != nil { // return a generic jsonrpc response WriteResponse(w, nil, nil, InvalidRequest(err.Error())) return } ctx := context.WithValue(r.Context(), contextKeyHTTPRequest, r) ctx = context.WithValue(ctx, contextKeyRawJSON, json.RawMessage(buff)) ctx = context.WithValue(ctx, contextKeyHTTPResponseWriter, w) result, e := h(RequestContext{ctx}, &request) WriteResponse(w, &request, result, e) } func WriteResponse(w http.ResponseWriter, request *Request, result any, e *Error) { if result == nil && e == nil { return } response := generateResponse(request, result, e) b, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write(b) if f, ok := w.(http.Flusher); ok { f.Flush() } } func generateResponse(request *Request, result any, e *Error) any { response := &Response{ JSONRPC: JsonRPCVersion, } if request != nil { response.ID = request.ID } response.Result = result if e != nil { // NOTE: setting `response.Error = e` if e is nil causes `"error": null` to be included // in the Response. See https://play.golang.org/p/Oe3MFR3wwAu response.Error = e } return response } func WriteBatchResponse(w http.ResponseWriter, resp any) { b, err := json.Marshal(resp) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("content-length", strconv.Itoa(len(b))) w.Header().Set("Content-Type", "application/json") //w.Header().Set("transfer-encoding", "identity") w.WriteHeader(http.StatusOK) _, _ = w.Write(b) if f, ok := w.(http.Flusher); ok { f.Flush() } } ================================================ FILE: packages/service/jsonrpc/history.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc const rollbackHistoryLimit = 100 type HistoryResult struct { List []map[string]string `json:"list"` } ================================================ FILE: packages/service/jsonrpc/http.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "compress/gzip" "context" "errors" "fmt" "io" "math" "mime" "net/http" "strconv" "strings" "sync" "time" ) const ( maxRequestContentLength = 1024 * 1024 * 5 contentType = "application/json" ) func validateRequest(r *http.Request) (int, error) { if r.Method == http.MethodPut || r.Method == http.MethodDelete { return http.StatusMethodNotAllowed, errors.New("method not allowed") } if r.ContentLength > maxRequestContentLength { err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) return http.StatusRequestEntityTooLarge, err } // Allow OPTIONS (regardless of content-type) if r.Method == http.MethodOptions { return 0, nil } // Check content-type if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { if mt == contentType { return 0, nil } } // Invalid content-type err := fmt.Errorf("invalid content type, only %s is supported", contentType) return http.StatusUnsupportedMediaType, err } func NewMiddlewares(srv http.Handler, m Mode) http.Handler { handler := newGzipHandler(srv) handler = clientMiddleware(handler, m) handler = tokenMiddleware(handler) handler = nodeStateMiddleware(handler) //handler = statsdMiddleware(handler) handler = recoverMiddleware(handler) return loggerMiddleware(handler) } type gzipResponseWriter struct { resp http.ResponseWriter gz *gzip.Writer contentLength uint64 // total length of the uncompressed response written uint64 // amount of written bytes from the uncompressed response hasLength bool // true if uncompressed response had Content-Length hasInit bool // true after init was called for the first time } var gzPool = sync.Pool{ New: func() interface{} { w := gzip.NewWriter(io.Discard) return w }, } // init runs just before response headers are written. Among other things, this function // also decides whether compression will be applied at all. func (w *gzipResponseWriter) init() { if w.hasInit { return } w.hasInit = true hdr := w.resp.Header() length := hdr.Get("content-length") if len(length) > 0 { if n, err := strconv.ParseUint(length, 10, 64); err != nil { w.hasLength = true w.contentLength = n } } // Setting Transfer-Encoding to "identity" explicitly disables compression. setIdentity := hdr.Get("transfer-encoding") == "identity" if !setIdentity { w.gz = gzPool.Get().(*gzip.Writer) w.gz.Reset(w.resp) hdr.Del("content-length") hdr.Set("content-encoding", "gzip") } } func (w *gzipResponseWriter) Header() http.Header { return w.resp.Header() } func (w *gzipResponseWriter) WriteHeader(status int) { w.init() w.resp.WriteHeader(status) } func (w *gzipResponseWriter) Write(b []byte) (int, error) { w.init() if w.gz == nil { // Compression is disabled. return w.resp.Write(b) } n, err := w.gz.Write(b) w.written += uint64(n) if w.hasLength && w.written >= w.contentLength { // The HTTP handler has finished writing the entire uncompressed response. Close // the gzip stream to ensure the footer will be seen by the client in case the // response is flushed after this call to write. err = w.gz.Close() } return n, err } func (w *gzipResponseWriter) Flush() { if w.gz != nil { w.gz.Flush() } if f, ok := w.resp.(http.Flusher); ok { f.Flush() } } func (w *gzipResponseWriter) close() { if w.gz == nil { return } w.gz.Close() gzPool.Put(w.gz) w.gz = nil } func newGzipHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } wrapper := &gzipResponseWriter{resp: w} defer wrapper.close() next.ServeHTTP(wrapper, r) }) } func ContextRequestTimeout(ctx context.Context) (time.Duration, bool) { timeout := time.Duration(math.MaxInt64) hasTimeout := false setTimeout := func(d time.Duration) { if d < timeout { timeout = d hasTimeout = true } } if deadline, ok := ctx.Deadline(); ok { setTimeout(time.Until(deadline)) } // If the context is an HTTP request context, use the server's WriteTimeout. httpSrv, ok := ctx.Value(http.ServerContextKey).(*http.Server) if ok && httpSrv.WriteTimeout > 0 { wt := httpSrv.WriteTimeout // When a write timeout is configured, we need to send the response message before // the HTTP server cuts connection. So our internal timeout must be earlier than // the server's true timeout. // // Note: Timeouts are sanitized to be a minimum of 1 second. // Also see issue: https://github.com/golang/go/issues/47229 wt -= 100 * time.Millisecond setTimeout(wt) } return timeout, hasTimeout } ================================================ FILE: packages/service/jsonrpc/id.go ================================================ package jsonrpc import ( "encoding/json" "strconv" ) // ID represents a JSON-RPC 2.0 request ID, which may be either a // string or number (or null, which is unsupported). type ID struct { // At most one of Num or Str may be nonzero. If both are zero // valued, then IsNum specifies which field's value is to be used // as the ID. Num uint64 Str string // IsString controls whether the Num or Str field's value should be // used as the ID, when both are zero valued. It must always be // set to true if the request ID is a string. IsString bool } func StringID(s string) ID { return ID{ Str: s, IsString: true, } } func IntID(i uint64) ID { return ID{ Num: i, IsString: false, } } func (id ID) String() string { if id.IsString { return strconv.Quote(id.Str) } return strconv.FormatUint(id.Num, 10) } // MarshalJSON implements json.Marshaler. func (id ID) MarshalJSON() ([]byte, error) { if id.IsString { return json.Marshal(id.Str) } return json.Marshal(id.Num) } // UnmarshalJSON implements json.Unmarshaler. func (id *ID) UnmarshalJSON(data []byte) error { // Support both uint64 and string IDs. var v uint64 if err := json.Unmarshal(data, &v); err == nil { *id = ID{Num: v} return nil } var v2 string if err := json.Unmarshal(data, &v2); err != nil { return err } *id = ID{Str: v2, IsString: true} return nil } ================================================ FILE: packages/service/jsonrpc/jwt.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" "github.com/golang-jwt/jwt/v4" "strings" ) var ( jwtSecret []byte jwtPrefix = "Bearer " jwtExpire = 28800 // By default, seconds jwtrefeshExpire = 600 // By default, seconds errJWTAuthValue = errors.New("wrong authorization value") errEcosystemNotFound = errors.New("ecosystem not found") ) // JWTClaims is storing jwt claims type JWTClaims struct { UID string `json:"uid,omitempty"` EcosystemID string `json:"ecosystem_id,omitempty"` KeyID string `json:"key_id,omitempty"` AccountID string `json:"account_id,omitempty"` RoleID string `json:"role_id,omitempty"` jwt.RegisteredClaims } func generateJWTToken(claims JWTClaims) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(jwtSecret) } func parseJWTToken(header string) (*jwt.Token, error) { if len(header) == 0 { return nil, nil } if strings.HasPrefix(header, jwtPrefix) { header = header[len(jwtPrefix):] } else { return nil, errJWTAuthValue } return jwt.ParseWithClaims(header, &JWTClaims{}, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(jwtSecret), nil }) } func getClientFromToken(token *jwt.Token, ecosysNameService types.EcosystemGetter) (*UserClient, error) { claims, ok := token.Claims.(*JWTClaims) if !ok { return nil, nil } if len(claims.KeyID) == 0 { return nil, nil } client := &UserClient{ EcosystemID: converter.StrToInt64(claims.EcosystemID), KeyID: converter.StrToInt64(claims.KeyID), AccountID: claims.AccountID, RoleID: converter.StrToInt64(claims.RoleID), } sID := converter.StrToInt64(claims.EcosystemID) name, err := ecosysNameService.GetEcosystemName(sID) if err != nil { return nil, err } client.EcosystemName = name return client, nil } func InitJwtSecret(secret []byte) { if secret == nil { panic("[JSON-RPC] jwt secret invalid") } jwtSecret = secret } ================================================ FILE: packages/service/jsonrpc/middlewares.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/service/node" "github.com/IBAX-io/go-ibax/packages/statsd" "github.com/didip/tollbooth" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" "net/http" "runtime/debug" "time" ) func clientMiddleware(next http.Handler, m Mode) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := getToken(r) var client *UserClient if token != nil { // get client from token var err error if client, err = getClientFromToken(token, m.EcosystemGetter); err != nil { WriteResponse(w, nil, nil, DefaultError(err.Error())) return } } if client == nil { // create client with default ecosystem client = &UserClient{EcosystemID: 1} } r = setClient(r, client) next.ServeHTTP(w, r) }) } func loggerFromRequest(r *http.Request) *log.Entry { return log.WithFields(log.Fields{ "headers": r.Header, "path": r.URL.Path, "protocol": r.Proto, "remote": r.RemoteAddr, }) } func loggerMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger := loggerFromRequest(r) logger.Info("received http request") r = setLogger(r, logger) next.ServeHTTP(w, r) }) } func recoverMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { logger := getLogger(r) logger.WithFields(log.Fields{ "type": consts.PanicRecoveredError, "error": err, "stack": string(debug.Stack()), }).Debug("panic recovered error") WriteResponse(w, nil, nil, InternalError("JSON RPC API recovered")) } }() next.ServeHTTP(w, r) }) } func nodeStateMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch node.NodePauseType() { case node.NoPause: next.ServeHTTP(w, r) return case node.PauseTypeUpdatingBlockchain: WriteResponse(w, nil, nil, DefaultError("Node is updating blockchain")) break case node.PauseTypeStopingNetwork: WriteResponse(w, nil, nil, DefaultError("Network is stopping")) break } }) } func tokenMiddleware(next http.Handler) http.Handler { const authHeader = "AUTHORIZATION" return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //token, err := RefreshToken(r.Header.Get(authHeader)) token, err := parseJWTToken(r.Header.Get(authHeader)) if err != nil { logger := getLogger(r) logger.WithFields(log.Fields{"type": consts.JWTError, "error": err}).Warning("starting session") } if token != nil && token.Valid { r = setToken(r, token) } next.ServeHTTP(w, r) }) } func statsdMiddleware(next http.Handler) http.Handler { const v = 1.0 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //request url route := mux.CurrentRoute(r) counterName := statsd.APIRouteCounterName(r.Method, route.GetName()) statsd.Client.Inc(counterName+statsd.Count, 1, v) startTime := time.Now() defer func() { statsd.Client.TimingDuration(counterName+statsd.Time, time.Since(startTime), v) }() next.ServeHTTP(w, r) }) } func limiterMiddleware(next http.Handler) http.Handler { //max ten requests per second limiter := tollbooth.NewLimiter(10, nil) return tollbooth.LimitHandler(limiter, next) } ================================================ FILE: packages/service/jsonrpc/namespace.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "reflect" ) type space string const ( namespaceSeparator = "." ) const ( namespaceRPC space = "rpc" NamespaceDebug space = "debug" NamespaceIBAX space = "ibax" NamespaceAdmin space = "admin" NamespaceNet space = "net" ) type RpcApis interface { GetApis() []any } type RpcServers struct { server *Server } type NewApiService interface { New(structObject any) (name string) Delete(name string) } type Method struct { services map[string]reflect.Value } func (a *Method) Delete(name string) { for k, _ := range a.services { if k == name { delete(a.services, k) break } } } func (a *Method) New(structObject any) (name string) { if a.services == nil { a.services = make(map[string]reflect.Value) } var findOut bool ref := GetStructValue(structObject) for _, v := range a.services { if v == ref { findOut = true } } name = ref.Type().String() if !findOut { a.services[name] = ref } return } func (r *RpcServers) Modules() ([]string, *Error) { r.server.service.mu.Lock() defer r.server.service.mu.Unlock() var methods []string for namespace, v := range r.server.service.services { for name := range v.callbacks { methods = append(methods, namespace+namespaceSeparator+name) } } return methods, nil } func GetStructValue(structObject any) reflect.Value { return reflect.ValueOf(structObject) } ================================================ FILE: packages/service/jsonrpc/namespace_debug.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc type DebugApi struct { de *debugApi } func (p *DebugApi) GetApis() []any { var apis []any if p == nil { return nil } if p.de != nil { apis = append(apis, p.de) } return apis } func NewDebugApi() *DebugApi { return &DebugApi{ de: newDebugApi(), } } ================================================ FILE: packages/service/jsonrpc/namespace_ibax.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc type IbaxApi struct { auth *authApi bk *blockChainApi common *commonApi tx *transactionApi account *accountsApi data *dataApi } func (p *IbaxApi) GetApis() []any { var apis []any if p == nil { return nil } if p.auth != nil { apis = append(apis, p.auth) } if p.bk != nil { apis = append(apis, p.bk) } if p.common != nil { apis = append(apis, p.common) } if p.tx != nil { apis = append(apis, p.tx) } if p.account != nil { apis = append(apis, p.account) } if p.data != nil { apis = append(apis, p.data) } return apis } func NewIbaxApi(m Mode) *IbaxApi { return &IbaxApi{ auth: newAuthApi(m), bk: newBlockChainApi(), common: newCommonApi(m), tx: newTransactionApi(), account: newAccountsApi(m), data: newDataApi(), } } ================================================ FILE: packages/service/jsonrpc/namespace_net.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/service/node" ) type NetApi struct { net *networkApi } func NewNetApi() *NetApi { return &NetApi{ net: NewNetworkApi(), } } func (p *NetApi) GetApis() []any { var apis []any if p == nil { return nil } if p.net != nil { apis = append(apis, p.net) } return apis } type networkApi struct { } func NewNetworkApi() *networkApi { n := &networkApi{} return n } func (n *networkApi) Status() string { return node.NodePauseType().String() } func (c *networkApi) GetNetwork() (*NetworkResult, *Error) { return &NetworkResult{ NetworkID: converter.Int64ToStr(conf.Config.LocalConf.NetworkID), CentrifugoURL: conf.Config.Centrifugo.URL, Test: syspar.IsTestMode(), Private: syspar.IsPrivateBlockchain(), HonorNodes: getNodesJSON(), }, nil } ================================================ FILE: packages/service/jsonrpc/params.go ================================================ package jsonrpc import ( "bytes" "encoding/json" "fmt" "reflect" "github.com/pkg/errors" ) // Params is an ARRAY of json.RawMessages. This is because *Ethereum* RPCs always use // arrays is their input parameter; this differs from the official JSONRPC spec, which allows // parameters of any type. // But, this assumption makes handling Params in our Ethereum API use-cases *so* much easier. type Param json.RawMessage type Params []Param // MarshalJSON returns m as the JSON encoding of m. func (m Param) MarshalJSON() ([]byte, error) { if m == nil { return []byte("null"), nil } return m, nil } // UnmarshalJSON sets *m to a copy of data. func (m *Param) UnmarshalJSON(data []byte) error { if m == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") } *m = append((*m)[0:0], data...) return nil } // MustParams can be used to generate JSONRPC Params field from well-known // data, which should not fail. // // Examples: // // request.Params = jsonrpc.MustParams("latest", true) func MustParams(params ...any) Params { out, err := MakeParams(params...) if err != nil { panic(err) } return out } // MakeParams generates JSONRPC parameters from its inputs, and should be used for // complex dynamic data which may fail to marshal, in which case the error is propagated // to the caller. // // Examples: // // params, err := jsonrpc.MakeParams(someComplexObject, "string", true) func MakeParams(params ...any) (Params, error) { if len(params) == 0 { return nil, nil } out := make(Params, len(params)) for i, param := range params { b, err := json.Marshal(param) if err != nil { return nil, err } out[i] = Param(b) } return out, nil } // UnmarshalInto will decode Params into the passed in values, which // must be pointer receivers. The type of the passed in value is used to Unmarshal the data. // UnmarshalInto will fail if the parameters cannot be converted to the passed-in types. // // Example: // // var blockNum string // var fullBlock bool // err := request.Params.UnmarshalInto(&blockNum, &fullBlock) // // IMPORTANT: While Go will compile with non-pointer receivers, the Unmarshal attempt will // *always* fail with an error. func (p Params) UnmarshalInto(receivers ...any) error { if p == nil { return nil } if len(p) < len(receivers) { return errors.New("not enough params to decode") } for i, r := range receivers { err := json.Unmarshal(p[i], r) if err != nil { return err } } return nil } func (p Params) UnmarshalValue(types []reflect.Type) (args []reflect.Value, err error) { defer func() { if err == nil { //Set any missing args to nil. prevent fn call panic for i := len(args); i < len(types); i++ { if types[i].Kind() != reflect.Ptr { args = nil err = fmt.Errorf("missing value for required argument %d", i+1) return } args = append(args, reflect.Zero(types[i])) } } }() if p == nil { return nil, nil } if len(p) > len(types) { return nil, errors.New(fmt.Sprintf("too many arguments, want %d", len(types))) } args = make([]reflect.Value, 0, len(types)) for i, _ := range p { dec := json.NewDecoder(bytes.NewReader(p[i])) argval := reflect.New(types[i]) if err := dec.Decode(argval.Interface()); err != nil { return args, fmt.Errorf("invalid argument %d: %v", i+1, err) } if argval.IsNil() && types[i].Kind() != reflect.Ptr { return args, fmt.Errorf("missing value for required argument %d", i+1) } args = append(args, argval.Elem()) } return args, nil } // UnmarshalSingleParam can be used in the (rare) case where only one of the Request.Params is // needed. For example we use this in Smart Routing to extract the blockNum value from RPCs without // decoding the entire Params array. // // Example: // // err := request.Params.UnmarshalSingleParam(pos, &blockNum) func (p Params) UnmarshalSingleParam(pos int, receiver any) error { if pos > (len(p) - 1) { return errors.New("not enough parameters to decode position") } param := p[pos] err := json.Unmarshal(param, receiver) return err } ================================================ FILE: packages/service/jsonrpc/raw_response.go ================================================ package jsonrpc import ( "encoding/json" ) // RawResponse keeps Result and Error as unparsed JSON // It is meant to be used to deserialize JSONPRC responses from downstream components // while Response is meant to be used to craft our own responses to clients. type RawResponse struct { JSONRPC string `json:"jsonrpc"` ID ID `json:"id"` Result json.RawMessage `json:"result,omitempty"` Error *json.RawMessage `json:"error,omitempty"` } // MarshalJSON implements json.Marshaler and adds the "jsonrpc":"2.0" // property. func (r RawResponse) MarshalJSON() ([]byte, error) { if r.Error != nil { response := struct { JSONRPC string `json:"jsonrpc"` ID ID `json:"id"` Error json.RawMessage `json:"error,omitempty"` }{ JSONRPC: JsonRPCVersion, ID: r.ID, Error: *r.Error, } return json.Marshal(response) } else { response := struct { JSONRPC string `json:"jsonrpc"` ID ID `json:"id"` Result json.RawMessage `json:"result,omitempty"` }{ JSONRPC: JsonRPCVersion, ID: r.ID, Result: r.Result, } if response.Result == nil { response.Result = jsonNull } return json.Marshal(response) } } // UnmarshalJSON implements json.Unmarshaler. func (r *RawResponse) UnmarshalJSON(data []byte) error { type tmpType RawResponse if err := json.Unmarshal(data, (*tmpType)(r)); err != nil { return err } return nil } ================================================ FILE: packages/service/jsonrpc/request.go ================================================ package jsonrpc import ( "bytes" "encoding/json" "github.com/pkg/errors" "io" ) const JsonRPCVersion = "2.0" type Request struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` ID ID `json:"id"` Params Params `json:"params"` } type BatchRequest []*Request type RequestWithNetwork struct { *Request Network string `json:"network"` } func NewRequest() *Request { return &Request{JSONRPC: JsonRPCVersion, ID: ID{Num: 1}} } // MakeRequest builds a Request from all its parts, but returns an error if the // params cannot be marshalled. func MakeRequest(id int, method string, params ...any) (*Request, error) { p, err := MakeParams(params...) if err != nil { return nil, err } return &Request{ JSONRPC: JsonRPCVersion, ID: ID{Num: uint64(id)}, Method: method, Params: p, }, nil } // MustRequest builds a request from all its parts but panics if the params cannot be marshaled, // so should only be used with well-known parameter data. func MustRequest(id int, method string, params ...any) *Request { r, err := MakeRequest(id, method, params...) if err != nil { panic(err) } return r } // MarshalJSON implements json.Marshaler and adds the "jsonrpc":"2.0" // property. func (r Request) MarshalJSON() ([]byte, error) { r2 := struct { Method string `json:"method"` Params Params `json:"params,omitempty"` ID *ID `json:"id,omitempty"` JSONRPC string `json:"jsonrpc"` }{ Method: r.Method, Params: r.Params, JSONRPC: JsonRPCVersion, } r2.ID = &r.ID return json.Marshal(r2) } // UnmarshalJSON implements json.Unmarshaler. func (r *Request) UnmarshalJSON(data []byte) error { var r2 struct { Method string `json:"method"` Params *json.RawMessage `json:"params,omitempty"` Meta *json.RawMessage `json:"meta,omitempty"` ID *ID `json:"id"` } // Detect if the "params" field is JSON "null" or just not present // by seeing if the field gets overwritten to nil. r2.Params = &json.RawMessage{} if err := json.Unmarshal(data, &r2); err != nil { return err } r.Method = r2.Method if r2.Params == nil { r.Params = nil } else if len(*r2.Params) == 0 { r.Params = nil } else { err := json.Unmarshal(*r2.Params, &r.Params) if err != nil { return err } } if r2.Method == "" { return errors.New("request is missing method") } if r2.ID == nil { return errors.New("request is missing ID") } else { r.ID = *r2.ID } r.JSONRPC = JsonRPCVersion return nil } func getBatch(r io.ReadCloser, ctxer func(json.RawMessage)) (reqs []*Request, batch bool, err error) { body := io.LimitReader(r, maxRequestContentLength) dec := json.NewDecoder(body) dec.UseNumber() var raw json.RawMessage err = dec.Decode(&raw) if err != nil { return nil, false, err } ctxer(raw) reqs, batch = parseRequest(raw) for k, req := range reqs { if req == nil { reqs[k] = &Request{} } } return reqs, batch, nil } func parseRequest(raw json.RawMessage) ([]*Request, bool) { if !isBatch(raw) { reqs := []*Request{{}} json.Unmarshal(raw, &reqs[0]) return reqs, false } dec := json.NewDecoder(bytes.NewReader(raw)) dec.Token() // skip '[' var reqs []*Request for dec.More() { reqs = append(reqs, new(Request)) dec.Decode(&reqs[len(reqs)-1]) } return reqs, true } func isBatch(raw json.RawMessage) bool { for _, c := range raw { // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { continue } return c == '[' } return false } // MarshalJSON implements json.Marshaler and adds the "jsonrpc":"2.0" // property. func (r RequestWithNetwork) MarshalJSON() ([]byte, error) { r2 := struct { Method string `json:"method"` Params Params `json:"params,omitempty"` ID *ID `json:"id,omitempty"` JSONRPC string `json:"jsonrpc"` Network string `json:"network"` }{ Method: r.Method, Params: r.Params, Network: r.Network, JSONRPC: JsonRPCVersion, } r2.ID = &r.ID return json.Marshal(r2) } // UnmarshalJSON implements json.Unmarshaler. func (r *RequestWithNetwork) UnmarshalJSON(data []byte) error { var r2 struct { Method string `json:"method"` Params *json.RawMessage `json:"params,omitempty"` Meta *json.RawMessage `json:"meta,omitempty"` Network string `json:"network"` ID *ID `json:"id"` } // Detect if the "params" field is JSON "null" or just not present // by seeing if the field gets overwritten to nil. r2.Params = &json.RawMessage{} r2.Network = r.Network if err := json.Unmarshal(data, &r2); err != nil { return err } r.Method = r2.Method if r2.Params == nil { r.Params = nil } else if len(*r2.Params) == 0 { r.Params = nil } else { err := json.Unmarshal(*r2.Params, &r.Params) if err != nil { return err } } if r2.ID == nil { return errors.New("request is missing ID") } else { r.ID = *r2.ID } return nil } ================================================ FILE: packages/service/jsonrpc/response.go ================================================ package jsonrpc import ( "encoding/json" ) var ( jsonNull = json.RawMessage("null") ) type Response struct { JSONRPC string `json:"jsonrpc"` ID ID `json:"id"` Result any `json:"result,omitempty"` Error any `json:"error,omitempty"` } type BatchResponse []*Response func NewResponse() *Response { return &Response{} } // MarshalJSON implements json.Marshaler and adds the "jsonrpc":"2.0" // property. func (r Response) MarshalJSON() ([]byte, error) { if r.Error != nil { response := struct { JSONRPC string `json:"jsonrpc"` ID ID `json:"id"` Error any `json:"error,omitempty"` }{ JSONRPC: JsonRPCVersion, ID: r.ID, Error: r.Error, } return json.Marshal(response) } else { response := struct { JSONRPC string `json:"jsonrpc"` ID ID `json:"id"` Result any `json:"result,omitempty"` }{ JSONRPC: JsonRPCVersion, ID: r.ID, Result: r.Result, } if response.Result == nil { response.Result = jsonNull } return json.Marshal(response) } } // UnmarshalJSON implements json.Unmarshaler. func (r *Response) UnmarshalJSON(data []byte) error { type tmpType Response if err := json.Unmarshal(data, (*tmpType)(r)); err != nil { return err } return nil } ================================================ FILE: packages/service/jsonrpc/server.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "fmt" log "github.com/sirupsen/logrus" "net/http" "sync/atomic" ) type Server struct { service serviceRegistry status int32 } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Permit dumb empty requests for remote health-checks (AWS) if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { w.WriteHeader(http.StatusOK) return } if code, err := validateRequest(r); err != nil { http.Error(w, err.Error(), code) return } if atomic.LoadInt32(&s.status) == 0 { return } s.service.ServeHTTP(w, r) } func NewServer(m Mode) *Server { server := &Server{ status: 1, } server.service.mode = m rpcService := &RpcServers{server} name := GetNamespace(namespaceRPC) err := server.RegisterName(name, rpcService) if err != nil { panic(fmt.Sprintf("register name[%s] failed:%s\n", name, err.Error())) } return server } func (s *Server) RegisterName(namespace string, function any) error { return s.service.registerName(namespace, function) } func (s *Server) Stop() { s.service.mu.Lock() defer s.service.mu.Unlock() if atomic.CompareAndSwapInt32(&s.status, 1, 0) { log.Debug("Json-RPC server shutting down") } } ================================================ FILE: packages/service/jsonrpc/service.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "context" "encoding/json" "fmt" "io" "net/http" "reflect" "strings" "sync" "time" ) type service struct { callbacks map[string]*callback // registered handlers } type serviceRegistry struct { mu sync.Mutex services map[string]service route func(ctx RequestContext, request *Request) mode Mode ctx context.Context cancel context.CancelFunc runWg sync.WaitGroup t1 time.Time } func (r *serviceRegistry) registerName(namespace string, stct any) error { stctVal := GetStructValue(stct) if namespace == "" { return fmt.Errorf("no service name for type %s", stctVal.Type().String()) } callbacks := suitableCallbacks(stctVal) if len(callbacks) == 0 { return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", stct) } r.mu.Lock() defer r.mu.Unlock() if r.services == nil { r.services = make(map[string]service) } svc, ok := r.services[namespace] if !ok { svc = service{ callbacks: make(map[string]*callback), } r.services[namespace] = svc } for name, cb := range callbacks { svc.callbacks[name] = cb } return nil } // ServeHTTP json-rpc server func (s *serviceRegistry) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.t1 = time.Now() ctx := context.WithValue(r.Context(), contextKeyHTTPRequest, r) ctx = context.WithValue(ctx, contextKeyHTTPResponseWriter, w) ctxer := func(raw json.RawMessage) { ctx = context.WithValue(ctx, contextKeyRawJSON, raw) } reqs, batch, err := getBatch(r.Body, ctxer) if err != nil { if err != io.EOF { WriteResponse(w, nil, nil, InvalidParamsError(err.Error())) return } } s.ctx, s.cancel = context.WithCancel(ctx) defer s.close(io.EOF) if batch { if len(reqs) == 0 { WriteResponse(w, nil, nil, InvalidInput("empty batch request")) return } s.runBatch(RequestContext{ctx}, reqs) return } result, e := s.run(RequestContext{ctx}, false, reqs) WriteResponse(w, reqs[0], result, e) } func (s *serviceRegistry) close(err error) { s.runWg.Wait() s.cancel() } func GetNamespace(name space) string { return string(name) } func (r *serviceRegistry) findCallback(method string) *callback { elem := strings.SplitN(method, namespaceSeparator, 2) if len(elem) != 2 { return nil } r.mu.Lock() defer r.mu.Unlock() return r.services[elem[0]].callbacks[elem[1]] } func (s *serviceRegistry) run(ctx RequestContext, isBatch bool, reqs []*Request) (any, *Error) { req := reqs[0] cb := s.findCallback(req.Method) if cb == nil { return nil, MethodNotFound(req.Method) } if cb.hasAuth { r := ctx.HTTPRequest() if err := authRequire(r); err != nil { return nil, err } } if cb.notSingle && isBatch { return nil, InvalidInput("not support batch") } args, err := req.Params.UnmarshalValue(cb.argTypes) if err != nil { return nil, InvalidParamsError(err.Error()) } return runMethod(ctx, s.mode, args, cb) } func runMethod(ctx RequestContext, m Mode, args []reflect.Value, cb *callback) (any, *Error) { return cb.call(ctx, m, args) } func (s *serviceRegistry) runBatch(reqCtx RequestContext, reqs []*Request) { s.callProc(func(ctx context.Context) { var ( timer *time.Timer cancel context.CancelFunc callBuffer = &batchCallBuffer{reqs: reqs, resp: make([]*any, 0, len(reqs))} ) ctx, cancel = context.WithCancel(ctx) defer cancel() if timeout, ok := ContextRequestTimeout(ctx); ok { timer = time.AfterFunc(timeout, func() { cancel() callBuffer.write(reqCtx, true) }) } for { // No need to handle rest of reqs if timed out. if ctx.Err() != nil { break } msg := callBuffer.nextRequest() if msg == nil { break } req := []*Request{msg} resp, err := s.run(reqCtx, true, req) callBuffer.pushResponse(&resp, err, msg) } if timer != nil { timer.Stop() } callBuffer.write(reqCtx, false) }) } func (s *serviceRegistry) callProc(fn func(ctx context.Context)) { s.runWg.Add(1) go func() { ctx, cancel := context.WithCancel(s.ctx) defer s.runWg.Done() defer cancel() fn(ctx) }() } type batchCallBuffer struct { mutex sync.Mutex reqs []*Request resp []*any wrote bool } // nextRequest returns the next unprocessed request. func (b *batchCallBuffer) nextRequest() *Request { b.mutex.Lock() defer b.mutex.Unlock() if len(b.reqs) == 0 { return nil } req := b.reqs[0] return req } // pushResponse add the response. func (b *batchCallBuffer) pushResponse(answer *any, err *Error, req *Request) { b.mutex.Lock() defer b.mutex.Unlock() result := generateResponse(req, answer, err) b.resp = append(b.resp, &result) b.reqs = b.reqs[1:] } // write response. func (b *batchCallBuffer) write(ctx RequestContext, isTimeOut bool) { b.mutex.Lock() defer b.mutex.Unlock() if b.wrote { return } if isTimeOut { for _, req := range b.reqs { result := generateResponse(req, nil, InternalError("request timed out")) b.resp = append(b.resp, &result) } } w := ctx.HTTPResponseWriter() b.wrote = true // can only write once if len(b.resp) > 0 { WriteBatchResponse(w, b.resp) } } ================================================ FILE: packages/service/jsonrpc/transaction.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "encoding/hex" "encoding/json" "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/transaction" log "github.com/sirupsen/logrus" "net/http" "strings" ) type transactionApi struct { } func newTransactionApi() *transactionApi { return &transactionApi{} } type sendTxResult struct { Hashes map[string]string `json:"hashes"` } func txHandlerBatches(r *http.Request, m Mode, mtx map[string][]byte) ([]string, error) { client := getClient(r) logger := getLogger(r) var txData [][]byte for _, datum := range mtx { txData = append(txData, datum) } if int64(len(txData)) > syspar.GetMaxTxSize() { logger.WithFields(log.Fields{"type": consts.ParameterExceeded, "max_size": syspar.GetMaxTxSize(), "size": len(txData)}).Error("transaction size exceeds max size") transaction.BadTxForBan(client.KeyID) return nil, fmt.Errorf("the size of tx is too big (%d)", len(txData)) } hash, err := m.ClientTxProcessor.ProcessClientTxBatches(txData, client.KeyID, logger) if err != nil { return nil, err } return hash, nil } func (t *transactionApi) SendTx(ctx RequestContext, auth Auth, mtx map[string][]byte) (*sendTxResult, *Error) { r := ctx.HTTPRequest() client := getClient(r) if transaction.IsKeyBanned(client.KeyID) { return nil, DefaultError(fmt.Sprintf("The key %d is banned till %s", client.KeyID, transaction.BannedTill(client.KeyID))) } if mtx == nil { return nil, InvalidParamsError(paramsEmpty) } result := &sendTxResult{Hashes: make(map[string]string)} hash, err := txHandlerBatches(r, auth.Mode, mtx) if err != nil { return nil, DefaultError(err.Error()) } for _, key := range hash { result.Hashes[key] = key } return result, nil } type txstatusError struct { Type string `json:"type,omitempty"` Error string `json:"error,omitempty"` Id string `json:"id,omitempty"` } type txstatusResult struct { BlockID string `json:"blockid"` Message *txstatusError `json:"errmsg,omitempty"` Result string `json:"result"` Penalty int64 `json:"penalty"` } func getTxStatus(r *http.Request, hash string) (*txstatusResult, error) { logger := getLogger(r) var status txstatusResult if _, err := hex.DecodeString(hash); err != nil { logger.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("decoding tx hash from hex") return nil, errors.New("hash is incorrect") } ts := &sqldb.TransactionStatus{} found, err := ts.Get([]byte(converter.HexToBin(hash))) if err != nil { logger.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("getting transaction status by hash") return nil, err } if !found { logger.WithFields(log.Fields{"type": consts.NotFound, "key": []byte(converter.HexToBin(hash))}).Debug("getting transaction status by hash") return nil, errors.New(fmt.Sprintf("hash %s has not been found", hash)) } checkErr := func() { if len(ts.Error) > 0 { if err := json.Unmarshal([]byte(ts.Error), &status.Message); err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "text": ts.Error, "error": err}).Warn("unmarshalling txstatus error") status.Message = &txstatusError{ Type: "txError", Error: ts.Error, } } } } if ts.BlockID > 0 { status.BlockID = converter.Int64ToStr(ts.BlockID) status.Penalty = ts.Penalty if ts.Penalty == 1 { checkErr() } else { status.Result = ts.Error } } else { checkErr() } return &status, nil } func (t *transactionApi) TxStatus(ctx RequestContext, auth Auth, hashes string) (*map[string]*txstatusResult, *Error) { result := map[string]*txstatusResult{} if hashes == "" { return nil, InvalidParamsError(paramsEmpty) } list := strings.Split(hashes, ",") r := ctx.HTTPRequest() for _, hash := range list { status, err := getTxStatus(r, hash) if err != nil { return nil, DefaultError(err.Error()) } result[hash] = status } return &result, nil } func (b *transactionApi) TxInfo(hash string, contractInfo *bool) (*TxInfoResult, *Error) { if hash == "" { return nil, InvalidParamsError(paramsEmpty) } var getInfo bool if contractInfo != nil { getInfo = *contractInfo } status, err := getTxInfo(hash, getInfo) if err != nil { return nil, DefaultError(err.Error()) } return status, nil } func (b *transactionApi) TxInfoMultiple(hashList []string, contractInfo *bool) (*MultiTxInfoResult, *Error) { if hashList == nil { return nil, InvalidParamsError(paramsEmpty) } result := &MultiTxInfoResult{ Results: make(map[string]*TxInfoResult), } var getInfo bool if contractInfo != nil { getInfo = *contractInfo } for _, hash := range hashList { status, err := getTxInfo(hash, getInfo) if err != nil { return nil, DefaultError(err.Error()) } result.Results[hash] = status } return result, nil } ================================================ FILE: packages/service/jsonrpc/txinfo.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package jsonrpc import ( "bytes" "encoding/hex" "errors" "github.com/IBAX-io/go-ibax/packages/block" "github.com/IBAX-io/go-ibax/packages/common" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" ) // gas fee info type feeInfo struct { Amount string `json:"amount"` TokenSymbol string `json:"token_symbol"` Digits int `json:"digits"` } type TxInfoResult struct { BlockID int64 `json:"blockid"` Confirm int `json:"confirm"` Data *smart.TxInfo `json:"data"` } type MultiTxInfoResult struct { Results map[string]*TxInfoResult `json:"results"` } type TxDetailResult struct { } func getTxInfo(txHash string, getInfo bool) (*TxInfoResult, error) { var status TxInfoResult hash, err := hex.DecodeString(txHash) if err != nil { return nil, errors.New("hash is incorrect") } ltx := &sqldb.LogTransaction{Hash: hash} found, err := ltx.GetByHash(nil, hash) if err != nil { return nil, err } if !found { return &status, nil } status.BlockID = ltx.Block var confirm sqldb.Confirmation found, err = confirm.GetConfirmation(ltx.Block) if err != nil { return nil, err } if found { status.Confirm = int(confirm.Good) } if getInfo { status.Data, err = transactionData(ltx.Block, hex.EncodeToString(ltx.Hash)) if err != nil { return nil, err } status.Data.Status = ltx.Status status.Data.Ecosystem = ltx.EcosystemID } return &status, nil } func transactionData(blockId int64, txHash string) (*smart.TxInfo, error) { info := &smart.TxInfo{} bk := &sqldb.BlockChain{} f, err := bk.Get(blockId) if err != nil { return nil, err } if !f { return nil, errors.New("not found") } blck, err := block.UnmarshallBlock(bytes.NewBuffer(bk.Data), false) if err != nil { return nil, err } for _, tx := range blck.Transactions { hashStr := hex.EncodeToString(tx.Hash()) //find next if hashStr != txHash { continue } info.Address = converter.AddressToString(tx.KeyID()) info.Hash = hashStr info.Size = common.StorageSize(len(tx.Payload())).TerminalString() info.CreatedAt = tx.Timestamp() if tx.IsSmartContract() { info.Expedite = tx.SmartContract().TxSmart.Expedite if tx.SmartContract().TxContract != nil { info.ContractName = tx.SmartContract().TxContract.Name } info.Params = tx.SmartContract().TxData if tx.Type() == types.TransferSelfTxType { info.Params = make(map[string]any) info.Params["transferSelf"] = tx.SmartContract().TxSmart.TransferSelf } if tx.Type() == types.UtxoTxType { info.Params = make(map[string]any) info.Params["utxo"] = tx.SmartContract().TxSmart.UTXO } } //find out break break } info.BlockId = blck.Header.BlockId info.BlockHash = hex.EncodeToString(blck.Header.BlockHash) return info, nil } ================================================ FILE: packages/service/node/node_actualization.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package node import ( "context" "time" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) // DefaultBlockchainGap is default value for the number of lagging blocks const DefaultBlockchainGap int64 = 10 type NodeActualizer struct { availableBlockchainGap int64 } func NewNodeActualizer(availableBlockchainGap int64) NodeActualizer { return NodeActualizer{ availableBlockchainGap: availableBlockchainGap, } } // Run is starting node monitoring func (n *NodeActualizer) Run(ctx context.Context) { go func() { log.Info("Node Actualizer monitoring starting") for { if ctx.Err() != nil { log.WithFields(log.Fields{"error": ctx.Err(), "type": consts.ContextError}).Error("context error") return } actual, err := n.checkBlockchainActuality(ctx) if err != nil { log.WithFields(log.Fields{"type": consts.BCActualizationError, "err": err}).Error("checking blockchain actuality") return } if !actual && !IsNodePaused() { log.Info("Node Actualizer is pausing node activity") n.pauseNodeActivity() } if actual && IsNodePaused() { log.Info("Node Actualizer is resuming node activity") n.resumeNodeActivity() } time.Sleep(time.Second * 5) } }() } func (n *NodeActualizer) checkBlockchainActuality(ctx context.Context) (bool, error) { curBlock := &sqldb.InfoBlock{} _, err := curBlock.Get() if err != nil { return false, errors.Wrapf(err, "retrieving info block") } remoteHosts, err := GetNodesBanService().FilterBannedHosts(syspar.GetRemoteHosts()) if err != nil { return false, err } _, maxBlockID, err := tcpclient.HostWithMaxBlock(ctx, remoteHosts) if err != nil { return false, errors.Wrapf(err, "choosing best host") } // Currently this node is downloading blockchain if curBlock.BlockID == 0 || curBlock.BlockID+n.availableBlockchainGap < maxBlockID { return false, nil } foreignBlock := &sqldb.BlockChain{} _, err = foreignBlock.GetMaxForeignBlock(conf.Config.KeyID) if err != nil { return false, errors.Wrapf(err, "retrieving last foreign block") } // Node did not accept any blocks for an hour t := time.Unix(foreignBlock.Time, 0) if time.Since(t).Minutes() > 30 && len(remoteHosts) > 1 { return false, nil } return true, nil } func (n *NodeActualizer) pauseNodeActivity() { np.Set(PauseTypeUpdatingBlockchain) } func (n *NodeActualizer) resumeNodeActivity() { np.Unset() } ================================================ FILE: packages/service/node/node_ban.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package node import ( "bytes" "sync" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/transaction" "github.com/IBAX-io/go-ibax/packages/types" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) type localBannedNode struct { HonorNode *syspar.HonorNode LocalUnBanTime time.Time } type NodesBanService struct { localBannedNodes map[int64]localBannedNode honorNodes []syspar.HonorNode m *sync.Mutex } var nbs *NodesBanService // GetNodesBanService is returning nodes ban service func GetNodesBanService() *NodesBanService { return nbs } // InitNodesBanService initializing nodes ban storage func InitNodesBanService() error { nbs = &NodesBanService{ localBannedNodes: make(map[int64]localBannedNode), m: &sync.Mutex{}, } nbs.refreshNodes() return nil } // RegisterBadBlock is set node to local ban and saving bad block to global registry func (nbs *NodesBanService) RegisterBadBlock(node syspar.HonorNode, badBlockId, blockTime int64, reason string, register bool) error { if nbs.IsBanned(node) { return nil } nbs.localBan(node) if !register { return nil } err := nbs.newBadBlock(node, badBlockId, blockTime, reason) if err != nil { return err } return nil } // IsBanned is allows to check node ban (local or global) func (nbs *NodesBanService) IsBanned(node syspar.HonorNode) bool { nbs.refreshNodes() nbs.m.Lock() defer nbs.m.Unlock() nodeKeyID := crypto.Address(node.PublicKey) // Searching for local ban now := time.Now() if fn, ok := nbs.localBannedNodes[nodeKeyID]; ok { if now.Equal(fn.LocalUnBanTime) || now.After(fn.LocalUnBanTime) { delete(nbs.localBannedNodes, nodeKeyID) return false } return true } // Searching for global ban. // Here we don't estimating global ban expiration. If ban time doesn't equal zero - we assuming // that node is still banned (even if `unban` time has already passed) for _, fn := range nbs.honorNodes { if bytes.Equal(fn.PublicKey, node.PublicKey) { if !fn.UnbanTime.Equal(time.Unix(0, 0)) { return true } else { break } } } return false } func (nbs *NodesBanService) refreshNodes() { nbs.m.Lock() nbs.honorNodes = syspar.GetNodes() nbs.m.Unlock() } func (nbs *NodesBanService) localBan(node syspar.HonorNode) { nbs.m.Lock() defer nbs.m.Unlock() ts := time.Now().Unix() te := time.Now().Add(syspar.GetLocalNodeBanTime()).Unix() if te == ts { te = ts + 120 } nbs.localBannedNodes[crypto.Address(node.PublicKey)] = localBannedNode{ HonorNode: &node, LocalUnBanTime: time.Unix(te, 0), //LocalUnBanTime: time.Now().Add(syspar.GetLocalNodeBanTime()), } } func (nbs *NodesBanService) newBadBlock(producer syspar.HonorNode, blockId, blockTime int64, reason string) error { var currentNode syspar.HonorNode nbs.m.Lock() for _, fn := range nbs.honorNodes { if bytes.Equal(fn.PublicKey, syspar.GetNodePubKey()) { currentNode = fn break } } nbs.m.Unlock() if len(currentNode.PublicKey) == 0 { return errors.New("cant find current node in honor nodes list") } vm := script.GetVM() contract := smart.VMGetContract(vm, "NewBadBlock", 1) info := contract.Info() sc := types.SmartTransaction{ Header: &types.Header{ ID: int(info.ID), EcosystemID: 1, Time: time.Now().Unix(), KeyID: conf.Config.KeyID, }, Params: map[string]any{ "ProducerNodeID": crypto.Address(producer.PublicKey), "ConsumerNodeID": crypto.Address(currentNode.PublicKey), "BlockID": blockId, "Timestamp": blockTime, "Reason": reason, }, } stp := &transaction.SmartTransactionParser{ SmartContract: &smart.SmartContract{TxSmart: new(types.SmartTransaction)}, } txData, err := stp.BinMarshalWithPrivate(&sc, syspar.GetNodePrivKey(), true) if err != nil { return err } return transaction.CreateTransaction(txData, stp.Hash, conf.Config.KeyID, stp.Timestamp) } func (nbs *NodesBanService) FilterHosts(hosts []string) ([]string, []string, error) { var goodHosts, banHosts []string for _, h := range hosts { n, err := syspar.GetNodeByHost(h) if err != nil { log.WithFields(log.Fields{"error": err, "host": h}).Error("getting node by host") return nil, nil, err } if nbs.IsBanned(n) { banHosts = append(banHosts, n.TCPAddress) } else { goodHosts = append(goodHosts, n.TCPAddress) } } return goodHosts, banHosts, nil } func (nbs *NodesBanService) FilterBannedHosts(hosts []string) (goodHosts []string, err error) { goodHosts, _, err = nbs.FilterHosts(hosts) return } ================================================ FILE: packages/service/node/node_paused.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package node import ( "sync" ) const ( NoPause PauseType = 0 PauseTypeUpdatingBlockchain PauseType = 1 + iota PauseTypeStopingNetwork ) // np contains the reason why a node should not generating blocks var np = &NodePaused{PauseType: NoPause} type PauseType int type NodePaused struct { mutex sync.RWMutex PauseType PauseType } func (p PauseType) String() string { switch p { case NoPause: return "node server status is running" case PauseTypeUpdatingBlockchain: return "node server is updating" case PauseTypeStopingNetwork: return "node server is stopped" } return "node server is unknown" } func (np *NodePaused) Set(pt PauseType) { np.mutex.Lock() defer np.mutex.Unlock() np.PauseType = pt } func (np *NodePaused) Unset() { np.mutex.Lock() defer np.mutex.Unlock() np.PauseType = NoPause } func (np *NodePaused) Get() PauseType { np.mutex.RLock() defer np.mutex.RUnlock() return np.PauseType } func (np *NodePaused) IsSet() bool { np.mutex.RLock() defer np.mutex.RUnlock() return np.PauseType != NoPause } func IsNodePaused() bool { return np.IsSet() } func PauseNodeActivity(pt PauseType) { np.Set(pt) } func NodePauseType() PauseType { return np.Get() } ================================================ FILE: packages/service/node/node_relevance.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package node import ( "context" "time" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/network/tcpclient" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) var updatingEndWhilePaused = make(chan struct{}) type NodeRelevanceService struct { availableBlockchainGap int64 checkingInterval time.Duration } func NewNodeRelevanceService() *NodeRelevanceService { var availableBlockchainGap int64 = consts.AvailableBCGap if syspar.GetRbBlocks1() > consts.AvailableBCGap { availableBlockchainGap = syspar.GetRbBlocks1() - consts.AvailableBCGap } checkingInterval := syspar.GetMaxBlockTimeDuration() * time.Duration(syspar.GetRbBlocks1()-consts.DefaultNodesConnectDelay) return &NodeRelevanceService{ availableBlockchainGap: availableBlockchainGap, checkingInterval: checkingInterval, } } // Run is starting node monitoring func (n *NodeRelevanceService) Run(ctx context.Context) { go func() { log.Info("Node relevance monitoring started") for { relevance, err := n.checkNodeRelevance(ctx) if err != nil { log.WithFields(log.Fields{"type": consts.BCRelevanceError, "err": err}).Error("checking blockchain relevance") return } if !relevance && !IsNodePaused() { log.Info("Node Relevance Service is pausing node activity") n.pauseNodeActivity() } if relevance && IsNodePaused() { log.Info("Node Relevance Service is resuming node activity") n.resumeNodeActivity() } select { case <-time.After(n.checkingInterval): case <-updatingEndWhilePaused: } } }() } func NodeDoneUpdatingBlockchain() { if IsNodePaused() { updatingEndWhilePaused <- struct{}{} } } func (n *NodeRelevanceService) checkNodeRelevance(ctx context.Context) (relevant bool, err error) { curBlock := &sqldb.InfoBlock{} _, err = curBlock.Get() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "err": err}).Error("retrieving info block from db") return false, errors.Wrapf(err, "retrieving info block from db") } var ( tx = &sqldb.Transaction{} r bool ) r, err = tx.GetStopNetwork() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "err": err}).Info("retrieving transaction from db") return false, nil } if r { return false, nil } var ( remoteHosts []string ) if syspar.IsHonorNodeMode() { remoteHosts, err = GetNodesBanService().FilterBannedHosts(syspar.GetRemoteHosts()) } else { candidateNodes, err := sqldb.GetCandidateNode(syspar.SysInt(syspar.NumberNodes)) if err == nil && len(candidateNodes) > 0 { for _, node := range candidateNodes { remoteHosts = append(remoteHosts, node.TcpAddress) } } } if err != nil { return false, err } // Node is single in blockchain network and it can't be irrelevant if len(remoteHosts) == 0 { return true, nil } _, maxBlockID, err := tcpclient.HostWithMaxBlock(ctx, remoteHosts) if err != nil { if err == tcpclient.ErrNodesUnavailable { return false, nil } return false, errors.Wrapf(err, "choosing best host") } // Node can't connect to others if maxBlockID == -1 { log.WithFields(log.Fields{"hosts": remoteHosts}).Info("can't connect to others, stopping node relevance") return false, nil } // Node blockchain is stale if curBlock.BlockID+n.availableBlockchainGap < maxBlockID { log.WithFields(log.Fields{"maxBlockID": maxBlockID, "curBlockID": curBlock.BlockID, "Gap": n.availableBlockchainGap}).Info("blockchain is stale, stopping node relevance") return false, nil } return true, nil } func (n *NodeRelevanceService) pauseNodeActivity() { np.Set(PauseTypeUpdatingBlockchain) } func (n *NodeRelevanceService) resumeNodeActivity() { np.Unset() } ================================================ FILE: packages/service/protos/build.sh ================================================ #!/bin/bash # install protoc 3.7.1 # export GO111MODULES=on # go install google.golang.org/protobuf/cmd/protoc-gen-go@latest # go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest protoc -I ./ ./*.proto \ -I ./googleapis \ --go_out ./gengo/ --go_opt paths=source_relative \ --go-grpc_out ./gengo/ --go-grpc_opt paths=source_relative \ --grpc-gateway_out ./gengo/ --grpc-gateway_opt paths=source_relative ================================================ FILE: packages/service/protos/googleapis/google/api/annotations.proto ================================================ // Copyright 2015 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; import "google/api/http.proto"; import "google/protobuf/descriptor.proto"; option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; option java_multiple_files = true; option java_outer_classname = "AnnotationsProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; extend google.protobuf.MethodOptions { // See `HttpRule`. HttpRule http = 72295728; } ================================================ FILE: packages/service/protos/googleapis/google/api/http.proto ================================================ // Copyright 2015 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; option cc_enable_arenas = true; option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; option java_multiple_files = true; option java_outer_classname = "HttpProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; // Defines the HTTP configuration for an API service. It contains a list of // [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method // to one or more HTTP REST API methods. message Http { // A list of HTTP configuration rules that apply to individual API methods. // // **NOTE:** All service configuration rules follow "last one wins" order. repeated HttpRule rules = 1; // When set to true, URL path parameters will be fully URI-decoded except in // cases of single segment matches in reserved expansion, where "%2F" will be // left encoded. // // The default behavior is to not decode RFC 6570 reserved characters in multi // segment matches. bool fully_decode_reserved_expansion = 2; } // # gRPC Transcoding // // gRPC Transcoding is a feature for mapping between a gRPC method and one or // more HTTP REST endpoints. It allows developers to build a single API service // that supports both gRPC APIs and REST APIs. Many systems, including [Google // APIs](https://github.com/googleapis/googleapis), // [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC // Gateway](https://github.com/grpc-ecosystem/grpc-gateway), // and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature // and use it for large scale production services. // // `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies // how different portions of the gRPC request message are mapped to the URL // path, URL query parameters, and HTTP request body. It also controls how the // gRPC response message is mapped to the HTTP response body. `HttpRule` is // typically specified as an `google.api.http` annotation on the gRPC method. // // Each mapping specifies a URL path template and an HTTP method. The path // template may refer to one or more fields in the gRPC request message, as long // as each field is a non-repeated field with a primitive (non-message) type. // The path template controls how fields of the request message are mapped to // the URL path. // // Example: // // service Messaging { // rpc GetMessage(GetMessageRequest) returns (Message) { // option (google.api.http) = { // get: "/v1/{name=messages/*}" // }; // } // } // message GetMessageRequest { // string name = 1; // Mapped to URL path. // } // message Message { // string text = 1; // The resource content. // } // // This enables an HTTP REST to gRPC mapping as below: // // HTTP | gRPC // -----|----- // `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` // // Any fields in the request message which are not bound by the path template // automatically become HTTP query parameters if there is no HTTP request body. // For example: // // service Messaging { // rpc GetMessage(GetMessageRequest) returns (Message) { // option (google.api.http) = { // get:"/v1/messages/{message_id}" // }; // } // } // message GetMessageRequest { // message SubMessage { // string subfield = 1; // } // string message_id = 1; // Mapped to URL path. // int64 revision = 2; // Mapped to URL query parameter `revision`. // SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. // } // // This enables a HTTP JSON to RPC mapping as below: // // HTTP | gRPC // -----|----- // `GET /v1/messages/123456?revision=2&sub.subfield=foo` | // `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: // "foo"))` // // Note that fields which are mapped to URL query parameters must have a // primitive type or a repeated primitive type or a non-repeated message type. // In the case of a repeated type, the parameter can be repeated in the URL // as `...?param=A¶m=B`. In the case of a message type, each field of the // message is mapped to a separate parameter, such as // `...?foo.a=A&foo.b=B&foo.c=C`. // // For HTTP methods that allow a request body, the `body` field // specifies the mapping. Consider a REST update method on the // message resource collection: // // service Messaging { // rpc UpdateMessage(UpdateMessageRequest) returns (Message) { // option (google.api.http) = { // patch: "/v1/messages/{message_id}" // body: "message" // }; // } // } // message UpdateMessageRequest { // string message_id = 1; // mapped to the URL // Message message = 2; // mapped to the body // } // // The following HTTP JSON to RPC mapping is enabled, where the // representation of the JSON in the request body is determined by // protos JSON encoding: // // HTTP | gRPC // -----|----- // `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: // "123456" message { text: "Hi!" })` // // The special name `*` can be used in the body mapping to define that // every field not bound by the path template should be mapped to the // request body. This enables the following alternative definition of // the update method: // // service Messaging { // rpc UpdateMessage(Message) returns (Message) { // option (google.api.http) = { // patch: "/v1/messages/{message_id}" // body: "*" // }; // } // } // message Message { // string message_id = 1; // string text = 2; // } // // // The following HTTP JSON to RPC mapping is enabled: // // HTTP | gRPC // -----|----- // `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: // "123456" text: "Hi!")` // // Note that when using `*` in the body mapping, it is not possible to // have HTTP parameters, as all fields not bound by the path end in // the body. This makes this option more rarely used in practice when // defining REST APIs. The common usage of `*` is in custom methods // which don't use the URL at all for transferring data. // // It is possible to define multiple HTTP methods for one RPC by using // the `additional_bindings` option. Example: // // service Messaging { // rpc GetMessage(GetMessageRequest) returns (Message) { // option (google.api.http) = { // get: "/v1/messages/{message_id}" // additional_bindings { // get: "/v1/users/{user_id}/messages/{message_id}" // } // }; // } // } // message GetMessageRequest { // string message_id = 1; // string user_id = 2; // } // // This enables the following two alternative HTTP JSON to RPC mappings: // // HTTP | gRPC // -----|----- // `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` // `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: // "123456")` // // ## Rules for HTTP mapping // // 1. Leaf request fields (recursive expansion nested messages in the request // message) are classified into three categories: // - Fields referred by the path template. They are passed via the URL path. // - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP // request body. // - All other fields are passed via the URL query parameters, and the // parameter name is the field path in the request message. A repeated // field can be represented as multiple query parameters under the same // name. // 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields // are passed via URL path and HTTP request body. // 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all // fields are passed via URL path and URL query parameters. // // ### Path template syntax // // Template = "/" Segments [ Verb ] ; // Segments = Segment { "/" Segment } ; // Segment = "*" | "**" | LITERAL | Variable ; // Variable = "{" FieldPath [ "=" Segments ] "}" ; // FieldPath = IDENT { "." IDENT } ; // Verb = ":" LITERAL ; // // The syntax `*` matches a single URL path segment. The syntax `**` matches // zero or more URL path segments, which must be the last part of the URL path // except the `Verb`. // // The syntax `Variable` matches part of the URL path as specified by its // template. A variable template must not contain other variables. If a variable // matches a single path segment, its template may be omitted, e.g. `{var}` // is equivalent to `{var=*}`. // // The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` // contains any reserved character, such characters should be percent-encoded // before the matching. // // If a variable contains exactly one path segment, such as `"{var}"` or // `"{var=*}"`, when such a variable is expanded into a URL path on the client // side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The // server side does the reverse decoding. Such variables show up in the // [Discovery // Document](https://developers.google.com/discovery/v1/reference/apis) as // `{var}`. // // If a variable contains multiple path segments, such as `"{var=foo/*}"` // or `"{var=**}"`, when such a variable is expanded into a URL path on the // client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. // The server side does the reverse decoding, except "%2F" and "%2f" are left // unchanged. Such variables show up in the // [Discovery // Document](https://developers.google.com/discovery/v1/reference/apis) as // `{+var}`. // // ## Using gRPC API Service Configuration // // gRPC API Service Configuration (service config) is a configuration language // for configuring a gRPC service to become a user-facing product. The // service config is simply the YAML representation of the `google.api.Service` // proto message. // // As an alternative to annotating your proto file, you can configure gRPC // transcoding in your service config YAML files. You do this by specifying a // `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same // effect as the proto annotation. This can be particularly useful if you // have a proto that is reused in multiple services. Note that any transcoding // specified in the service config will override any matching transcoding // configuration in the proto. // // Example: // // http: // rules: // # Selects a gRPC method and applies HttpRule to it. // - selector: example.v1.Messaging.GetMessage // get: /v1/messages/{message_id}/{sub.subfield} // // ## Special notes // // When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the // proto to JSON conversion must follow the [proto3 // specification](https://developers.google.com/protocol-buffers/docs/proto3#json). // // While the single segment variable follows the semantics of // [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String // Expansion, the multi segment variable **does not** follow RFC 6570 Section // 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion // does not expand special characters like `?` and `#`, which would lead // to invalid URLs. As the result, gRPC Transcoding uses a custom encoding // for multi segment variables. // // The path variables **must not** refer to any repeated or mapped field, // because client libraries are not capable of handling such variable expansion. // // The path variables **must not** capture the leading "/" character. The reason // is that the most common use case "{var}" does not capture the leading "/" // character. For consistency, all path variables must share the same behavior. // // Repeated message fields must not be mapped to URL query parameters, because // no client library can support such complicated mapping. // // If an API needs to use a JSON array for request or response body, it can map // the request or response body to a repeated field. However, some gRPC // Transcoding implementations may not support this feature. message HttpRule { // Selects a method to which this rule applies. // // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. string selector = 1; // Determines the URL pattern is matched by this rules. This pattern can be // used with any of the {get|put|post|delete|patch} methods. A custom method // can be defined using the 'custom' field. oneof pattern { // Maps to HTTP GET. Used for listing and getting information about // resources. string get = 2; // Maps to HTTP PUT. Used for replacing a resource. string put = 3; // Maps to HTTP POST. Used for creating a resource or performing an action. string post = 4; // Maps to HTTP DELETE. Used for deleting a resource. string delete = 5; // Maps to HTTP PATCH. Used for updating a resource. string patch = 6; // The custom pattern is used for specifying an HTTP method that is not // included in the `pattern` field, such as HEAD, or "*" to leave the // HTTP method unspecified for this rule. The wild-card rule is useful // for services that provide content to Web (HTML) clients. CustomHttpPattern custom = 8; } // The name of the request field whose value is mapped to the HTTP request // body, or `*` for mapping all request fields not captured by the path // pattern to the HTTP body, or omitted for not having any HTTP request body. // // NOTE: the referred field must be present at the top-level of the request // message type. string body = 7; // Optional. The name of the response field whose value is mapped to the HTTP // response body. When omitted, the entire response message will be used // as the HTTP response body. // // NOTE: The referred field must be present at the top-level of the response // message type. string response_body = 12; // Additional HTTP bindings for the selector. Nested bindings must // not contain an `additional_bindings` field themselves (that is, // the nesting may only be one level deep). repeated HttpRule additional_bindings = 11; } // A custom pattern is used for defining custom HTTP verb. message CustomHttpPattern { // The name of this custom HTTP verb. string kind = 1; // The path matched by this custom verb. string path = 2; } ================================================ FILE: packages/smart/builtin_excel.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "bytes" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" xl "github.com/360EntSecGroup-Skylar/excelize" log "github.com/sirupsen/logrus" ) // GetDataFromXLSX returns json by parameters range func GetDataFromXLSX(sc *SmartContract, binaryID, startLine, linesCount, sheetNum int64) (data []any, err error) { book, err := excelBookFromStoredBinary(sc, binaryID) if err != nil || book == nil { return nil, err } sheetName := book.GetSheetName(int(sheetNum)) rows := book.GetRows(sheetName) endLine := startLine + linesCount if endLine > int64(len(rows)) { endLine = int64(len(rows)) } processedRows := []any{} for ; startLine < endLine; startLine++ { var row []any for _, item := range rows[startLine] { row = append(row, item) } processedRows = append(processedRows, row) } return processedRows, nil } // GetRowsCountXLSX returns count of rows from excel file func GetRowsCountXLSX(sc *SmartContract, binaryID, sheetNum int64) (int64, error) { book, err := excelBookFromStoredBinary(sc, binaryID) if err != nil { return -1, err } sheetName := book.GetSheetName(int(sheetNum)) rows := book.GetRows(sheetName) return int64(len(rows)), nil } func excelBookFromStoredBinary(sc *SmartContract, binaryID int64) (*xl.File, error) { bin := &sqldb.Binary{} bin.SetTablePrefix(converter.Int64ToStr(sc.TxSmart.EcosystemID)) found, err := bin.GetByID(binaryID) if err != nil { return nil, err } if !found { log.WithFields(log.Fields{"binary_id": binaryID}).Error("binary_id not found") return nil, nil } return xl.OpenReader(bytes.NewReader(bin.Data)) } ================================================ FILE: packages/smart/contract.go ================================================ package smart import ( "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) // Contract contains the information about the contract. type Contract struct { Name string Called uint32 FreeRequest bool TxGovAccount int64 // state wallet Rate float64 // money rate TableAccounts string StackCont []any // Stack of called contracts Extend map[string]any Block *script.CodeBlock } func (c *Contract) Info() *script.ContractInfo { return c.Block.GetContractInfo() } // LoadContracts reads and compiles contracts from smart_contracts tables func LoadContracts() error { contract := &sqldb.Contract{} count, err := contract.Count(nil) if err != nil { return logErrorDB(err, "getting count of contracts") } defer script.GetVM().FlushExtern() var offset int listCount := consts.ContractList for ; int64(offset) < count; offset += listCount { list, err := contract.GetList(offset, listCount) if err != nil { return logErrorDB(err, "getting list of contracts") } if err = loadContractList(list); err != nil { return err } } return nil } // LoadContract reads and compiles contract of new state func LoadContract(transaction *sqldb.DbTransaction, ecosystem int64) (err error) { contract := &sqldb.Contract{} defer script.GetVM().FlushExtern() list, err := contract.GetFromEcosystem(transaction, ecosystem) if err != nil { return logErrorDB(err, "selecting all contracts from ecosystem") } if err = loadContractList(list); err != nil { return err } return } func VMGetContract(vm *script.VM, name string, state uint32) *Contract { if len(name) == 0 { return nil } name = script.StateName(state, name) obj, ok := vm.Objects[name] if ok && obj.Type == script.ObjectType_Contract { return &Contract{Name: name, Block: obj.GetCodeBlock()} } return nil } func VMGetContractByID(vm *script.VM, id int32) *Contract { var tableID int64 if id > consts.ShiftContractID { tableID = int64(id - consts.ShiftContractID) id = int32(tableID + vm.ShiftContract) } idcont := id if len(vm.Children) <= int(idcont) { return nil } if vm.Children[idcont] == nil || vm.Children[idcont].Type != script.ObjectType_Contract { return nil } if tableID > 0 && vm.Children[idcont].GetContractInfo().Owner.TableID != tableID { return nil } return &Contract{Name: vm.Children[idcont].GetContractInfo().Name, Block: vm.Children[idcont]} } // GetContract returns true if the contract exists in smartVM func GetContract(name string, state uint32) *Contract { return VMGetContract(script.GetVM(), name, state) } // GetUsedContracts returns the list of contracts which are called from the specified contract func GetUsedContracts(name string, state uint32, full bool) []string { return vmGetUsedContracts(script.GetVM(), name, state, full) } // GetContractByID returns true if the contract exists func GetContractByID(id int32) *Contract { return VMGetContractByID(script.GetVM(), id) } // GetFunc returns the block of the specified function in the contract func (contract *Contract) GetFunc(name string) *script.CodeBlock { if block, ok := (*contract).Block.Objects[name]; ok && block.Type == script.ObjectType_Func { return block.GetCodeBlock() } return nil } func loadContractList(list []sqldb.Contract) error { if script.GetVM().ShiftContract == 0 { script.LoadSysFuncs(script.GetVM(), 1) script.GetVM().ShiftContract = int64(len(script.GetVM().Children) - 1) } for _, item := range list { clist, err := script.ContractsList(item.Value) if err != nil { return err } owner := script.OwnerInfo{ StateID: uint32(item.EcosystemID), Active: false, TableID: item.ID, WalletID: item.WalletID, TokenID: item.TokenID, } if err = script.GetVM().Compile([]rune(item.Value), &owner); err != nil { logErrorValue(err, consts.EvalError, "Load Contract", strings.Join(clist, `,`)) } } return nil } func vmGetUsedContracts(vm *script.VM, name string, state uint32, full bool) []string { contract := VMGetContract(vm, name, state) if contract == nil || contract.Info().Used == nil { return nil } ret := make([]string, 0) used := make(map[string]bool) for key := range contract.Info().Used { ret = append(ret, key) used[key] = true if full { sub := vmGetUsedContracts(vm, key, state, full) for _, item := range sub { if _, ok := used[item]; !ok { ret = append(ret, item) used[item] = true } } } } return ret } ================================================ FILE: packages/smart/datetime.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "time" "github.com/pkg/errors" ) const ( dateTimeFormat = "2006-01-02 15:04:05" ) // Date formats timestamp to specified date format func Date(timeFormat string, timestamp int64) string { t := time.Unix(timestamp, 0) return t.Format(timeFormat) } func BlockTime(sc *SmartContract) string { var blockTime int64 if sc.BlockHeader != nil { blockTime = sc.BlockHeader.Timestamp } if sc.CLB { blockTime = time.Now().Unix() } return Date(dateTimeFormat, blockTime) } func DateTime(unix int64) string { return Date(dateTimeFormat, unix) } func DateTimeLocation(unix int64, locationName string) (string, error) { loc, err := time.LoadLocation(locationName) if err != nil { return "", errors.Wrap(err, "Load location") } return time.Unix(unix, 0).In(loc).Format(dateTimeFormat), nil } func AddDate(unix, years, months, days int64) int64 { return time.Unix(unix, 0).AddDate(int(years), int(months), int(days)).Unix() } func UnixDateTime(value string) int64 { t, err := time.Parse(dateTimeFormat, value) if err != nil { return 0 } return t.Unix() } func UnixDateTimeLocation(value, locationName string) (int64, error) { loc, err := time.LoadLocation(locationName) if err != nil { return 0, errors.Wrap(err, "Load location") } t, err := time.ParseInLocation(dateTimeFormat, value, loc) if err != nil { return 0, errors.Wrap(err, "Parse time") } return t.Unix(), nil } ================================================ FILE: packages/smart/errors.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "errors" "fmt" ) const ( eContractLoop = `there is loop in %s contract` eContractExist = `contract %s already exists` eLatin = `Name %s must only contain latin, digit and '_', '-' characters` eAccessContract = `%s can only be called with condition: %s` eColumnExist = `column %s exists` eColumnNotExist = `column %s doesn't exist` eColumnType = `Type '%s' of columns is not supported` eNotCustomTable = `%s is not a custom table` eEmptyCond = `%v condition is empty` eIncorrectSignature = `incorrect signature %s` eItemNotFound = `item %d has not been found` eManyColumns = `Too many columns. Limit is %d` eNotCondition = `There is not %s in parameters` eParamNotFound = `Parameter %s has not been found` eRecordNotFound = `Record %s has not been found` eTableExists = `table %s exists` eTableNotFound = `table %s has not been found` eTypeJSON = `Type %T doesn't support json marshalling` eUnknownContract = `Unknown contract %s` eUnsupportedType = "Unsupported type %T" eWrongRandom = `wrong random parameters min: %v, max: %v` eGreaterThan = `%s must be greater than 0` eTableNotEmpty = `Table %s is not empty` eColumnNotDeleted = `Column %s cannot be deleted` eRollbackContract = `Wrong rollback of the latest contract %d != %d` eExternalNet = `External network %s is not defined` eKeyNotFound = `sender %s has not been found` eEcoKeyNotFound = `sender %s has not been found in ecosystem %d` eEcoKeyDisable = `%s disable in ecosystem %d` eEcoFuelRate = `fuel rate must be greater than 0 or empty in ecosystem %d` eEcoCurrentBalance = `account %s current balance is not enough in ecosystem %d` eEcoCurrentBalanceDiff = eEcoCurrentBalance + `, at least [%s] difference` ) var ( errDelayedContract = errors.New(`incorrect delayed contract`) errAccessDenied = errors.New(`access denied`) errConditionEmpty = errors.New(`conditions is empty`) errContractNotFound = errors.New(`contract has not been found`) errTaxes = errors.New("not enough money to pay the taxes fee") errEmptyColumn = errors.New(`column name is empty`) errWrongColumn = errors.New(`column name cannot begin with digit`) errNotFound = errors.New(`record has not been found`) errContractChange = errors.New(`contract cannot be removed or inserted`) errDeletedKey = errors.New(`the key is deleted`) errDiffKeys = errors.New(`contract and user public keys are different`) errEmpty = errors.New(`empty value and condition`) errEmptyCond = errors.New(`the condition is empty`) errEmptyContract = errors.New(`empty contract name in ContractConditions`) errEmptyPublicKey = errors.New(`empty public key`) errFounderAccount = errors.New(`unknown founder account`) errKeyIDAccount = errors.New(`unknown address account`) errFuelRate = errors.New(`fuel rate must be greater than 0`) errIncorrectSign = errors.New(`incorrect sign`) errIncorrectType = errors.New(`incorrect type`) errInvalidValue = errors.New(`invalid value`) errNameChange = errors.New(`contracts or functions names cannot be changed`) errNegPrice = errors.New(`price value is negative`) errOneContract = errors.New(`only one contract must be in the record`) errPermEmpty = errors.New(`permissions are empty`) errRecursion = errors.New("recursion detected") errSameColumns = errors.New(`there are the same columns`) errTableName = errors.New(`the name of the table cannot begin with @`) errTableEmptyName = errors.New(`the table name cannot be empty`) errUndefColumns = errors.New(`columns are undefined`) errUpdNotExistRecord = errors.New(`update for not existing record`) errWrongSignature = errors.New(`wrong signature`) errParseTransaction = errors.New(`parse transaction`) errWhereUpdate = errors.New(`there is not Where in Update request`) errNotValidUTF = errors.New(`result is not valid utf-8 string`) errFloat = errors.New(`incorrect float value`) errFloatResult = errors.New(`incorrect float result`) errMaxPrice = fmt.Errorf(`price value is more than %d`, MaxPrice) ) ================================================ FILE: packages/smart/funcs.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "bytes" "database/sql" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/url" "reflect" "sort" "strconv" "strings" "time" "unicode/utf8" "github.com/IBAX-io/go-ibax/packages/clbmanager" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/common/crypto/base58" "github.com/IBAX-io/go-ibax/packages/common/crypto/hashalgo" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/scheduler" "github.com/IBAX-io/go-ibax/packages/scheduler/contract" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" "github.com/IBAX-io/go-ibax/packages/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" "golang.org/x/crypto/sha3" ) const ( nodeBanNotificationHeader = "Your node was banned" historyLimit = 250 contractTxType = 128 ) var ( ErrNotImplementedOnCLB = errors.New("Contract not implemented on CLB") ) var BOM = []byte{0xEF, 0xBB, 0xBF} type permTable struct { Insert string `json:"insert"` Update string `json:"update"` NewColumn string `json:"new_column"` Read string `json:"read,omitempty"` Filter string `json:"filter,omitempty"` } type permColumn struct { Update string `json:"update"` Read string `json:"read,omitempty"` } type TxInfo struct { BlockId int64 `json:"block_id"` BlockHash string `json:"block_hash"` Address string `json:"address"` Ecosystem int64 `json:"ecosystem"` Hash string `json:"hash"` Expedite string `json:"expedite"` ContractName string `json:"contract_name"` Params map[string]any `json:"params"` CreatedAt int64 `json:"created_at"` Size string `json:"size"` Status int64 `json:"status"` //0:success 1:penalty } type TableInfo struct { Columns map[string]string Table *sqldb.Table } type FlushInfo struct { ID uint32 // id Prev *script.CodeBlock // previous item, nil if the new item has been appended Info *script.ObjInfo Name string // the name } func (finfo *FlushInfo) FlushVM() { if finfo.Prev == nil { if finfo.ID != uint32(len(script.GetVM().Children)-1) { //logger.WithFields(log.Fields{"type": consts.ContractError, "value": finfo.ID, "len": len(GetVM().Children) - 1}).Error("flush rollback") } else { script.GetVM().Children = script.GetVM().Children[:len(script.GetVM().Children)-1] delete(script.GetVM().Objects, finfo.Name) } } else { script.GetVM().Children[finfo.ID] = finfo.Prev script.GetVM().Objects[finfo.Name] = finfo.Info } } // NotifyInfo is used for sending delayed notifications type NotifyInfo struct { Roles bool // if true then UpdateRolesNotifications, otherwise UpdateNotifications EcosystemID int64 List []string } var ( funcCallsDBP = map[string]struct{}{ "DBInsert": {}, "DBUpdate": {}, "DBUpdatePlatformParam": {}, "DBUpdateExt": {}, "DBSelect": {}, } writeFuncs = map[string]struct{}{ "CreateColumn": {}, "CreateTable": {}, "DBInsert": {}, "DBUpdate": {}, "DBUpdatePlatformParam": {}, "DBUpdateExt": {}, "CreateEcosystem": {}, "CreateContract": {}, "UpdateContract": {}, "CreateLanguage": {}, "EditLanguage": {}, "BindWallet": {}, "UnbindWallet": {}, "EditEcosysName": {}, "UpdateNodesBan": {}, "UpdateCron": {}, "CreateCLB": {}, "DeleteCLB": {}, "DelColumn": {}, "DelTable": {}, } // map for table name to parameter with conditions tableParamConditions = map[string]string{ "pages": "changing_page", "menu": "changing_menu", "contracts": "changing_contracts", "snippets": "changing_snippets", "languages": "changing_language", "tables": "changing_tables", "parameters": "changing_parameters", "app_params": "changing_app_params", } typeToPSQL = map[string]string{ `json`: `jsonb`, `varchar`: `varchar(102400)`, `character`: `character(1) NOT NULL DEFAULT '0'`, `number`: `bigint NOT NULL DEFAULT '0'`, `datetime`: `timestamp`, `double`: `double precision`, `money`: `decimal (30, 0) NOT NULL DEFAULT '0'`, `text`: `text`, `bytea`: `bytea NOT NULL DEFAULT '\x'`, } ) // EmbedFuncs is extending vm with embedded functions func EmbedFuncs(vt script.VMType) map[string]any { f := map[string]any{ "AddressToId": converter.AddressToID, "ColumnCondition": ColumnCondition, "Contains": strings.Contains, "ContractAccess": ContractAccess, "RoleAccess": RoleAccess, "ContractConditions": ContractConditions, "ContractName": contractName, "ValidateEditContractNewValue": ValidateEditContractNewValue, "CreateColumn": CreateColumn, "CreateTable": CreateTable, "DBInsert": DBInsert, "DBSelect": DBSelect, "DBUpdate": DBUpdate, "DBUpdatePlatformParam": UpdatePlatformParam, "DBUpdateExt": DBUpdateExt, "EcosysParam": EcosysParam, "AppParam": AppParam, "SysParamString": SysParamString, "SysParamInt": SysParamInt, "SysFuel": SysFuel, "Eval": Eval, "EvalCondition": EvalCondition, "Float": Float, "GetContractByName": GetContractByName, "GetContractById": GetContractById, "HMac": HMac, "Join": Join, "JSONDecode": JSONDecode, "JSONEncode": JSONEncode, "JSONEncodeIndent": JSONEncodeIndent, "IdToAddress": converter.IDToAddress, "Int": Int, "Len": Len, "Money": Money, "FormatMoney": FormatMoney, "PermColumn": PermColumn, "PermTable": PermTable, "Random": RandomInt, "RandomFloat": RandomFloat, "RandomDecimal": RandomDecimal, "Split": Split, "Str": Str, "Substr": Substr, "Replace": Replace, "Size": Size, "PubToID": PubToID, "SeedToID": crypto.AddressSeed, "HexToBytes": HexToBytes, "LangRes": LangRes, "HasPrefix": strings.HasPrefix, "HasSlice": HasSlice, "ValidateCondition": ValidateCondition, "TrimSpace": strings.TrimSpace, "ToLower": strings.ToLower, "ToUpper": strings.ToUpper, "CreateEcosystem": CreateEcosystem, "CreateContract": CreateContract, "UpdateContract": UpdateContract, "TableConditions": TableConditions, "CreateLanguage": CreateLanguage, "EditLanguage": EditLanguage, "BndWallet": BndWallet, "UnbndWallet": UnbndWallet, "RowConditions": RowConditions, "DecodeBase64": DecodeBase64, "EncodeBase64": EncodeBase64, "Hash": Hash, "DoubleHash": crypto.DoubleHash, "EditEcosysName": EditEcosysName, "GetColumnType": GetColumnType, "GetType": GetType, "AllowChangeCondition": AllowChangeCondition, "StringToBytes": StringToBytes, "BytesToString": BytesToString, "GetMapKeys": GetMapKeys, "SortedKeys": SortedKeys, "Append": Append, "EthereumAddress": EthereumAddress, "BitcoinLegacyAddress": BitcoinLegacyAddress, // 1 address public key uncompressed "BitcoinBip32Address": BitcoinBip32Address, // 1 address public key compressed "BitcoinBip49Address": BitcoinBip49Address, // 3 address public key compressed,path: m/49'/0'/0'/0/0 "BitcoinBip84Address": BitcoinBip84Address, // bc1q address public key compressed,path: m/84'/0'/0'/0/0 "BitcoinBip86Address": BitcoinBip86Address, // bc1p address public key compressed,path: m/86'/0'/0'/0/0 "TronAddress": TronAddress, "TopAmounts": TopAmounts, "GetLogTxCount": GetLogTxCount, "GetHistory": GetHistory, "GetHistoryRow": GetHistoryRow, "GetDataFromXLSX": GetDataFromXLSX, "GetRowsCountXLSX": GetRowsCountXLSX, "BlockTime": BlockTime, "IsObject": IsObject, "DateTime": DateTime, "AddDate": AddDate, "UnixDateTime": UnixDateTime, "DateTimeLocation": DateTimeLocation, "UnixDateTimeLocation": UnixDateTimeLocation, "UpdateNotifications": UpdateNotifications, "UpdateRolesNotifications": UpdateRolesNotifications, "DelTable": DelTable, "DelColumn": DelColumn, "HexToPub": crypto.HexToPub, "PubToHex": PubToHex, "UpdateNodesBan": UpdateNodesBan, "Log": Log, "Log10": Log10, "Pow": Pow, "Sqrt": Sqrt, "Round": Round, "Floor": Floor, "CheckCondition": CheckCondition, "IsHonorNodeKey": IsHonorNodeKey, "CheckSign": CheckSign, "CheckNumberChars": CheckNumberChars, "DateFormat": Date, "RegexpMatch": RegexpMatch, "DBCount": DBCount, "MathMod": MathMod, "MathModDecimal": MathModDecimal, "CreateView": CreateView, "SqrtDecimal": SqrtDecimal, "Div": Div, "GreaterThan": GreaterThan, "GreaterThanOrEqual": GreaterThanOrEqual, "LessThan": LessThan, "LessThanOrEqual": LessThanOrEqual, } switch vt { case script.VMType_CLB: f["HTTPRequest"] = HTTPRequest f["Date"] = Date f["HTTPPostJSON"] = HTTPPostJSON f["ValidateCron"] = ValidateCron f["UpdateCron"] = UpdateCron case script.VMType_CLBMaster: f["HTTPRequest"] = HTTPRequest f["Date"] = Date f["HTTPPostJSON"] = HTTPPostJSON f["ValidateCron"] = ValidateCron f["UpdateCron"] = UpdateCron f["CreateCLB"] = CreateCLB f["DeleteCLB"] = DeleteCLB f["StartCLB"] = StartCLB f["StopCLBProcess"] = StopCLBProcess f["GetCLBList"] = GetCLBList case script.VMType_Smart: f["GetBlock"] = GetBlock } return f } func accessContracts(sc *SmartContract, names ...string) bool { contract := sc.TxContract.StackCont[len(sc.TxContract.StackCont)-1].(string) for _, item := range names { if contract == `@1`+item { return true } } return false } // CompileContract is compiling contract func CompileContract(sc *SmartContract, code string, state, id, token int64) (any, error) { if err := validateAccess(sc, "CompileContract"); err != nil { return nil, err } return sc.VM.CompileBlock([]rune(code), &script.OwnerInfo{StateID: uint32(state), WalletID: id, TokenID: token}) } // ContractAccess checks whether the name of the executable contract matches one of the names listed in the parameters. func ContractAccess(sc *SmartContract, names ...any) bool { if conf.Config.FuncBench { return true } for _, iname := range names { switch name := iname.(type) { case string: if len(name) > 0 { if name[0] != '@' { name = fmt.Sprintf(`@%d`, sc.TxSmart.EcosystemID) + name } for i := len(sc.TxContract.StackCont) - 1; i >= 0; i-- { contName := sc.TxContract.StackCont[i].(string) if strings.HasPrefix(contName, `@`) { if contName == name { return true } break } } } } } return false } // RoleAccess checks whether the name of the role matches one of the names listed in the parameters. func RoleAccess(sc *SmartContract, ids ...any) (bool, error) { rolesList, err := sqldb.GetMemberRoles(sc.DbTransaction, sc.TxSmart.EcosystemID, sc.Key.AccountID) if err != nil { return false, err } rolesIndex := make(map[int64]bool) for _, id := range rolesList { rolesIndex[id] = true } for _, id := range ids { switch v := id.(type) { case int64: if rolesIndex[v] { return true, nil } break } } return false, nil } // ContractConditions calls the 'conditions' function for each of the contracts specified in the parameters func ContractConditions(sc *SmartContract, names ...any) (bool, error) { for _, iname := range names { name := iname.(string) if len(name) > 0 { contractName := script.StateName(uint32(sc.TxSmart.EcosystemID), name) getContract := VMGetContract(sc.VM, name, uint32(sc.TxSmart.EcosystemID)) if getContract == nil { contractName = script.StateName(0, name) getContract = VMGetContract(sc.VM, name, 0) if getContract == nil { return false, logErrorfShort(eUnknownContract, name, consts.NotFound) } } block := getContract.GetFunc(`conditions`) if block == nil { return true, nil } vars := sc.getExtend() //if sc.TxContract == nil { // sc.TxContract = getContract // sc.TxContract.Extend = vars //} if err := sc.AppendStack(name); err != nil { return false, err } methods := []string{`conditions`} err := script.RunContractByName(sc.VM, contractName, methods, vars, sc.Hash) if err != nil { return false, err } sc.PopStack(name) } else { return false, logError(errEmptyContract, consts.EmptyObject, "ContractConditions") } } return true, nil } func contractName(value string) (name string, err error) { var list []string list, err = script.ContractsList(value) if err == nil && len(list) > 0 { name = list[0] } return } func ValidateEditContractNewValue(sc *SmartContract, newValue, oldValue string) error { list, err := script.ContractsList(newValue) if err != nil { return err } curlist, err := script.ContractsList(oldValue) if err != nil { return err } if len(list) != len(curlist) { return errContractChange } for i := 0; i < len(list); i++ { var ok bool for j := 0; j < len(curlist); j++ { if curlist[j] == list[i] { ok = true break } } if !ok { return errNameChange } } return nil } func UpdateContract(sc *SmartContract, id int64, value, conditions string, recipient int64, tokenID string) error { if err := validateAccess(sc, "UpdateContract"); err != nil { return err } pars := make(map[string]any) ecosystemID := sc.TxSmart.EcosystemID var root any if len(value) > 0 { var err error root, err = CompileContract(sc, value, ecosystemID, recipient, converter.StrToInt64(tokenID)) if err != nil { return err } pars["value"] = value } if len(conditions) > 0 { pars["conditions"] = conditions } if len(pars) > 0 { if !sc.CLB { if err := SysRollback(sc, SysRollData{Type: "EditContract", ID: id}); err != nil { return err } } if _, err := DBUpdate(sc, "@1contracts", id, types.LoadMap(pars)); err != nil { return err } } if len(value) > 0 { if err := FlushContract(sc, root, id); err != nil { return err } } return nil } func CreateContract(sc *SmartContract, name, value, conditions string, tokenEcosystem, appID int64) (int64, error) { if err := validateAccess(sc, "CreateContract"); err != nil { return 0, err } var id int64 var err error isExists := GetContractByName(sc, name) if isExists != 0 { log.WithFields(log.Fields{"type": consts.ContractError, "name": name, "tableId": isExists}).Error("create existing contract") return 0, fmt.Errorf(eContractExist, script.StateName(uint32(sc.TxSmart.EcosystemID), name)) } root, err := CompileContract(sc, value, sc.TxSmart.EcosystemID, 0, tokenEcosystem) if err != nil { return 0, err } _, id, err = DBInsert(sc, "@1contracts", types.LoadMap(map[string]any{ "name": name, "value": value, "conditions": conditions, "wallet_id": 0, "token_id": tokenEcosystem, "app_id": appID, "ecosystem": sc.TxSmart.EcosystemID, })) if err != nil { return 0, err } if err = FlushContract(sc, root, id); err != nil { return 0, err } if !sc.CLB { err = SysRollback(sc, SysRollData{Type: "NewContract", Data: value}) if err != nil { return 0, err } } return id, nil } func getColumns(columns string) (colsSQL string, colout []byte, err error) { var ( sqlColType string cols []any out []byte ) if err = unmarshalJSON([]byte(columns), &cols, "columns from json"); err != nil { return } colperm := make(map[string]string) colList := make(map[string]bool) for _, icol := range cols { var data map[string]any switch v := icol.(type) { case string: if err = unmarshalJSON([]byte(v), &data, `columns permissions from json`); err != nil { return } default: data = v.(map[string]any) } colname := converter.EscapeSQL(strings.ToLower(data[`name`].(string))) if err = checkColumnName(colname); err != nil { return } if colList[colname] { err = errSameColumns return } sqlColType, err = columnType(data["type"].(string)) if err != nil { return } colList[colname] = true colsSQL += `"` + colname + `" ` + sqlColType + " ,\n" condition := `` switch v := data[`conditions`].(type) { case string: condition = v case map[string]any: out, err = marshalJSON(v, `conditions to json`) if err != nil { return } condition = string(out) } colperm[colname] = condition } colout, err = marshalJSON(colperm, `columns to json`) return } // CreateView is creating smart contract view table func CreateView(sc *SmartContract, vname, columns, where string, applicationID int64) (err error) { if err = validateAccess(sc, "CreateView"); err != nil { return } var ( viewName, tables, wheres, colSQLs string colout, whsout []byte ) viewName = qb.GetTableName(sc.TxSmart.EcosystemID, vname) var check = sqldb.Namer{} if check.HasExists(sc.DbTransaction, viewName) { return fmt.Errorf(eTableExists, vname) } colSQLs, colout, err = parseViewColumnSql(sc, columns) if err != nil { return err } tables, wheres, whsout, err = parseViewWhereSql(sc, where) if err != nil { return err } if err = sqldb.CreateView(sc.DbTransaction, viewName, tables, wheres, colSQLs); err != nil { return logErrorDB(err, "creating view table") } prefix, name := PrefixName(viewName) _, _, err = sc.insert([]string{`name`, `columns`, `wheres`, `app_id`, `ecosystem`}, []any{name, string(colout), string(whsout), applicationID, prefix}, `1_views`) if err != nil { return logErrorDB(err, "insert table info") } if !sc.CLB { if err = syspar.SysTableColType(sc.DbTransaction); err != nil { return logErrorDB(err, "updating sys table col type") } if err = SysRollback(sc, SysRollData{Type: "NewView", TableName: viewName}); err != nil { return err } } return } type ViewColSch struct { Table string `json:"table,omitempty"` Col string `json:"col,omitempty"` Alias string `json:"alias,omitempty"` } func parseViewColumnSql(sc *SmartContract, columns string) (colsSQL string, colout []byte, err error) { var ( cols, outarr []ViewColSch colList = make(map[string]bool) has = make(map[string]bool) ) if err = unmarshalJSON([]byte(columns), &cols, "columns from json"); err != nil { return } if len(cols) == 0 { err = errors.New("columns is empty") return } for i, icol := range cols { var c ViewColSch tableName := converter.ParseTable(icol.Table, sc.TxSmart.EcosystemID) if !has[tableName] { if !sc.DbTransaction.HasTableOrView(tableName) { err = fmt.Errorf(eTableNotFound, tableName) return } has[tableName] = true } colname := converter.EscapeSQL(strings.ToLower(icol.Col)) if err = checkColumnName(colname); err != nil { return } if colList[colname] { err = fmt.Errorf("column %s specified more than once", colname) return } c.Col = colname alias := converter.EscapeSQL(strings.ToLower(icol.Alias)) if len(alias) > 0 { if err = checkColumnName(alias); err != nil { return } colname = colname + ` AS ` + alias } colList[colname] = true w := `"` + tableName + `".` + colname if len(cols)-1 != i { colsSQL += w + ",\n" } else { colsSQL += w } c.Table = tableName c.Alias = alias outarr = append(outarr, c) } colout, err = marshalJSON(outarr, `view columns to json`) return } type ViewWheSch struct { TableOne string `json:"table1,omitempty"` TableTwo string `json:"table2,omitempty"` ColOne string `json:"col1,omitempty"` ColTwo string `json:"col2,omitempty"` Compare string `json:"compare,omitempty"` } func parseViewWhereSql(sc *SmartContract, columns string) (tabsSQL, whsSQL string, whsout []byte, err error) { var ( cols, outarr []ViewWheSch tabList = make(map[string]bool) tableArr = make([]string, 0) has = make(map[string]bool) ) if err = unmarshalJSON([]byte(columns), &cols, "columns from json"); err != nil { return } if len(cols) == 0 { err = errors.New("columns is empty") return } removeRepeated := func(arr []string) (newArr []string) { newArr = make([]string, 0) sort.Strings(arr) for i := 0; i < len(arr); i++ { repeat := false for j := i + 1; j < len(arr); j++ { if arr[i] == arr[j] { repeat = true break } } if !repeat { newArr = append(newArr, arr[i]) } } return newArr } for i, icol := range cols { tableName1 := converter.ParseTable(icol.TableOne, sc.TxSmart.EcosystemID) if !has[tableName1] { if !sc.DbTransaction.HasTableOrView(tableName1) { err = fmt.Errorf(eTableNotFound, tableName1) return } has[tableName1] = true } tableArr = append(tableArr, tableName1) tableName2 := converter.ParseTable(icol.TableTwo, sc.TxSmart.EcosystemID) if !has[tableName2] { if !sc.DbTransaction.HasTableOrView(tableName2) { err = fmt.Errorf(eTableNotFound, tableName2) return } has[tableName2] = true } tableArr = append(tableArr, tableName2) colName1 := converter.EscapeSQL(strings.ToLower(icol.ColOne)) if err = checkColumnName(colName1); err != nil { return } colName2 := converter.EscapeSQL(strings.ToLower(icol.ColTwo)) if err = checkColumnName(colName2); err != nil { return } compare := converter.EscapeSQL(strings.ToLower(icol.Compare)) if len(compare) == 0 { err = errors.New("compare operator size is empty") return } w := `"` + tableName1 + `".` + colName1 + ` ` + compare + ` "` + tableName2 + `".` + colName2 if len(cols)-1 != i { whsSQL += w + " AND " } else { whsSQL += w } var c ViewWheSch c.TableOne = tableName1 c.TableTwo = tableName2 c.ColOne = colName1 c.ColTwo = colName2 c.Compare = compare outarr = append(outarr, c) } arr := removeRepeated(tableArr) for i, tableName := range arr { if !tabList[tableName] { t := `"` + tableName + `"` if len(arr)-1 != i { tabsSQL += t + "," } else { tabsSQL += t } tabList[tableName] = true } } whsout, err = marshalJSON(outarr, `view wheres to json`) return } // CreateTable is creating smart contract table func CreateTable(sc *SmartContract, name, columns, permissions string, applicationID int64) (err error) { if err := validateAccess(sc, "CreateTable"); err != nil { return err } if len(name) == 0 { return errTableEmptyName } if !converter.IsLatin(name) { return fmt.Errorf(eLatin, name) } if (name[0] >= '0' && name[0] <= '9') || name[0] == '@' { return errTableName } tableName := qb.GetTableName(sc.TxSmart.EcosystemID, name) if sc.DbTransaction.IsTable(tableName) { return fmt.Errorf(eTableExists, name) } colsSQL, colout, err := getColumns(columns) if err != nil { return err } if err = sqldb.CreateTable(sc.DbTransaction, tableName, strings.TrimRight(colsSQL, ",\n")); err != nil { return logErrorDB(err, "creating tables") } var perm permTable if err = unmarshalJSON([]byte(permissions), &perm, `permissions to json`); err != nil { return err } permout, err := marshalJSON(perm, `permissions to JSON`) if err != nil { return err } prefix, name := PrefixName(tableName) _, _, err = sc.insert([]string{`name`, `columns`, `permissions`, `conditions`, `app_id`, `ecosystem`}, []any{name, string(colout), string(permout), `ContractAccess("@1EditTable")`, applicationID, prefix}, `1_tables`) if err != nil { return logErrorDB(err, "insert table info") } if !sc.CLB { if err = syspar.SysTableColType(sc.DbTransaction); err != nil { return logErrorDB(err, "updating sys table col type") } if err = SysRollback(sc, SysRollData{Type: "NewTable", TableName: tableName}); err != nil { return err } } return nil } func columnType(colType string) (string, error) { if sqlColType, ok := typeToPSQL[colType]; ok { return sqlColType, nil } return ``, fmt.Errorf(eColumnType, colType) } func mapToParams(values *types.Map) (params []string, val []any, err error) { for _, key := range values.Keys() { v, _ := values.Get(key) params = append(params, converter.Sanitize(key, ` ->+`)) val = append(val, v) } if len(params) == 0 { err = fmt.Errorf(`values are undefined`) } return } // DBInsert inserts a record into the specified database table func DBInsert(sc *SmartContract, tblname string, values *types.Map) (qcost int64, ret int64, err error) { if tblname == "platform_parameters" { return 0, 0, fmt.Errorf("platform parameters access denied") } tblname = qb.GetTableName(sc.TxSmart.EcosystemID, tblname) if err = sc.AccessTable(tblname, "insert"); err != nil { return } var ind int var lastID string if ind, err = sc.DbTransaction.NumIndexes(tblname); err != nil { err = logErrorDB(err, "num indexes") return } params, val, err := mapToParams(values) if err != nil { return } if reflect.TypeOf(val[0]) == reflect.TypeOf([]any{}) { val = val[0].([]any) } qcost, lastID, err = sc.insert(params, val, tblname) if ind > 0 { qcost *= int64(ind) } if err == nil { ret, _ = strconv.ParseInt(lastID, 10, 64) } return } // PrepareColumns replaces jsonb fields -> in the list of columns for db selecting // For example, name,doc->title => name,doc::jsonb->>'title' as "doc.title" func PrepareColumns(columns []string) string { colList := make([]string, 0) for _, icol := range columns { if strings.Contains(icol, `->`) { colfield := strings.Split(icol, `->`) if len(colfield) == 2 { icol = fmt.Sprintf(`%s::jsonb->>'%s' as "%[1]s.%[2]s"`, colfield[0], colfield[1]) } else { icol = fmt.Sprintf(`%s::jsonb#>>'{%s}' as "%[1]s.%[3]s"`, colfield[0], strings.Join(colfield[1:], `,`), strings.Join(colfield[1:], `.`)) } } else if !strings.ContainsAny(icol, `:*>"`) { icol = `"` + icol + `"` } colList = append(colList, icol) } return strings.Join(colList, `,`) } // DBSelect returns an array of values of the specified columns when there is selection of data 'offset', 'limit', 'where' func DBSelect(sc *SmartContract, tblname string, inColumns any, id int64, inOrder any, offset, limit int64, inWhere *types.Map, query any, group string, all bool) (int64, []any, error) { var ( err error rows *sql.Rows perm map[string]string columns []string order string ) columns, err = qb.GetColumns(inColumns) if err != nil { return 0, nil, err } tblname = qb.GetTableName(sc.TxSmart.EcosystemID, tblname) where, err := qb.GetWhere(inWhere) if err != nil { return 0, nil, err } if id != 0 { where = fmt.Sprintf(`id='%d'`, id) limit = 1 } if limit == 0 { limit = 25 } if limit < 0 || limit > consts.DBFindLimit { limit = consts.DBFindLimit } perm, err = sc.AccessTablePerm(tblname, `read`) if err != nil { return 0, nil, err } if err = sc.AccessColumns(tblname, &columns, false); err != nil { return 0, nil, err } q := sqldb.GetDB(sc.DbTransaction).Table(tblname).Select(PrepareColumns(columns)).Where(where) //group + order => false //group + no order => true //no group + order => true //no group + no order => true order, err = qb.GetOrder(tblname, inOrder, true) if err != nil { return 0, nil, err } if len(group) != 0 { q = q.Group(group) if inOrder != nil { order, err = qb.GetOrder(tblname, inOrder, false) if err != nil { return 0, nil, err } } } q = q.Order(order) if query != "" { q = q.Select(query) } if all { rows, err = q.Rows() } else { rows, err = q.Offset(int(offset)).Limit(int(limit)).Rows() } if err != nil { return 0, nil, logErrorDB(err, fmt.Sprintf("selecting rows from table %s %s where %s order %s", tblname, PrepareColumns(columns), where, order)) } defer rows.Close() cols, err := rows.Columns() if err != nil { return 0, nil, logErrorDB(err, "getting rows columns") } values := make([][]byte, len(cols)) scanArgs := make([]any, len(values)) for i := range values { scanArgs[i] = &values[i] } result := make([]any, 0, 50) for rows.Next() { err = rows.Scan(scanArgs...) if err != nil { return 0, nil, logErrorDB(err, "scanning next row") } row := types.NewMap() for i, col := range values { var value string if col != nil { value = string(col) } row.Set(cols[i], value) } result = append(result, reflect.ValueOf(row).Interface()) } if perm != nil && len(perm[`filter`]) > 0 { fltResult, err := sc.VM.EvalIf(perm[`filter`], uint32(sc.TxSmart.EcosystemID), sc.getExtend(), ) if err != nil { return 0, nil, err } if !fltResult { log.WithFields(log.Fields{"filter": perm["filter"]}).Error("Access denied") return 0, nil, errAccessDenied } } return 0, result, nil } // DBUpdateExt updates the record in the specified table. You can specify 'where' query in params and then the values for this query func DBUpdateExt(sc *SmartContract, tblname string, where *types.Map, values *types.Map) (qcost int64, err error) { if tblname == "platform_parameters" { return 0, fmt.Errorf("platform parameters access denied") } tblname = qb.GetTableName(sc.TxSmart.EcosystemID, tblname) if err = sc.AccessTable(tblname, "update"); err != nil { return } columns, val, err := mapToParams(values) if err != nil { return } if err = sc.AccessColumns(tblname, &columns, true); err != nil { return } qcost, _, err = sc.updateWhere(columns, val, tblname, where) return } // DBUpdate updates the item with the specified id in the table func DBUpdate(sc *SmartContract, tblname string, id int64, values *types.Map) (qcost int64, err error) { return DBUpdateExt(sc, tblname, types.LoadMap(map[string]any{`id`: id}), values) } // EcosysParam returns the value of the specified parameter for the ecosystem func EcosysParam(sc *SmartContract, name string) string { sp := &sqldb.StateParameter{} sp.SetTablePrefix(converter.Int64ToStr(sc.TxSmart.EcosystemID)) _, err := sp.Get(nil, name) if err != nil { return logErrorDB(err, "getting ecosystem param").Error() } return sp.Value } // AppParam returns the value of the specified app parameter for the ecosystem func AppParam(sc *SmartContract, app int64, name string, ecosystem int64) (string, error) { ap := &sqldb.AppParam{} ap.SetTablePrefix(converter.Int64ToStr(ecosystem)) _, err := ap.Get(sc.DbTransaction, app, name) if err != nil { return ``, logErrorDB(err, "getting app param") } return ap.Value, nil } // Eval evaluates the condition func Eval(sc *SmartContract, condition string) error { if len(condition) == 0 { return logErrorShort(errEmptyCond, consts.EmptyObject) } ret, err := sc.EvalIf(condition) if err != nil { return logError(err, consts.EvalError, "eval condition") } if !ret { return logErrorShort(errAccessDenied, consts.AccessDenied) } return nil } // CheckCondition evaluates the condition func CheckCondition(sc *SmartContract, condition string) (bool, error) { if len(condition) == 0 { return false, nil } ret, err := sc.EvalIf(condition) if err != nil { return false, logError(err, consts.EvalError, "eval condition") } return ret, nil } // FlushContract is flushing contract func FlushContract(sc *SmartContract, iroot any, id int64) error { if err := validateAccess(sc, "FlushContract"); err != nil { return err } root := iroot.(*script.CodeBlock) if id != 0 { if len(root.Children) != 1 || root.Children[0].Type != script.ObjectType_Contract { return errOneContract } } for i, item := range root.Children { if item.Type == script.ObjectType_Contract { root.Children[i].GetContractInfo().Owner.TableID = id } } for key, item := range root.Objects { if cur, ok := sc.VM.Objects[key]; ok { var id uint32 switch item.Type { case script.ObjectType_Contract: id = cur.GetCodeBlock().GetContractInfo().ID case script.ObjectType_Func: id = cur.GetCodeBlock().GetFuncInfo().ID } sc.FlushRollback = append(sc.FlushRollback, &FlushInfo{ ID: id, Prev: sc.VM.Children[id], Info: cur, Name: key, }) } else { sc.FlushRollback = append(sc.FlushRollback, &FlushInfo{ ID: uint32(len(sc.VM.Children)), Prev: nil, Info: nil, Name: key, }) } } sc.VM.FlushBlock(root) return nil } // IsObject returns true if there is the specified contract func IsObject(sc *SmartContract, name string, state int64) bool { return script.VMObjectExists(sc.VM, name, uint32(state)) } // Len returns the length of the slice func Len(in []any) int64 { if in == nil { return 0 } return int64(len(in)) } // PermTable is changing permission of table func PermTable(sc *SmartContract, name, permissions string) error { if err := validateAccess(sc, "PermTable"); err != nil { return err } var perm permTable if err := unmarshalJSON([]byte(permissions), &perm, `table permissions to json`); err != nil { return err } permout, err := marshalJSON(perm, `permission list to json`) if err != nil { return err } name = converter.EscapeSQL(strings.ToLower(name)) tbl := &sqldb.Table{} tbl.SetTablePrefix(converter.Int64ToStr(sc.TxSmart.EcosystemID)) found, err := tbl.Get(sc.DbTransaction, name) if err != nil { return err } if !found { return fmt.Errorf(eTableNotFound, name) } _, _, err = sc.update([]string{`permissions`}, []any{string(permout)}, `1_tables`, `id`, tbl.ID) return err } func columnConditions(sc *SmartContract, columns string) (err error) { var cols []any if err = unmarshalJSON([]byte(columns), &cols, "columns permissions from json"); err != nil { return } if len(cols) == 0 { return logErrorShort(errUndefColumns, consts.EmptyObject) } if len(cols) > syspar.GetMaxColumns() { return logErrorfShort(eManyColumns, syspar.GetMaxColumns(), consts.ParameterExceeded) } for _, icol := range cols { var data map[string]any switch v := icol.(type) { case string: if err = unmarshalJSON([]byte(v), &data, `columns permissions from json`); err != nil { return err } default: data = v.(map[string]any) } if data[`name`] == nil || data[`type`] == nil { return logErrorShort(errWrongColumn, consts.InvalidObject) } if len(typeToPSQL[data[`type`].(string)]) == 0 { return logErrorShort(errIncorrectType, consts.InvalidObject) } condition := `` switch v := data[`conditions`].(type) { case string: condition = v case map[string]any: out, err := marshalJSON(v, `conditions to json`) if err != nil { return err } condition = string(out) } perm, err := getPermColumns(condition) if err != nil { return logError(err, consts.EmptyObject, "Conditions is empty") } if len(perm.Update) == 0 { return logErrorShort(errConditionEmpty, consts.EmptyObject) } if err = script.VMCompileEval(sc.VM, perm.Update, uint32(sc.TxSmart.EcosystemID)); err != nil { return logError(err, consts.EvalError, "compile update conditions") } if len(perm.Read) > 0 { if err = script.VMCompileEval(sc.VM, perm.Read, uint32(sc.TxSmart.EcosystemID)); err != nil { return logError(err, consts.EvalError, "compile read conditions") } } } return nil } // TableConditions is contract func func TableConditions(sc *SmartContract, name, columns, permissions string) (err error) { isEdit := len(columns) == 0 name = strings.ToLower(name) if err := validateAccess(sc, "TableConditions"); err != nil { return err } prefix := converter.Int64ToStr(sc.TxSmart.EcosystemID) t := &sqldb.Table{} t.SetTablePrefix(prefix) exists, err := t.Get(sc.DbTransaction, name) if err != nil { return logErrorDB(err, "table exists") } if isEdit { if !exists { return logErrorfShort(eTableNotFound, name, consts.NotFound) } } else if exists { return logErrorfShort(eTableExists, name, consts.Found) } _, err = qb.GetColumns(name) if err != nil { return err } var perm permTable if err = unmarshalJSON([]byte(permissions), &perm, "permissions from json"); err != nil { return err } v := reflect.ValueOf(perm) for i := 0; i < v.NumField(); i++ { cond := v.Field(i).Interface().(string) name := v.Type().Field(i).Name if len(cond) == 0 && name != `Read` && name != `Filter` { return logErrorfShort(eEmptyCond, name, consts.EmptyObject) } if err = script.VMCompileEval(sc.VM, cond, uint32(sc.TxSmart.EcosystemID)); err != nil { return logError(err, consts.EvalError, "compile evaluating permissions") } } if isEdit { if err = sc.AccessTable(name, `update`); err != nil { if err = sc.AccessRights(`changing_tables`, false); err != nil { return err } } return nil } if err := columnConditions(sc, columns); err != nil { return err } if err := sc.AccessRights("new_table", false); err != nil { return err } return nil } // ValidateCondition checks if the condition can be compiled func ValidateCondition(sc *SmartContract, condition string, state int64) error { if len(condition) == 0 { return logErrorShort(errConditionEmpty, consts.EmptyObject) } return script.VMCompileEval(sc.VM, condition, uint32(state)) } // ColumnCondition is contract func func ColumnCondition(sc *SmartContract, tableName, name, coltype, permissions string) error { name = converter.EscapeSQL(strings.ToLower(name)) tableName = converter.EscapeSQL(strings.ToLower(tableName)) if err := validateAccess(sc, "ColumnCondition"); err != nil { return err } isExist := accessContracts(sc, nEditColumn) tEx := &sqldb.Table{} prefix := converter.Int64ToStr(sc.TxSmart.EcosystemID) tEx.SetTablePrefix(prefix) exists, err := tEx.IsExistsByPermissionsAndTableName(sc.DbTransaction, name, tableName) if err != nil { return logErrorDB(err, "querying that table is exists by permissions and table name") } if isExist { if !exists { return logErrorfShort(eColumnNotExist, name, consts.NotFound) } } else if exists { return logErrorfShort(eColumnExist, name, consts.Found) } _, err = qb.GetColumns(name) if err != nil { return err } if len(permissions) == 0 { return logErrorShort(errPermEmpty, consts.EmptyObject) } perm, err := getPermColumns(permissions) if err = script.VMCompileEval(sc.VM, perm.Update, uint32(sc.TxSmart.EcosystemID)); err != nil { return err } if len(perm.Read) > 0 { if err = script.VMCompileEval(sc.VM, perm.Read, uint32(sc.TxSmart.EcosystemID)); err != nil { return err } } tblName := qb.GetTableName(sc.TxSmart.EcosystemID, tableName) if isExist { return nil } count, err := sc.DbTransaction.GetColumnCount(tblName) if err != nil { return logErrorDB(err, "counting table columns") } if count >= int64(syspar.GetMaxColumns()) { return logErrorfShort(eManyColumns, syspar.GetMaxColumns(), consts.ParameterExceeded) } if len(typeToPSQL[coltype]) == 0 { return logErrorValue(errIncorrectType, consts.InvalidObject, "Unknown column type", coltype) } return sc.AccessTable(tblName, "new_column") } // AllowChangeCondition check access to change condition throught supper contract func AllowChangeCondition(sc *SmartContract, tblname string) error { _, name := converter.ParseName(tblname) if param, ok := tableParamConditions[name]; ok { return sc.AccessRights(param, false) } return nil } // RowConditions checks conditions for table row by id func RowConditions(sc *SmartContract, tblname string, id int64, conditionOnly bool) error { condition, err := sc.DbTransaction.GetRowConditionsByTableNameAndID( qb.GetTableName(sc.TxSmart.EcosystemID, tblname), id) if err != nil { return logErrorDB(err, "executing row condition query") } if len(condition) == 0 { return logErrorValue(fmt.Errorf(eItemNotFound, id), consts.NotFound, "record not found", tblname) } for _, v := range sc.TxContract.StackCont { if v == condition { return errRecursion } } if err := Eval(sc, condition); err != nil { if err == errAccessDenied && conditionOnly { return AllowChangeCondition(sc, tblname) } return err } return nil } func checkColumnName(name string) error { if len(name) == 0 { return errEmptyColumn } else if name[0] >= '0' && name[0] <= '9' { return errWrongColumn } if !converter.IsLatin(name) { return fmt.Errorf(eLatin, name) } return nil } func checkTableNameRule(name string) error { if len(name) == 0 { return errTableEmptyName } if (name[0] >= '0' && name[0] <= '9') || name[0] == '@' { return errTableName } if !converter.IsLatin(name) { return fmt.Errorf(eLatin, name) } return nil } // CreateColumn is creating column func CreateColumn(sc *SmartContract, tableName, name, colType, permissions string) (err error) { var ( sqlColType string permout []byte ) if err = validateAccess(sc, "CreateColumn"); err != nil { return } name = converter.EscapeSQL(strings.ToLower(name)) if err = checkColumnName(name); err != nil { return } tblname := qb.GetTableName(sc.TxSmart.EcosystemID, tableName) sqlColType, err = columnType(colType) if err != nil { return } err = sc.DbTransaction.AlterTableAddColumn(tblname, name, sqlColType) if err != nil { return logErrorDB(err, "adding column to the table") } type cols struct { ID int64 Columns string } temp := &cols{} err = sqldb.GetDB(sc.DbTransaction).Table(`1_tables`).Where("name = ? and ecosystem = ?", strings.ToLower(tableName), sc.TxSmart.EcosystemID).Select("id,columns").Find(temp).Error if err != nil { return } var perm map[string]string if err = unmarshalJSON([]byte(temp.Columns), &perm, `columns from the table`); err != nil { return err } perm[name] = permissions permout, err = marshalJSON(perm, `permissions to json`) if err != nil { return } _, _, err = sc.update([]string{`columns`}, []any{string(permout)}, `1_tables`, `id`, temp.ID) if err != nil { return err } if !sc.CLB { if err := syspar.SysTableColType(sc.DbTransaction); err != nil { return err } return SysRollback(sc, SysRollData{Type: "NewColumn", TableName: tblname, Data: name}) } return } // PermColumn is contract func func PermColumn(sc *SmartContract, tableName, name, permissions string) error { if err := validateAccess(sc, "PermColumn"); err != nil { return err } name = converter.EscapeSQL(strings.ToLower(name)) tableName = converter.EscapeSQL(strings.ToLower(tableName)) tables := `1_tables` type cols struct { Columns string } temp := &cols{} err := sqldb.GetDB(sc.DbTransaction).Table(tables).Where("name = ?", tableName).Select("columns").Find(temp).Error if err != nil { return logErrorDB(err, "querying columns by table name") } var perm map[string]string if err = unmarshalJSON([]byte(temp.Columns), &perm, `columns from json`); err != nil { return err } perm[name] = permissions permout, err := marshalJSON(perm, `column permissions to json`) if err != nil { return err } _, _, err = sc.update([]string{`columns`}, []any{string(permout)}, tables, `name`, tableName) return err } // HMac returns HMAC hash as raw or hex string func HMac(key, data string, raw_output bool) (ret string, err error) { hash, err := crypto.GetHMAC(key, data) if err != nil { return ``, logError(err, consts.CryptoError, "getting HMAC") } if raw_output { return string(hash), nil } return hex.EncodeToString(hash), nil } // GetMapKeys returns the array of keys of the map func GetMapKeys(in *types.Map) []any { keys := make([]any, 0, in.Size()) for _, k := range in.Keys() { keys = append(keys, k) } return keys } // SortedKeys returns the sorted array of keys of the map func SortedKeys(m *types.Map) []any { i, sorted := 0, make([]string, m.Size()) for _, k := range m.Keys() { sorted[i] = k i++ } sort.Strings(sorted) ret := make([]any, len(sorted)) for k, v := range sorted { ret[k] = v } return ret } func httpRequest(req *http.Request, headers map[string]any) (string, error) { for key, v := range headers { req.Header.Set(key, fmt.Sprint(v)) } client := &http.Client{} resp, err := client.Do(req) if err != nil { return ``, logError(err, consts.NetworkError, "http request") } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return ``, logError(err, consts.IOError, "reading http answer") } if resp.StatusCode != http.StatusOK { return ``, logError(fmt.Errorf(`%d %s`, resp.StatusCode, strings.TrimSpace(string(data))), consts.NetworkError, "http status code") } return string(data), nil } // HTTPRequest sends http request func HTTPRequest(requrl, method string, head *types.Map, params *types.Map) (string, error) { var ioform io.Reader headers := make(map[string]any) for _, key := range head.Keys() { v, _ := head.Get(key) headers[key] = v } form := &url.Values{} for _, key := range params.Keys() { v, _ := params.Get(key) form.Set(key, fmt.Sprint(v)) } if len(*form) > 0 { ioform = strings.NewReader(form.Encode()) } req, err := http.NewRequest(method, requrl, ioform) if err != nil { return ``, logError(err, consts.NetworkError, "new http request") } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return httpRequest(req, headers) } // HTTPPostJSON sends post http request with json func HTTPPostJSON(requrl string, head *types.Map, json_str string) (string, error) { req, err := http.NewRequest("POST", requrl, bytes.NewBuffer([]byte(json_str))) if err != nil { return ``, logError(err, consts.NetworkError, "new http request") } headers := make(map[string]any) for _, key := range head.Keys() { v, _ := head.Get(key) headers[key] = v } return httpRequest(req, headers) } // RandomInt returns a random value between min and max func RandomInt(sc *SmartContract, min int64, max int64) (int64, error) { if min < 0 || max < 0 || min >= max { return 0, logError(fmt.Errorf(eWrongRandom, min, max), consts.InvalidObject, "getting random") } return min + sc.Rand.Int63n(max-min), nil } func RandomFloat(sc *SmartContract, min float64, max float64) (float64, error) { if min < 0 || max < 0 || min >= max { return 0, logError(fmt.Errorf(eWrongRandom, min, max), consts.InvalidObject, "getting random") } return min + sc.Rand.Float64()*(max-min), nil } func RandomDecimal(sc *SmartContract, min decimal.Decimal, max decimal.Decimal) (decimal.Decimal, error) { if min.LessThan(decimal.Zero) || max.LessThan(decimal.Zero) || min.GreaterThanOrEqual(max) { return decimal.Zero, logError(fmt.Errorf(eWrongRandom, min, max), consts.InvalidObject, "getting random") } return decimal.NewFromFloat(sc.Rand.Float64()).Mul(max.Sub(min)).Add(min).Floor(), nil } func ValidateCron(cronSpec string) (err error) { _, err = scheduler.Parse(cronSpec) return } func UpdateCron(sc *SmartContract, id int64) error { cronTask := &sqldb.Cron{} cronTask.SetTablePrefix(converter.Int64ToStr(sc.TxSmart.EcosystemID)) ok, err := cronTask.Get(id) if err != nil { return logErrorDB(err, "get cron record") } if !ok { return nil } err = scheduler.UpdateTask(&scheduler.Task{ ID: cronTask.UID(), CronSpec: cronTask.Cron, Handler: &contract.ContractHandler{ Contract: cronTask.Contract, }, }) return err } func UpdateNodesBan(smartContract *SmartContract, timestamp int64) error { if conf.Config.IsSupportingCLB() { return ErrNotImplementedOnCLB } now := time.Unix(timestamp, 0) badBlocks := &sqldb.BadBlocks{} banRequests, err := badBlocks.GetNeedToBanNodes(now, syspar.GetIncorrectBlocksPerDay()) if err != nil { logError(err, consts.DBError, "get nodes need to be banned") return err } honorNodes := syspar.GetNodes() var updHonorNodes bool for i, honorNode := range honorNodes { // Removing ban in case ban time has already passed if honorNode.UnbanTime.Unix() > 0 && now.After(honorNode.UnbanTime) { honorNode.UnbanTime = time.Unix(0, 0) updHonorNodes = true } nodeKeyID := crypto.Address(honorNode.PublicKey) // Setting ban time if we have ban requests for the current node from 51% of all nodes. // Ban request is mean that node have added more or equal N(system parameter) of bad blocks for _, banReq := range banRequests { if banReq.ProducerNodeId == nodeKeyID && banReq.Count >= int64((len(honorNodes)/2)+1) { honorNode.UnbanTime = now.Add(syspar.GetNodeBanTime()) blocks, err := badBlocks.GetNodeBlocks(nodeKeyID, now) if err != nil { return logErrorDB(err, "getting node bad blocks for removing") } for _, b := range blocks { if _, err := DBUpdate(smartContract, "@1bad_blocks", b.ID, types.LoadMap(map[string]any{"deleted": "1"})); err != nil { return logErrorValue(err, consts.DBError, "deleting bad block", converter.Int64ToStr(b.ID)) } } banMessage := fmt.Sprintf( "%d/%d nodes voted for ban with %d or more blocks each", banReq.Count, len(honorNodes), syspar.GetIncorrectBlocksPerDay(), ) _, _, err = DBInsert( smartContract, "@1node_ban_logs", types.LoadMap(map[string]any{ "node_id": nodeKeyID, "banned_at": now.Format(time.RFC3339), "ban_time": int64(syspar.GetNodeBanTime() / time.Millisecond), // in ms "reason": banMessage, })) if err != nil { return logErrorValue(err, consts.DBError, "inserting log to node_ban_log", converter.Int64ToStr(banReq.ProducerNodeId)) } _, _, err = DBInsert( smartContract, "@1notifications", types.LoadMap(map[string]any{ "recipient->member_id": nodeKeyID, "notification->type": sqldb.NotificationTypeSingle, "notification->header": nodeBanNotificationHeader, "notification->body": banMessage, })) if err != nil { return logErrorValue(err, consts.DBError, "inserting log to node_ban_log", converter.Int64ToStr(banReq.ProducerNodeId)) } updHonorNodes = true } } honorNodes[i] = honorNode } if updHonorNodes { data, err := marshalJSON(honorNodes, `honor nodes`) if err != nil { return err } _, err = UpdatePlatformParam(smartContract, syspar.HonorNodes, string(data), "") if err != nil { return logErrorDB(err, "updating honor nodes") } } return nil } func GetBlock(blockID int64) (*types.Map, error) { block := sqldb.BlockChain{} ok, err := block.Get(blockID) if err != nil { return nil, logErrorDB(err, "getting block") } if !ok { return nil, nil } return types.LoadMap(map[string]any{ "id": block.ID, "time": block.Time, "key_id": block.KeyID, }), nil } // DecodeBase64 decodes base64 string func DecodeBase64(input string) (out string, err error) { var bin []byte bin, err = base64.StdEncoding.DecodeString(input) if err == nil { out = string(bin) } return } // EncodeBase64 encodes string in base64 func EncodeBase64(input string) (out string) { return base64.StdEncoding.EncodeToString([]byte(input)) } // Hash returns hash sum of data func Hash(data any) (string, error) { var b []byte switch v := data.(type) { case []uint8: b = v case string: b = []byte(v) default: return "", logErrorf(eUnsupportedType, v, consts.ConversionError, "converting to bytes") } hash := crypto.Hash(b) return hex.EncodeToString(hash[:]), nil } // GetColumnType returns the type of the column func GetColumnType(sc *SmartContract, tableName, columnName string) (string, error) { return sc.DbTransaction.GetColumnType(qb.GetTableName(sc.TxSmart.EcosystemID, tableName), columnName) } // GetType returns the name of the type of the value func GetType(val any) string { if val == nil { return `nil` } return reflect.TypeOf(val).String() } // StringToBytes converts string to bytes func StringToBytes(src string) []byte { return []byte(src) } // BytesToString converts bytes to string func BytesToString(src []byte) string { if bytes.HasPrefix(src, BOM) && utf8.Valid(src[len(BOM):]) { return string(src[len(BOM):]) } return string(src) } // CreateCLB allow create new CLB throught clbmanager func CreateCLB(sc *SmartContract, name, dbUser, dbPassword string, port int64) error { return clbmanager.Manager.CreateCLB(name, dbUser, dbPassword, int(port)) } // DeleteCLB delete clb func DeleteCLB(sc *SmartContract, name string) error { return clbmanager.Manager.DeleteCLB(name) } // StartCLB run CLB process func StartCLB(sc *SmartContract, name string) error { return clbmanager.Manager.StartCLB(name) } // StopCLBProcess stops CLB process func StopCLBProcess(sc *SmartContract, name string) error { return clbmanager.Manager.StopCLB(name) } // GetCLBList returns list CLB process with statuses func GetCLBList(sc *SmartContract) map[string]string { list, _ := clbmanager.Manager.ListProcessWithPorts() return list } func GetHistoryRaw(dbTx *sqldb.DbTransaction, ecosystem int64, tableName string, id, idRollback int64) ([]any, error) { table := qb.GetTableName(ecosystem, tableName) rows, err := sqldb.GetDB(dbTx).Table(table).Where("id=?", id).Rows() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("get current values") return nil, err } defer rows.Close() if !rows.Next() { return nil, errNotFound } // Get column names columns, err := rows.Columns() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("get columns") return nil, err } values := make([][]byte, len(columns)) scanArgs := make([]any, len(values)) for i := range values { scanArgs[i] = &values[i] } err = rows.Scan(scanArgs...) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("scan values") return nil, err } var value string curVal := types.NewMap() for i, col := range values { if col == nil { value = "NULL" } else { value = string(col) } curVal.Set(columns[i], value) } var rollbackList []any rollbackTx := &sqldb.RollbackTx{} txs, err := rollbackTx.GetRollbackTxsByTableIDAndTableName(converter.Int64ToStr(id), table, historyLimit) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("rollback history") return nil, err } for _, tx := range *txs { if len(rollbackList) > 0 { prev := rollbackList[len(rollbackList)-1].(*types.Map) prev.Set(`block_id`, converter.Int64ToStr(tx.BlockID)) prev.Set(`id`, converter.Int64ToStr(tx.ID)) block := sqldb.BlockChain{} if ok, err := block.Get(tx.BlockID); ok { prev.Set(`block_time`, time.Unix(block.Time, 0).Format(`2006-01-02 15:04:05`)) } else if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting block time") return nil, err } if idRollback == tx.ID { return rollbackList[len(rollbackList)-1:], nil } } if tx.Data == "" { continue } rollback := types.NewMap() for _, k := range curVal.Keys() { v, _ := curVal.Get(k) rollback.Set(k, v) } var updValues map[string]any if err := json.Unmarshal([]byte(tx.Data), &updValues); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling rollbackTx.Data from JSON") return nil, err } updMap := types.LoadMap(updValues) for _, k := range updMap.Keys() { v, _ := updMap.Get(k) rollback.Set(k, v) } rollbackList = append(rollbackList, rollback) curVal = rollback } if len(*txs) > 0 && len((*txs)[len(*txs)-1].Data) > 0 { prev := rollbackList[len(rollbackList)-1].(*types.Map) prev.Set(`block_id`, `1`) prev.Set(`id`, ``) prev.Set(`block_time`, ``) } if idRollback > 0 { return []any{}, nil } return rollbackList, nil } const ( // AddressLength is the expected length of the address AddressLength = 20 ) type Address [AddressLength]byte func (a Address) Hex() string { return string(a.checksumHex()) } func (a *Address) checksumHex() []byte { buf := a.hex() // compute checksum sha := sha3.NewLegacyKeccak256() sha.Write(buf[2:]) hash := sha.Sum(nil) for i := 2; i < len(buf); i++ { hashByte := hash[(i-2)/2] if i%2 == 0 { hashByte = hashByte >> 4 } else { hashByte &= 0xf } if buf[i] > '9' && hashByte > 7 { buf[i] -= 32 } } return buf[:] } func (a Address) hex() []byte { var buf [len(a)*2 + 2]byte copy(buf[:2], "0x") hex.Encode(buf[2:], a[:]) return buf[:] } func getBtcNetParams() *chaincfg.Params { netParams := &chaincfg.MainNetParams if syspar.IsTestMode() { netParams = &chaincfg.TestNet3Params } return netParams } // BitcoinLegacyAddress public key uncompressed, p2pkh address 1... func BitcoinLegacyAddress(pubKeyBytes []byte) (string, error) { // if the public key is 64 bytes, add 0x04 prefix if len(pubKeyBytes) == 64 { pubKeyBytes = append([]byte{0x04}, pubKeyBytes...) } pubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return "", err } serializedPubKey := pubKey.SerializeUncompressed() addressPubKey, err := btcutil.NewAddressPubKey(serializedPubKey, getBtcNetParams()) if err != nil { return "", err } address := addressPubKey.EncodeAddress() return address, nil } // BitcoinBip32Address bip32 path: m/32/0/{index} compressed, p2pkh address 1... func BitcoinBip32Address(pubKeyBytes []byte) (string, error) { // if the public key is 64 bytes, add 0x04 prefix if len(pubKeyBytes) == 64 { pubKeyBytes = append([]byte{0x04}, pubKeyBytes...) } pubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return "", err } serializedPubKey := pubKey.SerializeCompressed() addressPubKey, err := btcutil.NewAddressPubKey(serializedPubKey, getBtcNetParams()) if err != nil { return "", err } address := addressPubKey.EncodeAddress() return address, nil } // BitcoinBip49Address bip49 path: m/49'/0'/0'/0/{index}, segwit address p2sh 3... func BitcoinBip49Address(pubKeyBytes []byte) (string, error) { // if the public key is 64 bytes, add 0x04 prefix if len(pubKeyBytes) == 64 { pubKeyBytes = append([]byte{0x04}, pubKeyBytes...) } pubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return "", err } serializedPubKey := pubKey.SerializeCompressed() pkHash := btcutil.Hash160(serializedPubKey) witnessProg, err := btcutil.NewAddressWitnessPubKeyHash(pkHash, getBtcNetParams()) if err != nil { return "", err } script, err := txscript.PayToAddrScript(witnessProg) if err != nil { return "", err } addressScriptHash, err := btcutil.NewAddressScriptHash(script, getBtcNetParams()) if err != nil { return "", err } address := addressScriptHash.EncodeAddress() return address, nil } // BitcoinBip84Address bip84 path: m/84'/0'/0'/0/{index}, segwit address p2wpkh bc1q... func BitcoinBip84Address(pubKeyBytes []byte) (string, error) { // if the public key is 64 bytes, add 0x04 prefix if len(pubKeyBytes) == 64 { pubKeyBytes = append([]byte{0x04}, pubKeyBytes...) } pubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return "", err } serializedPubKey := pubKey.SerializeCompressed() witnessProg := btcutil.Hash160(serializedPubKey) addressWitnessPubKeyHash, err := btcutil.NewAddressWitnessPubKeyHash(witnessProg, getBtcNetParams()) if err != nil { return "", err } address := addressWitnessPubKeyHash.EncodeAddress() return address, nil } // BitcoinBip86Address bip86 path: m/86'/0'/0'/0/{index},taproot address bc1p... func BitcoinBip86Address(pubKeyBytes []byte) (string, error) { // if the public key is 64 bytes, add 0x04 prefix if len(pubKeyBytes) == 64 { pubKeyBytes = append([]byte{0x04}, pubKeyBytes...) } pubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return "", err } tapKey := txscript.ComputeTaprootKeyNoScript(pubKey) tweakedPubKeyHash := schnorr.SerializePubKey(tapKey) address, err := btcutil.NewAddressTaproot(tweakedPubKeyHash, getBtcNetParams()) if err != nil { return "", err } taprootAddress := address.EncodeAddress() return taprootAddress, nil } // EthereumAddress bip44 path: m/44'/60'/0'/0/{index} //func EthereumAddress(pubKeyBytes []byte) (string, error) { // // if the public key is 64 bytes, add 0x04 prefix // if len(pubKeyBytes) == 64 { // pubKeyBytes = append([]byte{0x04}, pubKeyBytes...) // } // pubKey, err := btcec.ParsePubKey(pubKeyBytes) // if err != nil { // return "", err // } // pubKeyBytes2 := pubKey.SerializeUncompressed()[1:] // Strip off the 0x04 prefix // address := eth_crypto.Keccak256(pubKeyBytes2)[12:] // Keccak-256 and take last 20 bytes // ethAddress := "0x" + fmt.Sprintf("%x", address) // return ethAddress, nil //} func EthereumAddress(pub []byte) string { var addr Address keccak256 := &hashalgo.Keccak256{} copy(addr[:], keccak256.GetHash(pub[:])[12:]) return addr.Hex() } func TronAddress(pub []byte) (string, error) { keccak256 := &hashalgo.Keccak256{} hash := keccak256.GetHash(pub[:]) hex20 := "41" + hex.EncodeToString(hash[len(hash)-20:]) return base58.FromHexAddress(hex20) } func GetLogTxCount(sc *SmartContract, ecosystemID int64) (int64, error) { return sqldb.GetLogTxCount(sc.DbTransaction, ecosystemID) } func TopAmounts(sc *SmartContract, ecosystem int64, rank int64, dense bool) ([]any, error) { return sqldb.GetTopAmounts(sc.DbTransaction, ecosystem, rank, dense) } func GetHistory(sc *SmartContract, tableName string, id int64) ([]any, error) { return GetHistoryRaw(sc.DbTransaction, sc.TxSmart.EcosystemID, tableName, id, 0) } func GetHistoryRow(sc *SmartContract, tableName string, id, idRollback int64) (*types.Map, error) { list, err := GetHistoryRaw(sc.DbTransaction, sc.TxSmart.EcosystemID, tableName, id, idRollback) if err != nil { return nil, err } result := types.NewMap() if len(list) > 0 { for _, key := range list[0].(*types.Map).Keys() { val, _ := list[0].(*types.Map).Get(key) result.Set(key, val) } } return result, nil } func UpdateNotifications(sc *SmartContract, ecosystemID int64, accounts ...any) { accountList := make([]string, 0, len(accounts)) for i, id := range accounts { switch v := id.(type) { case string: accountList = append(accountList, v) case []any: if i == 0 { UpdateNotifications(sc, ecosystemID, v...) return } } } sc.Notifications.AddAccounts(ecosystemID, accountList...) } func UpdateRolesNotifications(sc *SmartContract, ecosystemID int64, roles ...any) { rolesList := make([]int64, 0, len(roles)) for i, roleID := range roles { switch v := roleID.(type) { case int64: rolesList = append(rolesList, v) case string: rolesList = append(rolesList, converter.StrToInt64(v)) case []any: if i == 0 { UpdateRolesNotifications(sc, ecosystemID, v...) return } } } sc.Notifications.AddRoles(ecosystemID, rolesList...) } func DelColumn(sc *SmartContract, tableName, name string) (err error) { var ( count int64 permout []byte ) name = converter.EscapeSQL(strings.ToLower(name)) tblname := qb.GetTableName(sc.TxSmart.EcosystemID, strings.ToLower(tableName)) t := sqldb.Table{} prefix := converter.Int64ToStr(sc.TxSmart.EcosystemID) t.SetTablePrefix(prefix) TableName := tblname if strings.HasPrefix(TableName, prefix) { TableName = TableName[len(prefix)+1:] } if err = sc.AccessTable(tblname, "update"); err != nil { return } found, err := t.Get(sc.DbTransaction, TableName) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting table info") return err } if !found { log.WithFields(log.Fields{"type": consts.NotFound, "error": err}).Error("not found table info") return fmt.Errorf(eTableNotFound, tblname) } count, err = sc.DbTransaction.GetRecordsCountTx(tblname, ``) if err != nil { return } if count > 0 { return fmt.Errorf(eTableNotEmpty, tblname) } colType, err := sc.DbTransaction.GetColumnType(tblname, name) if err != nil { return err } if len(colType) == 0 { return fmt.Errorf(eColumnNotExist, name) } var perm map[string]string if err = unmarshalJSON([]byte(t.Columns), &perm, `columns from the table`); err != nil { return err } if _, ok := perm[name]; !ok { return fmt.Errorf(eColumnNotDeleted, name) } delete(perm, name) permout, err = marshalJSON(perm, `permissions to json`) if err != nil { return } if err = sc.DbTransaction.AlterTableDropColumn(tblname, name); err != nil { return } _, _, err = sc.update([]string{`columns`}, []any{string(permout)}, `1_tables`, `id`, t.ID) if err != nil { return err } if !sc.CLB { if err = syspar.SysTableColType(sc.DbTransaction); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating sys table col type") return err } data := map[string]string{"name": name, "type": colType} out, err := marshalJSON(data, `marshalling column info`) if err != nil { return err } return SysRollback(sc, SysRollData{Type: "DeleteColumn", TableName: tblname, Data: string(out)}) } return } func DelTable(sc *SmartContract, tableName string) (err error) { var ( count int64 ) tblname := qb.GetTableName(sc.TxSmart.EcosystemID, strings.ToLower(tableName)) t := sqldb.Table{} prefix := converter.Int64ToStr(sc.TxSmart.EcosystemID) t.SetTablePrefix(prefix) TableName := tblname if strings.HasPrefix(TableName, prefix) { TableName = TableName[len(prefix)+1:] } if err = sc.AccessTable(tblname, "update"); err != nil { return } found, err := t.Get(sc.DbTransaction, TableName) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting table info") return err } if !found { log.WithFields(log.Fields{"type": consts.NotFound, "error": err}).Error("not found table info") return fmt.Errorf(eTableNotFound, tblname) } count, err = sc.DbTransaction.GetRecordsCountTx(tblname, ``) if err != nil { return } if count > 0 { return fmt.Errorf(eTableNotEmpty, tblname) } if err = t.Delete(sc.DbTransaction); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting table") return err } if !sc.CLB { var ( out []byte ) cols, err := sc.DbTransaction.GetAllColumnTypes(tblname) if err != nil { return err } tinfo := TableInfo{Table: &t, Columns: make(map[string]string)} for _, item := range cols { if item["column_name"] == `id` { continue } tinfo.Columns[item["column_name"]] = sqldb.DataTypeToColumnType(item["data_type"]) } out, err = marshalJSON(tinfo, `marshalling table info`) if err != nil { return err } if err = syspar.SysTableColType(sc.DbTransaction); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating sys table col type") return err } if err = SysRollback(sc, SysRollData{Type: "DeleteTable", TableName: tblname, Data: string(out)}); err != nil { return err } } return sc.DbTransaction.DropTable(tblname) } func FormatMoney(sc *SmartContract, exp string, digit int64) (string, error) { var cents int64 if digit != 0 { cents = digit } else { sp := &sqldb.Ecosystem{} _, err := sp.Get(sc.DbTransaction, sc.TxSmart.EcosystemID) if err != nil { return `0`, logErrorDB(err, "getting money_digit param") } cents = sp.Digits } return converter.FormatMoney(exp, int32(cents)) } func PubToHex(in any) (ret string) { switch v := in.(type) { case string: ret = crypto.PubToHex([]byte(v)) case []byte: ret = crypto.PubToHex(v) } return } func SendExternalTransaction(sc *SmartContract, uid, url, externalContract string, params *types.Map, resultContract string) (err error) { var ( out, insertQuery string ) if _, err := syspar.GetThisNodePosition(); err != nil { return nil } out, err = JSONEncode(params) if err != nil { return err } logger := sc.GetLogger() sqlBuilder := &qb.SQLQueryBuilder{ Entry: logger, Table: `external_blockchain`, Fields: []string{`value`, `uid`, `url`, `external_contract`, `result_contract`, `tx_time`}, FieldValues: []any{out, uid, url, externalContract, resultContract, sc.Timestamp}, KeyTableChkr: sqldb.KeyTableChecker{}, } insertQuery, err = sqlBuilder.GetSQLInsertQuery(sqldb.NextIDGetter{Tx: sc.DbTransaction}) if err != nil { return } return sqldb.GetDB(sc.DbTransaction).Exec(insertQuery).Error } func IsHonorNodeKey(id int64) bool { if syspar.IsCandidateNodeMode() { return true } for _, item := range syspar.GetNodes() { if crypto.Address(item.PublicKey) == id { return true } } return false } ================================================ FILE: packages/smart/gas.go ================================================ package smart import ( "bytes" "encoding/json" "fmt" "sort" "strconv" "strings" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/gogo/protobuf/sortkeys" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) type ( FuelCategory struct { FuelType FuelType Decimal decimal.Decimal ConversionRate float64 Flag GasPayAbleType Arithmetic Arithmetic } Combustion struct { Flag int64 Percent int64 } PaymentInfo struct { TokenEco int64 ToID int64 TaxesID int64 FromID int64 PaymentType PaymentType FuelRate decimal.Decimal FuelCategories []FuelCategory PayWallet *sqldb.Key Ecosystem *sqldb.Ecosystem TaxesSize int64 Indirect bool Combustion *Combustion Penalty bool } multiPays []*PaymentInfo ) func newCombustion(flag int64, percent int64) *Combustion { if flag <= 0 { flag = 1 } if flag == 1 && percent < 0 { percent = 0 } return &Combustion{Flag: flag, Percent: percent} } func NewFuelCategory(fuelType FuelType, decimal decimal.Decimal, flag GasPayAbleType, convert float64) FuelCategory { f := new(FuelCategory) f.writeFuelType(fuelType) f.writeDecimal(decimal) f.writeFlag(flag) f.writeConversionRate(convert) return *f } func (f *FuelCategory) writeFuelType(fuelType FuelType) { f.FuelType = fuelType } func (f *FuelCategory) writeDecimal(decimal decimal.Decimal) { f.Decimal = decimal } func (f *FuelCategory) writeArithmetic(a Arithmetic) { f.Arithmetic = a } func (f *FuelCategory) resetArithmetic() { f.Arithmetic = Arithmetic_NATIVE } func (f *FuelCategory) writeFlag(tf GasPayAbleType) { if tf == GasPayAbleType_Invalid { tf = GasPayAbleType_Capable } f.Flag = tf } func (f *FuelCategory) writeConversionRate(cr float64) { if cr > 0 { f.ConversionRate = cr return } f.ConversionRate = 100 } func (f *FuelCategory) Detail() (string, any) { return f.FuelType.String(), f.FeesInfo() } func (f *FuelCategory) FeesInfo() any { detail := types.NewMap() detail.Set("decimal", f.Decimal) detail.Set("value", f.Fees()) detail.Set("conversion_rate", f.ConversionRate) detail.Set("flag", f.Flag) detail.Set("arithmetic", f.Arithmetic.String()) b, _ := JSONEncode(detail) s, _ := JSONDecode(b) return s } func (f *FuelCategory) Fees() decimal.Decimal { var value decimal.Decimal switch f.Arithmetic { case Arithmetic_NATIVE: value = f.Decimal case Arithmetic_MUL: value = f.Decimal.Mul(decimal.NewFromFloat(f.ConversionRate).Div(decimal.NewFromFloat(100))) case Arithmetic_DIV: value = f.Decimal.Div(decimal.NewFromFloat(f.ConversionRate).Div(decimal.NewFromFloat(100))) default: value = f.Decimal } return value.Floor() } func (c Combustion) Fees(sum decimal.Decimal) decimal.Decimal { return sum.Mul(decimal.NewFromInt(c.Percent)).Div(decimal.New(100, 0)).Floor() } func (c Combustion) Detail(sum decimal.Decimal) any { detail := types.NewMap() combustion := c.Fees(sum) detail.Set("flag", c.Flag) detail.Set("percent", c.Percent) detail.Set("before", sum) detail.Set("value", combustion) detail.Set("after", sum.Sub(combustion)) b, _ := JSONEncode(detail) s, _ := JSONDecode(b) return s } func (pay *PaymentInfo) PushFuelCategories(fes ...FuelCategory) { pay.FuelCategories = append(pay.FuelCategories, fes...) } func (pay *PaymentInfo) SetDecimalByType(fuelType FuelType, decimal decimal.Decimal) { for i, v := range pay.FuelCategories { if v.FuelType == fuelType { pay.FuelCategories[i].writeDecimal(decimal) break } } } func (pay *PaymentInfo) GetPayMoney() decimal.Decimal { var money decimal.Decimal for i := 0; i < len(pay.FuelCategories); i++ { f := pay.FuelCategories[i] money = money.Add(f.Fees()) } return money } func (pay *PaymentInfo) GetEstimate() decimal.Decimal { var money decimal.Decimal for i := 0; i < len(pay.FuelCategories); i++ { f := pay.FuelCategories[i] if f.FuelType == FuelType_vmCost_fee { continue } money.Add(f.Fees()) } return money } func (pay *PaymentInfo) Detail() any { detail := types.NewMap() for i := 0; i < len(pay.FuelCategories); i++ { detail.Set(pay.FuelCategories[i].Detail()) } detail.Set("taxes_size", pay.TaxesSize) detail.Set("payment_type", pay.PaymentType.String()) detail.Set("fuel_rate", pay.FuelRate) detail.Set("token_symbol", pay.Ecosystem.TokenSymbol) b, _ := JSONEncode(detail) s, _ := JSONDecode(b) return s } func (pay *PaymentInfo) DetailCombustion() any { c := pay.Combustion detail := types.NewMap() var money decimal.Decimal for i := 0; i < len(pay.FuelCategories); i++ { f := pay.FuelCategories[i] s := f.Fees().Mul(decimal.NewFromInt(c.Percent)).Div(decimal.New(100, 0)).Floor() detail.Set(f.FuelType.String(), s) money = money.Add(f.Fees()) } if !pay.Indirect && pay.TokenEco != consts.DefaultTokenEcosystem { detail.Set("combustion", pay.Combustion.Detail(money)) } detail.Set("token_symbol", pay.Ecosystem.TokenSymbol) detail.Set("fuel_rate", pay.FuelRate) b, _ := JSONEncode(detail) s, _ := JSONDecode(b) return s } func (f *PaymentInfo) checkVerify(sc *SmartContract) error { eco := f.TokenEco if err := sc.hasExistKeyID(eco, f.FromID); err != nil { return errors.Wrapf(err, fmt.Sprintf("From ID %d does not exist", f.ToID)) } if err := sc.hasExistKeyID(eco, f.ToID); err != nil { return errors.Wrapf(err, fmt.Sprintf("to ID %d does not exist", f.ToID)) } if err := sc.hasExistKeyID(eco, f.TaxesID); err != nil { return errors.Wrapf(err, fmt.Sprintf("taxes ID %d does not exist", f.TaxesID)) } if found, err := f.PayWallet.SetTablePrefix(eco).Get(sc.DbTransaction, f.FromID); err != nil || !found { if err != nil { sc.GetLogger().WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting wallet") return err } err = fmt.Errorf(eEcoKeyNotFound, converter.AddressToString(f.FromID), eco) sc.GetLogger().WithFields(log.Fields{"type": consts.ContractError, "error": err}).Error("looking for keyid in ecosystem") return err } if f.PaymentType == PaymentType_ContractCaller && !bytes.Equal(sc.Key.PublicKey, f.PayWallet.PublicKey) && !bytes.Equal(sc.TxSmart.PublicKey, f.PayWallet.PublicKey) && sc.TxSmart.SignedBy == 0 { sc.GetLogger().WithFields(log.Fields{"type": consts.ParameterExceeded, "error": errDiffKeys}).Error(errDiffKeys) return errDiffKeys } estimate := f.GetEstimate() amount := f.PayWallet.CapableAmount() if amount.LessThan(estimate) { sp := &sqldb.Ecosystem{} _, err := sp.Get(sc.DbTransaction, eco) if err != nil { sc.GetLogger().WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting ecosystem") return err } difference, _ := converter.FormatMoney(estimate.Sub(amount).String(), int32(sp.Digits)) sc.GetLogger().WithFields(log.Fields{"type": consts.NoFunds, "token_eco": eco, "difference": difference}).Error("current balance is not enough") return fmt.Errorf(eEcoCurrentBalanceDiff, f.PayWallet.AccountID, eco, difference) } return nil } func (sc *SmartContract) resetFromIDForNativePay(from int64) *PaymentInfo { origin := sc.multiPays[0] cpy := &PaymentInfo{ TokenEco: origin.TokenEco, ToID: origin.ToID, TaxesID: origin.TaxesID, FromID: from, PaymentType: origin.PaymentType, FuelRate: origin.FuelRate, FuelCategories: make([]FuelCategory, 0), Ecosystem: origin.Ecosystem, PayWallet: new(sqldb.Key), Combustion: origin.Combustion, TaxesSize: origin.TaxesSize, } return cpy } func (sc *SmartContract) payContract(errNeedPay bool) error { sc.Penalty = errNeedPay placeholder := `taxes for execution of %s contract` comment := fmt.Sprintf(placeholder, sc.TxContract.Name) status := pbgo.TxInvokeStatusCode_SUCCESS if errNeedPay { comment = "(error)" + comment status = pbgo.TxInvokeStatusCode_PENALTY ts := sqldb.TransactionStatus{} if err := ts.UpdatePenalty(sc.DbTransaction, sc.Hash); err != nil { return err } } for i := 0; i < len(sc.multiPays); i++ { pay := sc.multiPays[i] pay.Penalty = sc.Penalty pay.SetDecimalByType(FuelType_vmCost_fee, sc.TxUsedCost.Mul(pay.FuelRate).Mul(decimal.New(1, int32(pay.Ecosystem.Digits-sc.multiPays[0].Ecosystem.Digits)))) money := pay.GetPayMoney() wltAmount := pay.PayWallet.CapableAmount() if wltAmount.Cmp(money) < 0 { if !errNeedPay { return fmt.Errorf("%s not enough fee for taxes in ecosystem %d", pay.PayWallet.AccountID, pay.TokenEco) } money = wltAmount } if pay.Indirect { if err := sc.payTaxes(pay, money, GasScenesType_Direct, comment, status); err != nil { return err } continue } if pay.Combustion.Flag == 2 && pay.TokenEco != consts.DefaultTokenEcosystem { combustion := pay.Combustion.Fees(money) if err := sc.payTaxes(pay, combustion, GasScenesType_Combustion, comment, status); err != nil { return err } money = money.Sub(combustion) } taxes := money.Mul(decimal.NewFromInt(pay.TaxesSize)).Div(decimal.New(100, 0)).Floor() if err := sc.payTaxes(pay, money.Sub(taxes), GasScenesType_Reward, comment, status); err != nil { return err } if err := sc.payTaxes(pay, taxes, GasScenesType_Taxes, comment, status); err != nil { return err } } return nil } func (sc *SmartContract) accountBalanceSingle(eco, id int64) (decimal.Decimal, error) { key := &sqldb.Key{} _, err := key.SetTablePrefix(eco).Get(sc.DbTransaction, id) if err != nil { sc.GetLogger().WithFields(log.Fields{"type": consts.ParameterExceeded, "token_ecosystem": eco, "wallet": id}).Error("get key balance") return decimal.Zero, err } balance, _ := decimal.NewFromString(key.Amount) return balance, nil } func (sc *SmartContract) payTaxes(pay *PaymentInfo, sum decimal.Decimal, t GasScenesType, comment string, status pbgo.TxInvokeStatusCode) error { if sum.IsZero() { return nil } var toID int64 switch t { case GasScenesType_Reward, GasScenesType_Direct: toID = pay.ToID case GasScenesType_Combustion: toID = 0 case GasScenesType_Taxes: toID = pay.TaxesID } if err := sc.hasExistKeyID(pay.TokenEco, toID); err != nil { return err } var ( values *types.Map fromIDBalance, toIDBalance decimal.Decimal err error ) if pay.FromID == toID { if fromIDBalance, err = sc.accountBalanceSingle(pay.TokenEco, pay.FromID); err != nil { return err } toIDBalance = fromIDBalance } else { if _, _, err := sc.updateWhere( []string{`-amount`}, []any{sum}, "1_keys", types.LoadMap(map[string]any{ `id`: pay.FromID, `ecosystem`: pay.TokenEco, })); err != nil { return err } if _, _, err := sc.updateWhere( []string{"+amount"}, []any{sum}, "1_keys", types.LoadMap(map[string]any{ "id": toID, "ecosystem": pay.TokenEco, })); err != nil { return err } if fromIDBalance, err = sc.accountBalanceSingle(pay.TokenEco, pay.FromID); err != nil { return err } if toIDBalance, err = sc.accountBalanceSingle(pay.TokenEco, toID); err != nil { return err } } values = types.LoadMap(map[string]any{ "sender_id": pay.FromID, "sender_balance": fromIDBalance, "recipient_id": toID, "recipient_balance": toIDBalance, "amount": sum, "comment": comment, "status": int64(status), "block_id": sc.BlockHeader.BlockId, "txhash": sc.Hash, "ecosystem": pay.TokenEco, "type": int64(t), "created_at": sc.Timestamp, }) if t == GasScenesType_Reward || t == GasScenesType_Direct { values.Set("value_detail", pay.Detail()) } if t == GasScenesType_Combustion { values.Set("value_detail", pay.DetailCombustion()) } _, _, err = sc.insert(values.Keys(), values.Values(), `1_history`) if err != nil { return err } return nil } func (sc *SmartContract) needPayment() bool { return sc.TxSmart.EcosystemID > 0 && !sc.CLB && !syspar.IsPrivateBlockchain() && sc.payFreeContract() } func (sc *SmartContract) hasExistKeyID(eco, id int64) error { key := &sqldb.Key{} found, err := key.SetTablePrefix(eco).Get(sc.DbTransaction, id) if err != nil { return err } if !found { _, _, err = DBInsert(sc, "@1keys", types.LoadMap(map[string]any{ "id": id, "account": converter.IDToAddress(id), "ecosystem": eco, })) if err != nil { return err } } keyOne := &sqldb.Key{} if foundOne, err := keyOne.SetTablePrefix(1).Get(sc.DbTransaction, id); err != nil { return err } else if !foundOne { _, _, err = DBInsert(sc, "@1keys", types.LoadMap(map[string]any{ "id": id, "account": converter.IDToAddress(id), "ecosystem": 1, })) if err != nil { return err } } return nil } func (sc *SmartContract) getFromIdAndPayType(eco int64) (int64, PaymentType) { var ( ownerInfo = sc.TxContract.Info().Owner paymentType PaymentType fromID int64 ) paymentType = PaymentType_ContractCaller fromID = sc.TxSmart.KeyID if ownerInfo.WalletID != 0 { paymentType = PaymentType_ContractBinder fromID = ownerInfo.WalletID return fromID, paymentType } if sc.TxSmart.EcosystemID != consts.DefaultTokenEcosystem && eco != consts.DefaultTokenEcosystem { ew := &sqldb.StateParameter{} if found, _ := ew.SetTablePrefix(converter.Int64ToStr(sc.TxSmart.EcosystemID)). Get(sc.DbTransaction, sqldb.EcosystemWallet); found && len(ew.Value) > 0 { ecosystemWallet := converter.AddressToID(ew.Value) if ecosystemWallet != 0 { paymentType = PaymentType_EcosystemAddress fromID = ecosystemWallet } } } return fromID, paymentType } // getChangeAddress return the payment address associated with the ecosystem func (sc *SmartContract) getChangeAddress(eco int64) ([]*PaymentInfo, error) { var ( err error storageFee decimal.Decimal expediteFee decimal.Decimal pays []*PaymentInfo feeMode *sqldb.FeeModeInfo curPay = &PaymentInfo{ TokenEco: eco, ToID: sc.BlockHeader.KeyId, PayWallet: new(sqldb.Key), Ecosystem: new(sqldb.Ecosystem), Combustion: new(Combustion), FuelCategories: make([]FuelCategory, 0), TaxesSize: syspar.SysInt64(syspar.TaxesSize), } ) if _, err = curPay.Ecosystem.Get(sc.DbTransaction, curPay.TokenEco); err != nil { return nil, err } feeMode, err = curPay.Ecosystem.FeeMode() if err != nil { return nil, err } if feeMode == nil && eco != consts.DefaultTokenEcosystem { return nil, nil } var f2 float64 if feeMode != nil { f2 = feeMode.FollowFuel } if curPay.FuelRate, err = sc.fuelRate(curPay.TokenEco, f2); err != nil { return nil, err } if curPay.TaxesID, err = sc.taxesWallet(curPay.TokenEco); err != nil { return nil, err } if expediteFee, err = expediteFeeBy(sc.TxSmart.Expedite, int32(curPay.Ecosystem.Digits)); err != nil { return nil, err } storageFee = storageFeeBy(sc.TxSize, int32(curPay.Ecosystem.Digits)) curPay.FromID, curPay.PaymentType = sc.getFromIdAndPayType(curPay.TokenEco) if feeMode != nil && eco != consts.DefaultTokenEcosystem { // only eco > 1 curPay.Combustion = newCombustion(feeMode.Combustion.Flag, feeMode.Combustion.Percent) } if feeMode != nil && curPay.TokenEco != consts.DefaultTokenEcosystem { if curPay.PaymentType == PaymentType_ContractCaller { return nil, nil } keys := make([]string, 0, len(feeMode.FeeModeDetail)) for k := range feeMode.FeeModeDetail { keys = append(keys, k) } sort.Strings(keys) var indirectBool bool for i := 0; i < len(keys); i++ { k := keys[i] flag := feeMode.FeeModeDetail[k] if _, ok := FuelType_value[k]; ok && GasPayAbleType(flag.FlagToInt()) == GasPayAbleType_Capable { indirectBool = true break } } if !indirectBool { return nil, nil } // indirect to reward and taxes for other eco indirectPay := &PaymentInfo{ Indirect: true, TokenEco: curPay.TokenEco, Ecosystem: curPay.Ecosystem, ToID: curPay.FromID, FromID: sc.TxSmart.KeyID, PaymentType: PaymentType_ContractCaller, FuelRate: curPay.FuelRate, FuelCategories: make([]FuelCategory, 0), PayWallet: new(sqldb.Key), Combustion: new(Combustion), } // caller to reward and taxes for platform eco cpyPlatCaller := sc.resetFromIDForNativePay(sc.TxSmart.KeyID) cpyPlatCaller.PaymentType = PaymentType_ContractCaller // indirect to reward and taxes for platform eco cpyPlatIndirect := sc.resetFromIDForNativePay(curPay.FromID) cpyPlatIndirect.PaymentType = curPay.PaymentType curPay.FromID = sc.TxSmart.KeyID curPay.PaymentType = PaymentType_ContractCaller // reset sc multiPays sc.multiPays = sc.multiPays[:0] for i := 0; i < len(keys); i++ { k := keys[i] flag := feeMode.FeeModeDetail[k] var categoryFee decimal.Decimal switch k { case FuelType_vmCost_fee.String(): categoryFee = decimal.NewFromInt(0) case FuelType_storage_fee.String(): categoryFee = storageFee case FuelType_expedite_fee.String(): categoryFee = expediteFee default: continue } category := NewFuelCategory(FuelType(FuelType_value[k]), categoryFee, GasPayAbleType(flag.FlagToInt()), flag.ConversionRateToFloat()) switch category.Flag { case GasPayAbleType_Unable: category.writeDecimal(category.Decimal.Shift(int32(cpyPlatCaller.Ecosystem.Digits - curPay.Ecosystem.Digits))) cpyPlatCaller.PushFuelCategories(category) case GasPayAbleType_Capable: // exclude FuelType_expedite_fee if category.FuelType != FuelType_expedite_fee { curPay.PushFuelCategories(category) category.writeArithmetic(Arithmetic_MUL) } indirectPay.PushFuelCategories(category) category.resetArithmetic() category.writeDecimal(category.Decimal.Shift(int32(cpyPlatIndirect.Ecosystem.Digits - curPay.Ecosystem.Digits))) if category.FuelType == FuelType_expedite_fee { category.writeDecimal(category.Decimal.Div(decimal.NewFromFloat(feeMode.FollowFuel))) } cpyPlatIndirect.PushFuelCategories(category) } } pays = append(pays, cpyPlatCaller, cpyPlatIndirect, indirectPay, curPay) return pays, nil } curPay.PushFuelCategories( NewFuelCategory(FuelType_vmCost_fee, decimal.NewFromInt(0), GasPayAbleType_Unable, 100), NewFuelCategory(FuelType_storage_fee, storageFee, GasPayAbleType_Unable, 100), NewFuelCategory(FuelType_expedite_fee, expediteFee, GasPayAbleType_Unable, 100), ) pays = append(pays, curPay) return pays, nil } func (sc *SmartContract) prepareMultiPay() error { ownerInfo := sc.TxContract.Info().Owner if err := sc.appendTokens(ownerInfo.TokenID, sc.TxSmart.EcosystemID); err != nil { return err } for i := 0; i < len(sc.getTokenEcos()); i++ { eco := sc.getTokenEcos()[i] pays, err := sc.getChangeAddress(eco) if err != nil { return err } for _, pay := range pays { if err = pay.checkVerify(sc); err != nil { return err } } sc.multiPays = append(sc.multiPays, pays...) } return nil } func (sc *SmartContract) appendTokens(nums ...int64) error { sc.TokenEcosystems = make(map[int64]any) sc.TokenEcosystems[consts.DefaultTokenEcosystem] = nil for i := 0; i < len(nums); i++ { num := nums[i] if num <= 1 { continue } if _, ok := sc.TokenEcosystems[num]; ok { continue } ecosystems := &sqldb.Ecosystem{} _, err := ecosystems.Get(sc.DbTransaction, num) if err != nil { return err } if len(ecosystems.TokenSymbol) <= 0 { continue } sc.TokenEcosystems[num] = nil } return nil } func (sc *SmartContract) getTokenEcos() []int64 { var ecos []int64 for i := range sc.TokenEcosystems { ecos = append(ecos, i) } sortkeys.Int64s(ecos) return ecos } func (sc *SmartContract) payFreeContract() bool { var ( pfca []string ispay bool ) pfc := syspar.SysString(syspar.PayFreeContract) if len(pfc) > 0 { pfca = strings.Split(pfc, ",") } for _, value := range pfca { if strings.TrimSpace(value) == sc.TxContract.Name { ispay = true break } } return !ispay } func (sc *SmartContract) fuelRate(eco int64, followFuel float64) (decimal.Decimal, error) { var ( fuelRate decimal.Decimal err error zero = decimal.Zero ) if _, ok := syspar.HasFuelRate(eco); !ok { fuels := make([][]string, 0) err = json.Unmarshal([]byte(syspar.SysString(syspar.FuelRate)), &fuels) if err != nil { return zero, err } follow, _ := decimal.NewFromString(syspar.GetFuelRate(consts.DefaultTokenEcosystem)) times := decimal.NewFromFloat(followFuel) if times.LessThanOrEqual(zero) { times = decimal.New(1, 0) } follow = follow.Mul(times) var newFuel []string newFuel = append(newFuel, strconv.FormatInt(eco, 10), follow.String()) fuels = append(fuels, newFuel) fuel, err := json.Marshal(fuels) if err != nil { return zero, err } sc.taxes = true _, err = UpdatePlatformParam(sc, syspar.FuelRate, string(fuel), "") if err != nil { return zero, err } } fuelRate, err = decimal.NewFromString(syspar.GetFuelRate(eco)) if err != nil { return zero, err } if fuelRate.Cmp(zero) <= 0 { sc.GetLogger().WithFields(log.Fields{"type": consts.ParameterExceeded, "token_ecosystem": eco}).Error("fuel rate must be greater than 0") err = fmt.Errorf(eEcoFuelRate, eco) return zero, err } return fuelRate, nil } func (sc *SmartContract) taxesWallet(eco int64) (taxesID int64, err error) { if _, ok := syspar.HasTaxesWallet(eco); !ok { var taxesPub []byte err = sqldb.GetDB(sc.DbTransaction).Select("pub"). Model(&sqldb.Key{}).Where("id = ? AND ecosystem = 1", syspar.GetTaxesWallet(1)).Row().Scan(&taxesPub) if err != nil { return } id := PubToID(fmt.Sprintf("%x", taxesPub)) if err = sc.hasExistKeyID(eco, id); err != nil { return } taxes := make([][]string, 0) err = json.Unmarshal([]byte(syspar.SysString(syspar.TaxesWallet)), &taxes) if err != nil { return } var newTaxes []string var tax []byte newTaxes = append(newTaxes, strconv.FormatInt(eco, 10), strconv.FormatInt(id, 10)) taxes = append(taxes, newTaxes) tax, err = json.Marshal(taxes) if err != nil { return } sc.taxes = true _, err = UpdatePlatformParam(sc, syspar.TaxesWallet, string(tax), "") if err != nil { return } } taxesID = converter.StrToInt64(syspar.GetTaxesWallet(eco)) if taxesID == 0 { err = fmt.Errorf("get eco[%d] taxes wallet err", eco) } return } func storageFeeBy(txSize int64, digits int32) decimal.Decimal { var ( storageFee decimal.Decimal zero = decimal.Zero ) storageFee = decimal.NewFromInt(syspar.SysInt64(syspar.PriceTxSize)). Mul(decimal.New(1, digits)). Mul(decimal.NewFromInt(txSize)). Div(decimal.NewFromInt(consts.ChainSize)) if storageFee.LessThanOrEqual(zero) { storageFee = decimal.New(1, 0) } return storageFee } func expediteFeeBy(expedite string, digits int32) (decimal.Decimal, error) { zero := decimal.Zero if len(expedite) > 0 { expedite, err := decimal.NewFromString(expedite) if err != nil { return zero, fmt.Errorf("expediteFeeBy: %s", err) } if expedite.LessThan(zero) { return zero, fmt.Errorf(eGreaterThan, expedite) } return expedite.Shift(digits), nil } return zero, nil } ================================================ FILE: packages/smart/gas.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: gas.proto package smart import ( fmt "fmt" proto "github.com/gogo/protobuf/proto" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type PaymentType int32 const ( PaymentType_INVALID PaymentType = 0 PaymentType_ContractCaller PaymentType = 1 PaymentType_ContractBinder PaymentType = 2 PaymentType_EcosystemAddress PaymentType = 3 ) var PaymentType_name = map[int32]string{ 0: "INVALID", 1: "ContractCaller", 2: "ContractBinder", 3: "EcosystemAddress", } var PaymentType_value = map[string]int32{ "INVALID": 0, "ContractCaller": 1, "ContractBinder": 2, "EcosystemAddress": 3, } func (x PaymentType) String() string { return proto.EnumName(PaymentType_name, int32(x)) } func (PaymentType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_df176b4a803aa869, []int{0} } type GasScenesType int32 const ( GasScenesType_Unknown GasScenesType = 0 GasScenesType_Reward GasScenesType = 1 GasScenesType_Taxes GasScenesType = 2 GasScenesType_Direct GasScenesType = 15 GasScenesType_Combustion GasScenesType = 16 GasScenesType_TransferSelf GasScenesType = 24 ) var GasScenesType_name = map[int32]string{ 0: "Unknown", 1: "Reward", 2: "Taxes", 15: "Direct", 16: "Combustion", 24: "TransferSelf", } var GasScenesType_value = map[string]int32{ "Unknown": 0, "Reward": 1, "Taxes": 2, "Direct": 15, "Combustion": 16, "TransferSelf": 24, } func (x GasScenesType) String() string { return proto.EnumName(GasScenesType_name, int32(x)) } func (GasScenesType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_df176b4a803aa869, []int{1} } type GasPayAbleType int32 const ( GasPayAbleType_Invalid GasPayAbleType = 0 GasPayAbleType_Unable GasPayAbleType = 1 GasPayAbleType_Capable GasPayAbleType = 2 ) var GasPayAbleType_name = map[int32]string{ 0: "Invalid", 1: "Unable", 2: "Capable", } var GasPayAbleType_value = map[string]int32{ "Invalid": 0, "Unable": 1, "Capable": 2, } func (x GasPayAbleType) String() string { return proto.EnumName(GasPayAbleType_name, int32(x)) } func (GasPayAbleType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_df176b4a803aa869, []int{2} } type FuelType int32 const ( FuelType_UNKNOWN FuelType = 0 FuelType_vmCost_fee FuelType = 1 FuelType_storage_fee FuelType = 2 FuelType_expedite_fee FuelType = 3 ) var FuelType_name = map[int32]string{ 0: "UNKNOWN", 1: "vmCost_fee", 2: "storage_fee", 3: "expedite_fee", } var FuelType_value = map[string]int32{ "UNKNOWN": 0, "vmCost_fee": 1, "storage_fee": 2, "expedite_fee": 3, } func (x FuelType) String() string { return proto.EnumName(FuelType_name, int32(x)) } func (FuelType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_df176b4a803aa869, []int{3} } type Arithmetic int32 const ( Arithmetic_NATIVE Arithmetic = 0 Arithmetic_MUL Arithmetic = 3 Arithmetic_DIV Arithmetic = 4 ) var Arithmetic_name = map[int32]string{ 0: "NATIVE", 3: "MUL", 4: "DIV", } var Arithmetic_value = map[string]int32{ "NATIVE": 0, "MUL": 3, "DIV": 4, } func (x Arithmetic) String() string { return proto.EnumName(Arithmetic_name, int32(x)) } func (Arithmetic) EnumDescriptor() ([]byte, []int) { return fileDescriptor_df176b4a803aa869, []int{4} } func init() { proto.RegisterEnum("smart.PaymentType", PaymentType_name, PaymentType_value) proto.RegisterEnum("smart.GasScenesType", GasScenesType_name, GasScenesType_value) proto.RegisterEnum("smart.GasPayAbleType", GasPayAbleType_name, GasPayAbleType_value) proto.RegisterEnum("smart.FuelType", FuelType_name, FuelType_value) proto.RegisterEnum("smart.Arithmetic", Arithmetic_name, Arithmetic_value) } func init() { proto.RegisterFile("gas.proto", fileDescriptor_df176b4a803aa869) } var fileDescriptor_df176b4a803aa869 = []byte{ // 385 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0xc1, 0x6e, 0xd3, 0x40, 0x10, 0x86, 0xed, 0x84, 0xb6, 0x74, 0x02, 0xe9, 0x68, 0xc5, 0x81, 0x93, 0xef, 0x58, 0x6a, 0x73, 0x40, 0xe2, 0xee, 0x38, 0xa5, 0x32, 0x14, 0x53, 0xd1, 0x24, 0x54, 0x5c, 0xd0, 0xd8, 0x9e, 0xb8, 0xab, 0xda, 0xbb, 0xd6, 0xee, 0xa6, 0x8d, 0xdf, 0x82, 0xc7, 0xe2, 0xd8, 0x23, 0x47, 0x94, 0xbc, 0x08, 0xb2, 0x2b, 0xa2, 0xde, 0x76, 0xbf, 0xd1, 0xcc, 0xff, 0x49, 0x3f, 0x1c, 0x97, 0x64, 0xcf, 0x1a, 0xa3, 0x9d, 0x16, 0x07, 0xb6, 0x26, 0xe3, 0xc2, 0x1b, 0x18, 0x5d, 0x51, 0x5b, 0xb3, 0x72, 0xf3, 0xb6, 0x61, 0x31, 0x82, 0xa3, 0x24, 0x5d, 0x46, 0x97, 0xc9, 0x0c, 0x3d, 0x21, 0x60, 0x1c, 0x6b, 0xe5, 0x0c, 0xe5, 0x2e, 0xa6, 0xaa, 0x62, 0x83, 0xfe, 0x73, 0x36, 0x95, 0xaa, 0x60, 0x83, 0x03, 0xf1, 0x06, 0xf0, 0x3c, 0xd7, 0xb6, 0xb5, 0x8e, 0xeb, 0xa8, 0x28, 0x0c, 0x5b, 0x8b, 0xc3, 0x90, 0xe0, 0xf5, 0x05, 0xd9, 0xeb, 0x9c, 0x15, 0xdb, 0xff, 0xb7, 0x17, 0xea, 0x4e, 0xe9, 0x07, 0x85, 0x9e, 0x00, 0x38, 0xfc, 0xc6, 0x0f, 0x64, 0x0a, 0xf4, 0xc5, 0x31, 0x1c, 0xcc, 0x69, 0xc3, 0x16, 0x07, 0x1d, 0x9e, 0x49, 0xc3, 0xb9, 0xc3, 0x13, 0x31, 0x06, 0x88, 0x75, 0x9d, 0xad, 0xad, 0x93, 0x5a, 0x21, 0x0a, 0x84, 0x57, 0x73, 0x43, 0xca, 0xae, 0xd8, 0x5c, 0x73, 0xb5, 0xc2, 0xb7, 0xe1, 0x07, 0x18, 0x5f, 0x90, 0xbd, 0xa2, 0x36, 0xca, 0x2a, 0xde, 0xfb, 0xab, 0x7b, 0xaa, 0x64, 0xf1, 0x94, 0xb1, 0x50, 0x94, 0x55, 0x8c, 0x7e, 0x37, 0x88, 0xa9, 0xe9, 0x3f, 0x83, 0xf0, 0x13, 0xbc, 0xfc, 0xb8, 0xe6, 0x6a, 0x6f, 0x95, 0x7e, 0x4e, 0xbf, 0x7e, 0x4f, 0xd1, 0xeb, 0x22, 0xef, 0xeb, 0x58, 0x5b, 0xf7, 0x73, 0xc5, 0xdd, 0xd6, 0x09, 0x8c, 0xac, 0xd3, 0x86, 0x4a, 0xee, 0xc1, 0xa0, 0x73, 0xe0, 0x4d, 0xc3, 0x85, 0x74, 0x4f, 0x64, 0x18, 0x86, 0x00, 0x91, 0x91, 0xee, 0xb6, 0x66, 0x27, 0xf3, 0x2e, 0x32, 0x8d, 0xe6, 0xc9, 0xf2, 0x1c, 0x3d, 0x71, 0x04, 0xc3, 0x2f, 0x8b, 0x4b, 0x1c, 0x76, 0x8f, 0x59, 0xb2, 0xc4, 0x17, 0xd3, 0xf8, 0xf7, 0x36, 0xf0, 0x1f, 0xb7, 0x81, 0xff, 0x77, 0x1b, 0xf8, 0xbf, 0x76, 0x81, 0xf7, 0xb8, 0x0b, 0xbc, 0x3f, 0xbb, 0xc0, 0xfb, 0xf1, 0xae, 0x94, 0xee, 0x76, 0x9d, 0x9d, 0xe5, 0xba, 0x9e, 0x24, 0xd3, 0xe8, 0xe6, 0x54, 0xea, 0x49, 0xa9, 0x4f, 0x65, 0x46, 0x9b, 0x49, 0x43, 0xf9, 0x1d, 0x95, 0x6c, 0x27, 0x7d, 0x63, 0xd9, 0x61, 0xdf, 0xdf, 0xfb, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x62, 0xf6, 0x93, 0xcc, 0x01, 0x00, 0x00, } ================================================ FILE: packages/smart/math.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "github.com/shopspring/decimal" "math" "strconv" ) func parseFloat(x any) (float64, error) { var ( fx float64 err error ) switch v := x.(type) { case float64: fx = v case int64: fx = float64(v) case string: if fx, err = strconv.ParseFloat(v, 64); err != nil { return 0, errFloat } default: return 0, errFloat } return fx, nil } func isValidFloat(x float64) bool { return !(math.IsNaN(x) || math.IsInf(x, 1) || math.IsInf(x, -1)) } // Floor returns the greatest integer value less than or equal to x func Floor(x any) (int64, error) { fx, err := parseFloat(x) if err != nil { return 0, err } if fx = math.Floor(fx); isValidFloat(fx) { return int64(fx), nil } return 0, errFloatResult } // Log returns the natural logarithm of x func Log(x any) (float64, error) { fx, err := parseFloat(x) if err != nil { return 0, err } if fx = math.Log(fx); isValidFloat(fx) { return fx, nil } return 0, errFloatResult } // Log10 returns the decimal logarithm of x func Log10(x any) (float64, error) { fx, err := parseFloat(x) if err != nil { return 0, err } if fx = math.Log10(fx); isValidFloat(fx) { return fx, nil } return 0, errFloatResult } // Pow returns x**y, the base-x exponential of y func Pow(x, y any) (float64, error) { fx, err := parseFloat(x) if err != nil { return 0, err } fy, err := parseFloat(y) if err != nil { return 0, err } if fx = math.Pow(fx, fy); isValidFloat(fx) { return fx, nil } return 0, errFloatResult } // Round returns the nearest integer, rounding half away from zero func Round(x any) (int64, error) { fx, err := parseFloat(x) if err != nil { return 0, err } if fx = math.Round(fx); isValidFloat(fx) { return int64(fx), nil } return 0, errFloatResult } // Sqrt returns the square root of x func Sqrt(x any) (float64, error) { fx, err := parseFloat(x) if err != nil { return 0, err } if fx = math.Sqrt(fx); isValidFloat(fx) { return fx, nil } return 0, errFloatResult } func SqrtDecimal(str string) decimal.Decimal { y, _ := decimal.NewFromString(str) var z decimal.Decimal var three = decimal.NewFromInt(3) var two = decimal.NewFromInt(2) var one = decimal.NewFromInt(1) if y.GreaterThan(three) { z = y x := y.Div(two).Add(one) for x.LessThan(z) { z = x x = (y.Div(x).Add(x)).Div(two) } } else if !y.IsZero() { z = one } return z } func Div(str1 string, str2 string) string { x, _ := decimal.NewFromString(str1) y, _ := decimal.NewFromString(str2) return x.Div(y).String() } func GreaterThan(str1 string, str2 string) bool { x, _ := decimal.NewFromString(str1) y, _ := decimal.NewFromString(str2) return x.GreaterThan(y) } func GreaterThanOrEqual(str1 string, str2 string) bool { x, _ := decimal.NewFromString(str1) y, _ := decimal.NewFromString(str2) return x.GreaterThanOrEqual(y) } func LessThan(str1 string, str2 string) bool { x, _ := decimal.NewFromString(str1) y, _ := decimal.NewFromString(str2) return x.LessThan(y) } func LessThanOrEqual(str1 string, str2 string) bool { x, _ := decimal.NewFromString(str1) y, _ := decimal.NewFromString(str2) return x.LessThanOrEqual(y) } ================================================ FILE: packages/smart/selective.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/storage/sqldb/querycost" "github.com/IBAX-io/go-ibax/packages/types" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" log "github.com/sirupsen/logrus" ) func addRollback(sc *SmartContract, table, tableID, rollbackInfoStr, rollDataHashStr string) { rollbackTx := &types.RollbackTx{ BlockId: sc.BlockHeader.BlockId, TxHash: sc.Hash, NameTable: table, TableId: tableID, Data: rollbackInfoStr, DataHash: crypto.Hash([]byte(rollDataHashStr)), } sc.RollBackTx = append(sc.RollBackTx, rollbackTx) } func (sc *SmartContract) selectiveLoggingAndUpd(fields []string, ivalues []any, table string, inWhere *types.Map, generalRollback bool, exists bool) (int64, string, error) { var ( cost int64 logData map[string]string rows *sqldb.OneRow ) logger := sc.GetLogger() if generalRollback && sc.BlockHeader == nil { logger.WithFields(log.Fields{"type": consts.EmptyObject}).Error("Block is undefined") return 0, ``, fmt.Errorf(`it is impossible to write to DB when Block is undefined`) } for i, field := range fields { fields[i] = strings.ToLower(field) } sqlBuilder := &qb.SQLQueryBuilder{ Entry: logger, Table: table, Fields: fields, FieldValues: ivalues, Where: inWhere, TxEcoID: sc.TxSmart.EcosystemID, KeyTableChkr: sqldb.KeyTableChecker{}, } queryCoster := querycost.GetQueryCoster(querycost.FormulaQueryCosterType) if exists { selectQuery, err := sqlBuilder.GetSelectExpr() if err != nil { logger.WithError(err).Error("on getting sql select statement") return 0, "", err } selectCost, err := queryCoster.QueryCost(sc.DbTransaction, selectQuery) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table, "query": selectQuery, "fields": fields, "values": ivalues, "where": inWhere}).Error("getting query total cost") return 0, "", err } rows = sc.DbTransaction.GetOneRowTransaction(selectQuery) logData, err = rows.String() if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "query": selectQuery}).Error("getting one row transaction") return 0, "", err } cost += selectCost if len(logData) == 0 { logger.WithFields(log.Fields{"type": consts.NotFound, "err": errUpdNotExistRecord, "table": table, "fields": fields, "values": shortString(fmt.Sprintf("%+v", ivalues), 100), "where": inWhere, "query": shortString(selectQuery, 100)}).Error("updating for not existing record") return 0, "", errUpdNotExistRecord } if sqlBuilder.IsEmptyWhere() { logger.WithFields(log.Fields{"type": consts.NotFound, "error": errWhereUpdate}).Error("update without where") return 0, "", errWhereUpdate } } var rollDataHashStr string if !sqlBuilder.Where.IsEmpty() && len(logData) > 0 { updateExpr, err := sqlBuilder.GetSQLUpdateExpr(logData) if err != nil { logger.WithError(err).Error("on getting update expression for update") return 0, "", err } whereExpr, err := sqlBuilder.GetSQLWhereExpr() if err != nil { logger.WithError(err).Error("on getting where expression for update") return 0, "", err } if !sc.CLB { updateQuery := `UPDATE "` + sqlBuilder.Table + `" SET ` + updateExpr + " " + whereExpr updateCost, err := queryCoster.QueryCost(sc.DbTransaction, updateQuery) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "query": updateQuery}).Error("getting query total cost for update query") return 0, "", err } cost += updateCost } rollDataHashStr = `UPDATE "` + strings.Trim(sqlBuilder.Table, `"`) + `" SET ` + updateExpr + " " + whereExpr err = sc.DbTransaction.Update(sqlBuilder.Table, updateExpr, whereExpr) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": sqlBuilder.Table, "update": updateExpr, "where": whereExpr}).Error("getting update query") return 0, "", err } for i := 0; i < len(rows.List) && generalRollback; i++ { rollData := rows.List[i] rollbackInfoStr, err := sqlBuilder.GenerateRollBackInfoString(rollData) if err != nil { logger.WithError(err).Error("on generate rollback info string for update") return 0, "", err } addRollback(sc, sqlBuilder.Table, rollData["id"], rollbackInfoStr, rollDataHashStr) } var ids []string for _, result := range rows.List { if len(result["id"]) > 0 { ids = append(ids, result["id"]) } } if len(ids) == 1 { sqlBuilder.SetTableID(ids[0]) } else { sqlBuilder.SetTableID(strings.Join(ids, ",")) } return cost, sqlBuilder.TableID(), nil } insertQuery, err := sqlBuilder.GetSQLInsertQuery(sqldb.NextIDGetter{Tx: sc.DbTransaction}) if err != nil { logger.WithError(err).Error("on build insert query") return 0, "", err } insertCost, err := queryCoster.QueryCost(sc.DbTransaction, insertQuery) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "query": insertQuery}).Error("getting total query cost for insert query") return 0, "", err } rollDataHashStr = insertQuery cost += insertCost err = sc.DbTransaction.ExecSql(insertQuery) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err, "query": insertQuery}).Error("executing insert query") return 0, "", err } if generalRollback { var tid string tid = sqlBuilder.TableID() idNames := strings.SplitN(sqlBuilder.Table, `_`, 2) if len(idNames) == 2 { if sqlBuilder.KeyTableChkr.IsKeyTable(idNames[1]) { tid = sqlBuilder.TableID() + "," + sqlBuilder.GetEcosystem() } } addRollback(sc, sqlBuilder.Table, tid, "", rollDataHashStr) } return cost, sqlBuilder.TableID(), nil } func (sc *SmartContract) insert(fields []string, ivalues []any, table string) (int64, string, error) { return sc.selectiveLoggingAndUpd(fields, ivalues, table, nil, !sc.CLB && sc.Rollback, false) } func (sc *SmartContract) updateWhere(fields []string, values []any, table string, where *types.Map) (int64, string, error) { return sc.selectiveLoggingAndUpd(fields, values, table, where, !sc.CLB && sc.Rollback, true) } func (sc *SmartContract) update(fields []string, values []any, table string, whereField string, whereValue any) (int64, string, error) { return sc.updateWhere(fields, values, table, types.LoadMap(map[string]any{ whereField: fmt.Sprint(whereValue)})) } func shortString(raw string, length int) string { if len(raw) > length { return raw[:length] } return raw } ================================================ FILE: packages/smart/smart.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "encoding/hex" "encoding/json" "fmt" "math/rand" "sort" "strings" "unicode/utf8" "github.com/IBAX-io/go-ibax/packages/common" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) const ( // MaxPrice is a maximal value that price function can return MaxPrice = 100000000000000000 ) const ( CallDelayedContract = "@1CallDelayedContract" NewUserContract = "@1NewUser" NewBadBlockContract = "@1NewBadBlock" ) var ( builtinContract = map[string]bool{ CallDelayedContract: true, NewUserContract: true, NewBadBlockContract: true, } ) // SmartContract is storing smart contract data type SmartContract struct { CLB bool Rollback bool FullAccess bool SysUpdate bool VM *script.VM TxSmart *types.SmartTransaction TxData map[string]any TxContract *Contract TxFuel int64 // The fuel of executing contract TxCost int64 // Maximum cost of executing contract TxUsedCost decimal.Decimal // Used cost of CPU resources TXBlockFuel decimal.Decimal BlockHeader *types.BlockHeader PreBlockHeader *types.BlockHeader Loop map[string]bool Hash []byte Payload []byte Timestamp int64 TxSignature []byte TxSize int64 Size common.StorageSize PublicKeys [][]byte DbTransaction *sqldb.DbTransaction Rand *rand.Rand FlushRollback []*FlushInfo Notifications types.Notifications GenBlock bool TimeLimit int64 Key *sqldb.Key RollBackTx []*types.RollbackTx multiPays multiPays taxes bool Penalty bool TokenEcosystems map[int64]any OutputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo TxInputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo TxOutputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo PrevSysPar map[string]string EcoParams []sqldb.EcoParam } // AppendStack adds an element to the stack of contract call or removes the top element when name is empty func (sc *SmartContract) AppendStack(fn string) error { if sc.isAllowStack(fn) { cont := sc.TxContract for _, item := range cont.StackCont { if item == fn { return fmt.Errorf(eContractLoop, fn) } } cont.StackCont = append(cont.StackCont, fn) sc.TxContract.Extend[script.Extend_stack] = cont.StackCont } return nil } func (sc *SmartContract) PopStack(fn string) { if sc.isAllowStack(fn) { cont := sc.TxContract if len(cont.StackCont) > 0 { cont.StackCont = cont.StackCont[:len(cont.StackCont)-1] sc.TxContract.Extend[script.Extend_stack] = cont.StackCont } } } func (sc *SmartContract) isAllowStack(fn string) bool { // Stack contains only contracts c := VMGetContract(sc.VM, fn, uint32(sc.TxSmart.EcosystemID)) return c != nil } func InitVM() { script.GetVM().SetExtendCost(getCost) script.GetVM().SetFuncCallsDB(funcCallsDBP) script.GetVM().Extend(&script.ExtendData{ Objects: EmbedFuncs(defineVMType()), AutoPars: map[string]string{ `*smart.SmartContract`: `sc`, }, WriteFuncs: writeFuncs, }) } func defineVMType() script.VMType { if conf.Config.IsCLB() { return script.VMType_CLB } if conf.Config.IsCLBMaster() { return script.VMType_CLBMaster } return script.VMType_Smart } // GetLogger is returning logger func (sc *SmartContract) GetLogger() *log.Entry { var name string if sc.TxContract != nil { name = sc.TxContract.Name } return log.WithFields(log.Fields{"tx": fmt.Sprintf("%x", sc.Hash), "clb": sc.CLB, "name": name, "tx_eco": sc.TxSmart.EcosystemID}) } func GetAllContracts() (string, error) { var ret []string for k := range script.GetVM().Objects { ret = append(ret, k) } sort.Strings(ret) resultByte, err := json.Marshal(ret) result := string(resultByte) return result, err } // ActivateContract sets Active status of the contract in script.GetVM() func ActivateContract(tblid, state int64, active bool) { for i, item := range script.GetVM().CodeBlock.Children { if item != nil && item.Type == script.ObjectType_Contract { cinfo := item.GetContractInfo() if cinfo.Owner.TableID == tblid && cinfo.Owner.StateID == uint32(state) { script.GetVM().Children[i].GetContractInfo().Owner.Active = active } } } } // SetContractWallet changes WalletID of the contract in script.GetVM() func SetContractWallet(sc *SmartContract, tblid, state int64, wallet int64) error { if err := validateAccess(sc, "SetContractWallet"); err != nil { return err } for i, item := range script.GetVM().CodeBlock.Children { if item != nil && item.Type == script.ObjectType_Contract { cinfo := item.GetContractInfo() if cinfo.Owner.TableID == tblid && cinfo.Owner.StateID == uint32(state) { script.GetVM().Children[i].GetContractInfo().Owner.WalletID = wallet } } } return nil } func (sc *SmartContract) getExtend() map[string]any { var block, blockTime, blockKeyID, blockNodePosition int64 var perBlockHash string if sc.BlockHeader != nil { block = sc.BlockHeader.BlockId blockKeyID = sc.BlockHeader.KeyId blockTime = sc.BlockHeader.Timestamp blockNodePosition = sc.BlockHeader.NodePosition } if sc.PreBlockHeader != nil { perBlockHash = hex.EncodeToString(sc.PreBlockHeader.BlockHash) } head := sc.TxSmart extend := map[string]any{ script.Extend_type: head.ID, script.Extend_time: sc.Timestamp, script.Extend_ecosystem_id: head.EcosystemID, script.Extend_node_position: blockNodePosition, script.Extend_block: block, script.Extend_key_id: sc.Key.ID, script.Extend_account_id: sc.Key.AccountID, script.Extend_block_key_id: blockKeyID, script.Extend_parent: ``, script.Extend_txcost: sc.GetContractLimit(), script.Extend_txhash: sc.Hash, //script.Extend_result: ``, script.Extend_sc: sc, script.Extend_contract: sc.TxContract, script.Extend_block_time: blockTime, script.Extend_original_contract: ``, script.Extend_this_contract: ``, script.Extend_guest_key: consts.GuestKey, script.Extend_guest_account: consts.GuestAddress, script.Extend_black_hole_key: converter.HoleAddrMap[converter.BlackHoleAddr].K, script.Extend_black_hole_account: converter.HoleAddrMap[converter.BlackHoleAddr].S, script.Extend_white_hole_key: converter.HoleAddrMap[converter.WhiteHoleAddr].K, script.Extend_white_hole_account: converter.HoleAddrMap[converter.WhiteHoleAddr].S, script.Extend_pre_block_data_hash: perBlockHash, script.Extend_gen_block: sc.GenBlock, script.Extend_time_limit: sc.TimeLimit, } for key, val := range sc.TxData { extend[key] = val } return extend } func PrefixName(table string) (prefix, name string) { name = table if off := strings.IndexByte(table, '_'); off > 0 && table[0] >= '0' && table[0] <= '9' { prefix = table[:off] name = table[off+1:] } return } func (sc *SmartContract) IsCustomTable(table string) (isCustom bool, err error) { prefix, name := PrefixName(table) if len(prefix) > 0 { tables := &sqldb.Table{} tables.SetTablePrefix(prefix) found, err := tables.Get(sc.DbTransaction, name) if err != nil { return false, err } if found { return true, nil } } return false, nil } func (sc *SmartContract) AccessTablePerm(table, action string) (map[string]string, error) { var ( err error tablePermission map[string]string ) logger := sc.GetLogger() isRead := action == `read` if qb.GetTableName(sc.TxSmart.EcosystemID, table) == `1_parameters` || qb.GetTableName(sc.TxSmart.EcosystemID, table) == `1_app_params` { if isRead || sc.TxSmart.KeyID == converter.StrToInt64(EcosysParam(sc, `founder_account`)) { return tablePermission, nil } logger.WithFields(log.Fields{"type": consts.AccessDenied}).Error("Access denied") return tablePermission, errAccessDenied } if isCustom, err := sc.IsCustomTable(table); err != nil { logger.WithFields(log.Fields{"table": table, "error": err, "type": consts.DBError}).Error("checking custom table") return tablePermission, err } else if !isCustom { if isRead { return tablePermission, nil } return tablePermission, fmt.Errorf(eNotCustomTable, table) } prefix, name := PrefixName(table) tables := &sqldb.Table{} tables.SetTablePrefix(prefix) tablePermission, err = tables.GetPermissions(sc.DbTransaction, name, "") if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting table permissions") return tablePermission, err } if len(tablePermission[action]) > 0 { ret, err := sc.EvalIf(tablePermission[action]) if err != nil { logger.WithFields(log.Fields{"table": table, "action": action, "permissions": tablePermission[action], "error": err, "type": consts.EvalError}).Error("evaluating table permissions for action") return tablePermission, err } if !ret { logger.WithFields(log.Fields{"table": table, "action": action, "permissions": tablePermission[action], "type": consts.EvalError}).Error("access denied") return tablePermission, fmt.Errorf("table: %w", errAccessDenied) } } return tablePermission, nil } // AccessTable checks the access right to the table func (sc *SmartContract) AccessTable(table, action string) error { if sc.FullAccess { return nil } _, err := sc.AccessTablePerm(table, action) return err } func getPermColumns(input string) (perm permColumn, err error) { if strings.HasPrefix(input, `{`) { err = unmarshalJSON([]byte(input), &perm, `on perm columns`) } else { perm.Update = input } return } // AccessColumns checks access rights to the columns func (sc *SmartContract) AccessColumns(table string, columns *[]string, update bool) error { logger := sc.GetLogger() if sc.FullAccess { return nil } if qb.GetTableName(sc.TxSmart.EcosystemID, table) == `1_parameters` || qb.GetTableName(sc.TxSmart.EcosystemID, table) == `1_app_params` { if update { if sc.TxSmart.KeyID == converter.StrToInt64(EcosysParam(sc, `founder_account`)) { return nil } log.WithFields(log.Fields{"txSmart.KeyId": sc.TxSmart.KeyID}).Error("ACCESS DENIED") return errAccessDenied } return nil } prefix, name := PrefixName(table) tables := &sqldb.Table{} tables.SetTablePrefix(prefix) found, err := tables.Get(sc.DbTransaction, name) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting table columns") return err } if !found { if !update { return nil } return fmt.Errorf(eTableNotFound, table) } var cols map[string]string err = json.Unmarshal([]byte(tables.Columns), &cols) if err != nil { logger.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("getting table columns") return err } colNames := make([]string, 0, len(*columns)) for _, col := range *columns { if col == `*` { for column := range cols { colNames = append(colNames, column) } continue } colNames = append(colNames, col) } colList := make([]string, len(colNames)) for i, col := range colNames { colname := converter.Sanitize(col, `->`) if strings.Contains(colname, `->`) { colname = colname[:strings.Index(colname, `->`)] } colList[i] = colname } checked := make(map[string]bool) var notaccess bool for i, name := range colList { if status, ok := checked[name]; ok { if !status { colList[i] = `` } continue } cond := cols[name] if len(cond) > 0 { perm, err := getPermColumns(cond) if err != nil { logger.WithFields(log.Fields{"type": consts.InvalidObject, "error": err}).Error("getting access columns") return err } if update { cond = perm.Update } else { cond = perm.Read } if len(cond) > 0 { ret, err := sc.EvalIf(cond) if err != nil { logger.WithFields(log.Fields{"condition": cond, "column": name, "type": consts.EvalError}).Error("evaluating condition") return err } checked[name] = ret if !ret { if update { logger.WithFields(log.Fields{"table": table, "column": name, "condition": cond, "type": consts.EvalError}).Error("access denied") return fmt.Errorf("column: %w", errAccessDenied) } colList[i] = `` notaccess = true } } } } if !update && notaccess { retColumn := make([]string, 0) for i, val := range colList { if val != `` { retColumn = append(retColumn, colNames[i]) } } if len(retColumn) == 0 { return errAccessDenied } *columns = retColumn } return nil } func (sc *SmartContract) CheckAccess(tableName, columns string, ecosystem int64) (table string, perm map[string]string, cols string, err error) { var collist []string table = converter.ParseTable(tableName, ecosystem) collist, err = qb.GetColumns(columns) if err != nil { return } if !syspar.IsPrivateBlockchain() { cols = PrepareColumns(collist) return } perm, err = sc.AccessTablePerm(table, `read`) if err != nil { return } if err = sc.AccessColumns(table, &collist, false); err != nil { return } cols = PrepareColumns(collist) return } // AccessRights checks the access right by executing the condition value func (sc *SmartContract) AccessRights(condition string, iscondition bool) error { sp := &sqldb.StateParameter{} prefix := converter.Int64ToStr(sc.TxSmart.EcosystemID) sp.SetTablePrefix(prefix) _, err := sp.Get(sc.DbTransaction, condition) if err != nil { return err } conditions := sp.Value if iscondition { conditions = sp.Conditions } if len(conditions) > 0 { ret, err := sc.EvalIf(conditions) if err != nil { return err } if !ret { return errAccessDenied } } else { return fmt.Errorf(eNotCondition, condition) } return nil } // EvalIf counts and returns the logical value of the specified expression func (sc *SmartContract) EvalIf(conditions string) (bool, error) { return sc.VM.EvalIf(conditions, uint32(sc.TxSmart.EcosystemID), sc.getExtend()) } // GetContractLimit returns the default maximal cost of contract func (sc *SmartContract) GetContractLimit() (ret int64) { // default maximum cost of F if len(sc.TxSmart.MaxSum) > 0 { sc.TxCost = converter.StrToInt64(sc.TxSmart.MaxSum) } else { sc.TxCost = syspar.GetMaxCost() } return sc.TxCost } func (sc *SmartContract) GetSignedBy(public []byte) (int64, error) { signedBy := sc.TxSmart.KeyID if sc.TxSmart.SignedBy != 0 { var isNode bool signedBy = sc.TxSmart.SignedBy if syspar.IsCandidateNodeMode() { return signedBy, nil } honorNodes := syspar.GetNodes() delay := sqldb.DelayedContract{} if ok, _ := delay.GetByContract(sc.DbTransaction, sc.TxContract.Name); !ok && !builtinContract[sc.TxContract.Name] { return 0, fmt.Errorf("%w: %v", errDelayedContract, sc.TxContract.Name) } if len(honorNodes) > 0 { for _, node := range honorNodes { if crypto.Address(node.PublicKey) == signedBy { isNode = true break } } } else { isNode = crypto.Address(syspar.GetNodePubKey()) == signedBy } if sc.TxContract.Name == NewUserContract && !isNode { return signedBy, nil } if !isNode { return 0, errDelayedContract } } else if len(public) > 0 && sc.TxSmart.KeyID != crypto.Address(public) { return 0, errDiffKeys } return signedBy, nil } // CallContract calls the contract functions according to the specified flags func (sc *SmartContract) CallContract(point string) (string, error) { var ( result string err error ) logger := sc.GetLogger() retError := func(err error) (string, error) { eText := err.Error() if !strings.HasPrefix(eText, `{`) && err != script.ErrVMTimeLimit { err = script.SetVMError(`panic`, eText) } return ``, err } if err = sc.checkTxSign(); err != nil { return ``, err } needPayment := sc.needPayment() if needPayment { err = sc.prepareMultiPay() if err != nil { logger.WithError(err).Error("prepare multi") return ``, err } } sc.TxContract.Extend = sc.getExtend() if err = sc.AppendStack(sc.TxContract.Name); err != nil { logger.WithFields(log.Fields{"type": consts.ContractError, "error": err}).Error("loop in contract") return retError(err) } sc.VM = script.GetVM() ctrctExtend := sc.TxContract.Extend before := ctrctExtend[script.Extend_txcost].(int64) txSizeFuel := syspar.GetSizeFuel() * sc.TxSize / 1024 ctrctExtend[script.Extend_txcost] = ctrctExtend[script.Extend_txcost].(int64) - txSizeFuel _, nameContract := converter.ParseName(sc.TxContract.Name) ctrctExtend[script.Extend_original_contract] = nameContract ctrctExtend[script.Extend_this_contract] = nameContract methods := []string{`conditions`, `action`} err = script.RunContractById(sc.VM, int32(sc.TxSmart.ID), methods, sc.TxContract.Extend, sc.Hash) if ctrctExtend[script.Extend_txcost].(int64) < 0 { sc.TxFuel = before } else { sc.TxFuel = before - ctrctExtend[script.Extend_txcost].(int64) } sc.TxUsedCost = decimal.New(sc.TxFuel, 0) if err == nil { if ctrctExtend[script.Extend_result] != nil { result = fmt.Sprint(ctrctExtend[script.Extend_result]) if !utf8.ValidString(result) { result, err = retError(errNotValidUTF) } if len(result) > 255 { result = result[:255] + `...` } } } lp: if err != nil { sc.RollBackTx = nil sc.DbTransaction.BinLogSql = nil if errReset := sc.DbTransaction.ResetSavepoint(point); errReset != nil { return retError(errors.Wrap(err, errReset.Error())) } if needPayment { if errPay := sc.payContract(true); errPay != nil { sc.RollBackTx = nil sc.DbTransaction.BinLogSql = nil if errRollsp := sc.DbTransaction.RollbackSavepoint(point); errRollsp != nil { return retError(errors.Wrap(err, errRollsp.Error())) } return errors.Wrap(err, errPay.Error()).Error(), nil } return err.Error(), nil } return retError(err) } if needPayment { if errPay := sc.payContract(false); errPay != nil { err = errPay goto lp } } return result, nil } func (sc *SmartContract) checkTxSign() error { var public []byte if len(sc.TxSmart.PublicKey) > 0 && string(sc.TxSmart.PublicKey) != `null` { public = sc.TxSmart.PublicKey } signedBy, err := sc.GetSignedBy(public) if err != nil { return err } isFound, err := sc.Key.SetTablePrefix(sc.TxSmart.EcosystemID).Get(sc.DbTransaction, signedBy) if err != nil { sc.GetLogger().WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting wallet") return err } if !isFound { err = fmt.Errorf(eEcoKeyNotFound, converter.AddressToString(signedBy), sc.TxSmart.EcosystemID) sc.GetLogger().WithFields(log.Fields{"type": consts.ContractError, "error": err}).Error("looking for keyid") return err } if sc.Key.Disable() { err = fmt.Errorf(eEcoKeyDisable, converter.AddressToString(signedBy), sc.TxSmart.EcosystemID) sc.GetLogger().WithFields(log.Fields{"type": consts.ContractError, "error": err}).Error("disable keyid") return err } if len(sc.Key.PublicKey) > 0 { public = sc.Key.PublicKey } if len(public) == 0 { sc.GetLogger().WithFields(log.Fields{"type": consts.EmptyObject}).Error("empty public key") return errEmptyPublicKey } sc.PublicKeys = append(sc.PublicKeys, public) var CheckSignResult bool CheckSignResult, err = utils.CheckSign(sc.PublicKeys, sc.Hash, sc.TxSignature, false) if err != nil { sc.GetLogger().WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("checking tx data sign") return err } if !CheckSignResult { sc.GetLogger().WithFields(log.Fields{"type": consts.InvalidObject}).Error("incorrect sign") return errIncorrectSign } return nil } ================================================ FILE: packages/smart/smart_p.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "encoding/hex" "encoding/json" "fmt" "math" "reflect" "regexp" "strconv" "strings" "time" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/language" "github.com/IBAX-io/go-ibax/packages/migration" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/IBAX-io/go-ibax/packages/utils/metric" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) const ( nBindWallet = "BindWallet" nUnbindWallet = "UnbindWallet" nEditColumn = "EditColumn" nEditContract = "EditContract" nEditEcosystemName = "EditEcosystemName" nEditLang = "EditLang" nEditLangJoint = "EditLangJoint" nEditTable = "EditTable" nImport = "Import" nNewColumn = "NewColumn" nNewContract = "NewContract" nNewEcosystem = "NewEcosystem" nNewLang = "NewLang" nNewLangJoint = "NewLangJoint" nNewTable = "NewTable" nNewTableJoint = "NewTableJoint" nNewUser = "NewUser" nBlockReward = "BlockReward" nCallDelayedContract = "CallDelayedContract" ) // SignRes contains the data of the signature type SignRes struct { Param string `json:"name"` Text string `json:"text"` } // TxSignJSON is a structure for additional signs of transaction type TxSignJSON struct { ForSign string `json:"forsign"` Field string `json:"field"` Title string `json:"title"` Params []SignRes `json:"params"` } func getCost(name string) int64 { if price, ok := syspar.GetPriceExec(utils.ToSnakeCase(name)); ok { return price } return -1 } // UpdatePlatformParam updates the system parameter func UpdatePlatformParam(sc *SmartContract, name, value, conditions string) (int64, error) { var ( fields []string values []any ) par := &sqldb.PlatformParameter{} found, err := par.Get(sc.DbTransaction, name) if err != nil { return 0, logErrorDB(err, "system parameter get") } if !found { return 0, logErrorf(eParamNotFound, name, consts.NotFound, "system parameter get") } cond := par.Conditions if len(cond) > 0 && !sc.taxes { ret, err := sc.EvalIf(cond) if err != nil { return 0, logError(err, consts.EvalError, "evaluating conditions") } if !ret { return 0, logErrorShort(errAccessDenied, consts.AccessDenied) } } if len(value) > 0 { var ( ok, checked bool list [][]string ) ival := converter.StrToInt64(value) check: switch name { case syspar.GapsBetweenBlocks: ok = ival > 0 && ival < 86400 case syspar.RbBlocks1, syspar.NumberNodes: ok = ival > 0 && ival < 1000 case syspar.TaxesSize, syspar.PriceCreateRate, syspar.PriceTxSize, syspar.BlockReward: ok = ival >= 0 case syspar.MaxBlockSize, syspar.MaxTxSize, syspar.MaxTxCount, syspar.MaxColumns, syspar.MaxIndexes, syspar.MaxBlockUserTx, syspar.MaxTxFuel, syspar.MaxBlockFuel, syspar.MaxForsignSize: ok = ival > 0 case syspar.FuelRate, syspar.TaxesWallet: if err := unmarshalJSON([]byte(value), &list, `system param`); err != nil { return 0, err } for _, item := range list { if len(item) != 2 || converter.StrToInt64(item[0]) <= 0 || (name == syspar.FuelRate && converter.StrToInt64(item[1]) <= 0) || (name == syspar.TaxesWallet && converter.StrToInt64(item[1]) == 0) { break check } } checked = true case syspar.HonorNodes: var fnodes []*syspar.HonorNode if err := json.Unmarshal([]byte(value), &fnodes); err != nil { break check } if len(fnodes) > 1 { if err = syspar.DuplicateHonorNode(fnodes); err != nil { return 0, logErrorValue(err, consts.InvalidObject, err.Error(), value) } } checked = len(fnodes) > 0 default: if strings.HasPrefix(name, `extend_cost_`) || strings.HasSuffix(name, `_price`) { ok = ival >= 0 break } checked = true } if !checked && (!ok || converter.Int64ToStr(ival) != value) { return 0, logErrorValue(errInvalidValue, consts.InvalidObject, errInvalidValue.Error(), value) } fields = append(fields, "value") values = append(values, value) } if len(conditions) > 0 { if err := script.VMCompileEval(sc.VM, conditions, 0); err != nil { return 0, logErrorValue(err, consts.EvalError, "compiling eval", conditions) } fields = append(fields, "conditions") values = append(values, conditions) } if len(fields) == 0 { return 0, logErrorShort(errEmpty, consts.EmptyObject) } _, _, err = sc.update(fields, values, "1_platform_parameters", "id", par.ID) if err != nil { return 0, err } err = syspar.SysUpdate(sc.DbTransaction) if err != nil { return 0, logErrorDB(err, "updating syspar") } sc.SysUpdate = true return 0, nil } // SysParamString returns the value of the system parameter func SysParamString(name string) string { return syspar.SysString(name) } // SysParamInt returns the value of the system parameter func SysParamInt(name string) int64 { return syspar.SysInt64(name) } // SysFuel returns the fuel rate func SysFuel(state int64) string { return syspar.GetFuelRate(state) } // Int converts the value to a number func Int(v any) (int64, error) { return converter.ValueToInt(v) } // Str converts the value to a string func Str(v any) (ret string) { if v == nil { return } return fmt.Sprintf(`%v`, v) } // Money converts the value into a numeric type for money func Money(v any) (decimal.Decimal, error) { return script.ValueToDecimal(v) } // Float converts the value to float64 func Float(v any) (ret float64) { return script.ValueToFloat(v) } // Join is joining input with separator func Join(input []any, sep string) string { var ret string for i, item := range input { if i > 0 { ret += sep } ret += fmt.Sprintf(`%v`, item) } return ret } // Split splits the input string to array func Split(input, sep string) []any { out := strings.Split(input, sep) result := make([]any, len(out)) for i, val := range out { result[i] = reflect.ValueOf(val).Interface() } return result } // PubToID returns a numeric identifier for the public key specified in the hexadecimal form. func PubToID(hexkey string) int64 { pubkey, err := crypto.HexToPub(hexkey) if err != nil { logErrorValue(err, consts.CryptoError, "decoding hexkey to string", hexkey) return 0 } return crypto.Address(pubkey) } func CheckSign(pub, data, sign string) (bool, error) { pk, err := hex.DecodeString(pub) if err != nil { return false, err } s, err := hex.DecodeString(sign) if err != nil { return false, err } pk = crypto.CutPub(pk) return crypto.Verify(pk, []byte(data), s) } func CheckNumberChars(data string) bool { dat := []byte(data) dl := len(dat) for i := 0; i < dl; i++ { d := dat[i] if (d >= 0x30 && d <= 0x39) || (d >= 0x41 && d <= 0x5A) || (d >= 0x61 && d <= 0x7A) { } else { return false } } return true } // HexToBytes converts the hexadecimal representation to []byte func HexToBytes(hexdata string) ([]byte, error) { return hex.DecodeString(hexdata) } // LangRes returns the language resource func LangRes(sc *SmartContract, idRes string) string { ret, _ := language.LangText(sc.DbTransaction, idRes, int(sc.TxSmart.EcosystemID), sc.TxSmart.Lang) return ret } // NewLang creates new language func CreateLanguage(sc *SmartContract, name, trans string) (id int64, err error) { if err := validateAccess(sc, "CreateLanguage"); err != nil { return 0, err } idStr := converter.Int64ToStr(sc.TxSmart.EcosystemID) if err = language.UpdateLang(int(sc.TxSmart.EcosystemID), name, trans); err != nil { return 0, err } if _, id, err = DBInsert(sc, `@1languages`, types.LoadMap(map[string]any{"name": name, "ecosystem": idStr, "res": trans})); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("inserting new language") return 0, err } return id, nil } // EditLanguage edits language func EditLanguage(sc *SmartContract, id int64, name, trans string) error { if err := validateAccess(sc, "EditLanguage"); err != nil { return err } if err := language.UpdateLang(int(sc.TxSmart.EcosystemID), name, trans); err != nil { return err } if _, err := DBUpdate(sc, `@1languages`, id, types.LoadMap(map[string]any{"name": name, "res": trans})); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("inserting new language") return err } return nil } // GetContractByName returns id of the contract with this name func GetContractByName(sc *SmartContract, name string) int64 { contract := VMGetContract(sc.VM, name, uint32(sc.TxSmart.EcosystemID)) if contract == nil { return 0 } info := contract.Info() if info == nil { return 0 } return info.Owner.TableID } // GetContractById returns the name of the contract with this id func GetContractById(sc *SmartContract, id int64) string { _, ret, err := DBSelect(sc, "contracts", "value", id, `id`, 0, 1, nil, "", "", false) if err != nil || len(ret) != 1 { logErrorDB(err, "getting contract name") return `` } re := regexp.MustCompile(`(?is)^\s*contract\s+([\d\w_]+)\s*{`) var val string if v, found := ret[0].(*types.Map).Get("value"); found { val = v.(string) } names := re.FindStringSubmatch(val) if len(names) != 2 { return `` } return names[1] } // EvalCondition gets the condition and check it func EvalCondition(sc *SmartContract, table, name, condfield string) error { tableName := converter.ParseTable(table, sc.TxSmart.EcosystemID) query := `SELECT ` + converter.EscapeName(condfield) + ` FROM "` + tableName + `" WHERE name = ? and ecosystem = ?` conditions, err := sc.DbTransaction.Single(query, name, sc.TxSmart.EcosystemID).String() if err != nil { return logErrorDB(err, "executing single query") } if len(conditions) == 0 { return logErrorfShort(eRecordNotFound, name, consts.NotFound) } return Eval(sc, conditions) } // Replace replaces old substrings to new substrings func Replace(s, old, new string) string { return strings.Replace(s, old, new, -1) } // CreateEcosystem creates a new ecosystem func CreateEcosystem(sc *SmartContract, wallet int64, name string) (int64, error) { if err := validateAccess(sc, "CreateEcosystem"); err != nil { return 0, err } var sp sqldb.StateParameter sp.SetTablePrefix(`1`) found, err := sp.Get(sc.DbTransaction, `founder_account`) if err != nil { return 0, logErrorDB(err, "getting founder") } if !found || len(sp.Value) == 0 { return 0, logErrorShort(errFounderAccount, consts.NotFound) } id, err := sc.DbTransaction.GetNextID("1_ecosystems") if err != nil { return 0, logErrorDB(err, "generating next ecosystem id") } appID, err := sc.DbTransaction.GetNextID("1_applications") if err != nil { return 0, logErrorDB(err, "generating next application id") } if err = sqldb.ExecSchemaEcosystem(sc.DbTransaction, migration.SqlData{ Ecosystem: int(id), Wallet: wallet, Name: name, Founder: converter.StrToInt64(sp.Value), AppID: appID, Account: converter.AddressToString(wallet), }); err != nil { return 0, logErrorDB(err, "executing ecosystem schema") } idStr := converter.Int64ToStr(id) if err := LoadContract(sc.DbTransaction, id); err != nil { return 0, err } if !sc.CLB { if err := SysRollback(sc, SysRollData{Type: "NewEcosystem", ID: id}); err != nil { return 0, err } } sc.FullAccess = true if _, _, err = DBInsert(sc, "@1parameters", types.LoadMap(map[string]any{ "name": "ecosystem_wallet", "value": "0", "conditions": `ContractConditions("DeveloperCondition")`, "ecosystem": idStr, })); err != nil { return 0, logErrorDB(err, "inserting system parameter") } if _, _, err = DBInsert(sc, "@1applications", types.LoadMap(map[string]any{ "name": "System", "conditions": `ContractConditions("MainCondition")`, "ecosystem": id, })); err != nil { return 0, logErrorDB(err, "inserting application") } if _, _, err = DBInsert(sc, `@1pages`, types.LoadMap(map[string]any{"ecosystem": idStr, "name": "default_page", "app_id": appID, "value": SysParamString("default_ecosystem_page"), "menu": "default_menu", "conditions": `ContractConditions("DeveloperCondition")`})); err != nil { return 0, logErrorDB(err, "inserting default page") } if _, _, err = DBInsert(sc, `@1menu`, types.LoadMap(map[string]any{"ecosystem": idStr, "name": "default_menu", "value": SysParamString("default_ecosystem_menu"), "title": "default", "conditions": `ContractConditions("DeveloperCondition")`})); err != nil { return 0, logErrorDB(err, "inserting default menu") } var ( ret []any pub string ) _, ret, err = DBSelect(sc, "@1keys", "pub", wallet, `id`, 0, 1, nil, "", "", false) if err != nil { return 0, logErrorDB(err, "getting pub key") } if Len(ret) > 0 { if v, found := ret[0].(*types.Map).Get("pub"); found { pub = v.(string) } } if _, _, err := DBInsert(sc, `@1keys`, types.LoadMap(map[string]any{ "id": wallet, "account": converter.AddressToString(wallet), "pub": pub, "ecosystem": idStr, })); err != nil { return 0, logErrorDB(err, "inserting key") } sc.FullAccess = false // because of we need to know which ecosystem to rollback. // All tables will be deleted so it's no need to rollback data from tables if _, _, err := DBInsert(sc, "@1ecosystems", types.LoadMap(map[string]any{ "id": id, "name": name, })); err != nil { return 0, logErrorDB(err, "insert new ecosystem to stat table") } return id, err } // EditEcosysName set newName for ecosystem func EditEcosysName(sc *SmartContract, sysID int64, newName string) error { if err := validateAccess(sc, "EditEcosysName"); err != nil { return err } _, err := DBUpdate(sc, "@1ecosystems", sysID, types.LoadMap(map[string]any{"name": newName})) return err } // Size returns the length of the string func Size(s string) int64 { return int64(len(s)) } // Substr returns the substring of the string func Substr(s string, off int64, slen int64) string { ilen := int64(len(s)) if off < 0 || slen < 0 || off > ilen { return `` } if off+slen > ilen { return s[off:] } return s[off : off+slen] } // BndWallet sets wallet_id to current wallet and updates value in vm func BndWallet(sc *SmartContract, tblid int64, state int64) error { if err := validateAccess(sc, "BindWallet"); err != nil { log.Error("BindWallet access denied") return err } if _, _, err := sc.update([]string{"wallet_id"}, []any{sc.TxSmart.KeyID}, "1_contracts", "id", tblid); err != nil { log.WithFields(log.Fields{"error": err, "contract_id": tblid}).Error("on updating contract wallet") return err } return SetContractWallet(sc, tblid, state, sc.TxSmart.KeyID) } // UnbndWallet sets Active status of the contract in smartVM func UnbndWallet(sc *SmartContract, tblid int64, state int64) error { if err := validateAccess(sc, "UnbindWallet"); err != nil { return err } if _, _, err := sc.update([]string{"wallet_id"}, []any{0}, "1_contracts", "id", tblid); err != nil { log.WithFields(log.Fields{"error": err, "contract_id": tblid}).Error("on updating contract wallet") return err } return SetContractWallet(sc, tblid, state, 0) } // CheckSignature checks the additional signatures for the contract func CheckSignature(sc *SmartContract, i map[string]any, name string) error { state, name := converter.ParseName(name) sn := sqldb.Signature{} sn.SetTablePrefix(converter.Int64ToStr(int64(state))) _, err := sn.Get(name) if err != nil { return logErrorDB(err, "executing single query") } if len(sn.Value) == 0 { return nil } hexsign, err := hex.DecodeString(i[`Signature`].(string)) if len(hexsign) == 0 || err != nil { return logError(errWrongSignature, consts.ConversionError, "converting signature to hex") } var sign TxSignJSON if err = unmarshalJSON([]byte(sn.Value), &sign, `unmarshalling sign`); err != nil { return err } wallet := i[`key_id`].(int64) forsign := fmt.Sprintf(`%d,%d`, uint64(i[`time`].(int64)), uint64(wallet)) for _, isign := range sign.Params { val := i[isign.Param] if val == nil { val = `` } forsign += fmt.Sprintf(`,%v`, val) } CheckSignResult, err := utils.CheckSign(sc.PublicKeys, []byte(forsign), hexsign, true) if err != nil { return err } if !CheckSignResult { return logErrorfShort(eIncorrectSignature, forsign, consts.InvalidObject) } return nil } // DBSelectMetrics returns list of metrics by name and time interval func DBSelectMetrics(sc *SmartContract, metric, timeInterval, aggregateFunc string) ([]any, error) { if conf.Config.IsSupportingCLB() { return nil, ErrNotImplementedOnCLB } timeBlock := time.Unix(sc.Timestamp, 0).Format(`2006-01-02 15:04:05`) result, err := sqldb.GetMetricValues(metric, timeInterval, aggregateFunc, timeBlock) if err != nil { return nil, logErrorDB(err, "get values of metric") } return result, nil } // DBCollectMetrics returns actual values of all metrics // This function used to further store these values func DBCollectMetrics(sc *SmartContract) []any { if conf.Config.IsSupportingCLB() { return nil } c := metric.NewCollector( metric.CollectMetricDataForEcosystemTables, metric.CollectMetricDataForEcosystemTx, ) return c.Values(sc.Timestamp) } // JSONDecode converts json string to object func JSONDecode(input string) (ret any, err error) { err = unmarshalJSON([]byte(input), &ret, "unmarshalling json") ret = types.ConvertMap(ret) return } // JSONEncodeIndent converts object to json string func JSONEncodeIndent(input any, indent string) (string, error) { rv := reflect.ValueOf(input) if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() == reflect.Struct && reflect.TypeOf(input).String() != `*types.Map` { return "", logErrorfShort(eTypeJSON, input, consts.TypeError) } var ( b []byte err error ) if len(indent) == 0 { b, err = json.Marshal(input) } else { b, err = json.MarshalIndent(input, ``, indent) } if err != nil { return ``, logError(err, consts.JSONMarshallError, `marshalling json`) } out := string(b) out = strings.Replace(out, `\u003c`, `<`, -1) out = strings.Replace(out, `\u003e`, `>`, -1) out = strings.Replace(out, `\u0026`, `&`, -1) return out, nil } // JSONEncode converts object to json string func JSONEncode(input any) (string, error) { return JSONEncodeIndent(input, ``) } // Append syn for golang 'append' function func Append(slice []any, val any) []any { return append(slice, val) } func HasSlice(element any, slice any) bool { s := reflect.ValueOf(slice) if s.Kind() != reflect.Slice { return false } for i := 0; i < s.Len(); i++ { if reflect.DeepEqual(element, s.Index(i).Interface()) { return true } } return false } // RegexpMatch validates regexp func RegexpMatch(str, reg string) bool { if strings.Contains(reg, `\u`) || strings.Contains(reg, `\U`) { var err error reg, err = strconv.Unquote(`"` + reg + `"`) if err != nil { return false } } re := regexp.MustCompile(reg) return re.MatchString(str) } func DBCount(sc *SmartContract, tableName string, inWhere *types.Map) (count int64, err error) { tblname := qb.GetTableName(sc.TxSmart.EcosystemID, tableName) where, err := qb.GetWhere(inWhere) if err != nil { return 0, err } err = sqldb.GetDB(sc.DbTransaction).Table(tblname).Where(where).Count(&count).Error return } func MathMod(x, y float64) float64 { return math.Mod(x, y) } func MathModDecimal(x, y decimal.Decimal) decimal.Decimal { return x.Mod(y) } func TransferSelf(sc *SmartContract, value string, source string, target string) (flag bool, err error) { fromID := sc.TxSmart.KeyID outputsMap := sc.OutputsMap txInputsMap := sc.TxInputsMap txOutputsMap := sc.TxOutputsMap //txHash := sc.Hash ecosystem := sc.TxSmart.EcosystemID blockId := sc.BlockHeader.BlockId //dbTx := sc.DbTransaction keyUTXO := sqldb.KeyUTXO{Ecosystem: ecosystem, KeyId: fromID} //sum, _ := decimal.NewFromString(value) payValue, _ := decimal.NewFromString(value) status := pbgo.TxInvokeStatusCode_SUCCESS var values *types.Map var balance decimal.Decimal if strings.EqualFold("UTXO", source) && strings.EqualFold("Account", target) { txInputs := sqldb.GetUnusedOutputsMap(keyUTXO, outputsMap) if len(txInputs) == 0 { return false, fmt.Errorf(eEcoCurrentBalance, converter.IDToAddress(fromID), ecosystem) } totalAmount := decimal.Zero for _, input := range txInputs { outputValue, _ := decimal.NewFromString(input.OutputValue) totalAmount = totalAmount.Add(outputValue) } if totalAmount.GreaterThanOrEqual(payValue) && payValue.GreaterThan(decimal.Zero) { flag = true // The transfer was successful //txOutputs = append(txOutputs, sqldb.SpentInfo{OutputKeyId: toID, OutputValue: value, BlockId: blockId, Ecosystem: ecosystem}) totalAmount = totalAmount.Sub(payValue) if _, _, err := sc.updateWhere([]string{"+amount"}, []any{payValue}, "1_keys", types.LoadMap(map[string]any{"id": fromID, "ecosystem": ecosystem})); err != nil { return false, err } if balance, err = sc.accountBalanceSingle(ecosystem, fromID); err != nil { return false, err } } else { return false, fmt.Errorf(eEcoCurrentBalance, converter.IDToAddress(fromID), ecosystem) } // The change var txOutputs []sqldb.SpentInfo if totalAmount.GreaterThan(decimal.Zero) { txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: 0, OutputKeyId: fromID, OutputValue: totalAmount.String(), BlockId: blockId, Ecosystem: ecosystem, Type: consts.UTXO_Type_Self_UTXO}) // The change } if len(txInputs) > 0 { sqldb.PutAllOutputsMap(txInputs, txInputsMap) } if len(txOutputs) > 0 { sqldb.PutAllOutputsMap(txOutputs, txOutputsMap) } values = types.LoadMap(map[string]any{ "sender_id": fromID, "sender_balance": balance, "recipient_id": fromID, "recipient_balance": balance, "amount": payValue, "comment": source, "status": int64(status), "block_id": sc.BlockHeader.BlockId, "txhash": sc.Hash, "ecosystem": ecosystem, "type": int64(GasScenesType_TransferSelf), "created_at": sc.Timestamp, }) _, _, err = sc.insert(values.Keys(), values.Values(), `1_history`) if err != nil { return false, err } sc.TxInputsMap = txInputsMap sc.TxOutputsMap = txOutputsMap return true, nil } else if strings.EqualFold("Account", source) && strings.EqualFold("UTXO", target) { var totalAmount decimal.Decimal var txOutputs []sqldb.SpentInfo if totalAmount, err = sc.accountBalanceSingle(ecosystem, fromID); err != nil { return false, err } if totalAmount.GreaterThanOrEqual(payValue) && payValue.GreaterThan(decimal.Zero) { flag = true // The transfer was successful txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: 0, OutputKeyId: fromID, OutputValue: value, BlockId: blockId, Ecosystem: ecosystem, Type: consts.UTXO_Type_Self_Account}) totalAmount = totalAmount.Sub(payValue) if _, _, err = sc.updateWhere([]string{`-amount`}, []any{payValue}, "1_keys", types.LoadMap(map[string]any{`id`: fromID, `ecosystem`: ecosystem})); err != nil { return false, err } if balance, err = sc.accountBalanceSingle(ecosystem, fromID); err != nil { return false, err } } else { return false, fmt.Errorf(eEcoCurrentBalance, converter.IDToAddress(fromID), ecosystem) } if len(txOutputs) > 0 { sqldb.PutAllOutputsMap(txOutputs, txOutputsMap) } values = types.LoadMap(map[string]any{ "sender_id": fromID, "sender_balance": balance, "recipient_id": fromID, "recipient_balance": balance, "amount": payValue, "comment": source, "status": int64(status), "block_id": sc.BlockHeader.BlockId, "txhash": sc.Hash, "ecosystem": ecosystem, "type": int64(GasScenesType_TransferSelf), "created_at": sc.Timestamp, }) _, _, err = sc.insert(values.Keys(), values.Values(), `1_history`) if err != nil { return false, err } sc.TxInputsMap = txInputsMap sc.TxOutputsMap = txOutputsMap return true, nil } return false, errors.New("transfer self fail") } func UtxoToken(sc *SmartContract, toID int64, value string) (flag bool, err error) { cache := sc.PrevSysPar getParams := func(name string) (map[int64]string, error) { res := make(map[int64]string) if len(cache[name]) > 0 { ifuels := make([][]string, 0) err = json.Unmarshal([]byte(cache[name]), &ifuels) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling params from json") return res, err } for _, item := range ifuels { if len(item) < 2 { continue } res[converter.StrToInt64(item[0])] = item[1] } } return res, nil } var fuels = make(map[int64]string) var wallets = make(map[int64]string) var expediteFee decimal.Decimal fuels, err = getParams(syspar.FuelRate) wallets, err = getParams(syspar.TaxesWallet) fromID := sc.TxSmart.KeyID outputsMap := sc.OutputsMap txInputsMap := sc.TxInputsMap txOutputsMap := sc.TxOutputsMap ecoParams := sc.EcoParams var comPercents = make(map[int64]int64) for _, eco := range ecoParams { comPercents[eco.Id] = eco.Percent } var ecoDigits = make(map[int64]int) for _, eco := range ecoParams { ecoDigits[eco.Id] = eco.Digits } //txHash := sc.Hash ecosystem := sc.TxSmart.EcosystemID ecoDigits1 := consts.MoneyDigits ecoDigits2 := ecoDigits[ecosystem] blockId := sc.BlockHeader.BlockId //dbTx := sc.DbTransaction keyUTXO := sqldb.KeyUTXO{Ecosystem: ecosystem, KeyId: fromID} txInputs := sqldb.GetUnusedOutputsMap(keyUTXO, outputsMap) if len(txInputs) == 0 { return false, fmt.Errorf(eEcoCurrentBalance, converter.IDToAddress(fromID), ecosystem) } if expediteFee, err = expediteFeeBy(sc.TxSmart.Expedite, consts.MoneyDigits); err != nil { return false, err } totalAmount := decimal.Zero var txOutputs []sqldb.SpentInfo for _, input := range txInputs { outputValue, _ := decimal.NewFromString(input.OutputValue) totalAmount = totalAmount.Add(outputValue) } var outputIndex int32 = 0 // taxes_size = 3 TaxesSize := syspar.SysInt64(syspar.TaxesSize) // if : ecosystem = 2 ,rule : taxes ecosystem 1 and 2 if ecosystem != consts.DefaultTokenEcosystem { // rule : taxes ecosystem 1 { var txOutputs1 []sqldb.SpentInfo ecosystem1 := int64(consts.DefaultTokenEcosystem) keyUTXO1 := sqldb.KeyUTXO{Ecosystem: ecosystem1, KeyId: fromID} txInputs1 := sqldb.GetUnusedOutputsMap(keyUTXO1, outputsMap) if len(txInputs1) == 0 { return false, fmt.Errorf(eEcoCurrentBalance, converter.IDToAddress(fromID), ecosystem1) } totalAmount1 := decimal.Zero for _, input1 := range txInputs1 { outputValue1, _ := decimal.NewFromString(input1.OutputValue) totalAmount1 = totalAmount1.Add(outputValue1) } var money1 = decimal.Zero var fuelRate1 = decimal.Zero var taxes1 = decimal.Zero if ret, ok := fuels[ecosystem1]; ok { fuelRate1, err = decimal.NewFromString(ret) if err != nil { return false, err } // ecosystem fuelRate /10 *( bit + len(input)) money1 = fuelRate1.Div(decimal.NewFromInt(10)).Mul(decimal.NewFromInt(sc.TxSize).Add(decimal.NewFromInt(int64(len(txInputs1))))) // utxo ecosystem 1 expediteFee money1 = money1.Add(expediteFee) if money1.GreaterThan(totalAmount1) { money1 = totalAmount1 } taxes1 = money1.Mul(decimal.NewFromInt(TaxesSize)).Div(decimal.New(100, 0)).Floor() } if money1.GreaterThan(decimal.Zero) && taxes1.GreaterThan(decimal.Zero) { if taxesWallet, ok := wallets[ecosystem1]; ok { taxesID := converter.StrToInt64(taxesWallet) flag = true // 97% txOutputs1 = append(txOutputs1, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: sc.BlockHeader.KeyId, OutputValue: money1.Sub(taxes1).String(), BlockId: blockId, Ecosystem: ecosystem1, Type: consts.UTXO_Type_Packaging}) outputIndex++ // 3% txOutputs1 = append(txOutputs1, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: taxesID, OutputValue: taxes1.String(), BlockId: blockId, Ecosystem: ecosystem1, Type: consts.UTXO_Type_Taxes}) outputIndex++ totalAmount1 = totalAmount1.Sub(money1) } } if totalAmount1.GreaterThan(decimal.Zero) { txOutputs1 = append(txOutputs1, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: fromID, OutputValue: totalAmount1.String(), BlockId: blockId, Ecosystem: ecosystem1, Type: consts.UTXO_Type_Output}) // The change outputIndex++ } if len(txInputs1) > 0 && len(txOutputs1) > 0 { sqldb.PutAllOutputsMap(txInputs1, txInputsMap) sqldb.PutAllOutputsMap(txOutputs1, txOutputsMap) } } // rule : taxes ecosystem 2 { ecosystem2 := ecosystem var money2 = decimal.Zero var fuelRate2 = decimal.Zero var taxes2 = decimal.Zero ret, ok := fuels[ecosystem2] percent, hasPercent := comPercents[ecosystem2] if ok && hasPercent { fuelRate2, err = decimal.NewFromString(ret) if err != nil { return false, err } // ecosystem fuelRate /10 *( bit + len(input)) money2 = fuelRate2.Mul(decimal.New(1, int32(ecoDigits2-ecoDigits1))).Div(decimal.NewFromInt(10)).Mul(decimal.NewFromInt(sc.TxSize).Add(decimal.NewFromInt(int64(len(txInputs))))) if money2.GreaterThan(totalAmount) { money2 = totalAmount } percentMoney2 := decimal.Zero if percent > 0 && money2.GreaterThan(decimal.Zero) { percentMoney2 = money2.Mul(decimal.NewFromInt(percent)).Div(decimal.New(100, 0)).Floor() if percentMoney2.GreaterThan(decimal.Zero) { txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: 0, OutputValue: percentMoney2.String(), BlockId: blockId, Ecosystem: ecosystem2, Type: consts.UTXO_Type_Combustion}) outputIndex++ money2 = money2.Sub(percentMoney2) totalAmount = totalAmount.Sub(percentMoney2) } } taxes2 = money2.Mul(decimal.NewFromInt(TaxesSize)).Div(decimal.New(100, 0)).Floor() } if money2.GreaterThan(decimal.Zero) && taxes2.GreaterThan(decimal.Zero) { if taxesWallet, ok := wallets[ecosystem2]; ok { taxesID := converter.StrToInt64(taxesWallet) flag = true // 97% txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: sc.BlockHeader.KeyId, OutputValue: money2.Sub(taxes2).String(), BlockId: blockId, Ecosystem: ecosystem2, Type: consts.UTXO_Type_Packaging}) outputIndex++ // 3% txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: taxesID, OutputValue: taxes2.String(), BlockId: blockId, Ecosystem: ecosystem2, Type: consts.UTXO_Type_Taxes}) outputIndex++ totalAmount = totalAmount.Sub(money2) } } } } // if : ecosystem = 1 , rule : taxes ecosystem 1 if ecosystem == consts.DefaultTokenEcosystem { ecosystem1 := int64(consts.DefaultTokenEcosystem) var money1 = decimal.Zero var fuelRate1 = decimal.Zero var taxes1 = decimal.Zero if ret, ok := fuels[ecosystem1]; ok { fuelRate1, err = decimal.NewFromString(ret) if err != nil { return false, err } else { // ecosystem fuelRate /10 *( bit + len(input)) money1 = fuelRate1.Div(decimal.NewFromInt(10)).Mul(decimal.NewFromInt(sc.TxSize).Add(decimal.NewFromInt(int64(len(txInputs))))) // utxo ecosystem 1 expediteFee money1 = money1.Add(expediteFee) if money1.GreaterThan(totalAmount) { money1 = totalAmount } taxes1 = money1.Mul(decimal.NewFromInt(TaxesSize)).Div(decimal.New(100, 0)).Floor() } } if money1.GreaterThan(decimal.Zero) && taxes1.GreaterThan(decimal.Zero) { if taxesWallet, ok := wallets[ecosystem1]; ok { taxesID := converter.StrToInt64(taxesWallet) flag = true // 97% txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: sc.BlockHeader.KeyId, OutputValue: money1.Sub(taxes1).String(), BlockId: blockId, Ecosystem: ecosystem1, Type: consts.UTXO_Type_Packaging}) outputIndex++ // 3% txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: taxesID, OutputValue: taxes1.String(), BlockId: blockId, Ecosystem: ecosystem1, Type: consts.UTXO_Type_Taxes}) outputIndex++ totalAmount = totalAmount.Sub(money1) } } } payValue, _ := decimal.NewFromString(value) if totalAmount.GreaterThanOrEqual(payValue) && payValue.GreaterThan(decimal.Zero) { flag = true // The transfer was successful txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: toID, OutputValue: value, BlockId: blockId, Ecosystem: ecosystem, Type: consts.UTXO_Type_Transfer}) outputIndex++ totalAmount = totalAmount.Sub(payValue) } else { flag = false err = fmt.Errorf(eEcoCurrentBalance, converter.IDToAddress(fromID), ecosystem) } // The change if totalAmount.GreaterThan(decimal.Zero) { txOutputs = append(txOutputs, sqldb.SpentInfo{OutputIndex: outputIndex, OutputKeyId: fromID, OutputValue: totalAmount.String(), BlockId: blockId, Ecosystem: ecosystem, Type: consts.UTXO_Type_Output}) // The change outputIndex++ } if len(txInputs) > 0 && len(txOutputs) > 0 { sqldb.PutAllOutputsMap(txInputs, txInputsMap) sqldb.PutAllOutputsMap(txOutputs, txOutputsMap) } sc.TxInputsMap = txInputsMap sc.TxOutputsMap = txOutputsMap return flag, err } ================================================ FILE: packages/smart/smart_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "testing" "github.com/IBAX-io/go-ibax/packages/script" "github.com/stretchr/testify/require" ) type TestSmart struct { Input string Output string } func TestNewContract(t *testing.T) { test := []TestSmart{ {`contract NewCitizen { data { Public bytes MyVal string } func conditions { Println( "Front") //$tmp = "Test string" // Println("NewCitizen Front", $tmp, $key_id, $ecosystem_id, $PublicKey ) } settings { abc = 123 } func action { // Println("NewCitizen Main", $tmp, $type, $key_id ) // DBInsert(Sprintf( "%d_citizens", $ecosystem_id), "public_key,block_id", $PublicKey, $block) } } `, ``}, } owner := script.OwnerInfo{ StateID: 1, Active: false, TableID: 1, WalletID: 0, TokenID: 0, } InitVM() for _, item := range test { if err := script.GetVM().Compile([]rune(item.Input), &owner); err != nil { t.Error(err) } } cnt := GetContract(`NewCitizen`, 1) cfunc := cnt.GetFunc(`conditions`) _, err := script.VMRun(script.GetVM(), cfunc, nil, map[string]any{}, nil) if err != nil { t.Error(err) } } func TestCheckAppend(t *testing.T) { appendTestContract := `contract AppendTest { action { var list array list = Append(list, "naw_value") Println(list) } }` owner := script.OwnerInfo{ StateID: 1, Active: false, TableID: 1, WalletID: 0, TokenID: 0, } require.NoError(t, script.GetVM().Compile([]rune(appendTestContract), &owner)) cnt := GetContract("AppendTest", 1) cfunc := cnt.GetFunc("action") _, err := script.VMRun(script.GetVM(), cfunc, nil, map[string]any{}, nil) require.NoError(t, err) } ================================================ FILE: packages/smart/sysrollback.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) const ( SysName = `@system` ) type SysRollData struct { Type string `json:"type,omitempty"` EcosystemID int64 `json:"ecosystem,omitempty"` ID int64 `json:"id,omitempty"` Data string `json:"data,omitempty"` TableName string `json:"table,omitempty"` } func SysRollback(sc *SmartContract, data SysRollData) error { out, err := marshalJSON(data, `marshaling sys rollback`) if err != nil { return err } rollbackSys := &types.RollbackTx{ BlockId: sc.BlockHeader.BlockId, TxHash: sc.Hash, NameTable: SysName, TableId: converter.Int64ToStr(sc.TxSmart.EcosystemID), Data: string(out), DataHash: crypto.Hash(out), } sc.RollBackTx = append(sc.RollBackTx, rollbackSys) return nil } // SysRollbackTable is rolling back table func SysRollbackTable(dbTx *sqldb.DbTransaction, sysData SysRollData) error { err := dbTx.DropTable(sysData.TableName) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("dropping table") return err } return nil } // SysRollbackView is rolling back table func SysRollbackView(DbTransaction *sqldb.DbTransaction, sysData SysRollData) error { err := sqldb.DropView(DbTransaction, sysData.TableName) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("dropping view") return err } return nil } // SysRollbackColumn is rolling back column func SysRollbackColumn(dbTx *sqldb.DbTransaction, sysData SysRollData) error { return dbTx.AlterTableDropColumn(sysData.TableName, sysData.Data) } // SysRollbackContract performs rollback for the contract func SysRollbackContract(name string, EcosystemID int64) error { vm := script.GetVM() if c := VMGetContract(vm, name, uint32(EcosystemID)); c != nil { id := c.Info().ID if int(id) != len(vm.Children)-1 { err := fmt.Errorf(eRollbackContract, id, len(vm.Children)-1) log.WithFields(log.Fields{"type": consts.VMError, "error": err}).Error("rollback contract") return err } vm.Children = vm.Children[:id] delete(vm.Objects, c.Name) } return nil } func SysRollbackNewContract(sysData SysRollData, EcosystemID string) error { contractList, err := script.ContractsList(sysData.Data) if err != nil { return err } for _, contract := range contractList { if err := SysRollbackContract(contract, converter.StrToInt64(EcosystemID)); err != nil { return err } } return nil } // SysFlushContract is flushing contract func SysFlushContract(iroot any, id int64, active bool) error { root := iroot.(*script.CodeBlock) if id != 0 { if len(root.Children) != 1 || root.Children[0].Type != script.ObjectType_Contract { return fmt.Errorf(`only one contract must be in the record`) } } for i, item := range root.Children { if item.Type == script.ObjectType_Contract { root.Children[i].GetContractInfo().Owner.TableID = id root.Children[i].GetContractInfo().Owner.Active = active } } script.GetVM().FlushBlock(root) return nil } // SysSetContractWallet changes WalletID of the contract in smartVM func SysSetContractWallet(tblid, state int64, wallet int64) error { for i, item := range script.GetVM().CodeBlock.Children { if item != nil && item.Type == script.ObjectType_Contract { cinfo := item.GetContractInfo() if cinfo.Owner.TableID == tblid && cinfo.Owner.StateID == uint32(state) { script.GetVM().Children[i].GetContractInfo().Owner.WalletID = wallet } } } return nil } // SysRollbackEditContract rollbacks the contract func SysRollbackEditContract(transaction *sqldb.DbTransaction, sysData SysRollData, EcosystemID string) error { fields, err := transaction.GetOneRowTransaction(`select * from "1_contracts" where id=?`, sysData.ID).String() if err != nil { return err } if len(fields["value"]) > 0 { var owner *script.OwnerInfo for i, item := range script.GetVM().CodeBlock.Children { if item != nil && item.Type == script.ObjectType_Contract { cinfo := item.GetContractInfo() if cinfo.Owner.TableID == sysData.ID && cinfo.Owner.StateID == uint32(converter.StrToInt64(EcosystemID)) { owner = script.GetVM().Children[i].GetContractInfo().Owner break } } } if owner == nil { err = errContractNotFound log.WithFields(log.Fields{"type": consts.VMError, "error": err}).Error("getting existing contract") return err } wallet := owner.WalletID if len(fields["wallet_id"]) > 0 { wallet = converter.StrToInt64(fields["wallet_id"]) } root, err := script.GetVM().CompileBlock([]rune(fields["value"]), &script.OwnerInfo{StateID: uint32(owner.StateID), WalletID: wallet, TokenID: owner.TokenID}) if err != nil { log.WithFields(log.Fields{"type": consts.VMError, "error": err}).Error("compiling contract") return err } err = SysFlushContract(root, owner.TableID, owner.Active) if err != nil { log.WithFields(log.Fields{"type": consts.VMError, "error": err}).Error("flushing contract") return err } } else if len(fields["wallet_id"]) > 0 { return SysSetContractWallet(sysData.ID, converter.StrToInt64(EcosystemID), converter.StrToInt64(fields["wallet_id"])) } return nil } // SysRollbackEcosystem is rolling back ecosystem func SysRollbackEcosystem(dbTx *sqldb.DbTransaction, sysData SysRollData) error { tables := make([]string, 0) for table := range converter.FirstEcosystemTables { tables = append(tables, table) err := dbTx.Delete(`1_`+table, fmt.Sprintf(`where ecosystem='%d'`, sysData.ID)) if err != nil { return err } } if sysData.ID == 1 { tables = append(tables, `node_ban_logs`, `bad_blocks`, `platform_parameters`, `ecosystems`) for _, name := range tables { err := dbTx.DropTable(fmt.Sprintf("%d_%s", sysData.ID, name)) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("dropping table") return err } } } else { vm := script.GetVM() for vm.Children[len(vm.Children)-1].Type == script.ObjectType_Contract { cinfo := vm.Children[len(vm.Children)-1].GetContractInfo() if int64(cinfo.Owner.StateID) != sysData.ID { break } if err := SysRollbackContract(cinfo.Name, sysData.ID); err != nil { return err } } } return nil } // SysRollbackActivate sets Deactive status of the contract in smartVM func SysRollbackActivate(sysData SysRollData) error { ActivateContract(sysData.ID, sysData.EcosystemID, false) return nil } // SysRollbackDeactivate sets Active status of the contract in smartVM func SysRollbackDeactivate(sysData SysRollData) error { ActivateContract(sysData.ID, sysData.EcosystemID, true) return nil } // SysRollbackDeleteColumn is rolling back delete column func SysRollbackDeleteColumn(dbTx *sqldb.DbTransaction, sysData SysRollData) error { var ( data map[string]string ) err := unmarshalJSON([]byte(sysData.Data), &data, `rollback delete to json`) if err != nil { return err } sqlColType, err := columnType(data["type"]) if err != nil { return err } err = dbTx.AlterTableAddColumn(sysData.TableName, data["name"], sqlColType) if err != nil { return logErrorDB(err, "adding column to the table") } return nil } // SysRollbackDeleteTable is rolling back delete table func SysRollbackDeleteTable(dbTx *sqldb.DbTransaction, sysData SysRollData) error { var ( data TableInfo colsSQL string ) err := unmarshalJSON([]byte(sysData.Data), &data, `rollback delete table to json`) if err != nil { return err } for key, item := range data.Columns { colsSQL += `"` + key + `" ` + typeToPSQL[item] + " ,\n" } err = sqldb.CreateTable(dbTx, sysData.TableName, strings.TrimRight(colsSQL, ",\n")) if err != nil { return logErrorDB(err, "creating tables") } prefix, _ := PrefixName(sysData.TableName) data.Table.SetTablePrefix(prefix) err = data.Table.Create(dbTx) if err != nil { return logErrorDB(err, "insert table info") } return nil } ================================================ FILE: packages/smart/utils.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package smart import ( "bytes" "encoding/json" "fmt" "github.com/pkg/errors" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) func logError(err error, errType string, comment string) error { log.WithFields(log.Fields{"type": errType, "error": err}).Error(comment) return err } func logErrorf(pattern string, param any, errType string, comment string) error { err := fmt.Errorf(pattern, param) log.WithFields(log.Fields{"type": errType, "error": err}).Error(comment) return err } func logErrorShort(err error, errType string) error { return logError(err, errType, err.Error()) } func logErrorfShort(pattern string, param any, errType string) error { return logErrorShort(fmt.Errorf(pattern, param), errType) } func logErrorValue(err error, errType string, comment, value string) error { log.WithFields(log.Fields{"type": errType, "error": err, "value": value}).Error(comment) return err } func logErrorDB(err error, comment string) error { return logError(err, consts.DBError, comment) } func unmarshalJSON(input []byte, v any, comment string) (err error) { d := json.NewDecoder(bytes.NewReader(input)) d.UseNumber() if err = d.Decode(&v); err != nil { return errors.Wrap(err, comment) } return nil } func marshalJSON(v any, comment string) (out []byte, err error) { out, err = json.Marshal(v) if err != nil { return nil, errors.Wrap(err, comment) } return } func validateAccess(sc *SmartContract, funcName string) error { condition := syspar.GetAccessExec(utils.ToSnakeCase(funcName)) if err := Eval(sc, condition); err != nil { err = fmt.Errorf(eAccessContract, funcName, condition) return logError(err, consts.IncorrectCallingContract, err.Error()) } return nil } func FillTxData(fieldInfos []*script.FieldInfo, params map[string]any) (map[string]any, error) { txData := make(map[string]any) for _, fitem := range fieldInfos { var ( v any ok bool err error index = fitem.Name ) if _, ok := params[index]; !ok { if fitem.ContainsTag(script.TagOptional) { txData[index] = script.GetFieldDefaultValue(fitem.Original) continue } return nil, fmt.Errorf(eParamNotFound, index) } switch fitem.Original { case script.DtBool: if v, ok = params[index].(bool); !ok { err = fmt.Errorf("invalid bool type") break } case script.DtFloat: switch val := params[index].(type) { case float64: v = val case uint64: v = float64(val) case int64: v = float64(val) default: err = fmt.Errorf("invalid float type") break } case script.DtInt: switch t := params[index].(type) { case int: v = int64(t) case int8: v = int64(t) case int16: v = int64(t) case int32: v = int64(t) case int64: v = t case uint: v = int64(t) case uint8: v = int64(t) case uint16: v = int64(t) case uint32: v = int64(t) case uint64: v = int64(t) default: err = fmt.Errorf("invalid int type") } case script.DtAddress: switch t := params[index].(type) { case int64: v = t case uint64: v = int64(t) default: err = fmt.Errorf("invalid int type") } case script.DtMoney: var s string if s, ok = params[index].(string); !ok { err = fmt.Errorf("invalid money type") break } v, err = decimal.NewFromString(s) if err != nil { break } if v.(decimal.Decimal).LessThan(decimal.New(1, 0)) || v.(decimal.Decimal). Mod(decimal.New(1, 0)). GreaterThan(decimal.Zero) { err = fmt.Errorf("inconsistent with the smallest reference unit and its integer multiples") break } case script.DtString: if v, ok = params[index].(string); !ok { err = fmt.Errorf("invalid string type") break } case script.DtBytes: if v, ok = params[index].([]byte); !ok { err = fmt.Errorf("invalid bytes type") break } case script.DtArray: if v, ok = params[index].([]any); !ok { err = fmt.Errorf("invalid array type") break } for i, subv := range v.([]any) { switch val := subv.(type) { case map[any]any: imap := make(map[string]any) for ikey, ival := range val { imap[fmt.Sprint(ikey)] = ival } v.([]any)[i] = types.LoadMap(imap) } } case script.DtMap: var val map[any]any if val, ok = params[index].(map[any]any); !ok { err = fmt.Errorf("invalid map type") break } imap := make(map[string]any) for ikey, ival := range val { imap[fmt.Sprint(ikey)] = ival } v = types.LoadMap(imap) case script.DtFile: var val map[string]any if val, ok = params[index].(map[string]any); !ok { err = fmt.Errorf("invalid file type") break } if v, ok = types.NewFileFromMap(val); !ok { err = fmt.Errorf("invalid attrs of file") break } } if err != nil { return nil, fmt.Errorf("invalid param '%s': %w", index, err) } if _, ok = txData[fitem.Name]; !ok { txData[fitem.Name] = v } } if len(txData) != len(fieldInfos) { return nil, fmt.Errorf("invalid number of parameters") } return txData, nil } ================================================ FILE: packages/statsd/statsd.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package statsd import ( "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/cactus/go-statsd-client/v5/statsd" ) const ( Count = ".count" Time = ".time" ) var Client statsd.Statter func Init(conf conf.StatsDConfig) error { var err error config := &statsd.ClientConfig{ Address: fmt.Sprintf("%s:%d", conf.Host, conf.Port), Prefix: conf.Name, UseBuffered: false, } Client, err = statsd.NewClientWithConfig(config) if err != nil { return err } return nil } func Close() { if Client != nil { Client.Close() } } func APIRouteCounterName(method, pattern string) string { routeCounterName := strings.Replace(strings.Replace(pattern, ":", "", -1), "/", ".", -1) return "api." + strings.ToLower(method) + "." + routeCounterName } func DaemonCounterName(daemonName string) string { return "daemon." + daemonName } ================================================ FILE: packages/storage/kvdb/leveldb/leveldb.go ================================================ package leveldb import ( "fmt" "reflect" "strings" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/util" ) var DBlevel *leveldb.DB var GLeveldbIsactive bool type levelDBGetterPutterDeleter interface { Get([]byte, *opt.ReadOptions) ([]byte, error) Put([]byte, []byte, *opt.WriteOptions) error Write(batch *leveldb.Batch, wo *opt.WriteOptions) error Delete([]byte, *opt.WriteOptions) error NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator } func GetLevelDB(tx *leveldb.Transaction) levelDBGetterPutterDeleter { if tx != nil { return tx } return DBlevel } func prefixFunc(prefix string) func([]byte) []byte { return func(hash []byte) []byte { return []byte(prefix + string(hash)) } } func prefixStringFunc(prefix string) func(key string) []byte { return func(key string) []byte { return []byte(prefix + key) } } func Init_leveldb(filename string) error { var err error DBlevel, err = leveldb.OpenFile(filename, nil) if err == nil { GLeveldbIsactive = true } return err } func Struct2Map(obj any) map[string]any { t := reflect.TypeOf(obj) v := reflect.ValueOf(obj) var data = make(map[string]any) for i := 0; i < t.NumField(); i++ { data[t.Field(i).Name] = v.Field(i).Interface() } return data } func DBGetAllKey(prefix string, bvalue bool) (*[]string, error) { var ( ret []string //key []string ) found := prefix != "nil" iter := DBlevel.NewIterator(nil, nil) for iter.Next() { key := string(iter.Key()) if found { if strings.HasPrefix(key, prefix) { if bvalue { value := string(iter.Value()) s := fmt.Sprintf("Key[%s]=[%s]\n", key, value) ret = append(ret, s) } else { ret = append(ret, key) } } } else { if bvalue { value := string(iter.Value()) s := fmt.Sprintf("Key[%s]=[%s]\n", key, value) ret = append(ret, s) } else { ret = append(ret, key) } } } iter.Release() return &ret, iter.Error() } ================================================ FILE: packages/storage/kvdb/redis/goredis.go ================================================ package redis import ( "errors" "fmt" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/go-redis/redis" ) var ( Gclient0 *redis.Client // Gclient1 *redis.Client // GRedisIsactive bool rediserr = errors.New("redis no run error") ) type RedisParams struct { Key string `json:"key"` Value string `json:"value"` } func RedisInit(conf conf.RedisConfig) error { var ( err error ) GRedisIsactive = false Gclient0 = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", conf.Host, conf.Port), Password: conf.Password, // no password set DB: conf.DbName, // use default DB }) _, err = Gclient0.Ping().Result() if err != nil { return err } Gclient1 = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", conf.Host, conf.Port), Password: conf.Password, // no password set DB: 1, // use default DB }) _, err = Gclient1.Ping().Result() if err != nil { return err } GRedisIsactive = true return nil } func (rp *RedisParams) Setdb() error { err := rediserr if GRedisIsactive { err = Gclient0.Set(rp.Key, rp.Value, 0).Err() } return err } func (rp *RedisParams) Getdb() error { err := rediserr if GRedisIsactive { val, err1 := Gclient0.Get(rp.Key).Result() rp.Value = val return err1 } return err } func (rp *RedisParams) Getdbsize() (int64, error) { err := rediserr if GRedisIsactive { return Gclient0.DBSize().Result() } return 0, err } func (rp *RedisParams) Cleardb() error { err := rediserr var cursor uint64 var n int var keys []string if GRedisIsactive { err = nil for { var key []string var err error key, cursor, err = Gclient0.Scan(cursor, "*", 10).Result() if err != nil { return err } n += len(keys) keys = append(keys, key...) if cursor == 0 { break } } for _, k := range keys { err = Gclient0.Del(k).Err() if err != nil { return err } } } return err } func (rp *RedisParams) Getdb1() error { err := rediserr if GRedisIsactive { val, err1 := Gclient1.Get(rp.Key).Result() rp.Value = val return err1 } return err } ================================================ FILE: packages/storage/kvdb/redis/maxblockid.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package redis import ( "github.com/vmihailenco/msgpack/v5" ) // BlockID is model type BlockID struct { ID int64 Time int64 Name string } var MihPrefix = "blockid-" //marshal func (b *BlockID) Marshal() ([]byte, error) { if res, err := msgpack.Marshal(b); err != nil { return nil, err } else { return res, err } } //unmarshal func (b *BlockID) Unmarshal(bt []byte) error { if err := msgpack.Unmarshal(bt, &b); err != nil { return err } return nil } //Get by name func (b *BlockID) GetbyName(name string) (bool, error) { rp := &RedisParams{ Key: MihPrefix + name, } if err := rp.Getdb1(); err != nil { return false, err } if err := b.Unmarshal([]byte(rp.Value)); err != nil { return false, err } return true, nil } //Get by name func (b *BlockID) GetRangeByName(n1, n2 string, count int64) (bool, error) { var nb1, nb2 BlockID rp1 := &RedisParams{ Key: MihPrefix + n1, } if err := rp1.Getdb1(); err != nil { if err.Error() == "redis: nil" { rp := &RedisParams{} num, err := rp.Getdbsize() if err != nil { return false, err } if num > count { return true, err } else { return false, err } //return false, err } return false, err } if err := nb1.Unmarshal([]byte(rp1.Value)); err != nil { return false, err } rp2 := &RedisParams{ Key: MihPrefix + n2, } if err := rp2.Getdb1(); err != nil { return false, err } if err := nb2.Unmarshal([]byte(rp2.Value)); err != nil { return false, err } if (nb2.ID - nb1.ID) > count { return true, nil } return false, nil } ================================================ FILE: packages/storage/sqldb/app_param.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/converter" ) // AppParam is model type AppParam struct { ecosystem int64 ID int64 `gorm:"primary_key;not null"` AppID int64 `gorm:"not null"` Name string `gorm:"not null;size:100"` Value string `gorm:"not null"` Conditions string `gorm:"not null"` } // TableName returns name of table func (sp *AppParam) TableName() string { if sp.ecosystem == 0 { sp.ecosystem = 1 } return `1_app_params` } // SetTablePrefix is setting table prefix func (sp *AppParam) SetTablePrefix(tablePrefix string) { sp.ecosystem = converter.StrToInt64(tablePrefix) } // Get is retrieving model from database func (sp *AppParam) Get(dbTx *DbTransaction, app int64, name string) (bool, error) { return isFound(GetDB(dbTx).Where("ecosystem=? and app_id=? and name = ?", sp.ecosystem, app, name).First(sp)) } // GetAllAppParameters is returning all state parameters func (sp *AppParam) GetAllAppParameters(app int64, offset, limit *int, names []string) ([]AppParam, error) { parameters := make([]AppParam, 0) q := DBConn.Table(sp.TableName()).Where(`ecosystem = ?`, sp.ecosystem).Where(`app_id = ?`, app) if len(names) > 0 { //if any select names,then all return q = q.Where("name IN ?", names) } else { if offset != nil { q = q.Offset(*offset) } if limit != nil { q = q.Limit(*limit) } } err := q.Find(¶meters).Error if err != nil { return nil, err } return parameters, nil } ================================================ FILE: packages/storage/sqldb/bad_blocks.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "time" ) type BadBlocks struct { ID int64 ProducerNodeId int64 BlockId int64 ConsumerNodeId int64 BlockTime time.Time Deleted bool } // TableName returns name of table func (r BadBlocks) TableName() string { return "1_bad_blocks" } // BanRequests represents count of unique ban requests for node type BanRequests struct { ProducerNodeId int64 Count int64 } // GetNeedToBanNodes is returns list of ban requests for each node func (r *BadBlocks) GetNeedToBanNodes(now time.Time, blocksPerNode int) ([]BanRequests, error) { var res []BanRequests err := DBConn. Raw( `SELECT producer_node_id, COUNT(consumer_node_id) as count FROM ( SELECT producer_node_id, consumer_node_id, count(DISTINCT block_id) FROM "1_bad_blocks" WHERE block_time > ?::date - interval '24 hours' AND deleted = 0 GROUP BY producer_node_id, consumer_node_id HAVING count(DISTINCT block_id) >= ?) AS tbl GROUP BY producer_node_id`, now, blocksPerNode, ). Scan(&res). Error return res, err } func (r *BadBlocks) GetNodeBlocks(nodeId int64, now time.Time) ([]BadBlocks, error) { var res []BadBlocks err := DBConn. Table(r.TableName()). Model(&BadBlocks{}). Where( "producer_node_id = ? AND block_time > ?::date - interval '24 hours' AND deleted = ?", nodeId, now, false, ). Scan(&res). Error return res, err } ================================================ FILE: packages/storage/sqldb/binary.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "fmt" "github.com/IBAX-io/go-ibax/packages/converter" ) const BinaryTableSuffix = "_binaries" // Binary represents record of {prefix}_binaries table type Binary struct { ecosystem int64 ID int64 Name string Data []byte Hash string MimeType string } // SetTablePrefix is setting table prefix func (b *Binary) SetTablePrefix(prefix string) { b.ecosystem = converter.StrToInt64(prefix) } // SetTableName sets name of table func (b *Binary) SetTableName(tableName string) { ecosystem, _ := converter.ParseName(tableName) b.ecosystem = ecosystem } // TableName returns name of table func (b *Binary) TableName() string { if b.ecosystem == 0 { b.ecosystem = 1 } return `1_binaries` } // Get is retrieving model from database func (b *Binary) Get(appID int64, account, name string) (bool, error) { return isFound(DBConn.Where("ecosystem=? and app_id = ? AND account = ? AND name = ?", b.ecosystem, appID, account, name).Select("id,name,hash").First(b)) } // Link returns link to binary data func (b *Binary) Link() string { return fmt.Sprintf(`/data/%s/%d/%s/%s`, b.TableName(), b.ID, "data", b.Hash) } // GetByID is retrieving model from db by id func (b *Binary) GetByID(id int64) (bool, error) { return isFound(DBConn.Where("id=?", id).First(b)) } ================================================ FILE: packages/storage/sqldb/blockchain.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "time" ) // BlockChain is model type BlockChain struct { ID int64 `gorm:"primary_key;not_null"` Hash []byte `gorm:"not null"` RollbacksHash []byte `gorm:"not null"` Data []byte `gorm:"not null"` EcosystemID int64 `gorm:"not null"` KeyID int64 `gorm:"not null"` NodePosition int64 `gorm:"not null"` Time int64 `gorm:"not null"` Tx int32 `gorm:"not null"` ConsensusMode int32 `gorm:"not null"` CandidateNodes []byte `gorm:"not null;default:null"` } // TableName returns name of table func (BlockChain) TableName() string { return "block_chain" } // Create is creating record of model func (b *BlockChain) Create(dbTx *DbTransaction) error { return GetDB(dbTx).Create(b).Error } // Get is retrieving model from database func (b *BlockChain) Get(blockID int64) (bool, error) { return isFound(DBConn.Where("id = ?", blockID).First(b)) } // GetByHash is retrieving model from database func (b *BlockChain) GetByHash(BlockHash []byte) (bool, error) { return isFound(DBConn.Where("hash = ?", BlockHash).First(b)) } // GetMaxBlock returns last block existence func (b *BlockChain) GetMaxBlock() (bool, error) { return isFound(DBConn.Last(b)) } // GetMaxForeignBlock returns last block generated not by key_id func (b *BlockChain) GetMaxForeignBlock(keyId int64) (bool, error) { return isFound(DBConn.Order("id DESC").Where("key_id != ?", keyId).First(b)) } // GetBlockchain is retrieving chain of blocks from database func GetBlockchain(startBlockID int64, endblockID int64, order ordering) ([]BlockChain, error) { var err error blockchain := new([]BlockChain) orderStr := "id " + string(order) query := DBConn.Model(&BlockChain{}).Order(orderStr) if endblockID > 0 { query = query.Where("id > ? AND id <= ?", startBlockID, endblockID).Find(&blockchain) } else { query = query.Where("id > ?", startBlockID).Find(&blockchain) } if query.Error != nil { return nil, err } return *blockchain, nil } // GetBlocks is retrieving limited chain of blocks from database func (b *BlockChain) GetBlocks(startFromID int64, limit int) ([]BlockChain, error) { var err error blockchain := new([]BlockChain) if startFromID > 0 { err = DBConn.Order("id desc").Limit(limit).Where("id > ?", startFromID).Find(&blockchain).Error } else { err = DBConn.Order("id desc").Limit(limit).Find(&blockchain).Error } return *blockchain, err } // GetBlocksFrom is retrieving ordered chain of blocks from database func (b *BlockChain) GetBlocksFrom(startFromID int64, ordering string, limit int) ([]BlockChain, error) { blockchain := new([]BlockChain) q := DBConn.Model(&BlockChain{}).Order("id "+ordering).Where("id > ?", startFromID) if limit > 0 { q = q.Limit(limit) } err := q.Find(&blockchain).Error return *blockchain, err } // GetReverseBlockchain returns records of blocks in reverse ordering func (b *BlockChain) GetReverseBlockchain(endBlockID int64, limit int) ([]BlockChain, error) { var err error blockchain := new([]BlockChain) err = DBConn.Model(&BlockChain{}).Order("id DESC").Where("id <= ?", endBlockID).Limit(limit).Find(&blockchain).Error return *blockchain, err } // GetNodeBlocksAtTime returns records of blocks for time interval and position of node func (b *BlockChain) GetNodeBlocksAtTime(from, to time.Time, node int64) ([]BlockChain, error) { var err error blockchain := new([]BlockChain) err = DBConn.Model(&BlockChain{}).Where("node_position = ? AND time BETWEEN ? AND ?", node, from.Unix(), to.Unix()).Find(&blockchain).Error return *blockchain, err } // DeleteById is deleting block by ID func (b *BlockChain) DeleteById(dbTx *DbTransaction, id int64) error { return GetDB(dbTx).Where("id = ?", id).Delete(BlockChain{}).Error } func GetTxCount() (int64, error) { var txCount int64 row := DBConn.Raw("SELECT SUM(tx) tx_count FROM block_chain").Select("tx_count").Row() err := row.Scan(&txCount) return txCount, err } func GetBlockCountByNode(NodePosition int64, consensusMode int32) (int64, error) { var BlockCount int64 row := DBConn.Raw("SELECT count(*) block_count FROM block_chain where node_Position = ? AND consensus_mode = ?", NodePosition, consensusMode).Select("block_count").Row() err := row.Scan(&BlockCount) return BlockCount, err } func (b *BlockChain) GetRecentBlockChain(startBlockId int64, maxBlockId int64) ([]BlockChain, error) { blockchain := new([]BlockChain) err := DBConn.Where("id > ? and id <= ?", startBlockId, maxBlockId).Find(&blockchain).Error return *blockchain, err } ================================================ FILE: packages/storage/sqldb/candidate_node.go ================================================ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/shopspring/decimal" ) type CandidateNode struct { ID int64 `gorm:"column:id" json:"id"` ApiAddress string `gorm:"column:api_address" json:"apiAddress"` TcpAddress string `gorm:"column:tcp_address" json:"tcpAddress"` NodePubKey string `gorm:"column:node_pub_key" json:"nodePubKey"` DateCreated int64 `gorm:"column:date_created" json:"dateCreated"` Deleted uint8 `gorm:"column:deleted" json:"deleted"` DateDeleted int64 `gorm:"column:date_deleted" json:"dateDeleted"` Website string `gorm:"column:website" json:"website"` ReplyCount int64 `gorm:"column:reply_count" json:"replyCount"` DateReply int64 `gorm:"column:date_reply" json:"dateReply"` EarnestTotal decimal.Decimal `gorm:"column:earnest_total" json:"earnestTotal"` NodeName string `gorm:"column:node_name" json:"nodeName"` ReferendumTotal decimal.Decimal `gorm:"column:referendum_total" json:"referendumTotal"` CandidateNodes []byte `json:"candidateNodes"` } type CandidateNodes []CandidateNode func (nodes CandidateNodes) Len() int { return len(nodes) } func (nodes CandidateNodes) Swap(i, j int) { nodes[i], nodes[j] = nodes[j], nodes[i] } func (nodes CandidateNodes) Less(i, j int) bool { return nodes[i].DateReply > nodes[j].DateReply } // TableName returns name of table func (ib *CandidateNode) TableName() string { return "1_candidate_node_requests" } // GetCandidateNode returns last good block func GetCandidateNode(numberOfNodes int) (CandidateNodes, error) { var candidateNodes CandidateNodes pledgeAmount, err := GetPledgeAmount() if err != nil { return nil, err } err = GetDB(nil).Where("deleted = ? and earnest_total >= ?", 0, pledgeAmount).Order("referendum_total desc,date_updated_referendum asc,reply_count desc,date_reply desc").Limit(numberOfNodes).Find(&candidateNodes).Error if err != nil { return nil, err } return candidateNodes, nil } func (c *CandidateNode) UpdateCandidateNodeInfo() error { pledgeAmount, err := GetPledgeAmount() if err != nil { return err } err = GetDB(nil).Model(&c).Where("tcp_address = ? and deleted = ? and earnest_total >= ?", c.TcpAddress, 0, pledgeAmount).Updates(CandidateNode{ReplyCount: c.ReplyCount, DateReply: c.DateReply, CandidateNodes: c.CandidateNodes}).Error if err != nil { return err } return nil } func (c *CandidateNode) GetCandidateNodeByAddress(tcpAddress string) error { pledgeAmount, err := GetPledgeAmount() if err != nil { return err } err = GetDB(nil).Where("tcp_address = ? and deleted = ? and earnest_total >= ?", tcpAddress, 0, pledgeAmount).Find(&c).Error if err != nil { return err } return nil } func (c *CandidateNode) GetCandidateNodeByPublicKey(nodePublicKey string) error { pledgeAmount, err := GetPledgeAmount() if err != nil { return err } err = GetDB(nil).Where("node_pub_key = ? and deleted = ? and earnest_total >= ?", nodePublicKey, 0, pledgeAmount).Find(&c).Error if err != nil { return err } return nil } func (c *CandidateNode) GetCandidateNodeById(id int64) error { pledgeAmount, err := GetPledgeAmount() if err != nil { return err } err = GetDB(nil).Where("id = ? and deleted = ? and earnest_total >= ?", id, 0, pledgeAmount).First(&c).Error if err != nil { return err } return nil } func GetPledgeAmount() (int64, error) { var ( pledgeAmount string err error amount decimal.Decimal ) row := DBConn.Raw(`select ap.value from "1_app_params" ap, "1_applications" a where ap.app_id = a.id and a."name" = 'CandidateNode' and ap."name" = 'limit_candidate_pack' and a.deleted = 0 and a.ecosystem = 1`).Row() err = row.Scan(&pledgeAmount) if err != nil { return 0, err } amount, err = decimal.NewFromString(pledgeAmount) if err != nil { return 0, err } return amount.Mul(decimal.New(1, consts.MoneyDigits)).IntPart(), nil } ================================================ FILE: packages/storage/sqldb/confirmations.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb // Confirmation is model type Confirmation struct { BlockID int64 `gorm:"primary_key"` Good int32 `gorm:"not null"` Bad int32 `gorm:"not null"` Time int64 `gorm:"not null"` } // GetGoodBlock returns last good block func (c *Confirmation) GetGoodBlock(goodCount int) (bool, error) { return isFound(DBConn.Where("good >= ?", goodCount).Last(&c)) } // GetConfirmation returns if block with blockID exists func (c *Confirmation) GetConfirmation(blockID int64) (bool, error) { return isFound(DBConn.Where("block_id= ?", blockID).First(&c)) } // Save is saving model func (c *Confirmation) Save() error { return DBConn.Save(c).Error } // GetGoodBlockLast returns last good block func (c *Confirmation) GetGoodBlockLast() (bool, error) { var sp PlatformParameter count, err := sp.GetNumberOfHonorNodes() if err != nil { return false, err } return isFound(DBConn.Where("good >= ?", int(count/2)).Last(&c)) } // GetGoodBlock returns last good block func (c *Confirmation) CheckAllowGenBlock() (bool, error) { prevBlock := &InfoBlock{} _, err := prevBlock.Get() if err != nil { return false, err } var sp PlatformParameter count, err := sp.GetNumberOfHonorNodes() if err != nil { return false, err } if count == 0 { return true, nil } f, err := c.GetGoodBlock(count / 2) if err != nil { return false, err } if f { if prevBlock.BlockID-c.BlockID < 1 { return true, nil } } return false, err } ================================================ FILE: packages/storage/sqldb/contract.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import "github.com/IBAX-io/go-ibax/packages/converter" // Contract represents record of 1_contracts table type Contract struct { ID int64 `json:"id,omitempty"` Name string `json:"name,omitempty"` Value string `json:"value,omitempty"` WalletID int64 `json:"wallet_id,omitempty"` Active bool `json:"active,omitempty"` TokenID int64 `json:"token_id,omitempty"` Conditions string `json:"conditions,omitempty"` AppID int64 `json:"app_id,omitempty"` EcosystemID int64 `gorm:"column:ecosystem" json:"ecosystem_id,omitempty"` } // TableName returns name of table func (c *Contract) TableName() string { return `1_contracts` } // Get is retrieving id contracts from database func (c *Contract) Get(Id int64) (bool, error) { return isFound(DBConn.Where("id = ?", Id).First(c)) } // GetList is retrieving records from database func (c *Contract) GetList(offset, limit int) ([]Contract, error) { result := new([]Contract) err := DBConn.Table(c.TableName()).Offset(offset).Limit(limit).Order("id asc").Find(&result).Error return *result, err } // GetFromEcosystem retrieving ecosystem contracts from database func (c *Contract) GetFromEcosystem(db *DbTransaction, ecosystem int64) ([]Contract, error) { result := new([]Contract) err := GetDB(db).Table(c.TableName()).Where("ecosystem = ?", ecosystem).Order("id asc").Find(&result).Error return *result, err } // Count returns count of records in table func (c *Contract) Count(db *DbTransaction) (count int64, err error) { err = GetDB(db).Table(c.TableName()).Count(&count).Error return } func (c *Contract) GetListByEcosystem(offset, limit int) ([]Contract, error) { var list []Contract err := DBConn.Table(c.TableName()).Offset(offset).Limit(limit). Order("id asc").Where("ecosystem = ?", c.EcosystemID). Find(&list).Error return list, err } func (c *Contract) CountByEcosystem() (n int64, err error) { err = DBConn.Table(c.TableName()).Where("ecosystem = ?", c.EcosystemID).Count(&n).Error return } func (c *Contract) ToMap() (v map[string]string) { v = make(map[string]string) v["id"] = converter.Int64ToStr(c.ID) v["name"] = c.Name v["value"] = c.Value v["wallet_id"] = converter.Int64ToStr(c.WalletID) v["token_id"] = converter.Int64ToStr(c.TokenID) v["conditions"] = c.Conditions v["app_id"] = converter.Int64ToStr(c.AppID) v["ecosystem_id"] = converter.Int64ToStr(c.EcosystemID) return } // GetByApp returns all contracts belonging to selected app func (c *Contract) GetByApp(appID int64, ecosystemID int64) ([]Contract, error) { var result []Contract err := DBConn.Select("id, name").Where("app_id = ? and ecosystem = ?", appID, ecosystemID).Find(&result).Error return result, err } ================================================ FILE: packages/storage/sqldb/cron.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "fmt" ) // Cron represents record of {prefix}_cron table type Cron struct { tableName string ID int64 Cron string Contract string } // SetTablePrefix is setting table prefix func (c *Cron) SetTablePrefix(prefix string) { c.tableName = prefix + "_cron" } // TableName returns name of table func (c *Cron) TableName() string { return c.tableName } // Get is retrieving model from database func (c *Cron) Get(id int64) (bool, error) { return isFound(DBConn.Where("id = ?", id).First(c)) } // GetAllCronTasks is returning all cron tasks func (c *Cron) GetAllCronTasks() ([]*Cron, error) { var crons []*Cron err := DBConn.Table(c.TableName()).Find(&crons).Error return crons, err } // UID returns unique identifier for cron task func (c *Cron) UID() string { return fmt.Sprintf("%s_%d", c.tableName, c.ID) } ================================================ FILE: packages/storage/sqldb/database.go ================================================ package sqldb import ( "database/sql" "fmt" "gorm.io/gorm" ) func GetNodeRows(tableName string) (int64, error) { var count int64 err := DBConn.Table(tableName).Count(&count).Error if err == gorm.ErrRecordNotFound { return 0, nil } if err != nil { return 0, err } return count, nil } func GetRowsInfo(rows *sql.Rows, sqlQuest string) ([]map[string]any, error) { var result []map[string]any defer rows.Close() columns, err := rows.Columns() if err != nil { return result, fmt.Errorf("getrows Columns err:%s in query %s", err, sqlQuest) } values := make([]any, len(columns)) scanArgs := make([]any, len(values)) for i := range values { scanArgs[i] = &values[i] } for rows.Next() { err = rows.Scan(scanArgs...) if err != nil { return result, fmt.Errorf("getRows scan err:%s in query %s", err, sqlQuest) } var value any rez := make(map[string]any) for i, col := range values { // Here we can check if the value is nil (NULL value) if col == nil { value = "NULL" } else { value = col } rez[columns[i]] = value } result = append(result, rez) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("getRows rows err:%s in query %s", err, sqlQuest) } return result, nil } ================================================ FILE: packages/storage/sqldb/db.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "errors" "fmt" "strings" "time" log "github.com/sirupsen/logrus" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/storage/kvdb/redis" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) var ( // DBConn is orm connection DBConn *gorm.DB // ErrRecordNotFound is Not Found Record wrapper ErrRecordNotFound = gorm.ErrRecordNotFound // ErrDBConn database connection error ErrDBConn = errors.New("database connection error") ) var notAutoIncrement = map[string]bool{ "1_keys": true, } // non-self-increasing costs const notAutoIncrementCost int64 = 1 type KeyTableChecker struct{} func (ktc KeyTableChecker) IsKeyTable(tableName string) bool { val, exist := converter.FirstEcosystemTables[tableName] return exist && val } type NextIDGetter struct { Tx *DbTransaction } func (g NextIDGetter) GetNextID(tableName string) (int64, error) { return g.Tx.GetNextID(tableName) } func isFound(db *gorm.DB) (bool, error) { if errors.Is(db.Error, ErrRecordNotFound) { return false, nil } return true, db.Error } // InitDB drop all tables and exec db schema func InitDB(cfg conf.DBConfig) error { err := GormInit(cfg) if err != nil || DBConn == nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("initializing DB") return ErrDBConn } if err = NewDbTransaction(DBConn).DropTables(); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("dropping all tables") return err } if conf.Config.Redis.Enable { err = redis.RedisInit(conf.Config.Redis) if err != nil { log.WithFields(log.Fields{ "host": conf.Config.Redis.Host, "port": conf.Config.Redis.Port, "db_password": conf.Config.Redis.Password, "db_name": conf.Config.Redis.DbName, "type": consts.DBError, }).Error("can't init redis") return err } var rd redis.RedisParams err := rd.Cleardb() if err != nil { return err } } if err = ExecSchema(); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing db schema") return err } install := &Install{Progress: ProgressComplete} if err = install.Create(); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating install") return err } if err := ExecCLBSchema(consts.DefaultCLB, conf.Config.KeyID); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating CLB schema") return err } if err := ExecSubSchema(); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating CLB schema") return err } return nil } // GormInit is initializes Gorm connection func GormInit(conf conf.DBConfig) error { var err error dsn := fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=disable password=%s TimeZone=UTC", conf.Host, conf.Port, conf.User, conf.Name, conf.Password) open: DBConn, err = gorm.Open(postgres.New(postgres.Config{ DSN: dsn, PreferSimpleProtocol: true, // disables implicit prepared statement usage }), &gorm.Config{ AllowGlobalUpdate: true, //allow global update //PrepareStmt: true, Logger: logger.Default.LogMode(logger.Silent), // start Logger, show detail log }) //DBConn, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { if strings.Contains(err.Error(), "SQLSTATE 3D000") { err := createDatabase(fmt.Sprintf("host=%s port=%d user=%s password=%s sslmode=disable TimeZone=UTC", conf.Host, conf.Port, conf.User, conf.Password), conf.Name) if err != nil { return err } goto open } log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("cant open connection to DB") DBConn = nil return err } sqlDB, err := DBConn.DB() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("cant get sql DB") DBConn = nil return err } sqlDB.SetConnMaxLifetime(time.Minute * 10) sqlDB.SetMaxIdleConns(conf.MaxIdleConns) sqlDB.SetMaxOpenConns(conf.MaxOpenConns) if err = setupConnOptions(DBConn); err != nil { return err } return nil } func createDatabase(dsn string, dbName string) error { db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Error), }) if err != nil { return err } result := db.Exec("create database " + dbName) defer func() { d, _ := db.DB() d.Close() }() return result.Error } func setupConnOptions(conr *gorm.DB) error { if err := conr.Exec(fmt.Sprintf(`set lock_timeout = %d;`, conf.Config.DB.LockTimeout)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("can't set lock timeout") return err } if err := conr.Exec(fmt.Sprintf(`set idle_in_transaction_session_timeout = %d;`, conf.Config.DB.IdleInTxTimeout)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("can't set idle_in_transaction_session_timeout") return err } return conr.Exec("SET TIME ZONE 'UTC'").Error } // GormClose is closing Gorm connection func GormClose() error { if DBConn != nil { sqlDB, err := DBConn.DB() if err != nil { return err } if err = sqlDB.Close(); err != nil { return err } DBConn = nil } return nil } // DbTransaction is gorm.DB wrapper type DbTransaction struct { conn *gorm.DB BinLogSql [][]byte } func NewDbTransaction(conn *gorm.DB) *DbTransaction { return &DbTransaction{conn: conn} } func (d *DbTransaction) Debug() *DbTransaction { d.conn = d.conn.Debug() return d } // StartTransaction is beginning transaction func StartTransaction() (*DbTransaction, error) { conn := DBConn.Begin() if conn.Error != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": conn.Error}).Error("cannot start transaction because of connection error") return nil, conn.Error } if err := setupConnOptions(conn); err != nil { return nil, err } return &DbTransaction{ conn: conn, }, nil } // Rollback is transaction rollback func (tr *DbTransaction) Rollback() error { return tr.conn.Rollback().Error } // Commit is transaction commit func (tr *DbTransaction) Commit() error { return tr.conn.Commit().Error } // Connection returns connection of database func (tr *DbTransaction) Connection() *gorm.DB { return tr.conn } // Savepoint creates PostgreSQL Savepoint func (tr *DbTransaction) Savepoint(mark string) error { return tr.Connection().SavePoint(mark).Error } // RollbackSavepoint rollbacks PostgreSQL Savepoint func (tr *DbTransaction) RollbackSavepoint(mark string) error { return tr.Connection().RollbackTo(mark).Error } func (tr *DbTransaction) ResetSavepoint(mark string) error { if err := tr.RollbackSavepoint(mark); err != nil { return err } return tr.Savepoint(mark) } // GetDB is returning gorm.DB func GetDB(tr *DbTransaction) *gorm.DB { if tr != nil && tr.conn != nil { return tr.conn } return DBConn } // DropTables is dropping all of the tables func (dbTx *DbTransaction) DropTables() error { return GetDB(dbTx).Exec(` DO $$ DECLARE r RECORD; BEGIN FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; END LOOP; END $$; `).Error } // GetRecordsCountTx is counting all records of table in transaction func (dbTx *DbTransaction) GetRecordsCountTx(tableName, where string) (count int64, err error) { dbQuery := GetDB(dbTx).Table(tableName) if len(where) > 0 { dbQuery = dbQuery.Where(where) } if !notAutoIncrement[tableName] { err := dbQuery.Select("id").Order("id DESC").Limit(1).Scan(&count).Error if err != nil { return 0, err } } else { //err = dbQuery.Count(&count).Error count = notAutoIncrementCost } return count, err } // Update is updating table rows func (dbTx *DbTransaction) Update(tblname, set, where string) error { sql := `UPDATE "` + strings.Trim(tblname, `"`) + `" SET ` + set + " " + where return dbTx.ExecSql(sql) } // ExecSql is exec sql func (dbTx *DbTransaction) ExecSql(sql string) error { queryFn := func(tx *gorm.DB) *gorm.DB { return tx.Exec(sql) } err := queryFn(GetDB(dbTx)).Error if err != nil { return err } dbTx.BinLogSql = append(dbTx.BinLogSql, []byte(sql)) return nil } // Delete is deleting table rows func (dbTx *DbTransaction) Delete(tblname, where string) error { return dbTx.ExecSql(`DELETE FROM "` + tblname + `" ` + where) } // GetColumnCount is counting rows in table func (dbTx *DbTransaction) GetColumnCount(tableName string) (int64, error) { var count int64 err := GetDB(dbTx).Raw("SELECT count(*) FROM information_schema.columns WHERE table_name=?", tableName).Row().Scan(&count) if err == gorm.ErrRecordNotFound { return 0, nil } if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing raw query") return 0, err } return count, nil } // AlterTableAddColumn is adding column to table func (dbTx *DbTransaction) AlterTableAddColumn(tableName, columnName, columnType string) error { return dbTx.ExecSql(`ALTER TABLE "` + tableName + `" ADD COLUMN "` + columnName + `" ` + columnType) } // AlterTableDropColumn is dropping column from table func (dbTx *DbTransaction) AlterTableDropColumn(tableName, columnName string) error { return dbTx.ExecSql(`ALTER TABLE "` + tableName + `" DROP COLUMN "` + columnName + `"`) } // CreateIndex is creating index on table column func (dbTx *DbTransaction) CreateIndex(indexName, tableName, onColumn string) error { return GetDB(dbTx).Exec(`CREATE INDEX "` + indexName + `_index" ON "` + tableName + `" (` + onColumn + `)`).Error } // GetColumnDataTypeCharMaxLength is returns max length of table column func (dbTx *DbTransaction) GetColumnDataTypeCharMaxLength(tableName, columnName string) (map[string]string, error) { return dbTx.GetOneRow(`select data_type,character_maximum_length from information_schema.columns where table_name = ? AND column_name = ?`, tableName, columnName).String() } // GetAllColumnTypes returns column types for table func (dbTx *DbTransaction) GetAllColumnTypes(tblname string) ([]map[string]string, error) { return dbTx.GetAllTransaction(`SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ? ORDER BY ordinal_position ASC`, -1, tblname) } func DataTypeToColumnType(dataType string) string { var itype string switch { case dataType == "character varying": itype = `varchar` case dataType == `bigint`: itype = "number" case dataType == `jsonb`: itype = "json" case strings.HasPrefix(dataType, `timestamp`): itype = "datetime" case strings.HasPrefix(dataType, `numeric`): itype = "money" case strings.HasPrefix(dataType, `double`): itype = "double" case strings.HasPrefix(dataType, `bytea`): itype = "bytea" default: itype = dataType } return itype } // GetColumnType is returns type of column func (dbTx *DbTransaction) GetColumnType(tblname, column string) (itype string, err error) { coltype, err := dbTx.GetColumnDataTypeCharMaxLength(tblname, column) if err != nil { return } if dataType, ok := coltype["data_type"]; ok { itype = DataTypeToColumnType(dataType) } return } // DropTable is dropping table func (dbTx *DbTransaction) DropTable(tableName string) error { return GetDB(dbTx).Migrator().DropTable(tableName) } // NumIndexes is counting table indexes func (dbTx *DbTransaction) NumIndexes(tblname string) (int, error) { var indexes int64 err := GetDB(dbTx).Raw(fmt.Sprintf(`select count( i.relname) from pg_class t, pg_class i, pg_index ix, pg_attribute a where t.oid = ix.indrelid and i.oid = ix.indexrelid and a.attrelid = t.oid and a.attnum = ANY(ix.indkey) and t.relkind = 'r' and t.relname = '%s'`, tblname)).Row().Scan(&indexes) if err == gorm.ErrRecordNotFound { return 0, nil } if err != nil { return 0, err } return int(indexes - 1), nil } // IsIndex returns is table column is an index func (dbTx *DbTransaction) IsIndex(tblname, column string) (bool, error) { row, err := dbTx.GetOneRow(`select t.relname as table_name, i.relname as index_name, a.attname as column_name from pg_class t, pg_class i, pg_index ix, pg_attribute a where t.oid = ix.indrelid and i.oid = ix.indexrelid and a.attrelid = t.oid and a.attnum = ANY(ix.indkey) and t.relkind = 'r' and t.relname = ? and a.attname = ?`, tblname, column).String() return len(row) > 0 && row[`column_name`] == column, err } // GetNextID returns next ID of table func (dbTx *DbTransaction) GetNextID(table string) (int64, error) { var id int64 rows, err := GetDB(dbTx).Raw(`select id from "` + table + `" order by id desc limit 1`).Rows() if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": table}).Error("selecting next id from table") return 0, err } rows.Next() rows.Scan(&id) rows.Close() return id + 1, err } // IsTable returns is table exists func (dbTx *DbTransaction) IsTable(tblname string) bool { var name string err := GetDB(dbTx).Table("information_schema.tables"). Where("table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name=?", tblname). Select("table_name").Row().Scan(&name) if err != nil { return false } return name == tblname } func (dbTx *DbTransaction) HasTableOrView(names string) bool { var name string GetDB(dbTx).Table("information_schema.tables"). Where("table_type IN ('BASE TABLE', 'VIEW') AND table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name=?", names). Select("table_name").Row().Scan(&name) return name == names } // GetColumnByID returns the value of the column from the table by id func GetColumnByID(table, column string, id int64) (result string, err error) { dbTr := new(DbTransaction) columnList, err := dbTr.GetAllColumnTypes(table) if err != nil { return "", err } var exist bool for _, list := range columnList { for k, v := range list { if k == "column_name" { if v == column { exist = true break } } } if exist { break } } if !exist { return "", errors.New("column invalid") } err = GetDB(nil).Table(table).Select(column).Where("id=?", id).Row().Scan(&result) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting column by id") } return } // DropDatabase kill all process and drop database func (dbTx *DbTransaction) DropDatabase(name string) error { query := `SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = ?` if err := GetDB(dbTx).Exec(query, name).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "dbname": name}).Error("on kill db process") return err } if err := GetDB(dbTx).Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", name)).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "dbname": name}).Error("on drop db") return err } return nil } // GetSumColumn returns the value of the column from the table by id func (dbTx *DbTransaction) GetSumColumn(table, column, where string) (result string, err error) { err = GetDB(dbTx).Table(table).Select("sum(" + column + ")").Where(where).Row().Scan(&result) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("sum column") } return } // GetSumColumnCount returns the value of the column from the table by id func (dbTx *DbTransaction) GetSumColumnCount(table, column, where string) (result int, err error) { err = GetDB(dbTx).Table(table).Select("count(*)").Where(where).Row().Scan(&result) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("sum column") } return } type Namer struct { TableType string } type SchemaInter interface { HasExists(tr *DbTransaction, name string) bool } func (v Namer) HasExists(tr *DbTransaction, names string) bool { var typs string switch v.TableType { case "table": typs = `= 'BASE TABLE'` case "view": typs = `= 'VIEW'` default: typs = `IN ('BASE TABLE', 'VIEW')` } var name string GetDB(tr).Table("information_schema.tables"). Where(fmt.Sprintf("table_type %s AND table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name='%s'", typs, names)). Select("table_name").Row().Scan(&name) return name == names } ================================================ FILE: packages/storage/sqldb/delayed_contract.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb const tableDelayedContracts = "1_delayed_contracts" const availableDelayedContracts = 0 // DelayedContract represents record of 1_delayed_contracts table type DelayedContract struct { ID int64 `gorm:"primary_key;not null"` Contract string `gorm:"not null"` KeyID int64 `gorm:"not null"` BlockID int64 `gorm:"not null"` EveryBlock int64 `gorm:"not null"` Counter int64 `gorm:"not null"` HighRate int64 `gorm:"not null"` Limit int64 `gorm:"not null"` Delete bool `gorm:"not null"` Conditions string `gorm:"not null"` } // TableName returns name of table func (DelayedContract) TableName() string { return tableDelayedContracts } // GetAllDelayedContractsForBlockID returns contracts that want to execute for blockID func GetAllDelayedContractsForBlockID(blockID int64) ([]*DelayedContract, error) { var contracts []*DelayedContract if err := DBConn.Where(`block_id <= ? AND ?%every_block = 0 AND deleted = ? AND (counter < "1_delayed_contracts".limit OR "1_delayed_contracts".limit = 0)`, blockID, blockID, availableDelayedContracts).Order("high_rate desc").Find(&contracts).Error; err != nil { return nil, err } return contracts, nil } // Get is retrieving model from database func (dc *DelayedContract) Get(id int64) (bool, error) { return isFound(DBConn.Where("id = ?", id).First(dc)) } // GetByContract is retrieving model by contract from database func (dc *DelayedContract) GetByContract(dbTx *DbTransaction, contract string) (bool, error) { return isFound(GetDB(dbTx).Where("contract = ? AND deleted = ?", contract, availableDelayedContracts).First(dc)) } func GetAllDelayedContract() ([]*DelayedContract, error) { var contracts []*DelayedContract if err := DBConn.Where(" deleted = ?", availableDelayedContracts).Find(&contracts).Error; err != nil { return nil, err } return contracts, nil } ================================================ FILE: packages/storage/sqldb/ecosystem.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "encoding/json" "strconv" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/pkg/errors" ) const ecosysTable = "1_ecosystems" // Ecosystem is model type Ecosystem struct { ID int64 `gorm:"primary_key;not null"` Name string IsValued bool EmissionAmount string `gorm:"type:jsonb"` TokenSymbol string TokenName string TypeEmission int64 TypeWithdraw int64 Digits int64 Info string `gorm:"type:jsonb"` FeeModeInfo string `json:"fee_mode_info" gorm:"type:jsonb"` } type FeeModeFlag struct { Flag string `json:"flag"` ConversionRate string `json:"conversion_rate"` } type EcoParam struct { Id int64 Percent int64 Digits int } func (f FeeModeFlag) FlagToInt() int64 { ret, _ := strconv.ParseInt(f.Flag, 10, 64) return ret } func (f FeeModeFlag) ConversionRateToFloat() float64 { ret, _ := strconv.ParseFloat(f.ConversionRate, 64) return ret } type Combustion struct { Flag int64 `json:"flag"` Percent int64 `json:"percent"` } type FeeModeInfo struct { FeeModeDetail map[string]FeeModeFlag `json:"fee_mode_detail"` Combustion Combustion `json:"combustion"` FollowFuel float64 `json:"follow_fuel"` } // TableName returns name of table // only first ecosystem has this entity func (sys *Ecosystem) TableName() string { return ecosysTable } // GetAllSystemStatesIDs is retrieving all ecosystems ids func GetAllSystemStatesIDs() ([]int64, []string, error) { if !NewDbTransaction(DBConn).IsTable(ecosysTable) { return nil, nil, nil } ecosystems := new([]Ecosystem) if err := DBConn.Select("id,name").Order("id asc").Find(&ecosystems).Error; err != nil { return nil, nil, err } ids := make([]int64, len(*ecosystems)) names := make([]string, len(*ecosystems)) for i, s := range *ecosystems { ids[i] = s.ID names[i] = s.Name } return ids, names, nil } func GetAllSystemCount() (int64, error) { if !NewDbTransaction(DBConn).IsTable(ecosysTable) { return 0, nil } var total int64 if err := DBConn.Model(Ecosystem{}).Count(&total).Error; err != nil { return 0, err } return total, nil } // GetEcoParam is ecosystem combustion percent, digits func GetEcoParam(db *DbTransaction, ids []int64) ([]EcoParam, error) { query := ` SELECT eco.id,(eco.fee_mode_info::json#>>'{combustion,percent}')::int as percent ,eco.digits FROM "1_parameters" as par LEFT JOIN "1_ecosystems" as eco ON par.ecosystem = eco.id WHERE par.name = 'utxo_fee' and par.value = '1' and par.ecosystem IN ? ` var ret []EcoParam if len(ids) > 0 { err := GetDB(db).Raw(query, ids).Scan(&ret).Error if err != nil { return nil, err } } return ret, nil } // Get is fill receiver from db func (sys *Ecosystem) Get(dbTx *DbTransaction, id int64) (bool, error) { return isFound(GetDB(dbTx).First(sys, "id = ?", id)) } // Delete is deleting record func (sys *Ecosystem) Delete(dbTx *DbTransaction) error { return GetDB(dbTx).Delete(sys).Error } // FeeMode is get ecosystem fee mode func (sys *Ecosystem) FeeMode() (*FeeModeInfo, error) { if len(sys.TokenSymbol) == 0 || len(sys.FeeModeInfo) == 0 || sys.ID == consts.DefaultTokenEcosystem { return nil, nil } var info = &FeeModeInfo{} err := json.Unmarshal([]byte(sys.FeeModeInfo), info) if err != nil { return nil, errors.Wrapf(err, "Unmarshal eco[%d] feemode err", sys.ID) } return info, nil } // GetTokenSymbol is get ecosystem token symbol func (sys *Ecosystem) GetTokenSymbol(dbTx *DbTransaction, id int64) (bool, error) { return isFound(GetDB(dbTx).Select("token_symbol").First(sys, "id = ?", id)) } ================================================ FILE: packages/storage/sqldb/ecosystem_parameter.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/converter" ) var ( EcosystemWallet = "ecosystem_wallet" ) // StateParameter is model type StateParameter struct { ecosystem int64 ID int64 `gorm:"primary_key;not null"` Name string `gorm:"not null;size:100"` Value string `gorm:"not null"` Conditions string `gorm:"not null"` } // TableName returns name of table func (sp *StateParameter) TableName() string { if sp.ecosystem == 0 { sp.ecosystem = 1 } return `1_parameters` } // SetTablePrefix is setting table prefix func (sp *StateParameter) SetTablePrefix(prefix string) *StateParameter { sp.ecosystem = converter.StrToInt64(prefix) return sp } // Get is retrieving model from database func (sp *StateParameter) Get(dbTx *DbTransaction, name string) (bool, error) { return isFound(GetDB(dbTx).Where("ecosystem = ? and name = ?", sp.ecosystem, name).First(sp)) } // GetAllStateParameters is returning all state parameters func (sp *StateParameter) GetAllStateParameters(offset, limit *int, names []string) ([]StateParameter, error) { parameters := make([]StateParameter, 0) q := DBConn.Table(sp.TableName()).Where(`ecosystem = ?`, sp.ecosystem) if len(names) > 0 { //if any select names,then all return q.Where("name IN ?", names) } else { if offset != nil { q = q.Offset(*offset) } if limit != nil { q = q.Limit(*limit) } } err := q.Find(¶meters).Error if err != nil { return nil, err } return parameters, nil } ================================================ FILE: packages/storage/sqldb/external_blockchain.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "strings" "github.com/IBAX-io/go-ibax/packages/converter" ) // ExternalBlockchain represents a txinfo table type ExternalBlockchain struct { Id int64 `gorm:"primary_key;not null"` Value string `gorm:"not null"` ExternalContract string `gorm:"not null"` ResultContract string `gorm:"not null"` Url string `gorm:"not null"` Uid string `gorm:"not null"` TxTime int64 `gorm:"not null"` Sent int64 `gorm:"not null"` Hash []byte `gorm:"not null"` Attempts int64 `gorm:"not null"` } // GetExternalList returns the list of network tx func GetExternalList() (list []ExternalBlockchain, err error) { err = DBConn.Table("external_blockchain"). Order("id desc").Scan(&list).Error return } // DelExternalList deletes sent tx func DelExternalList(list []int64) error { slist := make([]string, len(list)) for i, v := range list { slist[i] = converter.Int64ToStr(v) } return DBConn.Exec("delete from external_blockchain where id in (" + strings.Join(slist, `,`) + ")").Error } func HashExternalTx(id int64, hash []byte) error { return DBConn.Exec("update external_blockchain set hash=?, sent = 1 where id = ?", hash, id).Error } func IncExternalAttempt(id int64) error { return DBConn.Exec("update external_blockchain set attempts=attempts+1 where id = ?", id).Error } ================================================ FILE: packages/storage/sqldb/history.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/consts" "gorm.io/gorm" "github.com/shopspring/decimal" ) // History represent record of history table type History struct { ecosystem int64 ID int64 SenderID int64 RecipientID int64 SenderBalance decimal.Decimal RecipientBalance decimal.Decimal Amount decimal.Decimal Comment string `json:"comment,omitempty"` BlockID int64 `json:"block_id,omitempty"` TxHash []byte `gorm:"column:txhash"` CreatedAt int64 `json:"created_at,omitempty"` Type int64 } // SetTablePrefix is setting table prefix func (h *History) SetTablePrefix(prefix int64) *History { h.ecosystem = prefix return h } // TableName returns table name func (h *History) TableName() string { if h.ecosystem == 0 { h.ecosystem = 1 } return `1_history` } // MoneyTransfer from to amount type MoneyTransfer struct { SenderID int64 RecipientID int64 Amount decimal.Decimal } // SenderTxCount struct to scan query result type SenderTxCount struct { SenderID int64 TxCount int64 } // Get is retrieving model from database func (ts *History) Get(transactionHash []byte) (bool, error) { return isFound(DBConn.Table(ts.TableName()).Where("txhash = ?", transactionHash).First(ts)) } // GetExcessCommonTokenMovementPerDay returns sum of amounts 24 hours func GetExcessCommonTokenMovementPerDay(tx *DbTransaction) (amount decimal.Decimal, err error) { db := GetDB(tx) type result struct { Amount decimal.Decimal } var res result err = db.Table("1_history").Select("SUM(amount) as amount"). Where("to_timestamp(created_at) > NOW() - interval '24 hours' AND amount > 0").Scan(&res).Error return res.Amount, err } // GetExcessFromToTokenMovementPerDay returns from to pairs where sum of amount greather than fromToPerDayLimit per 24 hours func GetExcessFromToTokenMovementPerDay(tx *DbTransaction) (excess []MoneyTransfer, err error) { db := GetDB(tx) err = db.Table("1_history"). Select("sender_id, recipient_id, SUM(amount) amount"). Where("to_timestamp(created_at) > NOW() - interval '24 hours' AND amount > 0"). Group("sender_id, recipient_id"). Having("SUM(amount) > ?", consts.FromToPerDayLimit). Scan(&excess).Error return excess, err } // GetExcessTokenMovementQtyPerBlock returns from to pairs where money transactions count greather than tokenMovementQtyPerBlockLimit per 24 hours func GetExcessTokenMovementQtyPerBlock(tx *DbTransaction, blockID int64) (excess []SenderTxCount, err error) { db := GetDB(tx) err = db.Table("1_history"). Select("sender_id, count(*) tx_count"). Where("block_id = ? AND amount > ?", blockID, 0). Group("sender_id"). Having("count(*) > ?", consts.TokenMovementQtyPerBlockLimit). Scan(&excess).Error return excess, err } func GetWalletRecordHistory(tx *DbTransaction, keyId string, searchType string, limit, offset int) (histories []History, total int64, err error) { db := GetDB(tx) var query *gorm.DB if searchType == "income" { query = db.Table("1_history"). Where("recipient_id = ?", keyId) } else if searchType == "outcome" { query = db.Table("1_history"). Where("sender_id = ?", keyId) } else { query = db.Table("1_history"). Where("recipient_id = ? OR sender_id = ?", keyId, keyId) } err = query.Count(&total).Error if err != nil { return nil, 0, err } err = query. Order("id desc"). Limit(limit). Offset(offset).Scan(&histories).Error return } ================================================ FILE: packages/storage/sqldb/info_block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/converter" ) // InfoBlock is model type InfoBlock struct { Hash []byte `gorm:"not null"` EcosystemID int64 `gorm:"not null default 0"` KeyID int64 `gorm:"not null default 0"` NodePosition string `gorm:"not null default 0"` BlockID int64 `gorm:"not null"` Time int64 `gorm:"not null"` CurrentVersion string `gorm:"not null"` Sent int8 `gorm:"not null"` RollbacksHash []byte `gorm:"not null"` ConsensusMode int32 `gorm:"not null"` CandidateNodes []byte `gorm:"not null"` } // TableName returns name of table func (ib *InfoBlock) TableName() string { return "info_block" } // Get is retrieving model from database func (ib *InfoBlock) Get() (bool, error) { return isFound(DBConn.Last(ib)) } // Update is update model func (ib *InfoBlock) Update(dbTx *DbTransaction) error { return GetDB(dbTx).Model(&InfoBlock{}).Updates(ib).Error } // GetUnsent is retrieving model from database func (ib *InfoBlock) GetUnsent() (bool, error) { return isFound(DBConn.Where("sent = ?", "0").First(&ib)) } // Create is creating record of model func (ib *InfoBlock) Create(dbTx *DbTransaction) error { return GetDB(dbTx).Create(ib).Error } // MarkSent update model sent field func (ib *InfoBlock) MarkSent() error { return DBConn.Model(ib).Update("sent", 1).Error } // UpdRollbackHash update model rollbacks_hash field func UpdRollbackHash(dbTx *DbTransaction, hash []byte) error { return GetDB(dbTx).Model(&InfoBlock{}).Update("rollbacks_hash", hash).Error } // BlockGetUnsent returns InfoBlock func BlockGetUnsent() (*InfoBlock, error) { ib := &InfoBlock{} found, err := ib.GetUnsent() if !found { return nil, err } return ib, err } // Marshall returns block as []byte func (ib *InfoBlock) Marshall() []byte { if ib != nil { toBeSent := converter.DecToBin(ib.BlockID, 3) return append(toBeSent, ib.Hash...) } return []byte{} } ================================================ FILE: packages/storage/sqldb/install.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb // ProgressComplete status of installation progress const ProgressComplete = "complete" // Install is model type Install struct { Progress string `gorm:"not null;size:10"` } // TableName returns name of table func (i *Install) TableName() string { return "install" } // Get is retrieving model from database func (i *Install) Get() error { return DBConn.Find(i).Error } // Create is creating record of model func (i *Install) Create() error { return DBConn.Create(i).Error } ================================================ FILE: packages/storage/sqldb/keys.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "fmt" "github.com/shopspring/decimal" "github.com/IBAX-io/go-ibax/packages/converter" ) // Key is model type Key struct { ecosystem int64 accountKeyID int64 `gorm:"-"` ID int64 `gorm:"primary_key;not null"` AccountID string `gorm:"column:account;not null"` PublicKey []byte `gorm:"column:pub;not null"` Amount string `gorm:"not null"` Maxpay string `gorm:"not null"` Deleted int64 `gorm:"not null"` Blocked int64 `gorm:"not null"` } // SetTablePrefix is setting table prefix func (m *Key) SetTablePrefix(prefix int64) *Key { m.ecosystem = prefix return m } // TableName returns name of table func (m Key) TableName() string { if m.ecosystem == 0 { m.ecosystem = 1 } return `1_keys` } func (m *Key) Disable() bool { return m.Deleted != 0 || m.Blocked != 0 } func (m *Key) CapableAmount() decimal.Decimal { amount := decimal.Zero if len(m.Amount) > 0 { amount, _ = decimal.NewFromString(m.Amount) } maxpay := decimal.Zero if len(m.Maxpay) > 0 { maxpay, _ = decimal.NewFromString(m.Maxpay) } if maxpay.GreaterThan(decimal.Zero) && maxpay.LessThan(amount) { amount = maxpay } return amount } // Get is retrieving model from database func (m *Key) Get(db *DbTransaction, wallet int64) (bool, error) { return isFound(GetDB(db).Where("id = ? and ecosystem = ?", wallet, m.ecosystem).First(m)) } func (m *Key) AccountKeyID() int64 { if m.accountKeyID == 0 { m.accountKeyID = converter.StringToAddress(m.AccountID) } return m.accountKeyID } // KeyTableName returns name of key table func KeyTableName(prefix int64) string { return fmt.Sprintf("%d_keys", prefix) } // GetKeysCount returns common count of keys func GetKeysCount() (int64, error) { var cnt int64 row := DBConn.Raw(`SELECT count(*) key_count FROM "1_keys" WHERE ecosystem = 1`).Select("key_count").Row() err := row.Scan(&cnt) return cnt, err } ================================================ FILE: packages/storage/sqldb/language.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/converter" ) // Language is model type Language struct { ecosystem int64 ID int64 `gorm:"primary_key;not null"` Name string `gorm:"not null;size:100"` Res string `gorm:"type:jsonb"` Conditions string `gorm:"not null"` } // SetTablePrefix is setting table prefix func (l *Language) SetTablePrefix(prefix string) { l.ecosystem = converter.StrToInt64(prefix) } // TableName returns name of table func (l *Language) TableName() string { if l.ecosystem == 0 { l.ecosystem = 1 } return `1_languages` } // GetAll is retrieving all records from database func (l *Language) GetAll(dbTx *DbTransaction, prefix string) ([]Language, error) { result := new([]Language) err := GetDB(dbTx).Table("1_languages").Where("ecosystem = ?", prefix).Order("name asc").Find(&result).Error return *result, err } // ToMap is converting model to map func (l *Language) ToMap() map[string]string { result := make(map[string]string, 0) result["name"] = l.Name result["res"] = l.Res result["conditions"] = l.Conditions return result } ================================================ FILE: packages/storage/sqldb/log_transaction.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "gorm.io/gorm" ) // LogTransaction is model type LogTransaction struct { Hash []byte `gorm:"primary_key;not null"` Block int64 `gorm:"not null"` //TxData []byte `gorm:"not null"` Timestamp int64 `gorm:"not null"` Address int64 `gorm:"not null"` EcosystemID int64 `gorm:"not null"` Status int64 `gorm:"not null"` ContractName string `gorm:"not null"` } // GetByHash returns LogTransactions existence by hash func (lt *LogTransaction) GetByHash(dbTx *DbTransaction, hash []byte) (bool, error) { return isFound(GetDB(dbTx).Where("hash = ?", hash).First(lt)) } // Create is creating record of model func (lt *LogTransaction) Create(dbTx *DbTransaction) error { return GetDB(dbTx).Create(lt).Error } func CreateLogTransactionBatches(dbTx *gorm.DB, lts []*LogTransaction) error { if len(lts) == 0 { return nil } return dbTx.Model(&LogTransaction{}).Create(<s).Error } // DeleteLogTransactionsByHash is deleting record by hash func DeleteLogTransactionsByHash(dbTx *DbTransaction, hash []byte) (int64, error) { query := GetDB(dbTx).Exec("DELETE FROM log_transactions WHERE hash = ?", hash) return query.RowsAffected, query.Error } // GetLogTransactionsCount count records by transaction hash func GetLogTransactionsCount(hash []byte) (int64, error) { var rowsCount int64 if err := DBConn.Table("log_transactions").Where("hash = ?", hash).Count(&rowsCount).Error; err != nil { return -1, err } return rowsCount, nil } // GetLogTxCount count records by ecosystemID func GetLogTxCount(dbTx *DbTransaction, ecosystemID int64) (int64, error) { var rowsCount int64 if err := GetDB(dbTx).Table("log_transactions").Where("ecosystem_id = ? and status = 0", ecosystemID).Count(&rowsCount).Error; err != nil { return -1, err } return rowsCount, nil } ================================================ FILE: packages/storage/sqldb/members.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import "github.com/IBAX-io/go-ibax/packages/converter" // Member represents a ecosystem member type Member struct { ecosystem int64 ID int64 `gorm:"primary_key;not null"` MemberName string `gorm:"not null"` ImageID *int64 MemberInfo string `gorm:"type:jsonb"` } // SetTablePrefix is setting table prefix func (m *Member) SetTablePrefix(prefix string) { m.ecosystem = converter.StrToInt64(prefix) } // TableName returns name of table func (m *Member) TableName() string { if m.ecosystem == 0 { m.ecosystem = 1 } return `1_members` } // Count returns count of records in table func (m *Member) Count() (count int64, err error) { err = DBConn.Table(m.TableName()).Where(`ecosystem=?`, m.ecosystem).Count(&count).Error return } // Get init m as member with ID func (m *Member) Get(account string) (bool, error) { return isFound(DBConn.Where("ecosystem=? and account = ?", m.ecosystem, account).First(m)) } ================================================ FILE: packages/storage/sqldb/menu.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import "github.com/IBAX-io/go-ibax/packages/converter" // Menu is model type Menu struct { ecosystem int64 ID int64 `gorm:"primary_key;not null" json:"id"` Name string `gorm:"not null" json:"name"` Title string `gorm:"not null" json:"title"` Value string `gorm:"not null" json:"value"` Conditions string `gorm:"not null" json:"conditions"` } // SetTablePrefix is setting table prefix func (m *Menu) SetTablePrefix(prefix string) { m.ecosystem = converter.StrToInt64(prefix) } // TableName returns name of table func (m Menu) TableName() string { if m.ecosystem == 0 { m.ecosystem = 1 } return `1_menu` } // Get is retrieving model from database func (m *Menu) Get(name string) (bool, error) { return isFound(DBConn.Where("ecosystem=? and name = ?", m.ecosystem, name).First(m)) } ================================================ FILE: packages/storage/sqldb/metric.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "time" "github.com/IBAX-io/go-ibax/packages/types" ) const tableNameMetrics = "1_metrics" // Metric represents record of system_metrics table type Metric struct { ID int64 `gorm:"primary_key;not null"` Time int64 `gorm:"not null"` Metric string `gorm:"not null"` Key string `gorm:"not null"` Value int64 `gorm:"not null"` } // TableName returns name of table func (Metric) TableName() string { return tableNameMetrics } // EcosystemTx represents value of metric type EcosystemTx struct { UnixTime int64 Ecosystem string Count int64 } // GetEcosystemTxPerDay returns the count of transactions per day for ecosystems, // processes data for two days func GetEcosystemTxPerDay(timeBlock int64) ([]*EcosystemTx, error) { curDate := time.Unix(timeBlock, 0).Format(`2006-01-02`) sql := `SELECT EXTRACT(EPOCH FROM to_timestamp(bc.time)::date)::int "unix_time", SUBSTRING(rtx.table_name FROM '^\d+') "ecosystem", COUNT(*) FROM rollback_tx rtx INNER JOIN block_chain bc ON bc.id = rtx.block_id WHERE to_timestamp(bc.time)::date >= (DATE('` + curDate + `') - interval '1' day)::date GROUP BY unix_time, ecosystem ORDER BY unix_time, ecosystem` var ecosystemTx []*EcosystemTx err := DBConn.Raw(sql).Scan(&ecosystemTx).Error if err != nil { return nil, err } return ecosystemTx, err } // GetMetricValues returns aggregated metric values in the time interval func GetMetricValues(metric, timeInterval, aggregateFunc, timeBlock string) ([]any, error) { rows, err := DBConn.Table(tableNameMetrics).Select("key,"+aggregateFunc+"(value)"). Where("metric = ? AND time >= EXTRACT(EPOCH FROM TIMESTAMP '"+timeBlock+"' - CAST(? AS INTERVAL))", metric, timeInterval).Order("id asc"). Group("key, id").Rows() if err != nil { return nil, err } var ( result = []any{} key string value string ) defer rows.Close() for rows.Next() { if err := rows.Scan(&key, &value); err != nil { return nil, err } result = append(result, types.LoadMap(map[string]any{ "key": key, "value": value, })) } return result, nil } ================================================ FILE: packages/storage/sqldb/migration_history.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "time" ) const noVersion = "0.0.0" // MigrationHistory is model type MigrationHistory struct { ID int64 `gorm:"primary_key;not null"` Version string `gorm:"not null"` DateApplied int64 `gorm:"not null"` } // TableName returns name of table func (mh *MigrationHistory) TableName() string { return "migration_history" } // CurrentVersion returns current version of database migrations func (mh *MigrationHistory) CurrentVersion() (string, error) { if !NewDbTransaction(DBConn).IsTable(mh.TableName()) { return noVersion, nil } err := DBConn.Last(mh).Error if mh.Version == "" { return noVersion, nil } return mh.Version, err } // ApplyMigration executes database schema and writes migration history func (mh *MigrationHistory) ApplyMigration(version, query string) error { err := DBConn.Exec(query).Error if err != nil { return err } return DBConn.Create(&MigrationHistory{Version: version, DateApplied: time.Now().Unix()}).Error } ================================================ FILE: packages/storage/sqldb/node_ban_logs.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import "time" type NodeBanLogs struct { ID int64 BannedAt time.Time BanTime time.Duration Reason string } // TableName returns name of table func (r NodeBanLogs) TableName() string { return "1_node_ban_logs" } ================================================ FILE: packages/storage/sqldb/notification.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "fmt" "github.com/IBAX-io/go-ibax/packages/converter" ) const ( notificationTableSuffix = "_notifications" NotificationTypeSingle = 1 NotificationTypeRole = 2 ) // Notification structure type Notification struct { ecosystem int64 ID int64 `gorm:"primary_key;not null"` Recipient string `gorm:"type:jsonb"` Sender string `gorm:"type:jsonb"` Notification string `gorm:"type:jsonb"` PageParams string `gorm:"type:jsonb"` ProcessingInfo string `gorm:"type:jsonb"` PageName string `gorm:"size:255"` DateCreated int64 DateStartProcessing int64 DateClosed int64 Closed bool } // SetTablePrefix set table Prefix func (n *Notification) SetTablePrefix(tablePrefix string) { n.ecosystem = converter.StrToInt64(tablePrefix) } // TableName returns table name func (n *Notification) TableName() string { if n.ecosystem == 0 { n.ecosystem = 1 } return `1_notifications` } type NotificationsCount struct { RecipientID int64 `gorm:"recipient_id"` Account string `gorm:"account"` RoleID int64 `gorm:"role_id"` Count int64 `gorm:"count"` } // GetNotificationsCount returns all unclosed notifications by users and ecosystem through role_id // if userIDs is nil or empty then filter will be skipped func GetNotificationsCount(ecosystemID int64, accounts []string) ([]NotificationsCount, error) { result := make([]NotificationsCount, 0, len(accounts)) for _, account := range accounts { query := `SELECT k.id as "recipient_id", '0' as "role_id", count(n.id), k.account FROM "1_keys" k LEFT JOIN "1_notifications" n ON n.ecosystem = k.ecosystem AND n.closed = 0 AND n.notification->>'type' = '1' and n.recipient->>'account' = k.account WHERE k.ecosystem = ? AND k.account = ? GROUP BY recipient_id, k.account, role_id UNION SELECT k.id as "recipient_id", rp.role->>'id' as "role_id", count(n.id), k.account FROM "1_keys" k INNER JOIN "1_roles_participants" rp ON rp.member->>'account' = k.account LEFT JOIN "1_notifications" n ON n.ecosystem = k.ecosystem AND n.closed = 0 AND n.notification->>'type' = '2' AND n.recipient->>'role_id' = rp.role->>'id' AND (n.date_start_processing = 0 OR n.processing_info->>'account' = k.account) WHERE k.ecosystem=? AND k.account = ? GROUP BY recipient_id, k.account, role_id` list := make([]NotificationsCount, 0) err := GetDB(nil).Raw(query, ecosystemID, account, ecosystemID, account).Scan(&list).Error if err != nil { return nil, err } result = append(result, list...) } return result, nil } func getNotificationCountFilter(users []int64, ecosystemID int64) (filter string, params []any) { filter = fmt.Sprintf(` WHERE closed = 0 and ecosystem = '%d' `, ecosystemID) if len(users) > 0 { filter += `AND recipient->>'member_id' IN (?) ` usersStrs := []string{} for _, user := range users { usersStrs = append(usersStrs, converter.Int64ToStr(user)) } params = append(params, usersStrs) } return } ================================================ FILE: packages/storage/sqldb/notification_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "testing" "github.com/stretchr/testify/assert" ) type testItem struct { Input []int64 Filter string ParamsLength int } func TestGetNotificationCountFilter(t *testing.T) { testTable := []testItem{ { Input: []int64{3, 5}, Filter: ` WHERE closed = false AND recipient_id IN (?) `, ParamsLength: 1, }, { Input: nil, Filter: ` WHERE closed = false `, ParamsLength: 0, }, } for i, item := range testTable { filter, params := getNotificationCountFilter(item.Input, 1) assert.Equal(t, item.Filter, filter, "on %d step wrong filter %s", i, filter) assert.Equal(t, item.ParamsLength, len(params), "on %d step wrong params length %d", i, len(params)) } } ================================================ FILE: packages/storage/sqldb/ordering.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb type ordering string const ( // OrderASC as ASC OrderASC = ordering("ASC") // OrderDESC as DESC OrderDESC = ordering("DESC") ) ================================================ FILE: packages/storage/sqldb/pages.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import "github.com/IBAX-io/go-ibax/packages/converter" // Page is model type Page struct { ecosystem int64 ID int64 `gorm:"primary_key;not null" json:"id,omitempty"` Name string `gorm:"not null" json:"name,omitempty"` Value string `gorm:"not null" json:"value,omitempty"` Menu string `gorm:"not null;size:255" json:"menu,omitempty"` ValidateCount int64 `gorm:"not null" json:"nodesCount,omitempty"` AppID int64 `gorm:"column:app_id;not null" json:"app_id,omitempty"` Conditions string `gorm:"not null" json:"conditions,omitempty"` } // SetTablePrefix is setting table prefix func (p *Page) SetTablePrefix(prefix string) { p.ecosystem = converter.StrToInt64(prefix) } // TableName returns name of table func (p *Page) TableName() string { if p.ecosystem == 0 { p.ecosystem = 1 } return `1_pages` } // Get is retrieving model from database func (p *Page) Get(name string) (bool, error) { return isFound(DBConn.Where("ecosystem=? and name = ?", p.ecosystem, name).First(p)) } // Count returns count of records in table func (p *Page) Count() (count int64, err error) { err = DBConn.Table(p.TableName()).Count(&count).Error return } // GetByApp returns all pages belonging to selected app func (p *Page) GetByApp(appID int64, ecosystemID int64) ([]Page, error) { var result []Page err := DBConn.Select("id, name").Where("app_id = ? and ecosystem = ?", appID, ecosystemID).Find(&result).Error return result, err } ================================================ FILE: packages/storage/sqldb/platform_parameter.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "encoding/json" ) // PlatformParameter is model type PlatformParameter struct { ID int64 `gorm:"primary_key;not null;"` Name string `gorm:"not null;size:255"` Value string `gorm:"not null"` Conditions string `gorm:"not null"` } // TableName returns name of table func (sp PlatformParameter) TableName() string { return "1_platform_parameters" } // Get is retrieving model from database func (sp *PlatformParameter) Get(dbTx *DbTransaction, name string) (bool, error) { return isFound(GetDB(dbTx).Where("name = ?", name).First(sp)) } // GetTransaction is retrieving model from database using transaction func (sp *PlatformParameter) GetTransaction(dbTx *DbTransaction, name string) (bool, error) { return isFound(GetDB(dbTx).Where("name = ?", name).First(sp)) } // GetJSONField returns fields as json func (sp *PlatformParameter) GetJSONField(jsonField string, name string) (string, error) { var result string err := DBConn.Table("1_platform_parameters").Where("name = ?", name).Select(jsonField).Row().Scan(&result) return result, err } // GetValueParameterByName returns value parameter by name func (sp *PlatformParameter) GetValueParameterByName(name, value string) (*string, error) { var result *string err := DBConn.Raw(`SELECT value->'`+value+`' FROM "1_platform_parameters" WHERE name = ?`, name).Row().Scan(&result) if err != nil { return nil, err } return result, nil } // GetAllPlatformParameters returns all platform parameters func GetAllPlatformParameters(dbTx *DbTransaction, offset, limit *int, names []string) ([]PlatformParameter, error) { parameters := new([]PlatformParameter) q := GetDB(dbTx) if len(names) > 0 { //if any select names,then all return q = q.Where("name IN ?", names) } else { if offset != nil { q = q.Offset(*offset) } if limit != nil { q = q.Limit(*limit) } } if err := q.Find(¶meters).Error; err != nil { return nil, err } return *parameters, nil } // ToMap is converting PlatformParameter to map func (sp *PlatformParameter) ToMap() map[string]string { result := make(map[string]string, 0) result["name"] = sp.Name result["value"] = sp.Value result["conditions"] = sp.Conditions return result } // Update is update model func (sp PlatformParameter) Update(dbTx *DbTransaction, value string) error { return GetDB(dbTx).Model(sp).Where("name = ?", sp.Name).Update(`value`, value).Error } // SaveArray is saving array func (sp *PlatformParameter) SaveArray(dbTx *DbTransaction, list [][]string) error { ret, err := json.Marshal(list) if err != nil { return err } return sp.Update(dbTx, string(ret)) } func (sp *PlatformParameter) GetNumberOfHonorNodes() (int, error) { var hns []map[string]any f, err := sp.GetTransaction(nil, `honor_nodes`) if err != nil { return 0, err } if f { if len(sp.Value) > 0 { if err := json.Unmarshal([]byte(sp.Value), &hns); err != nil { return 0, err } } } if len(hns) == 0 || len(hns) == 1 { return 0, nil } return len(hns), nil } ================================================ FILE: packages/storage/sqldb/queryBuilder/expression.go ================================================ package queryBuilder import ( "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" ) func GetOrder(tblname string, inOrder any, withDefault bool) (string, error) { var ( orders []string defaultSortOrder = map[string]string{ `keys`: "ecosystem,id", `members`: "ecosystem,id", } ) cols := types.NewMap() sanitize := func(in string, value any) { in = converter.Sanitize(strings.ToLower(in), ``) if len(in) > 0 { cols.Set(in, true) in = `"` + in + `"` if fmt.Sprint(value) == `-1` { in += ` desc` } else if fmt.Sprint(value) == `1` { in += ` asc` } else { in += ` asc` } orders = append(orders, in) } else { orders = append(orders, `"id" asc`) } } if withDefault { if v, ok := defaultSortOrder[tblname[2:]]; ok { for _, item := range strings.Split(v, `,`) { cols.Set(item, false) } } else { cols.Set(`id`, false) } } switch v := inOrder.(type) { case string: sanitize(v, nil) case *types.Map: for _, ikey := range v.Keys() { item, _ := v.Get(ikey) sanitize(ikey, item) } case map[string]any: for ikey, item := range v { sanitize(ikey, item) } case []any: for _, item := range v { switch param := item.(type) { case string: sanitize(param, nil) case *types.Map: for _, ikey := range param.Keys() { item, _ := param.Get(ikey) sanitize(ikey, item) } case map[string]any: for key, value := range param { sanitize(key, value) } } } } for _, key := range cols.Keys() { if state, found := cols.Get(key); !found || !state.(bool) { orders = append(orders, key) } } if err := CheckNow(orders...); err != nil { return ``, err } return strings.Join(orders, `,`), nil } func GetColumns(inColumns any) ([]string, error) { var columns []string switch v := inColumns.(type) { case string: if len(v) > 0 { columns = strings.Split(v, `,`) } case []any: for _, name := range v { switch col := name.(type) { case string: columns = append(columns, col) } } } if len(columns) == 0 { columns = []string{`*`} } for i, v := range columns { columns[i] = converter.Sanitize(strings.ToLower(v), `*->`) } if err := CheckNow(columns...); err != nil { return nil, err } return columns, nil } func GetTableName(ecosystem int64, tblname string) string { return converter.ParseTable(tblname, ecosystem) } ================================================ FILE: packages/storage/sqldb/queryBuilder/query_builder.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package queryBuilder import ( "encoding/hex" "encoding/json" "errors" "fmt" "regexp" "sort" "strings" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) const ( prefTimestamp = "timestamp" prefTimestampSpace = "timestamp " ) var ( checkNowRE = regexp.MustCompile(`(now\s*\(\s*\)|localtime|current_date|current_time|current_timestamp)`) ErrNow = errors.New(`it is prohibited to use current_time or current_date or localtime or current_timestamp sql keyword`) ) // KeyTableChecker checks table type KeyTableChecker interface { IsKeyTable(string) bool } type NextIDGetter interface { GetNextID(string) (int64, error) } type SQLQueryBuilder struct { *log.Entry tableID string Table string isKeyTable bool prepared bool keyEcosystem string keyName string Fields []string FieldValues []any stringValues []string Where *types.Map KeyTableChkr KeyTableChecker whereExpr string TxEcoID int64 } func (b *SQLQueryBuilder) Prepare() error { if b.prepared { return nil } idNames := strings.SplitN(b.Table, `_`, 2) if len(idNames) == 2 { b.keyName = idNames[1] if b.KeyTableChkr.IsKeyTable(b.keyName) { b.isKeyTable = true if b.TxEcoID > 1 { if b.Table == "1_keys" { b.keyEcosystem = idNames[0] } else { b.keyEcosystem = converter.Int64ToStr(b.TxEcoID) } } else { b.keyEcosystem = idNames[0] } b.Table = `1_` + b.keyName if contains, ecosysIndx := isParamsContainsEcosystem(b.Fields, b.FieldValues); contains { if b.Where.IsEmpty() { b.keyEcosystem = fmt.Sprint(b.FieldValues[ecosysIndx]) } } else { b.Fields = append(b.Fields, "ecosystem") b.FieldValues = append(b.FieldValues, b.keyEcosystem) } } } if err := b.normalizeValues(); err != nil { b.WithError(err).Error("on normalize field values") return err } values, err := converter.InterfaceSliceToStr(b.FieldValues) if err != nil { b.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("on convert field values to string") return err } b.stringValues = values b.prepared = true return nil } func (b *SQLQueryBuilder) SetTableID(id string) { b.tableID = id } func (b *SQLQueryBuilder) TableID() string { return b.tableID } func (b *SQLQueryBuilder) GetSelectExpr() (string, error) { if err := b.Prepare(); err != nil { return "", err } fieldsExpr, err := b.GetSQLSelectFieldsExpr() if err != nil { b.WithError(err).Error("on getting sql fields statement") return "", err } whereExpr, err := b.GetSQLWhereExpr() if err != nil { b.WithError(err).Error("on getting sql where statement") return "", err } return fmt.Sprintf(`SELECT %s FROM "%s" %s`, fieldsExpr, b.Table, whereExpr), nil } func (b *SQLQueryBuilder) GetSQLSelectFieldsExpr() (string, error) { if err := b.Prepare(); err != nil { return "", err } sqlFields := make([]string, 0, len(b.Fields)+1) sqlFields = append(sqlFields, "id") for i := range b.Fields { b.Fields[i] = strings.TrimSpace(strings.ToLower(b.Fields[i])) sqlFields = append(sqlFields, toSQLField(b.Fields[i])) } return strings.Join(sqlFields, ","), nil } func (b *SQLQueryBuilder) GetSQLWhereExpr() (string, error) { var err error if err = b.Prepare(); err != nil { return "", err } if b.Where.IsEmpty() { return "", nil } if b.whereExpr != "" { return b.whereExpr, nil } if b.isKeyTable { if _, isEcosystem := b.Where.Get(`ecosystem`); !isEcosystem { b.Where.Set(`ecosystem`, converter.StrToInt64(b.keyEcosystem)) } } b.whereExpr, err = GetWhere(b.Where) if err != nil { return ``, err } if len(b.whereExpr) > 0 { b.whereExpr = " WHERE " + b.whereExpr return b.whereExpr, nil } return "", nil } func (b *SQLQueryBuilder) GetSQLUpdateExpr(logData map[string]string) (string, error) { if err := b.Prepare(); err != nil { return "", err } expressions := make([]string, 0, len(b.Fields)) jsonFields := make(map[string]map[string]string) for i := 0; i < len(b.Fields); i++ { if b.isKeyTable && b.Fields[i] == "ecosystem" { continue } if strings.Contains(b.Fields[i], `->`) { colfield := strings.Split(b.Fields[i], `->`) if len(colfield) == 2 { if jsonFields[colfield[0]] == nil { jsonFields[colfield[0]] = make(map[string]string) } jsonFields[colfield[0]][colfield[1]] = escapeSingleQuotes(b.stringValues[i]) continue } } if syspar.IsByteColumn(b.Table, b.Fields[i]) && len(b.stringValues[i]) != 0 { expressions = append(expressions, b.Fields[i]+"="+toSQLHexExpr(b.stringValues[i])) } else if b.Fields[i][:1] == "+" || b.Fields[i][:1] == "-" { expressions = append(expressions, toArithmeticUpdateExpr(b.Fields[i], b.stringValues[i])) } else if b.stringValues[i] == `NULL` { expressions = append(expressions, b.Fields[i]+"= NULL") } else if strings.HasPrefix(b.Fields[i], prefTimestampSpace) { expressions = append(expressions, toTimestampUpdateExpr(b.Fields[i], b.stringValues[i])) } else if strings.HasPrefix(b.stringValues[i], prefTimestampSpace) { expressions = append(expressions, b.Fields[i]+`= timestamp '`+escapeSingleQuotes(b.stringValues[i][len(`timestamp `):])+`'`) } else { expressions = append(expressions, `"`+b.Fields[i]+`"='`+escapeSingleQuotes(b.stringValues[i])+`'`) } } var jsonColumnArr []string for name := range jsonFields { jsonColumnArr = append(jsonColumnArr, name) } sort.Strings(jsonColumnArr) for _, name := range jsonColumnArr { val := jsonFields[name] var initial string out, err := json.Marshal(val) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.JSONMarshallError}).Error("marshalling update columns for jsonb") return "", err } if len(logData[name]) > 0 && logData[name] != `NULL` { initial = name } else { initial = `'{}'` } expressions = append(expressions, fmt.Sprintf(`%s=%s::jsonb || '%s'::jsonb`, name, initial, string(out))) } return strings.Join(expressions, ","), nil } func (b *SQLQueryBuilder) GetSQLInsertQuery(idGetter NextIDGetter) (string, error) { if err := b.Prepare(); err != nil { return "", err } isID := false insFields := []string{} insValues := []string{} jsonFields := make(map[string]map[string]string) for i := 0; i < len(b.Fields); i++ { if b.Fields[i] == `id` { isID = true b.tableID = escapeSingleQuotes(b.stringValues[i]) } if strings.Contains(b.Fields[i], `->`) { colfield := strings.Split(b.Fields[i], `->`) if len(colfield) == 2 { if jsonFields[colfield[0]] == nil { jsonFields[colfield[0]] = make(map[string]string) } jsonFields[colfield[0]][colfield[1]] = escapeSingleQuotes(b.stringValues[i]) continue } } insFields = append(insFields, toSQLField(b.Fields[i])) insValues = append(insValues, b.toSQLValue(b.stringValues[i], b.Fields[i])) } var jsonColumnArr []string for name := range jsonFields { jsonColumnArr = append(jsonColumnArr, name) } sort.Strings(jsonColumnArr) for _, name := range jsonColumnArr { val := jsonFields[name] out, err := json.Marshal(val) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.JSONMarshallError}).Error("marshalling update columns for jsonb") return "", err } insFields = append(insFields, name) insValues = append(insValues, fmt.Sprintf(`'%s'::jsonb`, string(out))) } if !isID { id, err := idGetter.GetNextID(b.Table) if err != nil { return "", err } b.tableID = converter.Int64ToStr(id) insFields = append(insFields, `id`) insValues = append(insValues, wrapString(b.tableID, "'")) } flds := strings.Join(insFields, ",") vls := strings.Join(insValues, ",") return fmt.Sprintf(`INSERT INTO "%s" (%s) VALUES (%s)`, b.Table, flds, vls), nil } func (b SQLQueryBuilder) GenerateRollBackInfoString(logData map[string]string) (string, error) { rollbackInfo := make(map[string]string) for k, v := range logData { var eco bool idNames := strings.SplitN(b.Table, `_`, 2) if len(idNames) == 2 { b.keyName = idNames[1] if b.KeyTableChkr.IsKeyTable(b.keyName) { eco = true } } if k == `id` || (b.isKeyTable && !eco) { continue } rollbackInfo[k] = v } jsonRollbackInfo, err := json.Marshal(rollbackInfo) if err != nil { b.Logger.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marshalling rollback info to json") return "", err } return string(jsonRollbackInfo), nil } func (b SQLQueryBuilder) toSQLValue(rawValue, rawField string) string { if syspar.IsByteColumn(b.Table, rawField) && len(rawValue) != 0 { return toSQLHexExpr(rawValue) } if rawValue == `NULL` { return `NULL` } if strings.HasPrefix(rawField, prefTimestamp) { return toWrapedTimestamp(rawValue) } if strings.HasPrefix(rawValue, prefTimestamp) { return toTimestamp(rawValue) } return wrapString(escapeSingleQuotes(rawValue), "'") } func (b SQLQueryBuilder) normalizeValues() error { for i, v := range b.FieldValues { switch val := v.(type) { case string: if strings.HasPrefix(strings.TrimSpace(val), prefTimestamp) { if err := CheckNow(val); err != nil { return err } } if len(b.Fields) > i && syspar.IsByteColumn(b.Table, b.Fields[i]) { if vbyte, err := hex.DecodeString(val); err == nil { b.FieldValues[i] = vbyte } } } } return nil } func isParamsContainsEcosystem(fields []string, ivalues []any) (bool, int) { ecosysIndx := getFieldIndex(fields, `ecosystem`) if ecosysIndx >= 0 && len(ivalues) > ecosysIndx && converter.StrToInt64(fmt.Sprint(ivalues[ecosysIndx])) > 0 { return true, ecosysIndx } return false, -1 } func toSQLHexExpr(value string) string { return fmt.Sprintf(" decode('%s','HEX')", hex.EncodeToString([]byte(value))) } func toArithmeticUpdateExpr(field, value string) string { return field[1:] + "=" + field[1:] + field[:1] + escapeSingleQuotes(value) } func toTimestampUpdateExpr(field, value string) string { return field[len(prefTimestampSpace):] + `= to_timestamp('` + escapeSingleQuotes(value) + `')` } func toWrapedTimestamp(value string) string { return `to_timestamp('` + escapeSingleQuotes(value) + `')` } func toTimestamp(value string) string { return prefTimestampSpace + wrapString(escapeSingleQuotes(value[len(prefTimestampSpace):]), "'") } func toSQLField(rawField string) string { if rawField[:1] == "+" || rawField[:1] == "-" { return rawField[1:] } if strings.HasPrefix(rawField, prefTimestampSpace) { return rawField[len(prefTimestampSpace):] } if strings.Contains(rawField, `->`) { return rawField[:strings.Index(rawField, `->`)] } return wrapString(rawField, `"`) } func wrapString(raw, wrapper string) string { return wrapper + raw + wrapper } func escapeSingleQuotes(val string) string { return strings.Replace(val, `'`, `''`, -1) } // CheckNow allows check if the content contains postgres NOW() func CheckNow(inputs ...string) error { for _, item := range inputs { if checkNowRE.Match([]byte(strings.ToLower(item))) { return ErrNow } } return nil } func getFieldIndex(fields []string, name string) int { for i, v := range fields { if strings.ToLower(v) == name { return i } } return -1 } func (b *SQLQueryBuilder) GetEcosystem() string { return b.keyEcosystem } func (b *SQLQueryBuilder) IsEmptyWhere() bool { return len(b.whereExpr) == 0 } ================================================ FILE: packages/storage/sqldb/queryBuilder/query_builder_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package queryBuilder import ( "fmt" "testing" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) // query="SELECT ,,,id,amount,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"ecosystem\" // FROM \"1_keys\" \nWHERE AND id = -6752330173818123413 AND ecosystem = '1'\n" // fields="[+amount]" // values="[2912910000000]" // whereF="[id]" // whereV="[-6752330173818123413]" type TestKeyTableChecker struct { Val bool } func (tc TestKeyTableChecker) IsKeyTable(tableName string) bool { return tc.Val } func TestSqlFields(t *testing.T) { qb := SQLQueryBuilder{ Entry: log.WithFields(log.Fields{"mod": "test"}), Table: "1_keys", Fields: []string{"+amount"}, FieldValues: []any{2912910000000}, Where: types.LoadMap(map[string]any{`id`: `-6752330173818123413`}), KeyTableChkr: TestKeyTableChecker{true}, } fields, err := qb.GetSQLSelectFieldsExpr() if err != nil { t.Error(err) return } fmt.Println(fields) } ================================================ FILE: packages/storage/sqldb/queryBuilder/where.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package queryBuilder import ( "errors" "fmt" "regexp" "strings" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/types" ) var ( errWhereFalse = errors.New(`false result`) ) func PrepareWhere(where string) string { whereStr := where where = strings.Replace(where, "->", ";", -1) whereSlice := regexp.MustCompile(`;([\w\-]+)`).FindAllStringSubmatchIndex(where, -1) startWhere := 0 out := `` for i := 0; i < len(whereSlice); i++ { slice := whereSlice[i] if len(slice) != 4 { continue } if i < len(whereSlice)-1 && slice[1] == whereSlice[i+1][0] { colsWhere := []string{where[slice[2]:slice[3]]} from := slice[0] for i < len(whereSlice)-1 && slice[1] == whereSlice[i+1][0] { i++ slice = whereSlice[i] if len(slice) != 4 { break } colsWhere = append(colsWhere, where[slice[2]:slice[3]]) } out += fmt.Sprintf(`%s::jsonb#>>'{%s}'`, where[startWhere:from], strings.Join(colsWhere, `,`)) startWhere = slice[3] } else { out += fmt.Sprintf(`%s->>'%s'`, where[startWhere:slice[0]], where[slice[2]:slice[3]]) startWhere = slice[3] } } if len(out) > 0 { return out + where[startWhere:] } return whereStr } func GetWhere(inWhere *types.Map) (string, error) { var ( where string cond []string ) if inWhere == nil { inWhere = types.NewMap() } escape := func(value any) string { return strings.Replace(fmt.Sprint(value), `'`, `''`, -1) } oper := func(action string, v any) (string, error) { switch value := v.(type) { default: return fmt.Sprintf(`%s '%s'`, action, escape(value)), nil } } like := func(pattern string, v any) (string, error) { switch value := v.(type) { default: return fmt.Sprintf(pattern, escape(value)), nil } } in := func(action string, v any) (ret string, err error) { switch value := v.(type) { case string: if len(value) == 0 { return `false`, errWhereFalse } ret = fmt.Sprintf(`%s ('%s')`, action, value) case []any: var list []string for _, ival := range value { list = append(list, escape(ival)) } if len(list) > 0 { ret = fmt.Sprintf(`%s ('%s')`, action, strings.Join(list, `', '`)) } else { return `false`, errWhereFalse } } return } logic := func(action string, v any) (ret string, err error) { switch value := v.(type) { case []any: var list []string for _, ival := range value { switch avalue := ival.(type) { case *types.Map: where, err := GetWhere(avalue) if err != nil { return ``, err } if len(where) > 0 { list = append(list, where) } } } if len(list) > 0 { ret = fmt.Sprintf(`(%s)`, strings.Join(list, ` `+action+` `)) } } return } for _, key := range inWhere.Keys() { v, _ := inWhere.Get(key) key = PrepareWhere(converter.Sanitize(strings.ToLower(key), `->$`)) switch key { case `$like`: return like(`like '%%%s%%'`, v) case `$end`: return like(`like '%%%s'`, v) case `$begin`: return like(`like '%s%%'`, v) case `$ilike`: return like(`ilike '%%%s%%'`, v) case `$iend`: return like(`ilike '%%%s'`, v) case `$ibegin`: return like(`ilike '%s%%'`, v) case `$and`: icond, err := logic(`and`, v) if err != nil { return ``, err } cond = append(cond, icond) case `$or`: icond, err := logic(`or`, v) if err != nil { return ``, err } cond = append(cond, icond) case `$in`: return in(`in`, v) case `$nin`: return in(`not in`, v) case `$eq`: return oper(`=`, v) case `$neq`: return oper(`!=`, v) case `$gt`: return oper(`>`, v) case `$gte`: return oper(`>=`, v) case `$lt`: return oper(`<`, v) case `$lte`: return oper(`<=`, v) default: if !strings.Contains(key, `>`) && len(key) > 0 { key = `"` + key + `"` } switch value := v.(type) { case []any: var acond []string for _, iarr := range value { switch avalue := iarr.(type) { case *types.Map: ret, err := GetWhere(avalue) if err == errWhereFalse { acond = append(acond, ret) } else { if err != nil { return ``, err } acond = append(acond, fmt.Sprintf(`(%s %s)`, key, ret)) } default: acond = append(acond, fmt.Sprintf(`%s = '%s'`, key, escape(avalue))) } } if len(acond) > 0 { cond = append(cond, fmt.Sprintf(`(%s)`, strings.Join(acond, ` and `))) } case *types.Map: ret, err := GetWhere(value) if err == errWhereFalse { cond = append(cond, ret) } else { if err != nil { return ``, err } cond = append(cond, fmt.Sprintf(`(%s %s)`, key, ret)) } case []byte: cond = append(cond, fmt.Sprintf(`%s = %s`, key, fmt.Sprintf(" decode('%x','HEX')", value))) default: ival := escape(value) if ival == `$isnull` { ival = fmt.Sprintf(`%s is null`, key) } else { ival = fmt.Sprintf(`%s = '%s'`, key, ival) } cond = append(cond, ival) } } } if len(cond) > 0 { where = strings.Join(cond, ` and `) if err := CheckNow(where); err != nil { return ``, err } } return where, nil } ================================================ FILE: packages/storage/sqldb/querycost/explain.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package querycost import ( "database/sql" "encoding/json" "errors" "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) // explainQueryCost is counting query execution time func explainQueryCost(dbTx *sqldb.DbTransaction, withAnalyze bool, query string, args ...any) (int64, error) { var planStr string explainTpl := "EXPLAIN (FORMAT JSON) %s" if withAnalyze { explainTpl = "EXPLAIN ANALYZE (FORMAT JSON) %s" } err := sqldb.GetDB(dbTx).Raw(fmt.Sprintf(explainTpl, query), args...).Row().Scan(&planStr) switch { case err == sql.ErrNoRows: log.WithFields(log.Fields{"type": consts.DBError, "error": err, "query": query}).Error("no rows while explaining query") return 0, errors.New("No rows") case err != nil: log.WithFields(log.Fields{"type": consts.DBError, "error": err, "query": query}).Error("error explaining query") return 0, err } var queryPlan []map[string]any dec := json.NewDecoder(strings.NewReader(planStr)) dec.UseNumber() if err := dec.Decode(&queryPlan); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("decoding query plan from JSON") return 0, err } if len(queryPlan) == 0 { log.Error("Query plan is empty") return 0, errors.New("Query plan is empty") } firstNode := queryPlan[0] var plan any var ok bool if plan, ok = firstNode["Plan"]; !ok { log.Error("No Plan key in result") return 0, errors.New("No Plan key in result") } planMap, ok := plan.(map[string]any) if !ok { log.Error("Plan is not map[string]interface{}") return 0, errors.New("Plan is not map[string]interface{}") } totalCost, ok := planMap["Total Cost"] if !ok { return 0, errors.New("PlanMap has no TotalCost") } totalCostNum, ok := totalCost.(json.Number) if !ok { log.Error("PlanMap has no TotalCost") return 0, errors.New("Total cost is not a number") } totalCostF64, err := totalCostNum.Float64() if err != nil { log.Error("Total cost is not a number") return 0, err } return int64(totalCostF64), nil } ================================================ FILE: packages/storage/sqldb/querycost/formula.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package querycost import ( "errors" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) const ( Select = "select" Insert = "insert" Update = "update" Delete = "delete" Set = "set" From = "from" Into = "into" Quote = `"` Lparen = "(" ) const ( SelectCost = 1 UpdateCost = 1 InsertCost = 1 DeleteCost = 1 SelectRowCoeff = 0.0001 InsertRowCoeff = 0.0001 DeleteRowCoeff = 0.0001 UpdateRowCoeff = 0.0001 ) var FromStatementMissingError = errors.New("FROM statement missing") var DeleteMinimumThreeFieldsError = errors.New("DELETE query must consist minimum of 3 fields") var SetStatementMissingError = errors.New("SET statement missing") var IntoStatementMissingError = errors.New("INTO statement missing") var UnknownQueryTypeError = errors.New("Unknown query type") func strSliceIndex(fields []string, fieldToFind string) (index int) { for i, field := range fields { if field == fieldToFind { index = i break } } return } type TableRowCounter interface { RowCount(*sqldb.DbTransaction, string) (int64, error) } type DBCountQueryRowCounter struct { } func (d *DBCountQueryRowCounter) RowCount(transaction *sqldb.DbTransaction, tableName string) (int64, error) { count, err := transaction.GetRecordsCountTx(tableName, ``) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err, "table": tableName}).Error("Getting record count from table") } return count, err } type FormulaQueryCoster struct { rowCounter TableRowCounter } type QueryType interface { GetTableName() (string, error) CalculateCost(int64) int64 } type SelectQueryType string func (s SelectQueryType) GetTableName() (string, error) { queryFields := strings.Fields(string(s)) fromFieldIndex := strSliceIndex(queryFields, From) if fromFieldIndex == 0 { return "", nil } return strings.Trim(queryFields[fromFieldIndex+1], Quote), nil } func (s SelectQueryType) CalculateCost(rowCount int64) int64 { return SelectCost + int64(SelectRowCoeff*float64(rowCount)) } type UpdateQueryType string func (s UpdateQueryType) GetTableName() (string, error) { queryFields := strings.Fields(string(s)) setFieldIndex := strSliceIndex(queryFields, Set) if setFieldIndex == 0 { return "", SetStatementMissingError } return strings.Trim(queryFields[setFieldIndex-1], Quote), nil } func (s UpdateQueryType) CalculateCost(rowCount int64) int64 { return UpdateCost + int64(UpdateRowCoeff*float64(rowCount)) } type InsertQueryType string func (s InsertQueryType) GetTableName() (string, error) { queryFields := strings.Fields(string(s)) intoFieldIndex := strSliceIndex(queryFields, Into) if intoFieldIndex == 0 { return "", IntoStatementMissingError } tableNameValuesField := queryFields[intoFieldIndex+1] tableName := "" lparenIndex := strings.Index(tableNameValuesField, Lparen) if lparenIndex > 0 { tableName = tableNameValuesField[:lparenIndex] } else { tableName = tableNameValuesField } return strings.Trim(tableName, Quote), nil } func (s InsertQueryType) CalculateCost(rowCount int64) int64 { return InsertCost } type DeleteQueryType string func (s DeleteQueryType) GetTableName() (string, error) { queryFields := strings.Fields(string(s)) fromFieldIndex := strSliceIndex(queryFields, From) if fromFieldIndex == 0 { return "", FromStatementMissingError } // DELETE FROM table is minimum if len(queryFields) < 3 { return "", DeleteMinimumThreeFieldsError } return strings.Trim(queryFields[fromFieldIndex+1], Quote), nil } func (s DeleteQueryType) CalculateCost(rowCount int64) int64 { return DeleteCost + int64(DeleteRowCoeff*float64(rowCount)) } func (f *FormulaQueryCoster) QueryCost(transaction *sqldb.DbTransaction, query string, args ...any) (int64, error) { cleanedQuery := strings.TrimSpace(strings.ToLower(query)) var queryType QueryType switch { case strings.HasPrefix(cleanedQuery, Select): queryType = SelectQueryType(cleanedQuery) case strings.HasPrefix(cleanedQuery, Insert): queryType = InsertQueryType(cleanedQuery) case strings.HasPrefix(cleanedQuery, Update): queryType = UpdateQueryType(cleanedQuery) case strings.HasPrefix(cleanedQuery, Delete): queryType = DeleteQueryType(cleanedQuery) default: log.WithFields(log.Fields{"type": consts.ParseError, "query": query}).Error("parsing sql query") return 0, UnknownQueryTypeError } tableName, err := queryType.GetTableName() if err != nil { log.WithFields(log.Fields{"type": consts.ParseError, "query": query, "error": err}).Error("getting table name from sql query") return 0, err } rowCount, err := f.rowCounter.RowCount(transaction, tableName) if err != nil { return 0, err } return queryType.CalculateCost(rowCount), nil } ================================================ FILE: packages/storage/sqldb/querycost/formula_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package querycost import ( "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) type TestTableRowCounter struct { } const tableRowCount = 10000 func (t *TestTableRowCounter) RowCount(tx *sqldb.DbTransaction, tableName string) (int64, error) { if tableName == "small" { return tableRowCount, nil } return 0, errors.New("Unknown table") } type QueryCostByFormulaTestSuite struct { suite.Suite queryCoster QueryCoster } func (s *QueryCostByFormulaTestSuite) SetupTest() { s.queryCoster = &FormulaQueryCoster{&TestTableRowCounter{}} } func (s *QueryCostByFormulaTestSuite) TestQueryCostUnknownQueryType() { _, err := s.queryCoster.QueryCost(nil, "UNSELECT * FROM name") assert.Error(s.T(), err) assert.Equal(s.T(), err, UnknownQueryTypeError) } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromSelectNoTable() { tableName, err := SelectQueryType("select 3").GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "") } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromSelect() { tableName, err := SelectQueryType("select a from keys where 3=1").GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "keys") tableName, err = SelectQueryType(`select a, b, c from "1_keys" where 3=1`).GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "1_keys") } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromInsertNoInto() { _, err := InsertQueryType(`insert "1_keys"(id) values (1)`).GetTableName() assert.Error(s.T(), err) assert.Equal(s.T(), err, IntoStatementMissingError) } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromInsert() { tableName, err := InsertQueryType("insert into keys(a,b,c) values (1,2,3)").GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "keys") tableName, err = InsertQueryType(`insert into "1_keys" (a,b,c) values (1,2,3)`).GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "1_keys") } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromUpdateNoSet() { _, err := UpdateQueryType(`update keys a = b where id = 1`).GetTableName() assert.Error(s.T(), err) assert.Equal(s.T(), err, SetStatementMissingError) } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromUpdate() { tableName, err := UpdateQueryType("update keys set a = 1 where id = 2").GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "keys") tableName, err = UpdateQueryType(`update "1_keys" set a = 1`).GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "1_keys") } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromDeleteNoFrom() { _, err := DeleteQueryType(`delete table where id = 1`).GetTableName() assert.Error(s.T(), err) assert.Equal(s.T(), err, FromStatementMissingError) } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromDeleteNoTable() { _, err := DeleteQueryType(`delete from`).GetTableName() assert.Error(s.T(), err) assert.Equal(s.T(), err, DeleteMinimumThreeFieldsError) } func (s *QueryCostByFormulaTestSuite) TestGetTableNameFromDelete() { tableName, err := DeleteQueryType("delete from keys").GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "keys") tableName, err = DeleteQueryType(`delete from "1_keys" where a = 1`).GetTableName() assert.Nil(s.T(), err) assert.Equal(s.T(), tableName, "1_keys") } func (s *QueryCostByFormulaTestSuite) TestQueryCostSelect() { cost, err := s.queryCoster.QueryCost(nil, "SELECT * FROM small WHERE id = ?", 3) assert.Nil(s.T(), err) assert.Equal(s.T(), cost, SelectQueryType("").CalculateCost(tableRowCount)) } func (s *QueryCostByFormulaTestSuite) TestQueryCostUpdate() { cost, err := s.queryCoster.QueryCost(nil, "UPDATE small SET a = ?", 3) assert.Nil(s.T(), err) assert.Equal(s.T(), cost, UpdateQueryType("").CalculateCost(tableRowCount)) } func (s *QueryCostByFormulaTestSuite) TestQueryCostDelete() { cost, err := s.queryCoster.QueryCost(nil, "DELETE FROM small") assert.Nil(s.T(), err) assert.Equal(s.T(), cost, DeleteQueryType("").CalculateCost(tableRowCount)) } func (s *QueryCostByFormulaTestSuite) TestQueryCostInsert() { cost, err := s.queryCoster.QueryCost(nil, "INSERT INTO small(a,b) VALUES (1,2)") assert.Nil(s.T(), err) assert.Equal(s.T(), cost, InsertQueryType("").CalculateCost(tableRowCount)) } func (s *QueryCostByFormulaTestSuite) TestQueryCostInsertWrongTable() { _, err := s.queryCoster.QueryCost(nil, "INSERT INTO unknown(a,b) VALUES (1,2)") assert.Error(s.T(), err) } func TestQueryCostFormula(t *testing.T) { suite.Run(t, new(QueryCostByFormulaTestSuite)) } ================================================ FILE: packages/storage/sqldb/querycost/querycost.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package querycost import ( "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) type QueryCosterType int const ( ExplainQueryCosterType QueryCosterType = iota ExplainAnalyzeQueryCosterType QueryCosterType = iota FormulaQueryCosterType QueryCosterType = iota ) type QueryCoster interface { QueryCost(*sqldb.DbTransaction, string, ...any) (int64, error) } type ExplainQueryCoster struct { } func (*ExplainQueryCoster) QueryCost(transaction *sqldb.DbTransaction, query string, args ...any) (int64, error) { return explainQueryCost(transaction, true, query, args...) } type ExplainAnalyzeQueryCoster struct { } func (*ExplainAnalyzeQueryCoster) QueryCost(transaction *sqldb.DbTransaction, query string, args ...any) (int64, error) { return explainQueryCost(transaction, true, query, args...) } func GetQueryCoster(tp QueryCosterType) QueryCoster { switch tp { case ExplainQueryCosterType: return &ExplainQueryCoster{} case ExplainAnalyzeQueryCosterType: return &ExplainAnalyzeQueryCoster{} case FormulaQueryCosterType: return &FormulaQueryCoster{&DBCountQueryRowCounter{}} } return nil } ================================================ FILE: packages/storage/sqldb/queue_block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb // QueueBlock is model type QueueBlock struct { Hash []byte `gorm:"primary_key;not null"` BlockID int64 `gorm:"not null"` HonorNodeID int64 `gorm:"not null"` } // Get is retrieving model from database func (qb *QueueBlock) Get() (bool, error) { return isFound(DBConn.First(qb)) } // GetQueueBlockByHash is retrieving blocks queue by hash func (qb *QueueBlock) GetQueueBlockByHash(hash []byte) (bool, error) { return isFound(DBConn.Where("hash = ?", hash).First(qb)) } // Delete is deleting queue func (qb *QueueBlock) Delete() error { return DBConn.Delete(qb).Error } // DeleteQueueBlockByHash is deleting queue by hash func (qb *QueueBlock) DeleteQueueBlockByHash() error { query := DBConn.Exec("DELETE FROM queue_blocks WHERE hash = ?", qb.Hash) return query.Error } // DeleteOldBlocks is deleting old blocks func (qb *QueueBlock) DeleteOldBlocks() error { query := DBConn.Exec("DELETE FROM queue_blocks WHERE block_id <= ?", qb.BlockID) return query.Error } // Create is creating record of model func (qb *QueueBlock) Create() error { return DBConn.Create(qb).Error } ================================================ FILE: packages/storage/sqldb/queue_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/shopspring/decimal" ) // QueueTx is model type QueueTx struct { Hash []byte `gorm:"primary_key;not null"` Data []byte `gorm:"not null"` FromGate int `gorm:"not null"` Expedite decimal.Decimal `gorm:"not null"` Time int64 `gorm:"not null"` } // TableName returns name of table func (qt *QueueTx) TableName() string { return "queue_tx" } // DeleteTx is deleting tx func (qt *QueueTx) DeleteTx(dbTx *DbTransaction) error { return GetDB(dbTx).Delete(qt).Error } // Save is saving model func (qt *QueueTx) Save(dbTx *DbTransaction) error { return GetDB(dbTx).Save(qt).Error } // Create is creating record of model func (qt *QueueTx) Create() error { return DBConn.Create(qt).Error } // GetByHash is retrieving model from database by hash func (qt *QueueTx) GetByHash(dbTx *DbTransaction, hash []byte) (bool, error) { return isFound(GetDB(dbTx).Where("hash = ?", hash).First(qt)) } // DeleteQueueTxByHash is deleting queue tx by hash func DeleteQueueTxByHash(dbTx *DbTransaction, hash []byte) (int64, error) { query := GetDB(dbTx).Exec("DELETE FROM queue_tx WHERE hash = ?", hash) return query.RowsAffected, query.Error } // GetQueuedTransactionsCount counting queued transactions func GetQueuedTransactionsCount(hash []byte) (int64, error) { var rowsCount int64 err := DBConn.Table("queue_tx").Where("hash = ?", hash).Count(&rowsCount).Error return rowsCount, err } // GetAllUnverifiedAndUnusedTransactions is returns all unverified and unused transaction func GetAllUnverifiedAndUnusedTransactions(dbTx *DbTransaction, limit int) ([]*QueueTx, error) { query := `SELECT * FROM ( SELECT data, hash,expedite,time FROM queue_tx UNION SELECT data, hash,expedite,time FROM transactions WHERE verified = 0 AND used = 0 ) AS x ORDER BY expedite DESC,time ASC limit ?` var result []*QueueTx err := GetDB(dbTx).Raw(query, limit).Scan(&result).Error if err != nil { return nil, err } return result, nil } func DeleteQueueTxs(dbTx *DbTransaction, hs [][]byte) error { if len(hs) == 0 { return nil } return GetDB(dbTx).Delete(&QueueTx{}, hs).Error } ================================================ FILE: packages/storage/sqldb/responecode.go ================================================ package sqldb import ( "fmt" "net/http" ) var ( defaultStatus = http.StatusOK //ErrEcosystemNotFound = errors.New("Ecosystem not found") //errContract = errType{"E_CONTRACT", "There is not %s contract", http.StatusNotFound} //errDBNil = errType{"E_DBNIL", "DB is nil", defaultStatus} //errDeletedKey = errType{"E_DELETEDKEY", "The key is deleted", http.StatusForbidden} //errEcosystem = errType{"E_ECOSYSTEM", "Ecosystem %d doesn't exist", defaultStatus} //errEmptyPublic = errType{"E_EMPTYPUBLIC", "Public key is undefined", http.StatusBadRequest} //errKeyNotFound = errType{"E_KEYNOTFOUND", "Key has not been found", http.StatusNotFound} //errEmptySign = errType{"E_EMPTYSIGN", "Signature is undefined", defaultStatus} //errHashWrong = errType{"E_HASHWRONG", "Hash is incorrect", http.StatusBadRequest} //errHashNotFound = errType{"E_HASHNOTFOUND", "Hash has not been found", defaultStatus} //errHeavyPage = errType{"E_HEAVYPAGE", "This page is heavy", defaultStatus} //errInstalled = errType{"E_INSTALLED", "Chain is already installed", defaultStatus} //errInvalidWallet = errType{"E_INVALIDWALLET", "Wallet %s is not valid", http.StatusBadRequest} //errLimitForsign = errType{"E_LIMITFORSIGN", "Length of forsign is too big (%d)", defaultStatus} //errLimitTxSize = errType{"E_LIMITTXSIZE", "The size of tx is too big (%d)", defaultStatus} //errNotFound = errType{"E_NOTFOUND", "Page not found", http.StatusNotFound} //errNotFoundRecord = errType{"E_NOTFOUND", "Record not found", http.StatusNotFound} //errParamNotFound = errType{"E_PARAMNOTFOUND", "Parameter %s has not been found", http.StatusNotFound} //errPermission = errType{"E_PERMISSION", "Permission denied", http.StatusUnauthorized} //errQuery = errType{"E_QUERY", "DB query is wrong", http.StatusInternalServerError} //errRecovered = errType{"E_RECOVERED", "API recovered", http.StatusInternalServerError} //errServer = errType{"E_SERVER", "Server error", defaultStatus} //errSignature = errType{"E_SIGNATURE", "Signature is incorrect", http.StatusBadRequest} //errUnknownSign = errType{"E_UNKNOWNSIGN", "Unknown signature", defaultStatus} //errStateLogin = errType{"E_STATELOGIN", "%s is not a membership of ecosystem %s", http.StatusForbidden} //errTableNotFound = errType{"E_TABLENOTFOUND", "Table %s has not been found", http.StatusNotFound} //errToken = errType{"E_TOKEN", "Token is not valid", defaultStatus} //errTokenExpired = errType{"E_TOKENEXPIRED", "Token is expired by %s", http.StatusUnauthorized} //errUnauthorized = errType{"E_UNAUTHORIZED", "Unauthorized", http.StatusUnauthorized} //errUndefineval = errType{"E_UNDEFINEVAL", "Value %s is undefined", defaultStatus} //errUnknownUID = errType{"E_UNKNOWNUID", "Unknown uid", defaultStatus} //errCLB = errType{"E_CLB", "Virtual Dedicated Ecosystem %d doesn't exist", defaultStatus} //errCLBCreated = errType{"E_CLBCREATED", "Virtual Dedicated Ecosystem is already created", http.StatusBadRequest} //errRequestNotFound = errType{"E_REQUESTNOTFOUND", "Request %s doesn't exist", defaultStatus} //errUpdating = errType{"E_UPDATING", "Node is updating blockchain", http.StatusServiceUnavailable} //errStopping = errType{"E_STOPPING", "Network is stopping", http.StatusServiceUnavailable} //errNotImplemented = errType{"E_NOTIMPLEMENTED", "Not implemented", http.StatusNotImplemented} //errParamMoneyDigit = errType{"E_PARAMMONEYDIGIT", "The number of decimal places cannot be exceeded ( %s )", http.StatusBadRequest} //errDiffKey = CodeType{"E_DIFKEY", "Sender's key is different from tx key", defaultStatus} //errBannded = errType{"E_BANNED", "The key is banned till %s", http.StatusForbidden} //errCheckRole = errType{"E_CHECKROLE", "Access denied", http.StatusForbidden} //errNewUser = errType{"E_NEWUSER", "Can't create a new user", http.StatusUnauthorized} CodeSystembusy = CodeType{-1, "System is busy", http.StatusOK, ""} CodeSuccess = CodeType{0, "Success", http.StatusOK, "OK"} //CodeFileNotExists = CodeType{40001, "File %s not exists", http.StatusOK, ""} //CodeFileFormatNotSupports = CodeType{40002, "File %s format is not supported", http.StatusOK, ""} CodeIlgmediafiletype = CodeType{40003, "illegal media file type ", http.StatusOK, ""} CodeIlgfiletype = CodeType{40004, "illegal file type ", http.StatusOK, ""} CodeFilesize = CodeType{40005, "illegal file size ", http.StatusOK, ""} CodeImagesize = CodeType{40006, "illegal image file size ", http.StatusOK, ""} CodeVoicesize = CodeType{40007, "illegal voice file size ", http.StatusOK, ""} CodeVideosize = CodeType{40008, "illegal video file size ", http.StatusOK, ""} CodeRequestformat = CodeType{40009, "illegal request format ", http.StatusOK, ""} CodeThumbnailfilesize = CodeType{400010, "illegal thumbnail file size ", http.StatusOK, ""} CodeUrllength = CodeType{400011, "illegal URL length ", http.StatusOK, ""} CodeMultimediafileempty = CodeType{400012, "The multimedia file is empty ", http.StatusOK, ""} CodePostpacketempty = CodeType{400013, "POST packet is empty ", http.StatusOK, ""} CodeContentempty = CodeType{400014, "The content of the graphic message is empty. ", http.StatusOK, ""} CodeTextcmpty = CodeType{400015, "text message content is empty ", http.StatusOK, ""} CodeMultimediasizelimit = CodeType{400016, "multimedia file size exceeds limit ", http.StatusOK, ""} CodeParamNotNull = CodeType{400017, "Param message content exceeds limit ", http.StatusOK, ""} CodeParamOutRange = CodeType{400018, "Param out of range ", http.StatusOK, ""} CodeParam = CodeType{400019, "Param error ", http.StatusOK, ""} CodeParamNotExists = CodeType{400020, "Param is exists ", http.StatusOK, ""} CodeParamType = CodeType{400021, "Param type error ", http.StatusOK, ""} CodeParamKeyConflict = CodeType{400022, "Param Keyword conflict error ", http.StatusOK, ""} CodeRecordExists = CodeType{400023, "Record already exists ", http.StatusOK, ""} CodeRecordNotExists = CodeType{400024, "Record not exists error ", http.StatusOK, ""} CodeNewRecordNotRelease = CodeType{400025, "New Record not Release error ", http.StatusOK, ""} CodeReleaseRule = CodeType{400026, "Release rule error ", http.StatusOK, ""} CodeDeleteRule = CodeType{400027, "Delete Record delete rule error ", http.StatusOK, ""} CodeHelpDirNotExists = CodeType{400028, "Help parentdir not exists error ", http.StatusOK, ""} CodeDBfinderr = CodeType{400029, "DB find error ", http.StatusOK, ""} CodeDBcreateerr = CodeType{400030, "DB create error ", http.StatusOK, ""} CodeDBupdateerr = CodeType{400031, "DB update error ", http.StatusOK, ""} CodeDBdeleteerr = CodeType{400032, "DB delete error ", http.StatusOK, ""} CodeDBopertionerr = CodeType{400033, "DB opertion error ", http.StatusOK, ""} CodeJsonformaterr = CodeType{400034, "Json format error ", http.StatusOK, ""} CodeBodyformaterr = CodeType{400035, "Body format error ", http.StatusOK, ""} CodeFileNotExists = CodeType{400036, "File not exists", http.StatusOK, ""} //CodeFileFormatNotSupports = CodeType{40002, "File %s format is not supported", http.StatusOK, ""} CodeFileExists = CodeType{400037, "File already exists", http.StatusOK, ""} CodeFileFormatNotSupports = CodeType{400038, "File format is not supported", http.StatusOK, ""} CodeFileCreated = CodeType{400039, "Create File is not supported ", http.StatusOK, ""} CodeFileOpen = CodeType{400039, "Open File is not supported", http.StatusOK, ""} CodeCheckParam = CodeType{400040, "Param error: ", http.StatusOK, ""} CodeGenerateMine = CodeType{400041, "new miner generate faile ", http.StatusOK, ""} CodeImportMine = CodeType{400042, "import miner faile ", http.StatusOK, ""} CodeBooltype = CodeType{400043, "bool type error ", http.StatusOK, ""} CodeUpdateRule = CodeType{400044, "rule error ", http.StatusOK, ""} CodePermissionDenied = CodeType{400045, "Permission denied ", http.StatusOK, ""} CodeNotMineDevidBindActiveid = CodeType{400046, "not mine devid boind Activeid ", http.StatusOK, ""} CodeSignError = CodeType{400047, "sign err ", http.StatusOK, ""} //CodeNotMineDevidBindActiveid = CodeType{400046, "not mine devid boind Activeid ", http.StatusOK, ""} //CodeReleaseRule = CodeType{400042, "Release rule conflict %s ", http.StatusOK, ""} //CodeGenerateMine = CodeType{400041, "new miner generate faile ", http.StatusOK, ""} //CodeGenerateMine = CodeType{400041, "new miner generate faile ", http.StatusOK, ""} ) type CodeType struct { Code int `json:"code"` Message string `json:"message"` Status int `json:"status"` Msg string `json:"msg"` } // type errType struct { Err string `json:"error"` Message string `json:"msg"` Status int `json:"-"` } func (et errType) Error() string { return et.Err } func (et errType) Errorf(v ...any) errType { et.Message = fmt.Sprintf(et.Message, v...) return et } func (ct CodeType) Errorf(err error) CodeType { et, ok := err.(errType) if !ok { et.Message = err.Error() } ct.Message = fmt.Sprintln(ct.Message, et.Message) ct.Msg = http.StatusText(ct.Status) return ct } func (ct CodeType) String(dat string) CodeType { ct.Message += " " + dat ct.Msg = http.StatusText(ct.Status) return ct } func (ct CodeType) Success() CodeType { ct.Msg = http.StatusText(ct.Status) return ct } ================================================ FILE: packages/storage/sqldb/result.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "database/sql" "encoding/hex" "fmt" "github.com/IBAX-io/go-ibax/packages/converter" ) // SingleResult is a structure for the single result type SingleResult struct { result []byte err error } // Single is retrieving single result func (dbTx *DbTransaction) Single(query string, args ...any) *SingleResult { var result []byte err := GetDB(dbTx).Raw(query, args...).Row().Scan(&result) switch { case err == sql.ErrNoRows: return &SingleResult{[]byte(""), nil} case err != nil: return &SingleResult{[]byte(""), err} } return &SingleResult{result, nil} } // Int64 converts bytes to int64 func (r *SingleResult) Int64() (int64, error) { if r.err != nil { return 0, r.err } return converter.BytesToInt64(r.result), nil } // Int converts bytes to int func (r *SingleResult) Int() (int, error) { if r.err != nil { return 0, r.err } return converter.BytesToInt(r.result), nil } // Float64 converts string to float64 func (r *SingleResult) Float64() (float64, error) { if r.err != nil { return 0, r.err } return converter.StrToFloat64(string(r.result)), nil } // String returns string func (r *SingleResult) String() (string, error) { if r.err != nil { return "", r.err } return string(r.result), nil } // Bytes returns []byte func (r *SingleResult) Bytes() ([]byte, error) { if r.err != nil { return []byte(""), r.err } return r.result, nil } // OneRow is storing one row result type OneRow struct { result map[string]string List []map[string]string err error } // String is extracts result from OneRow as string func (r *OneRow) String() (map[string]string, error) { if r.err != nil { return r.result, r.err } return r.result, nil } // Bytes is extracts result from OneRow as []byte func (r *OneRow) Bytes() (map[string][]byte, error) { result := make(map[string][]byte) if r.err != nil { return result, r.err } for k, v := range r.result { result[k] = []byte(v) } return result, nil } // Int64 is extracts result from OneRow as int64 func (r *OneRow) Int64() (map[string]int64, error) { result := make(map[string]int64) if r.err != nil { return result, r.err } for k, v := range r.result { result[k] = converter.StrToInt64(v) } return result, nil } // Float64 is extracts result from OneRow as float64 func (r *OneRow) Float64() (map[string]float64, error) { result := make(map[string]float64) if r.err != nil { return result, r.err } for k, v := range r.result { result[k] = converter.StrToFloat64(v) } return result, nil } // Int is extracts result from OneRow as int func (r *OneRow) Int() (map[string]int, error) { result := make(map[string]int) if r.err != nil { return result, r.err } for k, v := range r.result { result[k] = converter.StrToInt(v) } return result, nil } // GetAllTransaction is retrieve all query result rows func (dbTx *DbTransaction) GetAllTransaction(query string, countRows int, args ...any) ([]map[string]string, error) { request := GetDB(dbTx).Raw(query, args...) if countRows > 0 { request = request.Limit(countRows) } rows, err := request.Rows() if err != nil { return nil, fmt.Errorf("%s in query %s %s", err, query, args) } defer rows.Close() result, err := getResult(rows, countRows) if err != nil { return nil, fmt.Errorf("%s in query %s %s", err, query, args) } return result, nil } // GetOneRowTransaction returns one row from transactions func (dbTx *DbTransaction) GetOneRowTransaction(query string, args ...any) *OneRow { result := make(map[string]string) all, err := dbTx.GetAllTransaction(query, -1, args...) if err != nil { return &OneRow{result: result, err: fmt.Errorf("%s in query %s %s", err, query, args)} } if len(all) == 0 { return &OneRow{result: result, err: nil} } return &OneRow{result: all[0], List: all, err: nil} } // GetOneRow returns one row func (dbTx *DbTransaction) GetOneRow(query string, args ...any) *OneRow { return dbTx.GetOneRowTransaction(query, args...) } func (dbTx *DbTransaction) GetRows(tableName, columns string, offset, limit int) ([]map[string]string, error) { query := GetDB(dbTx).Table(tableName).Order("id").Offset(offset).Limit(limit) if len(columns) > 0 { columns = "id," + columns query = query.Select(columns) } rows, err := query.Rows() if err != nil { return nil, err } defer rows.Close() return getResult(rows, -1) } func GetResult(rows *sql.Rows) ([]map[string]string, error) { return getResult(rows, -1) } // ListResult is a structure for the list result type ListResult struct { result []string err error } // String return the slice of strings func (r *ListResult) String() ([]string, error) { if r.err != nil { return r.result, r.err } return r.result, nil } // GetList returns the result of the query as ListResult variable func (dbTx *DbTransaction) GetList(query string, args ...any) *ListResult { var result []string all, err := dbTx.GetAllTransaction(query, -1, args...) if err != nil { return &ListResult{result, err} } for _, v := range all { for _, v2 := range v { result = append(result, v2) } } return &ListResult{result, nil} } func getResult(rows *sql.Rows, countRows int) ([]map[string]string, error) { var result []map[string]string defer rows.Close() //rows.ColumnTypes() columntypes, err := rows.ColumnTypes() if err != nil { return nil, err } // Get column names columns, err := rows.Columns() if err != nil { return nil, err } // Make a slice for the values values := make([][]byte /*sql.RawBytes*/, len(columns)) // rows.Scan wants '[]interface{}' as an argument, so we must copy the // references into such a slice // See http://code.google.com/p/go-wiki/wiki/InterfaceSlice for details scanArgs := make([]any, len(values)) for i := range values { scanArgs[i] = &values[i] } r := 0 // Fetch rows for rows.Next() { // get RawBytes from data err = rows.Scan(scanArgs...) if err != nil { return nil, err } // Now do something with the data. // Here we just print each column as a string. var value string rez := make(map[string]string) for i, col := range values { // Here we can check if the value is nil (NULL value) if col == nil { value = "NULL" } else { if columntypes[i].DatabaseTypeName() == "BYTEA" { value = hex.EncodeToString(col) } else { value = string(col) } } rez[columns[i]] = value } result = append(result, rez) r++ if countRows != -1 && r >= countRows { break } } if err = rows.Err(); err != nil { return nil, err } return result, nil } ================================================ FILE: packages/storage/sqldb/role.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb // Role is model type Role struct { ecosystem int64 ID int64 `gorm:"primary_key;not null" json:"id"` DefaultPage string `gorm:"not null" json:"default_page"` RoleName string `gorm:"not null" json:"role_name"` Deleted int64 `gorm:"not null" json:"deleted"` RoleType int64 `gorm:"not null" json:"role_type"` } // SetTablePrefix is setting table prefix func (r *Role) SetTablePrefix(prefix int64) { r.ecosystem = prefix } // TableName returns name of table func (r *Role) TableName() string { if r.ecosystem == 0 { r.ecosystem = 1 } return "1_roles" } // Get is retrieving model from database func (r *Role) Get(dbTx *DbTransaction, id int64) (bool, error) { return isFound(GetDB(dbTx).First(&r, id)) } ================================================ FILE: packages/storage/sqldb/roles_participants.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/converter" ) // RolesParticipants represents record of {prefix}roles_participants table type RolesParticipants struct { ecosystem int64 Id int64 Role string `gorm:"type:jsonb"` Member string `gorm:"type:jsonb"` Appointed string `gorm:"type:jsonb"` DateCreated int64 DateDeleted int64 Deleted bool } // SetTablePrefix is setting table prefix func (r *RolesParticipants) SetTablePrefix(prefix int64) *RolesParticipants { r.ecosystem = prefix return r } // TableName returns name of table func (r RolesParticipants) TableName() string { if r.ecosystem == 0 { r.ecosystem = 1 } return "1_roles_participants" } // GetActiveMemberRoles returns active assigned roles for memberID func (r *RolesParticipants) GetActiveMemberRoles(account string) ([]RolesParticipants, error) { roles := new([]RolesParticipants) err := DBConn.Table(r.TableName()).Where("ecosystem=? and member->>'account' = ? AND deleted = ?", r.ecosystem, account, 0).Find(&roles).Error return *roles, err } // MemberHasRole returns true if member has role func MemberHasRole(tx *DbTransaction, role, ecosys int64, account string) (bool, error) { db := GetDB(tx) var count int64 if err := db.Table("1_roles_participants").Where(`ecosystem=? and role->>'id' = ? and member->>'account' = ?`, ecosys, converter.Int64ToStr(role), account).Count(&count).Error; err != nil { return false, err } return count > 0, nil } // MemberHasRole returns true if member has role func MemberHasRolebyName(tx *DbTransaction, ecosys int64, role, account string) (bool, error) { db := GetDB(tx) var count int64 if err := db.Table("1_roles_participants").Where(`ecosystem=? and role->>'name' = ? and member->>'account' = ?`, ecosys, role, account).Count(&count).Error; err != nil { return false, err } return count > 0, nil } // GetMemberRoles return map[id]name all roles assign to member in ecosystem func GetMemberRoles(tx *DbTransaction, ecosys int64, account string) (roles []int64, err error) { query := `SELECT role->>'id' as "id" FROM "1_roles_participants" WHERE ecosystem = ? and deleted = '0' and member->>'account' = ?` list, err := tx.GetAllTransaction(query, -1, ecosys, account) if err != nil { return } for _, role := range list { roles = append(roles, converter.StrToInt64(role[`id`])) } return } // GetRoleMembers return []id all members assign to roles in ecosystem func GetRoleMembers(tx *DbTransaction, ecosys int64, roles []int64) (members []string, err error) { rolesList := make([]string, 0, len(roles)) for _, role := range roles { rolesList = append(rolesList, converter.Int64ToStr(role)) } query := `SELECT member->>'account' as "id" FROM "1_roles_participants" WHERE role->>'id' in (?) group by 1` list, err := tx.GetAllTransaction(query, -1, rolesList) if err != nil { return } for _, member := range list { members = append(members, member[`id`]) } return } ================================================ FILE: packages/storage/sqldb/rollback_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "bytes" "encoding/json" "gorm.io/gorm" ) // RollbackTx is model type RollbackTx struct { ID int64 `gorm:"primary_key;not null"` BlockID int64 `gorm:"not null" json:"block_id"` TxHash []byte `gorm:"not null" json:"tx_hash"` NameTable string `gorm:"not null;size:255;column:table_name" json:"table_name"` TableID string `gorm:"not null;size:255" json:"table_id"` Data string `gorm:"not null;type:jsonb" json:"data"` DataHash []byte `gorm:"not null" json:"data_hash"` } // TableName returns name of table func (*RollbackTx) TableName() string { return "rollback_tx" } // GetRollbackTransactions is returns rollback transactions func (rt *RollbackTx) GetRollbackTransactions(dbTx *DbTransaction, transactionHash []byte) ([]map[string]string, error) { return dbTx.GetAllTransaction("SELECT * from rollback_tx WHERE tx_hash = ? ORDER BY ID DESC", -1, transactionHash) } // GetBlockRollbackTransactions returns records of rollback by blockID func (rt *RollbackTx) GetBlockRollbackTransactions(dbTx *DbTransaction, blockID int64) ([]RollbackTx, error) { var rollbackTransactions []RollbackTx err := GetDB(dbTx).Where("block_id = ?", blockID).Omit("id").Order("id asc").Find(&rollbackTransactions).Error return rollbackTransactions, err } // GetRollbackTxsByTableIDAndTableName returns records of rollback by table name and id func (rt *RollbackTx) GetRollbackTxsByTableIDAndTableName(tableID, tableName string, limit int) (*[]RollbackTx, error) { var rollbackTx []RollbackTx if err := DBConn.Where("table_id = ? AND table_name = ?", tableID, tableName). Order("id desc").Limit(limit).Find(&rollbackTx).Error; err != nil { return nil, err } return &rollbackTx, nil } // DeleteByHash is deleting rollbackTx by hash func (rt *RollbackTx) DeleteByHash(dbTx *DbTransaction) error { return GetDB(dbTx).Exec("DELETE FROM rollback_tx WHERE tx_hash = ?", rt.TxHash).Error } // DeleteByHashAndTableName is deleting tx by hash and table name func (rt *RollbackTx) DeleteByHashAndTableName(dbTx *DbTransaction) error { return GetDB(dbTx).Where("tx_hash = ? and table_name = ?", rt.TxHash, rt.NameTable).Delete(rt).Error } func CreateBatchesRollbackTx(dbTx *gorm.DB, rts []*RollbackTx) error { if len(rts) == 0 { return nil } rollbackSys := &RollbackTx{} var err error if rollbackSys.ID, err = NewDbTransaction(dbTx).GetNextID(rollbackSys.TableName()); err != nil { return err } for i := 1; i < len(rts)+1; i++ { rts[i-1].ID = rollbackSys.ID + int64(i) - 1 } return dbTx.Model(&RollbackTx{}).Create(&rts).Error } // Get is retrieving model from database func (rt *RollbackTx) Get(dbTx *DbTransaction, transactionHash []byte, tableName string) (bool, error) { return isFound(GetDB(dbTx).Where("tx_hash = ? AND table_name = ?", transactionHash, tableName).Order("id desc").First(rt)) } func (rt *RollbackTx) GetRollbacksDiff(dbTx *DbTransaction, blockID int64) ([]byte, error) { list, err := rt.GetBlockRollbackTransactions(dbTx, blockID) if err != nil { return nil, err } buf := new(bytes.Buffer) enc := json.NewEncoder(buf) for _, rtx := range list { if err = enc.Encode(&rtx); err != nil { return nil, err } } return buf.Bytes(), nil } ================================================ FILE: packages/storage/sqldb/schema.go ================================================ package sqldb import ( "encoding/hex" "fmt" "os" "path/filepath" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/migration" "github.com/IBAX-io/go-ibax/packages/migration/clb" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) // ExecSchemaEcosystem is executing ecosystem schema func ExecSchemaEcosystem(db *DbTransaction, data migration.SqlData) error { if data.Ecosystem == 1 { q, err := migration.GetCommonEcosystemScript() if err != nil { return err } if err := db.ExecSql(q); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing comma ecosystem schema") return err } } q, err := migration.GetEcosystemScript(data) if err != nil { return err } if err := db.ExecSql(q); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing ecosystem schema") return err } if data.Ecosystem == 1 { q, err = migration.GetFirstEcosystemScript(data) if err != nil { return err } if err := db.ExecSql(q); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing first ecosystem schema") } q, err = migration.GetFirstTableScript(data) if err != nil { return err } if err := db.ExecSql(q); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing first tables schema") } } return nil } func ExecSubSchema() error { if conf.Config.IsSubNode() { if err := migration.InitMigrate(&MigrationHistory{}); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on executing clb script") return err } } return nil } // ExecCLBSchema is executing schema for off blockchainService func ExecCLBSchema(id int, wallet int64) error { if conf.Config.IsSupportingCLB() { if err := migration.InitMigrate(&MigrationHistory{}); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on executing clb script") return err } query := fmt.Sprintf(clb.GetCLBScript(), id, wallet, converter.AddressToString(wallet)) if err := DBConn.Exec(query).Error; err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("on executing clb script") return err } pubfunc := func(privateKeyFilename string) ([]byte, error) { var ( privkey, privKey, pubKey []byte err error ) privkey, err = os.ReadFile(filepath.Join(conf.Config.DirPathConf.KeysDir, privateKeyFilename)) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading private key from file") return nil, err } privKey, err = hex.DecodeString(string(privkey)) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("decoding private key from hex") return nil, err } pubKey, err = crypto.PrivateToPublic(privKey) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("converting private key to public") return nil, err } return pubKey, nil } nodePubKey, err := pubfunc(consts.NodePrivateKeyFilename) PubKey, err := pubfunc(consts.PrivateKeyFilename) nodeKeyID := crypto.Address(nodePubKey) keyID := crypto.Address(PubKey) amount := decimal.New(consts.FounderAmount, int32(consts.MoneyDigits)).String() if err = GetDB(nil).Exec(`insert into "1_keys" (account,pub,amount) values (?,?,?,?),(?,?,?,?)`, keyID, converter.AddressToString(keyID), PubKey, amount, nodeKeyID, converter.AddressToString(nodeKeyID), nodePubKey, 0).Error; err != nil { return err } } return nil } // ExecSchema is executing schema func ExecSchema() error { return migration.InitMigrate(&MigrationHistory{}) } // UpdateSchema run update migrations func UpdateSchema() error { if !conf.Config.IsCLBMaster() { b := &BlockChain{} if found, err := b.GetMaxBlock(); !found { return err } } return migration.UpdateMigrate(&MigrationHistory{}) } ================================================ FILE: packages/storage/sqldb/send_tx.go ================================================ package sqldb import ( "gorm.io/gorm" "gorm.io/gorm/clause" "github.com/shopspring/decimal" ) // SendTx is creates transaction //func SendTx(rtx types.TransactionInfoer, adminWallet int64) error { // ts := &TransactionStatus{ // Hash: rtx.TxHashes(), // Time: rtx.Time(), // Type: rtx.Type(), // WalletID: adminWallet, // } // foundts, err := ts.Get(rtx.TxHashes()) // if foundts { // log.WithFields(log.Fields{"tx_hash": rtx.TxHashes(), "wallet_id": adminWallet, "tx_time": ts.Time, "type": consts.DuplicateObject}).Error("double tx in transactions status") // return errors.New("duplicated transaction from transactions status") // } // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting transaction from transactions status") // return err // } // err = ts.Create() // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("transaction status create") // return err // } // // qtx := &QueueTx{ // Hash: rtx.TxHashes(), // Data: rtx.Bytes(), // Expedite: rtx.GetExpedite(), // Time: rtx.Time(), // } // foundqx, err := qtx.GetByHash(nil, rtx.TxHashes()) // if foundqx { // log.WithFields(log.Fields{"tx_hash": rtx.TxHashes(), "wallet_id": adminWallet, "tx_time": ts.Time, "type": consts.DuplicateObject}).Error("double tx in queue tx") // return errors.New("duplicated transaction from queue tx ") // } // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting transaction from queue tx") // return err // } // return qtx.Create() //} type RawTx struct { TxType byte Time int64 Hash []byte Data []byte Expedite string WalletID int64 } func (rtx *RawTx) GetExpedite() decimal.Decimal { expedite, _ := decimal.NewFromString(rtx.Expedite) return expedite } func SendTxBatches(rtxs []*RawTx) error { var rawTxs []*TransactionStatus var qtxs []*QueueTx for _, rtx := range rtxs { ts := &TransactionStatus{ Hash: rtx.Hash, Time: rtx.Time, Type: rtx.TxType, WalletID: rtx.WalletID, } rawTxs = append(rawTxs, ts) qtx := &QueueTx{ Hash: rtx.Hash, Data: rtx.Data, Expedite: rtx.GetExpedite(), Time: rtx.Time, } qtxs = append(qtxs, qtx) } return DBConn.Clauses(clause.OnConflict{DoNothing: true}).Transaction(func(tx *gorm.DB) error { if err := tx.Create(&rawTxs).Error; err != nil { return err } if err := tx.Create(&qtxs).Error; err != nil { return err } return nil }) } ================================================ FILE: packages/storage/sqldb/signatures.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb // Signature is model type Signature struct { tableName string Name string `gorm:"primary_key;not null;size:255"` Value string `gorm:"not null;type:jsonb"` Conditions string `gorm:"not null"` } // SetTablePrefix is setting table prefix func (s *Signature) SetTablePrefix(prefix string) { s.tableName = prefix + "_signatures" } // TableName returns name of table func (s *Signature) TableName() string { return s.tableName } // Get is retrieving model from database func (s *Signature) Get(name string) (bool, error) { return isFound(DBConn.Where("name = ?", name).First(s)) } ================================================ FILE: packages/storage/sqldb/snippet.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import "github.com/IBAX-io/go-ibax/packages/converter" // Snippet is code snippet type Snippet struct { ecosystem int64 ID int64 `gorm:"primary_key;not null" json:"id,omitempty"` Name string `gorm:"not null" json:"name,omitempty"` Value string `gorm:"not null" json:"value,omitempty"` Conditions string `gorm:"not null" json:"conditions,omitempty"` } // SetTablePrefix is setting table prefix func (bi *Snippet) SetTablePrefix(prefix string) { bi.ecosystem = converter.StrToInt64(prefix) } // TableName returns name of table func (bi *Snippet) TableName() string { if bi.ecosystem == 0 { bi.ecosystem = 1 } return `1_snippets` } // Get is retrieving model from database func (bi *Snippet) Get(name string) (bool, error) { return isFound(DBConn.Where("ecosystem=? and name = ?", bi.ecosystem, name).First(bi)) } // GetByApp returns all interface blocks belonging to selected app func (bi *Snippet) GetByApp(appID int64, ecosystemID int64) ([]Snippet, error) { var result []Snippet err := DBConn.Select("id, name").Where("app_id = ? and ecosystem = ?", appID, ecosystemID).Find(&result).Error return result, err } ================================================ FILE: packages/storage/sqldb/spent_info.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "errors" "fmt" "reflect" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" "gorm.io/gorm" "gorm.io/gorm/clause" ) // SpentInfo is model type SpentInfo struct { InputTxHash []byte `gorm:"default:(-)"` InputIndex int32 OutputTxHash []byte `gorm:"not null"` OutputIndex int32 `gorm:"not null"` OutputKeyId int64 `gorm:"not null"` OutputValue string `gorm:"not null"` Ecosystem int64 BlockId int64 Type int32 } type KeyUTXO struct { Ecosystem int64 //At string KeyId int64 // Asset string } func (k *KeyUTXO) String() string { return fmt.Sprintf("%d%s%d", k.Ecosystem, "@", k.KeyId) } // TableName returns name of table func (si *SpentInfo) TableName() string { return "spent_info" } // CreateSpentInfoBatches is creating record of model func CreateSpentInfoBatches(dbTx *gorm.DB, spentInfos []SpentInfo) error { //for _, info := range spentInfos { // fmt.Println(hex.EncodeToString(info.InputTxHash), info.InputIndex, hex.EncodeToString(info.OutputTxHash), info.OutputIndex, info.OutputKeyId, info.OutputValue, info.BlockId) //} return dbTx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "output_tx_hash"}, {Name: "output_key_id"}, {Name: "output_index"}}, DoUpdates: clause.AssignmentColumns([]string{"input_tx_hash", "input_index"}), Where: clause.Where{Exprs: []clause.Expression{ clause.Eq{Column: "spent_info.output_tx_hash", Value: gorm.Expr(`"excluded"."output_tx_hash"`)}, clause.Eq{Column: "spent_info.output_key_id", Value: gorm.Expr(`"excluded"."output_key_id"`)}, clause.Eq{Column: "spent_info.output_index", Value: gorm.Expr(`"excluded"."output_index"`)}, }}, }).CreateInBatches(spentInfos, 1000).Error } func GetTxOutputsEcosystem(db *DbTransaction, ecosystem int64, keyIds []int64) ([]SpentInfo, error) { query := ` SELECT si.output_tx_hash, si.output_index, si.output_key_id, si.output_value, si.ecosystem, si.block_id FROM spent_info si LEFT JOIN log_transactions AS tr ON si.output_tx_hash = tr.hash WHERE si.ecosystem = ? AND si.output_key_id IN ? AND si.input_tx_hash IS NULL ORDER BY si.output_key_id, si.block_id ASC, tr.timestamp ASC ` var result []SpentInfo err := GetDB(db).Raw(query, ecosystem, keyIds).Scan(&result).Error if err != nil { return nil, err } return result, nil } func GetTxOutputs(db *DbTransaction, keyIds []int64) ([]SpentInfo, error) { query := ` SELECT si.output_tx_hash, si.output_index, si.output_key_id, si.output_value, si.ecosystem, si.block_id FROM spent_info si LEFT JOIN log_transactions AS tr ON si.output_tx_hash = tr.hash WHERE si.output_key_id IN ? AND si.input_tx_hash IS NULL ORDER BY si.output_key_id, si.block_id ASC, tr.timestamp ASC ` var result []SpentInfo err := GetDB(db).Raw(query, keyIds).Scan(&result).Error if err != nil { return nil, err } return result, nil } func RollbackOutputs(blockID int64, db *DbTransaction, transferSelfHashes []string, logger *log.Entry) error { err := GetDB(db).Exec(`UPDATE spent_info SET input_tx_hash= null , input_index=0 WHERE input_tx_hash in ( SELECT output_tx_hash FROM "spent_info" WHERE block_id = ? )`, blockID).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Errorf("updating input_tx_hash rollback outputs by blockID : %d", blockID) return err } if len(transferSelfHashes) > 0 { err = GetDB(db).Exec(`UPDATE spent_info SET input_tx_hash= null , input_index=0 WHERE encode(input_tx_hash,'hex') in ?`, transferSelfHashes).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Errorf("updating input_tx_hash of transfer self for rollback outputs by blockID : %d", blockID) return err } } err = GetDB(db).Exec(`DELETE FROM spent_info WHERE block_id = ? `, blockID).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Errorf("deleting rollback outputs by blockID : %d", blockID) return err } return nil } func GetBlockOutputs(dbTx *DbTransaction, blockID int64) ([]SpentInfo, error) { var result []SpentInfo err := GetDB(dbTx).Where("block_id = ?", blockID).Find(&result).Error return result, err } func (si *SpentInfo) GetBalance(db *DbTransaction, keyId, ecosystem int64) (decimal.Decimal, error) { var amount decimal.Decimal f, err := isFound(GetDB(db).Table(si.TableName()).Select("coalesce(sum(output_value),'0') amount"). Where("input_tx_hash is NULL AND output_key_id = ? AND ecosystem = ?", keyId, ecosystem).Take(&amount)) if err != nil { return decimal.Zero, err } if !f { return decimal.Zero, errors.New("doesn't not exist UTXO output_key_id") } return amount, err } // GetTopAmounts returns top amounts by ecosystem, rank and dense func GetTopAmounts(db *DbTransaction, ecosystem int64, rank int64, dense bool) ([]any, error) { query := ` SELECT * FROM (SELECT sum ( amount ) AS amount, id, CASE WHEN ? THEN dense_rank() OVER (ORDER BY sum ( amount ) DESC) ELSE rank() OVER (ORDER BY sum ( amount ) DESC) END AS rank FROM (SELECT output_value AS amount, output_key_id AS id FROM spent_info LEFT JOIN "1_keys" ON spent_info.ecosystem = "1_keys".ecosystem AND id = spent_info.output_key_id WHERE input_tx_hash is null AND "spent_info".ecosystem = ? AND blocked = 0 AND deleted = 0 AND length ( pub ) > 0 UNION SELECT amount, id FROM "1_keys" WHERE ecosystem = ? AND blocked = 0 AND deleted = 0 AND amount > 0 AND length ( pub ) > 0 ) tmp GROUP BY id ORDER BY rank ASC ) r WHERE rank <= ? ORDER BY rank, id; ` rows, err := GetDB(db).Raw(query, dense, ecosystem, ecosystem, rank).Rows() if err != nil { return nil, err } defer rows.Close() cols, err := rows.Columns() if err != nil { return nil, fmt.Errorf("%w: %s", err, "getting rows columns") } values := make([][]byte, len(cols)) scanArgs := make([]any, len(values)) for i := range values { scanArgs[i] = &values[i] } result := make([]any, 0) for rows.Next() { err = rows.Scan(scanArgs...) if err != nil { return nil, fmt.Errorf("%w: %s", err, "scanning next row") } row := types.NewMap() for i, col := range values { var value string if col != nil { value = string(col) } row.Set(cols[i], value) } result = append(result, reflect.ValueOf(row).Interface()) } return result, nil } ================================================ FILE: packages/storage/sqldb/stop_daemons.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "time" ) // StopDaemon is model type StopDaemon struct { StopTime int64 `gorm:"not null"` } // TableName returns name of table func (sd *StopDaemon) TableName() string { return "stop_daemons" } // Create is creating record of model func (sd *StopDaemon) Create() error { return DBConn.Create(sd).Error } // Delete is deleting record func (sd *StopDaemon) Delete() error { return DBConn.Delete(&StopDaemon{}).Error } // Get is retrieving model from database func (sd *StopDaemon) Get() (bool, error) { return isFound(DBConn.First(sd)) } // SetStopNow is updating daemon stopping time to now func SetStopNow() error { stopTime := &StopDaemon{StopTime: time.Now().Unix()} return stopTime.Create() } ================================================ FILE: packages/storage/sqldb/tables.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "database/sql/driver" "encoding/json" "errors" "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/converter" "gorm.io/gorm" ) // const TableName = "1_tables" // Table is model type Table struct { ID int64 `gorm:"primary_key;not null"` Name string `gorm:"not null;size:100"` Permissions Permissions `gorm:"not null;type:jsonb"` Columns string `gorm:"not null"` Conditions string `gorm:"not null"` AppID int64 `gorm:"not null"` Ecosystem int64 `gorm:"not null"` } type Permissions struct { Insert string `json:"insert"` NewColumn string `json:"new_column"` Update string `json:"update"` Read string `json:"read"` Filter string `json:"filter"` } func (p Permissions) Value() (driver.Value, error) { data, err := json.Marshal(p) if err != nil { return nil, err } return string(data), err } func (p *Permissions) Scan(v any) error { data, ok := v.([]byte) if !ok { return errors.New("Bad permissions") } return json.Unmarshal(data, p) } // SetTablePrefix is setting table prefix func (t *Table) SetTablePrefix(prefix string) { t.Ecosystem = converter.StrToInt64(prefix) } // TableName returns name of table func (t *Table) TableName() string { if t.Ecosystem == 0 { t.Ecosystem = 1 } return `1_tables` } // Get is retrieving model from database func (t *Table) Get(dbTx *DbTransaction, name string) (bool, error) { return isFound(GetDB(dbTx).Where("ecosystem = ? and name = ?", t.Ecosystem, name).First(t)) } // Create is creating record of model func (t *Table) Create(dbTx *DbTransaction) error { return GetDB(dbTx).Create(t).Error } // Delete is deleting model from database func (t *Table) Delete(dbTx *DbTransaction) error { return GetDB(dbTx).Delete(t).Error } // IsExistsByPermissionsAndTableName returns columns existence by permission and table name func (t *Table) IsExistsByPermissionsAndTableName(dbTx *DbTransaction, columnName, tableName string) (bool, error) { return isFound(GetDB(dbTx).Where(`ecosystem = ? AND (columns-> ? ) is not null AND name = ?`, t.Ecosystem, columnName, tableName).First(t)) } // GetColumns returns columns from database func (t *Table) GetColumns(dbTx *DbTransaction, name, jsonKey string) (map[string]string, error) { keyStr := "" if jsonKey != "" { keyStr = `->'` + jsonKey + `'` } rows, err := GetDB(dbTx).Raw(`SELECT data.* FROM "1_tables", jsonb_each_text(columns`+keyStr+`) AS data WHERE ecosystem = ? AND name = ?`, t.Ecosystem, name).Rows() if err != nil { return nil, err } defer rows.Close() var key, value string result := map[string]string{} for rows.Next() { rows.Scan(&key, &value) result[key] = value } err = rows.Err() if err != nil { return nil, err } return result, nil } // GetPermissions returns table permissions by name func (t *Table) GetPermissions(dbTx *DbTransaction, name, jsonKey string) (map[string]string, error) { keyStr := "" if jsonKey != "" { keyStr = `->'` + jsonKey + `'` } rows, err := GetDB(dbTx).Raw(`SELECT data.* FROM "1_tables", jsonb_each_text(permissions`+keyStr+`) AS data WHERE ecosystem = ? AND name = ?`, t.Ecosystem, name).Rows() if err != nil { return nil, err } defer rows.Close() var key, value string result := map[string]string{} for rows.Next() { rows.Scan(&key, &value) result[key] = value } err = rows.Err() if err != nil { return nil, err } return result, nil } func (t *Table) Count() (count int64, err error) { err = GetDB(nil).Table(t.TableName()).Where("ecosystem= ?", t.Ecosystem).Count(&count).Error return } // CreateTable is creating table func CreateTable(dbTx *DbTransaction, tableName, colsSQL string) error { return dbTx.ExecSql(`CREATE TABLE "` + tableName + `" ( "id" bigint NOT NULL DEFAULT '0', ` + colsSQL + ` ); ALTER TABLE ONLY "` + tableName + `" ADD CONSTRAINT "` + tableName + `_pkey" PRIMARY KEY (id);`) } // CreateView is creating view table func CreateView(dbTx *DbTransaction, inViewName, inTables, inWhere, inColSQL string) error { inSQL := `CREATE VIEW "` + inViewName + `" AS SELECT ` + inColSQL + ` FROM ` + inTables + ` WHERE ` + inWhere + `;` return dbTx.ExecSql(inSQL) } // DropView is drop view table func DropView(dbTx *DbTransaction, inViewName string) error { return dbTx.ExecSql(`DROP VIEW "` + strings.Replace(fmt.Sprint(inViewName), `'`, `''`, -1) + `";`) } // GetAll returns all tables func (t *Table) GetAll(prefix string) ([]Table, error) { result := make([]Table, 0) err := DBConn.Table("1_tables").Where("ecosystem = ?", prefix).Find(&result).Error return result, err } // func (t *Table) GetList(offset, limit int) ([]Table, error) { // var list []Table // err := DBConn.Table(t.TableName()).Offset(offset).Limit(limit).Select("name").Order("name").Find(&list).Error // return list, err // } // GetRowConditionsByTableNameAndID returns value of `conditions` field for table row by id func (dbTx *DbTransaction) GetRowConditionsByTableNameAndID(tblname string, id int64) (string, error) { sql := `SELECT conditions FROM "` + tblname + `" WHERE id = ? LIMIT 1` return dbTx.Single(sql, id).String() } func GetTableQuery(table string, ecosystemID int64) *gorm.DB { if converter.FirstEcosystemTables[table] { return GetDB(nil).Table("1_"+table).Where("ecosystem = ?", ecosystemID) } return GetDB(nil).Table(converter.ParseTable(table, ecosystemID)) } func GetTableListQuery(table string, ecosystemID int64) *gorm.DB { if converter.FirstEcosystemTables[table] { return DBConn.Table("1_" + table) } return GetDB(nil).Table(converter.ParseTable(table, ecosystemID)) } ================================================ FILE: packages/storage/sqldb/transaction.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" "gorm.io/gorm" "gorm.io/gorm/clause" ) // This constants contains values of transactions priority const ( TransactionRateOnBlock transactionRate = iota + 1 TransactionRateApiContract TransactionRateStopNetwork ) const expediteOrder = `high_rate,expedite DESC,time ASC` type transactionRate int8 // Transaction is model type Transaction struct { Hash []byte `gorm:"private_key;not null"` Data []byte `gorm:"not null"` Used int8 `gorm:"not null"` HighRate transactionRate `gorm:"not null"` Expedite decimal.Decimal `gorm:"not null"` Type int8 `gorm:"not null"` KeyID int64 `gorm:"not null"` Sent int8 `gorm:"not null"` Verified int8 `gorm:"not null"` Time int64 `gorm:"not null"` } // GetAllUnusedTransactions is retrieving all unused transactions func GetAllUnusedTransactions(dbTx *DbTransaction, limit int) ([]*Transaction, error) { var transactions []*Transaction query := GetDB(dbTx).Where("used = ?", "0").Order(expediteOrder) if limit > 0 { query = query.Limit(limit) } if err := query.Find(&transactions).Error; err != nil { return nil, err } return transactions, nil } // GetAllUnsentTransactions is retrieving all unset transactions func GetAllUnsentTransactions(limit int) (*[]Transaction, error) { transactions := new([]Transaction) query := DBConn.Where("sent = ?", "0").Order(expediteOrder) if limit > 0 { query = query.Limit(limit) } if err := query.Find(&transactions).Error; err != nil { return nil, err } return transactions, nil } // GetTransactionCountAll count all transactions func GetTransactionCountAll() (int64, error) { var rowsCount int64 if err := DBConn.Table("transactions").Count(&rowsCount).Error; err != nil { return -1, err } return rowsCount, nil } // GetTransactionsCount count all transactions by hash func GetTransactionsCount(hash []byte) (int64, error) { var rowsCount int64 if err := DBConn.Table("transactions").Where("hash = ?", hash).Count(&rowsCount).Error; err != nil { return -1, err } return rowsCount, nil } // DeleteTransactionByHash deleting transaction by hash func DeleteTransactionByHash(dbTx *DbTransaction, hash []byte) error { return GetDB(dbTx).Where("hash = ?", hash).Delete(&Transaction{}).Error } // DeleteUsedTransactions deleting used transaction func DeleteUsedTransactions(dbTx *DbTransaction) (int64, error) { query := GetDB(dbTx).Exec("DELETE FROM transactions WHERE used = 1") return query.RowsAffected, query.Error } // DeleteTransactionIfUnused deleting unused transaction func DeleteTransactionIfUnused(dbTx *DbTransaction, transactionHash []byte) (int64, error) { query := GetDB(dbTx).Exec("DELETE FROM transactions WHERE hash = ? and used = 0 and verified = 0", transactionHash) return query.RowsAffected, query.Error } // MarkTransactionSent is marking transaction as sent func MarkTransactionSent(transactionHash []byte) (int64, error) { query := DBConn.Exec("UPDATE transactions SET sent = 1 WHERE hash = ?", transactionHash) return query.RowsAffected, query.Error } // MarkTransactionSentBatches is marking transaction as sent func MarkTransactionSentBatches(hashArr [][]byte) error { return DBConn.Exec("UPDATE transactions SET sent = 1 WHERE hash in(?)", hashArr).Error } // MarkTransactionUsed is marking transaction as used func MarkTransactionUsed(dbTx *DbTransaction, transactionHash []byte) (int64, error) { query := GetDB(dbTx).Exec("UPDATE transactions SET used = 1 WHERE hash = ?", transactionHash) return query.RowsAffected, query.Error } // MarkTransactionUnusedAndUnverified is marking transaction unused and unverified func MarkTransactionUnusedAndUnverified(dbTx *DbTransaction, transactionHash []byte) (int64, error) { query := GetDB(dbTx).Exec("UPDATE transactions SET used = 0, verified = 0 WHERE hash = ?", transactionHash) return query.RowsAffected, query.Error } // MarkVerifiedAndNotUsedTransactionsUnverified is marking verified and unused transaction as unverified func MarkVerifiedAndNotUsedTransactionsUnverified() (int64, error) { query := DBConn.Exec("UPDATE transactions SET verified = 0 WHERE verified = 1 AND used = 0") return query.RowsAffected, query.Error } // Read is checking transaction existence by hash func (t *Transaction) Read(hash []byte) (bool, error) { return isFound(DBConn.Where("hash = ?", hash).First(t)) } // Get is retrieving model from database func (t *Transaction) Get(transactionHash []byte) (bool, error) { return isFound(DBConn.Where("hash = ?", transactionHash).First(t)) } // GetVerified is checking transaction verification by hash func (t *Transaction) GetVerified(transactionHash []byte) (bool, error) { return isFound(DBConn.Where("hash = ? AND verified = 1", transactionHash).First(t)) } func (t *Transaction) BeforeCreate(db *gorm.DB) error { if t.HighRate == 0 { t.HighRate = GetTxRateByTxType(t.Type) } return nil } // Create is creating record of model func (t *Transaction) Create(db *DbTransaction) error { return GetDB(db).Create(&t).Error } // CreateTransactionBatches is creating record of model func CreateTransactionBatches(db *DbTransaction, trs []*Transaction) error { return GetDB(db).Clauses(clause.OnConflict{DoNothing: true}).Create(&trs).Error } func (t *Transaction) BeforeUpdate(db *gorm.DB) error { return db.Where("hash = ?", t.Hash).FirstOrCreate(&t).Error } func (t *Transaction) Update(db *DbTransaction) error { return GetDB(db).Where("hash = ?", t.Hash).Updates(&t).Error } func GetTxRateByTxType(txType int8) transactionRate { switch txType { case types.StopNetworkTxType: return TransactionRateStopNetwork default: return TransactionRateApiContract } } func GetManyTransactions(dbtx *DbTransaction, hashes [][]byte) ([]Transaction, error) { txes := []Transaction{} query := GetDB(dbtx).Where("hash in (?)", hashes).Find(&txes) if err := query.Error; err != nil { return nil, err } return txes, nil } func (t *Transaction) GetStopNetwork() (bool, error) { return isFound(DBConn.Where("type = ?", types.StopNetworkTxType).First(t)) } func (t *Transaction) GetTransactionRateStopNetwork() bool { return t.HighRate == TransactionRateStopNetwork } func DeleteTransactions(dbTx *gorm.DB, hs [][]byte) error { if len(hs) == 0 { return nil } return dbTx.Delete(&Transaction{}, hs).Error } ================================================ FILE: packages/storage/sqldb/transaction_status.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/pbgo" "gorm.io/gorm" ) // TransactionStatus is model type TransactionStatus struct { Hash []byte `gorm:"primary_key;not null"` Time int64 `gorm:"not null;"` Type byte `gorm:"not null"` WalletID int64 `gorm:"not null"` BlockID int64 `gorm:"not null"` Error string `gorm:"not null"` Penalty int64 `gorm:"not null"` } // TableName returns name of table func (ts *TransactionStatus) TableName() string { return "transactions_status" } // Create is creating record of model func (ts *TransactionStatus) Create() error { return DBConn.Create(ts).Error } // Get is retrieving model from database func (ts *TransactionStatus) Get(transactionHash []byte) (bool, error) { return isFound(DBConn.Where("hash = ?", transactionHash).First(ts)) } // UpdateBlockID is updating block id func (ts *TransactionStatus) UpdateBlockID(dbTx *DbTransaction, newBlockID int64, transactionHash []byte) error { return GetDB(dbTx).Model(&TransactionStatus{}).Where("hash = ?", transactionHash).Update("block_id", newBlockID).Error } func UpdateBlockMsgBatches(dbTx *gorm.DB, newBlockID int64, updBlockMsg []*pbgo.TxResult) error { if len(updBlockMsg) == 0 { return nil } var ( upErrStr, upBlockIdStr string hashArr []string header = "UPDATE transactions_status SET" colErr, colBlockId = "error = CASE hash", "block_id = CASE hash" ) for _, s := range updBlockMsg { if s == nil { continue } hashArr = append(hashArr, fmt.Sprintf("decode('%x','hex')", s.Hash)) upErrStr += fmt.Sprintf("WHEN decode('%x','hex') THEN '%s' ", s.Hash, strings.Replace(s.Result, `'`, `''`, -1)) upBlockIdStr += fmt.Sprintf("WHEN decode('%x','hex') THEN %d ", s.Hash, newBlockID) } if len(hashArr) == 0 { return nil } sqlStr := fmt.Sprintf("%s ", header) sqlStr += fmt.Sprintf(" %s %s END,", colErr, upErrStr) sqlStr += fmt.Sprintf(" %s %s END", colBlockId, upBlockIdStr) sqlStr += fmt.Sprintf(" WHERE hash in(%s)", strings.Join(hashArr, ",")) return dbTx.Exec(sqlStr).Error } // SetError is updating transaction status error func (ts *TransactionStatus) SetError(dbTx *DbTransaction, errorText string, transactionHash []byte) error { return GetDB(dbTx).Model(&TransactionStatus{}).Where("hash = ?", transactionHash).Update("error", errorText).Error } // UpdatePenalty is updating penalty func (ts *TransactionStatus) UpdatePenalty(dbTx *DbTransaction, transactionHash []byte) error { return GetDB(dbTx).Model(&TransactionStatus{}).Where("hash = ? AND penalty = 0", transactionHash).Update("penalty", int64(pbgo.TxInvokeStatusCode_PENALTY)).Error } ================================================ FILE: packages/storage/sqldb/transactions_attempts.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb // TransactionsAttempts is model type TransactionsAttempts struct { Hash []byte `gorm:"primary_key;not null"` Attempt int8 `gorm:"not null"` } // TableName returns name of table func (m TransactionsAttempts) TableName() string { return `transactions_attempts` } // GetByHash returns TransactionsAttempts existence by hash func (ta *TransactionsAttempts) GetByHash(dbTx *DbTransaction, hash []byte) (bool, error) { return isFound(GetDB(dbTx).Where("hash = ?", hash).First(&ta)) } // IncrementTxAttemptCount increases attempt column func IncrementTxAttemptCount(dbTx *DbTransaction, transactionHash []byte) (int64, error) { ta := &TransactionsAttempts{} found, err := ta.GetByHash(dbTx, transactionHash) if err != nil { return 0, err } if found { if ta.Attempt > 125 { return int64(ta.Attempt), nil } err = GetDB(dbTx).Exec("update transactions_attempts set attempt=attempt+1 where hash = ?", transactionHash).Error if err != nil { return 0, err } ta.Attempt++ } else { ta.Hash = transactionHash ta.Attempt = 1 if err = GetDB(dbTx).Create(ta).Error; err != nil { return 0, err } } return int64(ta.Attempt), nil } func DecrementTxAttemptCount(dbTx *DbTransaction, transactionHash []byte) error { return GetDB(dbTx).Exec("update transactions_attempts set attempt=attempt-1 where hash = ?", transactionHash).Error } func FindTxAttemptCount(dbTx *DbTransaction, count int) ([]*TransactionsAttempts, error) { var rs []*TransactionsAttempts if err := GetDB(dbTx).Where("attempt > ?", count).Find(&rs).Error; err != nil { return rs, err } return rs, nil } // GetByHash returns TransactionsAttempts existence by hash func DeleteTransactionsAttemptsByHash(dbTx *DbTransaction, hash []byte) error { return GetDB(dbTx).Table("transactions_attempts").Delete(&TransactionsAttempts{}, hash).Error } ================================================ FILE: packages/storage/sqldb/tx_record.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb import ( "database/sql" "reflect" "strings" "github.com/IBAX-io/go-ibax/packages/converter" ) func GetTxRecord(tx *DbTransaction, hashStr string) (resultList []any, err error) { db := GetDB(tx) // get record from rollback_tx var ( rollbackTxs []RollbackTx ) err = db.Table("rollback_tx").Where("tx_hash = ?", []byte(converter.HexToBin(hashStr))).Find(&rollbackTxs).Error if err != nil { return } for _, rtx := range rollbackTxs { id := rtx.TableID var ecosystem string tableName := rtx.NameTable if tableName == `1_keys` || tableName == "@system" { continue } if strings.Contains(id, ",") { ids := strings.Split(id, ",") if len(ids) == 2 { id, ecosystem = ids[0], ids[1] } } var ( rows *sql.Rows err error ) if ecosystem == "" { rows, err = db.Raw(`select * from "` + tableName + `" where id = ` + id).Rows() } else { rows, err = db.Raw(`select * from "` + tableName + `" where id = ` + id + " AND ecosystem = " + ecosystem).Rows() } defer rows.Close() if err == nil { cols, er := rows.Columns() if er != nil { continue } values := make([][]byte, len(cols)) scanArgs := make([]any, len(values)) for i := range values { scanArgs[i] = &values[i] } for rows.Next() { err = rows.Scan(scanArgs...) if err == nil { row := make(map[string]any) for i, col := range values { var value string if col != nil { value = string(col) } row[cols[i]] = value } resultList = append(resultList, reflect.ValueOf(row).Interface()) } } } } return } ================================================ FILE: packages/storage/sqldb/upd_full_nodes.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package sqldb ================================================ FILE: packages/storage/sqldb/utxo_token.go ================================================ package sqldb import ( "sync" ) var ( lock = &sync.RWMutex{} ) func InsertTxOutputs(outputTxHash []byte, txOutputsMapCtx map[KeyUTXO][]SpentInfo, outputsMap map[KeyUTXO][]SpentInfo) { lock.Lock() defer lock.Unlock() for keyUTXO, txOutput := range txOutputsMapCtx { spentInfos := outputsMap[keyUTXO] for i, _ := range txOutput { txOutput[i].OutputTxHash = outputTxHash } spentInfos = append(spentInfos, txOutput...) outputsMap[keyUTXO] = spentInfos } } func UpdateTxInputs(inputTxHash []byte, txInputsMapCtx map[KeyUTXO][]SpentInfo, outputsMap map[KeyUTXO][]SpentInfo) { lock.Lock() defer lock.Unlock() var inputIndex int32 for txKeyUTXO, _ := range txInputsMapCtx { spentInfos := outputsMap[txKeyUTXO] for i, info := range spentInfos { if len(info.InputTxHash) == 0 { outputsMap[txKeyUTXO][i].InputTxHash = inputTxHash outputsMap[txKeyUTXO][i].InputIndex = inputIndex inputIndex++ } } } } func PutAllOutputsMap(outputs []SpentInfo, outputsMap map[KeyUTXO][]SpentInfo) { lock.Lock() defer lock.Unlock() //if len(outputsMap) == 0 { // outputsMap = make(map[KeyUTXO][]SpentInfo) //} for _, output := range outputs { keyUTXO := KeyUTXO{Ecosystem: output.Ecosystem, KeyId: output.OutputKeyId} spentInfos := outputsMap[keyUTXO] spentInfos = append(spentInfos, output) PutOutputsMap(keyUTXO, spentInfos, outputsMap) } } func PutOutputsMap(keyUTXO KeyUTXO, outputs []SpentInfo, outputsMap map[KeyUTXO][]SpentInfo) { outputsMap[keyUTXO] = outputs } func GetUnusedOutputsMap(keyUTXO KeyUTXO, outputsMap map[KeyUTXO][]SpentInfo) []SpentInfo { lock.Lock() defer lock.Unlock() spentInfos := outputsMap[keyUTXO] var inputIndex int32 var list []SpentInfo for _, output := range spentInfos { if len(output.InputTxHash) == 0 { output.InputIndex = inputIndex inputIndex++ list = append(list, output) } } return list } func GetAllOutputs(outputsMap map[KeyUTXO][]SpentInfo) []SpentInfo { var list []SpentInfo for _, outputs := range outputsMap { list = append(list, outputs...) } outputsMap = make(map[KeyUTXO][]SpentInfo) return list } ================================================ FILE: packages/storage/storage.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package storage ================================================ FILE: packages/system/system.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package system ================================================ FILE: packages/system/system_notwindows.go ================================================ //go:build !windows /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package system func killChildProc() { } ================================================ FILE: packages/system/system_windows.go ================================================ //go:build windows /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package system import ( "os" ) /* #include #include #include #include void kill_childproc( DWORD myprocID) { PROCESSENTRY32 pe; memset(&pe, 0, sizeof(PROCESSENTRY32)); pe.dwSize = sizeof(PROCESSENTRY32); HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (Process32First(hSnap, &pe)) { BOOL bContinue = TRUE; while (bContinue) { if (pe.th32ParentProcessID == myprocID && memcmp( pe.szExeFile, "tmp_", 4 ) != 0 && memcmp(pe.szExeFile, "chain", 4) != 0) { HANDLE hChildProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); if (hChildProc) { kill_childproc(GetProcessId(hChildProc)); TerminateProcess(hChildProc, 1); CloseHandle(hChildProc); } } bContinue = Process32Next(hSnap, &pe); } } } */ import "C" func killChildProc() { C.kill_childproc(C.DWORD(os.Getpid())) } ================================================ FILE: packages/template/calculate.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package template import ( "errors" "fmt" "strconv" "strings" "unicode" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/shopspring/decimal" ) const ( tkNumber = iota tkAdd tkSub tkMul tkDiv tkLPar tkRPar expInt = 0 expFloat = 1 expMoney = 2 ) type token struct { Type int Value any } type opFunc func() var ( errExp = errors.New(`wrong expression`) errDiv = errors.New(`dividing by zero`) errPrecIsNegative = errors.New(`precision is negative`) errWhere = errors.New(`Where has wrong format`) ) func parsing(input string, itype int) (*[]token, error) { var err error tokens := make([]token, 0) newToken := func(itype int, value any) { tokens = append(tokens, token{itype, value}) } prevNumber := func() bool { return len(tokens) > 0 && tokens[len(tokens)-1].Type == tkNumber } prevOper := func() bool { return len(tokens) > 0 && (tokens[len(tokens)-1].Type >= tkAdd && tokens[len(tokens)-1].Type <= tkDiv) } var ( numlen int ) ops := map[rune]struct { id int pr int }{ '+': {tkAdd, 1}, '-': {tkSub, 1}, '*': {tkMul, 2}, '/': {tkDiv, 2}, } for off, ch := range input { if unicode.IsDigit(ch) || ch == '.' { numlen++ continue } if numlen > 0 { var val any switch itype { case expInt: val, err = strconv.ParseInt(input[off-numlen:off], 10, 64) case expFloat: val, err = strconv.ParseFloat(input[off-numlen:off], 64) case expMoney: val, err = decimal.NewFromString(input[off-numlen : off]) } if err != nil { return nil, err } if prevNumber() { return nil, errExp } newToken(tkNumber, val) numlen = 0 } if item, ok := ops[ch]; ok { if prevOper() { return nil, errExp } newToken(item.id, item.pr) continue } switch ch { case '(': if prevNumber() { return nil, errExp } newToken(tkLPar, 3) case ')': if prevOper() { return nil, errExp } newToken(tkRPar, 3) case ' ', '\t', '\n', '\r': default: return nil, errExp } } return &tokens, nil } func calcExp(tokens []token, resType int, prec string) string { var top int stack := make([]any, 0, 16) addInt := func() { stack[top-1] = stack[top-1].(int64) + stack[top].(int64) } addFloat := func() { stack[top-1] = stack[top-1].(float64) + stack[top].(float64) } addMoney := func() { stack[top-1] = stack[top-1].(decimal.Decimal).Add(stack[top].(decimal.Decimal)) } subInt := func() { stack[top-1] = stack[top-1].(int64) - stack[top].(int64) } subFloat := func() { stack[top-1] = stack[top-1].(float64) - stack[top].(float64) } subMoney := func() { stack[top-1] = stack[top-1].(decimal.Decimal).Sub(stack[top].(decimal.Decimal)) } mulInt := func() { stack[top-1] = stack[top-1].(int64) * stack[top].(int64) } mulFloat := func() { stack[top-1] = stack[top-1].(float64) * stack[top].(float64) } mulMoney := func() { stack[top-1] = stack[top-1].(decimal.Decimal).Mul(stack[top].(decimal.Decimal)) } divInt := func() { stack[top-1] = stack[top-1].(int64) / stack[top].(int64) } divFloat := func() { stack[top-1] = stack[top-1].(float64) / stack[top].(float64) } divMoney := func() { stack[top-1] = stack[top-1].(decimal.Decimal).Div(stack[top].(decimal.Decimal)) } funcs := map[int][]opFunc{ tkAdd: {addInt, addFloat, addMoney}, tkSub: {subInt, subFloat, subMoney}, tkMul: {mulInt, mulFloat, mulMoney}, tkDiv: {divInt, divFloat, divMoney}, } for _, item := range tokens { if item.Type == tkNumber { stack = append(stack, item.Value) } else { if len(stack) < 2 { return errExp.Error() } top = len(stack) - 1 if item.Type == tkDiv { switch resType { case expInt: if stack[top].(int64) == 0 { return errDiv.Error() } case expFloat: if stack[top].(float64) == 0 { return errDiv.Error() } case expMoney: if stack[top].(decimal.Decimal).Cmp(decimal.Zero) == 0 { return errDiv.Error() } } } funcs[item.Type][resType]() stack = stack[:top] } } if len(stack) != 1 { return errExp.Error() } if prec != "" { precInt := converter.StrToInt(prec) if resType != expInt { if precInt < 0 { return errPrecIsNegative.Error() } } if resType == expFloat { return decimal.NewFromFloat(stack[0].(float64)).Round(int32(precInt)).String() } if resType == expMoney { money := stack[0].(decimal.Decimal) return money.Round(int32(precInt)).String() } } if resType == expFloat { decStr, _ := decimal.NewFromString(fmt.Sprintf("%f", stack[0].(float64))) return decStr.String() } if resType == expMoney { return stack[0].(decimal.Decimal).String() } return fmt.Sprint(stack[0]) } func calculate(exp, etype, prec string) string { var resType int if len(etype) == 0 && strings.Contains(exp, `.`) { etype = `float` } switch etype { case `float`: resType = expFloat case `money`: resType = expMoney } tk, err := parsing(exp+` `, resType) if err != nil { return err.Error() } stack := make([]token, 0, len(*tk)) buf := make([]token, 0, 10) for _, item := range *tk { switch item.Type { case tkNumber: stack = append(stack, item) case tkLPar: buf = append(buf, item) case tkRPar: i := len(buf) - 1 for i >= 0 && buf[i].Type != tkLPar { stack = append(stack, buf[i]) i-- } if i < 0 { return errExp.Error() } buf = buf[:i] default: if len(buf) > 0 { last := buf[len(buf)-1] if last.Type != tkLPar && last.Value.(int) >= item.Value.(int) { stack = append(stack, last) buf[len(buf)-1] = item continue } } buf = append(buf, item) } } for i := len(buf) - 1; i >= 0; i-- { last := buf[i] if last.Type >= tkAdd && last.Type <= tkDiv { stack = append(stack, last) } else { return errExp.Error() } } return calcExp(stack, resType, prec) } ================================================ FILE: packages/template/dbfind.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package template import ( "encoding/json" "fmt" "strings" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) const ( columnTypeText = "text" columnTypeLongText = "long_text" columnTypeBlob = "blob" substringLength = 32 errComma = `unexpected comma` ) func dbfindExpressionBlob(column string) string { return fmt.Sprintf(`md5(%s) "%[1]s"`, column) } func dbfindExpressionLongText(column string) string { return fmt.Sprintf(`json_build_array( substr(%s, 1, %d), CASE WHEN length(%[1]s)>%[2]d THEN md5(%[1]s) END) "%[1]s"`, column, substringLength) } type valueLink struct { title string id string table string column string hash string } func (vl *valueLink) link() string { if len(vl.hash) > 0 { return fmt.Sprintf("/data/%s/%s/%s/%s", vl.table, vl.id, vl.column, vl.hash) } return "" } func (vl *valueLink) marshal() (string, error) { b, err := json.Marshal(map[string]string{ "title": vl.title, "link": vl.link(), }) if err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marshalling valueLink to JSON") return "", err } return string(b), nil } func trimString(in []rune) string { out := strings.TrimSpace(string(in)) if len(out) > 0 && out[0] == '"' && out[len(out)-1] == '"' { out = out[1 : len(out)-1] } return out } func ParseObject(in []rune) (any, int, error) { var ( ret any key string mapMode, quote bool ) length := len(in) if in[0] == '[' { ret = make([]any, 0) } else if in[0] == '{' { ret = types.NewMap() mapMode = true } else { return nil, 0, errWhere } addEmptyKey := func() { if mapMode { ret.(*types.Map).Set(key, "") } else if len(key) > 0 { ret = append(ret.([]any), types.LoadMap(map[string]any{key: ``})) } key = `` } start := 1 i := 1 prev := ' ' main: for ; i < length; i++ { ch := in[i] if quote && ch != '"' { continue } switch ch { case ']': if !mapMode { break main } case '}': if mapMode { break main } case '{', '[': par, off, err := ParseObject(in[i:]) if err != nil { return nil, i, err } if mapMode { if len(key) == 0 { switch v := par.(type) { case map[string]any: for ikey, ival := range v { ret.(*types.Map).Set(ikey, ival) } } } else { ret.(*types.Map).Set(key, par) key = `` } } else { if len(key) > 0 { par = types.LoadMap(map[string]any{key: par}) key = `` } ret = append(ret.([]any), par) } i += off start = i + 1 case '"': quote = !quote case ':': if len(key) == 0 { key = trimString(in[start:i]) start = i + 1 } case ',': val := trimString(in[start:i]) if prev == ch { return nil, i, fmt.Errorf(errComma) } if len(val) == 0 && len(key) > 0 { addEmptyKey() } if len(val) > 0 { if mapMode { ret.(*types.Map).Set(key, val) key = `` } else { if len(key) > 0 { ret = append(ret.([]any), types.LoadMap(map[string]any{key: val})) key = `` } else { ret = append(ret.([]any), val) } } } start = i + 1 } if ch != ' ' { prev = ch } } if prev == ',' { return nil, i, fmt.Errorf(errComma) } if start < i { if last := trimString(in[start:i]); len(last) > 0 { if mapMode { ret.(*types.Map).Set(key, last) } else { if len(key) > 0 { ret = append(ret.([]any), types.LoadMap(map[string]any{key: last})) key = `` } else { ret = append(ret.([]any), last) } } } else if len(key) > 0 { addEmptyKey() } } switch v := ret.(type) { case *types.Map: if v.Size() == 0 { ret = `` } case map[string]any: if len(v) == 0 { ret = `` } case []any: if len(v) == 0 { ret = `` } } return ret, i, nil } ================================================ FILE: packages/template/funcs.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package template import ( "encoding/csv" "encoding/json" "fmt" "reflect" "sort" "strconv" "strings" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/language" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" qb "github.com/IBAX-io/go-ibax/packages/storage/sqldb/queryBuilder" log "github.com/sirupsen/logrus" ) // Composite represents a composite contract type Composite struct { Name string `json:"name"` Data any `json:"data,omitempty"` } // Action describes a button action type Action struct { Name string `json:"name"` Params map[string]string `json:"params,omitempty"` } var ( funcs = make(map[string]tplFunc) tails = make(map[string]forTails) modes = [][]rune{{'(', ')'}, {'{', '}'}, {'[', ']'}} ) const ( columnNameKey = "column_name" dataTypeKey = "data_type" ) func init() { funcs[`Lower`] = tplFunc{lowerTag, defaultTag, `lower`, `Text`} funcs[`AddToolButton`] = tplFunc{defaultTailTag, defaultTailTag, `addtoolbutton`, `Title,Icon,Page,PageParams`} funcs[`Address`] = tplFunc{addressTag, defaultTag, `address`, `Wallet`} funcs[`PubToID`] = tplFunc{pubToIdTag, defaultTag, `pubtoid`, `Pub`} funcs[`AddressToId`] = tplFunc{addressIDTag, defaultTag, `addresstoid`, `Wallet`} funcs[`AppParam`] = tplFunc{appparTag, defaultTag, `apppar`, `Name,App,Index,Source,Ecosystem`} funcs[`Calculate`] = tplFunc{calculateTag, defaultTag, `calculate`, `Exp,Type,Prec`} funcs[`CmpTime`] = tplFunc{cmpTimeTag, defaultTag, `cmptime`, `Time1,Time2`} funcs[`Code`] = tplFunc{defaultTag, defaultTag, `code`, `Text`} funcs[`CodeAsIs`] = tplFunc{defaultTag, defaultTag, `code`, `#Text`} funcs[`DateTime`] = tplFunc{dateTimeTag, defaultTag, `datetime`, `DateTime,Format,Location`} funcs[`EcosysParam`] = tplFunc{ecosysparTag, defaultTag, `ecosyspar`, `Name,Index,Source,Ecosystem`} funcs[`Em`] = tplFunc{defaultTag, defaultTag, `em`, `Body,Class`} funcs[`GetVar`] = tplFunc{getvarTag, defaultTag, `getvar`, `Name`} funcs[`GetHistory`] = tplFunc{getHistoryTag, defaultTag, `gethistory`, `Source,Name,Id,RollbackId`} funcs[`Hint`] = tplFunc{defaultTag, defaultTag, `hint`, `Icon,Title,Text`} funcs[`ImageInput`] = tplFunc{defaultTag, defaultTag, `imageinput`, `Name,Width,Ratio,Format`} funcs[`InputErr`] = tplFunc{defaultTag, defaultTag, `inputerr`, `*`} funcs[`JsonToSource`] = tplFunc{jsontosourceTag, defaultTag, `jsontosource`, `Source,Data,Prefix`} funcs[`ArrayToSource`] = tplFunc{arraytosourceTag, defaultTag, `arraytosource`, `Source,Data,Prefix`} funcs[`LangRes`] = tplFunc{langresTag, defaultTag, `langres`, `Name,Lang`} funcs[`MenuGroup`] = tplFunc{menugroupTag, defaultTag, `menugroup`, `Title,Body,Icon`} funcs[`MenuItem`] = tplFunc{defaultTag, defaultTag, `menuitem`, `Title,Page,PageParams,Icon,Clb`} funcs[`Money`] = tplFunc{moneyTag, defaultTag, `money`, `Exp,Digit`} funcs[`Range`] = tplFunc{rangeTag, defaultTag, `range`, `Source,From,To,Step`} funcs[`SetTitle`] = tplFunc{defaultTag, defaultTag, `settitle`, `Title`} funcs[`SetVar`] = tplFunc{setvarTag, defaultTag, `setvar`, `Name,Value`} funcs[`Strong`] = tplFunc{defaultTag, defaultTag, `strong`, `Body,Class`} funcs[`SysParam`] = tplFunc{sysparTag, defaultTag, `syspar`, `Name`} funcs[`Button`] = tplFunc{buttonTag, buttonTag, `button`, `Body,Page,Class,Contract,Params,PageParams`} funcs[`Div`] = tplFunc{defaultTailTag, defaultTailTag, `div`, `Class,Body`} funcs[`ForList`] = tplFunc{forlistTag, defaultTag, `forlist`, `Source,Data,Index`} funcs[`Form`] = tplFunc{defaultTailTag, defaultTailTag, `form`, `Class,Body`} funcs[`If`] = tplFunc{ifTag, ifFull, `if`, `Condition,Body`} funcs[`Image`] = tplFunc{imageTag, defaultTailTag, `image`, `Src,Alt,Class`} funcs[`Include`] = tplFunc{includeTag, defaultTag, `include`, `Name`} funcs[`Input`] = tplFunc{defaultTailTag, defaultTailTag, `input`, `Name,Class,Placeholder,Type,Value,Disabled`} funcs[`Label`] = tplFunc{defaultTailTag, defaultTailTag, `label`, `Body,Class,For`} funcs[`LinkPage`] = tplFunc{defaultTailTag, defaultTailTag, `linkpage`, `Body,Page,Class,PageParams`} funcs[`Data`] = tplFunc{dataTag, defaultTailTag, `data`, `Source,Columns,Data`} funcs[`DBFind`] = tplFunc{dbfindTag, defaultTailTag, `dbfind`, `Name,Source`} funcs[`And`] = tplFunc{andTag, defaultTag, `and`, `*`} funcs[`Or`] = tplFunc{orTag, defaultTag, `or`, `*`} funcs[`P`] = tplFunc{defaultTailTag, defaultTailTag, `p`, `Body,Class`} funcs[`RadioGroup`] = tplFunc{defaultTailTag, defaultTailTag, `radiogroup`, `Name,Source,NameColumn,ValueColumn,Value,Class`} funcs[`Span`] = tplFunc{defaultTailTag, defaultTailTag, `span`, `Body,Class`} funcs[`QRcode`] = tplFunc{defaultTag, defaultTag, `qrcode`, `Text`} funcs[`Table`] = tplFunc{tableTag, defaultTailTag, `table`, `Source,Columns`} funcs[`Select`] = tplFunc{defaultTailTag, defaultTailTag, `select`, `Name,Source,NameColumn,ValueColumn,Value,Class`} funcs[`Chart`] = tplFunc{chartTag, defaultTailTag, `chart`, `Type,Source,FieldLabel,FieldValue,Colors`} funcs[`InputMap`] = tplFunc{defaultTailTag, defaultTailTag, "inputMap", "Name,@Value,Type,MapType"} funcs[`Map`] = tplFunc{defaultTag, defaultTag, "map", "@Value,MapType,Hmap"} funcs[`Binary`] = tplFunc{binaryTag, defaultTag, "binary", "AppID,Name,Account"} funcs[`GetColumnType`] = tplFunc{columntypeTag, defaultTag, `columntype`, `Table,Column`} funcs[`VarAsIs`] = tplFunc{varasisTag, defaultTag, `varasis`, `Name,Value`} tails[`addtoolbutton`] = forTails{map[string]tailInfo{ `Popup`: {tplFunc{popupTag, defaultTailFull, `popup`, `Width,Header`}, true}, }} tails[`button`] = forTails{map[string]tailInfo{ `Action`: {tplFunc{actionTag, defaultTailFull, `action`, `Name,Params`}, false}, `Alert`: {tplFunc{alertTag, defaultTailFull, `alert`, `Text,ConfirmButton,CancelButton,Icon`}, true}, `Popup`: {tplFunc{popupTag, defaultTailFull, `popup`, `Width,Header`}, true}, `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, `CompositeContract`: {tplFunc{compositeTag, defaultTailFull, `composite`, `Name,Data`}, false}, `ErrorRedirect`: {tplFunc{errredirTag, defaultTailFull, `errorredirect`, `ErrorID,PageName,PageParams`}, false}, }} tails[`div`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, `Show`: {tplFunc{showTag, defaultTailFull, `show`, `Condition`}, false}, `Hide`: {tplFunc{hideTag, defaultTailFull, `hide`, `Condition`}, false}, }} tails[`form`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`if`] = forTails{map[string]tailInfo{ `Else`: {tplFunc{elseTag, elseFull, `else`, `Body`}, true}, `ElseIf`: {tplFunc{elseifTag, elseifFull, `elseif`, `Condition,Body`}, false}, }} tails[`image`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`input`] = forTails{map[string]tailInfo{ `Validate`: {tplFunc{validateTag, validateFull, `validate`, `*`}, false}, `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`label`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`linkpage`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`data`] = forTails{map[string]tailInfo{ `Custom`: {tplFunc{customTag, customTagFull, `custom`, `Column,Body`}, false}, }} tails[`dbfind`] = forTails{map[string]tailInfo{ `Columns`: {tplFunc{tailTag, defaultTailFull, `columns`, `Columns`}, false}, `Count`: {tplFunc{tailTag, defaultTailFull, `count`, `CountVar`}, false}, `Where`: {tplFunc{tailTag, defaultTailFull, `where`, `Where`}, false}, `WhereId`: {tplFunc{tailTag, defaultTailFull, `whereid`, `WhereId`}, false}, `Order`: {tplFunc{tailTag, defaultTailFull, `order`, `Order`}, false}, `Limit`: {tplFunc{tailTag, defaultTailFull, `limit`, `Limit`}, false}, `Offset`: {tplFunc{tailTag, defaultTailFull, `offset`, `Offset`}, false}, `Custom`: {tplFunc{customTag, customTagFull, `custom`, `Column,Body`}, false}, `Vars`: {tplFunc{tailTag, defaultTailFull, `vars`, `Prefix`}, false}, `Cutoff`: {tplFunc{tailTag, defaultTailFull, `cutoff`, `Cutoff`}, false}, }} tails[`p`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`radiogroup`] = forTails{map[string]tailInfo{ `Validate`: {tplFunc{validateTag, validateFull, `validate`, `*`}, false}, `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`span`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`table`] = forTails{map[string]tailInfo{ `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`select`] = forTails{map[string]tailInfo{ `Validate`: {tplFunc{validateTag, validateFull, `validate`, `*`}, false}, `Style`: {tplFunc{tailTag, defaultTailFull, `style`, `Style`}, false}, }} tails[`inputMap`] = forTails{map[string]tailInfo{ `Validate`: {tplFunc{validateTag, validateFull, `validate`, `*`}, false}, }} tails[`binary`] = forTails{map[string]tailInfo{ `ById`: {tplFunc{tailTag, defaultTailFull, `id`, `id`}, false}, `Ecosystem`: {tplFunc{tailTag, defaultTailFull, `ecosystem`, `ecosystem`}, false}, }} } func defaultTag(par parFunc) string { setAllAttr(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func lowerTag(par parFunc) string { return strings.ToLower(macro((*par.Pars)[`Text`], par.Workspace.Vars)) } func moneyTag(par parFunc) string { var cents int64 if len((*par.Pars)[`Digit`]) > 0 { cents = converter.StrToInt64(macro((*par.Pars)[`Digit`], par.Workspace.Vars)) } else { ecosystem := getVar(par.Workspace, `ecosystem_id`) sp := &sqldb.Ecosystem{} _, err := sp.Get(nil, converter.StrToInt64(ecosystem)) if err != nil { return `0` } cents = sp.Digits } exp := macro((*par.Pars)[`Exp`], par.Workspace.Vars) m, err := converter.FormatMoney(exp, int32(cents)) if err != nil { return `0` } return m } func menugroupTag(par parFunc) string { setAllAttr(par) name := (*par.Pars)[`Title`] if par.RawPars != nil { if v, ok := (*par.RawPars)[`Title`]; ok { name = v } } par.Node.Attr[`name`] = name par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func forlistTag(par parFunc) (ret string) { var ( name, indexName string ) setAllAttr(par) if len((*par.Pars)[`Source`]) > 0 { name = par.Node.Attr[`source`].(string) } if len((*par.Pars)[`Index`]) > 0 { indexName = par.Node.Attr[`index`].(string) } else { indexName = name + `_index` } if len(name) == 0 || par.Workspace.Sources == nil { return } source := (*par.Workspace.Sources)[name] if source.Data == nil { return } root := node{} keys := make(map[string]bool) for key := range *par.Workspace.Vars { keys[key] = true } for index, item := range *source.Data { vals := map[string]string{indexName: converter.IntToStr(index + 1)} for i, icol := range *source.Columns { vals[icol] = item[i] } if index > 0 { for key := range *par.Workspace.Vars { if !keys[key] { delete(*par.Workspace.Vars, key) } } } for key, item := range vals { setVar(par.Workspace, key, item) } process((*par.Pars)[`Data`], &root, par.Workspace) for _, item := range root.Children { if item.Tag == `text` { item.Text = macroReplace(item.Text, par.Workspace.Vars) } } for key := range vals { delete(*par.Workspace.Vars, key) } } par.Node.Children = root.Children par.Owner.Children = append(par.Owner.Children, par.Node) return } func addressTag(par parFunc) string { idval := (*par.Pars)[`Wallet`] if len(idval) == 0 { idval = getVar(par.Workspace, `key_id`) } idval = processToText(par, macro(idval, par.Workspace.Vars)) id, _ := strconv.ParseInt(idval, 10, 64) if id == 0 { return `unknown address` } return converter.AddressToString(id) } func pubToIdTag(par parFunc) string { hexkey := (*par.Pars)[`Pub`] if len(hexkey) == 0 { return `0` } idval := processToText(par, macro(hexkey, par.Workspace.Vars)) pubkey, err := crypto.HexToPub(idval) if err != nil { return `0` } return converter.Int64ToStr(crypto.Address(pubkey)) } func addressIDTag(par parFunc) string { address := (*par.Pars)[`Wallet`] if len(address) == 0 { return getVar(par.Workspace, `key_id`) } id := converter.AddressToID(processToText(par, macro(address, par.Workspace.Vars))) if id == 0 { return `0` } return converter.Int64ToStr(id) } func calculateTag(par parFunc) string { return calculate(macro((*par.Pars)[`Exp`], par.Workspace.Vars), (*par.Pars)[`Type`], macro((*par.Pars)[`Prec`], par.Workspace.Vars)) } func paramToSource(par parFunc, val string) string { data := make([][]string, 0) cols := []string{`id`, `name`} types := []string{`text`, `text`} for key, item := range strings.Split(val, `,`) { item, _ = language.LangText(nil, item, converter.StrToInt(getVar(par.Workspace, `ecosystem_id`)), getVar(par.Workspace, `lang`)) data = append(data, []string{converter.IntToStr(key + 1), item}) } node := node{Tag: `data`, Attr: map[string]any{`columns`: &cols, `types`: &types, `data`: &data, `source`: (*par.Pars)[`Source`]}} par.Owner.Children = append(par.Owner.Children, &node) par.Workspace.SetSource((*par.Pars)[`Source`], &Source{ Columns: node.Attr[`columns`].(*[]string), Data: node.Attr[`data`].(*[][]string), }) return `` } func paramToIndex(par parFunc, val string) (ret string) { ind := converter.StrToInt(macro((*par.Pars)[`Index`], par.Workspace.Vars)) if alist := strings.Split(val, `,`); ind > 0 && len(alist) >= ind { ret, _ = language.LangText(nil, alist[ind-1], converter.StrToInt(getVar(par.Workspace, `ecosystem_id`)), getVar(par.Workspace, `lang`)) } return } func ecosysparTag(par parFunc) string { if len((*par.Pars)[`Name`]) == 0 { return `` } ecosystem := getVar(par.Workspace, `ecosystem_id`) if len((*par.Pars)[`Ecosystem`]) != 0 { ecosystem = macro((*par.Pars)[`Ecosystem`], par.Workspace.Vars) } sp := &sqldb.StateParameter{} sp.SetTablePrefix(ecosystem) parameterName := macro((*par.Pars)[`Name`], par.Workspace.Vars) _, err := sp.Get(nil, parameterName) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting ecosystem param") return err.Error() } val := sp.Value if len((*par.Pars)[`Source`]) > 0 { return paramToSource(par, val) } if len((*par.Pars)[`Index`]) > 0 { val = paramToIndex(par, val) } return val } func appparTag(par parFunc) string { if len((*par.Pars)[`Name`]) == 0 || len((*par.Pars)[`App`]) == 0 { return `` } ecosystem := getVar(par.Workspace, `ecosystem_id`) if len((*par.Pars)[`Ecosystem`]) != 0 { ecosystem = macro((*par.Pars)[`Ecosystem`], par.Workspace.Vars) } ap := &sqldb.AppParam{} ap.SetTablePrefix(ecosystem) _, err := ap.Get(nil, converter.StrToInt64(macro((*par.Pars)[`App`], par.Workspace.Vars)), macro((*par.Pars)[`Name`], par.Workspace.Vars)) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting app param") return err.Error() } val := ap.Value if len((*par.Pars)[`Source`]) > 0 { return paramToSource(par, val) } if len((*par.Pars)[`Index`]) > 0 { val = paramToIndex(par, val) } return val } func langresTag(par parFunc) string { lang := (*par.Pars)[`Lang`] if len(lang) == 0 { lang = getVar(par.Workspace, `lang`) } ret, _ := language.LangText(nil, (*par.Pars)[`Name`], int(converter.StrToInt64(getVar(par.Workspace, `ecosystem_id`))), lang) return ret } func sysparTag(par parFunc) (ret string) { if len((*par.Pars)[`Name`]) > 0 { ret = syspar.SysString(macro((*par.Pars)[`Name`], par.Workspace.Vars)) } return } func andTag(par parFunc) string { count := len(*par.Pars) for i := 0; i < count; i++ { if !ifValue((*par.Pars)[strconv.Itoa(i)], par.Workspace) { return `0` } } return `1` } func orTag(par parFunc) string { count := len(*par.Pars) for i := 0; i < count; i++ { if ifValue((*par.Pars)[strconv.Itoa(i)], par.Workspace) { return `1` } } return `0` } func alertTag(par parFunc) string { setAllAttr(par) par.Owner.Attr[`alert`] = par.Node.Attr return `` } func actionTag(par parFunc) string { setAllAttr(par) if len((*par.Pars)[`Name`]) == 0 { return `` } if par.Owner.Attr[`action`] == nil { par.Owner.Attr[`action`] = make([]Action, 0) } var params map[string]string if v, ok := par.Node.Attr["params"]; ok { params = make(map[string]string) for key, val := range v.(map[string]any) { if imap, ok := val.(map[string]any); ok { params[key] = macro(fmt.Sprint(imap["text"]), par.Workspace.Vars) } else { params[key] = macro(fmt.Sprint(val), par.Workspace.Vars) } } } par.Owner.Attr[`action`] = append(par.Owner.Attr[`action`].([]Action), Action{ Name: macro((*par.Pars)[`Name`], par.Workspace.Vars), Params: params, }) return `` } func defaultTailFull(par parFunc) string { setAllAttr(par) par.Owner.Tail = append(par.Owner.Tail, par.Node) return `` } func dataTag(par parFunc) string { setAllAttr(par) defaultTail(par, `data`) data := make([][]string, 0) cols := strings.Split((*par.Pars)[`Columns`], `,`) types := make([]string, len(cols)) for i := 0; i < len(types); i++ { types[i] = `text` } list, err := csv.NewReader(strings.NewReader((*par.Pars)[`Data`])).ReadAll() if err != nil { input := strings.Split((*par.Pars)[`Data`], "\n") par.Node.Attr[`error`] = err.Error() prefix := `line ` for err != nil && strings.HasPrefix(err.Error(), prefix) { errText := err.Error() line := converter.StrToInt64(errText[len(prefix):strings.IndexByte(errText, ',')]) if line < 1 { break } input = append(input[:line-1], input[line:]...) list, err = csv.NewReader(strings.NewReader(strings.Join(input, "\n"))).ReadAll() } } lencol := 0 defcol := 0 for _, item := range list { if lencol == 0 { defcol = len(cols) if par.Node.Attr[`customs`] != nil { for _, v := range par.Node.Attr[`customs`].([]string) { cols = append(cols, v) types = append(types, `tags`) } } lencol = len(cols) } row := make([]string, lencol) vals := make(map[string]Var) for i, icol := range cols { var ival string if i < defcol { if i < len(item) { ival = strings.TrimSpace(item[i]) } vals[icol] = Var{Value: ival} } else { root := node{} for key, item := range vals { (*par.Workspace.Vars)[key] = item } process(par.Node.Attr[`custombody`].([]string)[i-defcol], &root, par.Workspace) for key := range vals { delete(*par.Workspace.Vars, key) } out, err := json.Marshal(root.Children) if err == nil { ival = macro(string(out), &vals) } else { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marshalling custombody to JSON") } } row[i] = ival } data = append(data, row) } setAllAttr(par) delete(par.Node.Attr, `customs`) delete(par.Node.Attr, `custombody`) par.Node.Attr[`columns`] = &cols par.Node.Attr[`types`] = &types par.Node.Attr[`data`] = &data newSource(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func dbfindTag(par parFunc) string { var ( inColumns any columns []string state int64 err error perm map[string]string offset string cutoffColumns = make(map[string]bool) extendedColumns = make(map[string]string) queryColumns = make([]string, 0) ) if len((*par.Pars)[`Name`]) == 0 { return `` } defaultTail(par, `dbfind`) prefix := `` where := `` order := `` limit := 25 if par.Node.Attr[`columns`] != nil { fields := par.Node.Attr[`columns`].(string) if strings.HasPrefix(fields, `[`) { inColumns, _, err = ParseObject([]rune(fields)) if err != nil { return err.Error() } } else { inColumns = fields } } columns, err = qb.GetColumns(inColumns) if err != nil { return err.Error() } if par.Node.Attr[`where`] != nil { where = macro(par.Node.Attr[`where`].(string), par.Workspace.Vars) if strings.HasPrefix(where, `{`) { inWhere, _, err := ParseObject([]rune(where)) if err != nil { return err.Error() } switch v := inWhere.(type) { case string: if len(v) == 0 { where = `true` } else { return errWhere.Error() } case map[string]any: where, err = qb.GetWhere(types.LoadMap(v)) if err != nil { return err.Error() } case *types.Map: where, err = qb.GetWhere(v) if err != nil { return err.Error() } default: return errWhere.Error() } } else if len(where) > 0 { return errWhere.Error() } } if par.Node.Attr[`whereid`] != nil { where = fmt.Sprintf(` id='%d'`, converter.StrToInt64(macro(par.Node.Attr[`whereid`].(string), par.Workspace.Vars))) } if par.Node.Attr[`limit`] != nil { limit = converter.StrToInt(par.Node.Attr[`limit`].(string)) } if limit > consts.DBFindLimit { limit = consts.DBFindLimit } if par.Node.Attr[`offset`] != nil { offset = fmt.Sprintf(` offset %d`, converter.StrToInt(par.Node.Attr[`offset`].(string))) } if par.Node.Attr[`prefix`] != nil { prefix = par.Node.Attr[`prefix`].(string) limit = 1 } state = converter.StrToInt64(getVar(par.Workspace, `ecosystem_id`)) if par.Node.Attr["cutoff"] != nil { for _, v := range strings.Split(par.Node.Attr["cutoff"].(string), ",") { cutoffColumns[v] = true } } sc := par.Workspace.SmartContract tblname := converter.ParseTable(strings.Trim(macro((*par.Pars)[`Name`], par.Workspace.Vars), `"`), state) tblname = strings.ToLower(tblname) inColumns = `` if par.Node.Attr[`order`] != nil { order = macro(par.Node.Attr[`order`].(string), par.Workspace.Vars) if strings.HasPrefix(order, `[`) || strings.HasPrefix(order, `{`) { inColumns, _, err = ParseObject([]rune(order)) if err != nil { return err.Error() } } else { inColumns = order } } order, err = qb.GetOrder(tblname, inColumns, true) if err != nil { return err.Error() } order = ` order by ` + order rows, err := sc.DbTransaction.GetAllColumnTypes(tblname) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting column types from db") return err.Error() } columnTypes := make(map[string]string, len(rows)) for _, row := range rows { columnTypes[row[columnNameKey]] = row[dataTypeKey] } columnNames := make([]string, 0) perm, err = sc.AccessTablePerm(tblname, `read`) if err != nil || sc.AccessColumns(tblname, &columns, false) != nil { log.WithFields(log.Fields{"table": tblname, "columns": columns}).Error("ACCESS DENIED") return `Access denied` } if utils.StringInSlice(columns, `*`) { for _, col := range rows { queryColumns = append(queryColumns, col[columnNameKey]) columnNames = append(columnNames, col[columnNameKey]) } } else { if !utils.StringInSlice(columns, `id`) { columns = append(columns, `id`) } columnNames = make([]string, len(columns)) copy(columnNames, columns) queryColumns = strings.Split(smart.PrepareColumns(columns), ",") } for i, col := range queryColumns { col = strings.Trim(col, `"`) switch columnTypes[col] { case "bytea": extendedColumns[col] = columnTypeBlob queryColumns[i] = dbfindExpressionBlob(col) break case "text", "varchar", "character varying": if cutoffColumns[col] { extendedColumns[col] = columnTypeLongText queryColumns[i] = dbfindExpressionLongText(col) } break } } for i, field := range queryColumns { if !strings.ContainsAny(field, `:.>"`) { queryColumns[i] = `"` + field + `"` } } for i, key := range columnNames { if strings.Contains(key, `->`) { columnNames[i] = strings.Replace(key, `->`, `.`, -1) } columnNames[i] = strings.TrimSpace(columnNames[i]) } if par.Node.Attr[`countvar`] != nil { var count int64 err = sqldb.GetDB(nil).Table(tblname).Where(where).Count(&count).Error if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("selecting count from table in DBFind") } countStr := converter.Int64ToStr(count) par.Node.Attr[`count`] = countStr setVar(par.Workspace, par.Node.Attr[`countvar`].(string), countStr) delete(par.Node.Attr, `countvar`) } if len(where) > 0 { where = ` where ` + where } list, err := sc.DbTransaction.GetAllTransaction(`select `+strings.Join(queryColumns, `, `)+` from "`+tblname+`"`+ where+order+offset, limit) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all from db") return err.Error() } data := make([][]string, 0) types := make([]string, 0) lencol := 0 defcol := 0 for _, item := range list { if lencol == 0 { for _, key := range columnNames { if v, ok := extendedColumns[key]; ok { types = append(types, v) } else { types = append(types, columnTypeText) } } defcol = len(columnNames) if par.Node.Attr[`customs`] != nil { for _, v := range par.Node.Attr[`customs`].([]string) { columnNames = append(columnNames, v) types = append(types, `tags`) } } lencol = len(columnNames) } row := make([]string, lencol) for i, icol := range columnNames { var ival string if i < defcol { ival = item[icol] if ival == `NULL` { ival = `` } switch extendedColumns[icol] { case columnTypeBlob: link := &valueLink{id: item["id"], column: icol, table: tblname, hash: ival, title: ival} ival, err = link.marshal() if err != nil { return err.Error() } item[icol] = link.link() break case columnTypeLongText: var res []string err = json.Unmarshal([]byte(ival), &res) if err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "error": err}).Error("unmarshalling long text params from JSON") return err.Error() } link := &valueLink{id: item["id"], column: icol, table: tblname, hash: res[1], title: res[0]} ival, err = link.marshal() if err != nil { return err.Error() } break } } else { root := node{} for key, val := range item { (*par.Workspace.Vars)[key] = Var{Value: val} } process(par.Node.Attr[`custombody`].([]string)[i-defcol], &root, par.Workspace) for key := range item { delete(*par.Workspace.Vars, key) } out, err := json.Marshal(root.Children) if err == nil { ival = macro(string(out), mapToVar(item)) } else { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marshalling root children to JSON") } } if par.Node.Attr[`prefix`] != nil { setVar(par.Workspace, prefix+`_`+strings.Replace(icol, `.`, `_`, -1), ival) } row[i] = ival } data = append(data, row) } if perm != nil && len(perm[`filter`]) > 0 { result := make([]any, len(data)) for i, item := range data { row := make(map[string]string) for j, col := range columnNames { row[col] = item[j] } result[i] = reflect.ValueOf(row).Interface() } fltResult, err := sc.VM.EvalIf(perm[`filter`], uint32(sc.TxSmart.EcosystemID), map[string]any{ `data`: result, `ecosystem_id`: sc.TxSmart.EcosystemID, `key_id`: sc.TxSmart.KeyID, `sc`: sc, `block_time`: 0, `time`: sc.Timestamp}) if err != nil || !fltResult { return `Access denied` } for i := range data { for j, col := range columnNames { data[i][j] = result[i].(map[string]string)[col] } } } setAllAttr(par) delete(par.Node.Attr, `customs`) delete(par.Node.Attr, `custombody`) delete(par.Node.Attr, `prefix`) par.Node.Attr[`columns`] = &columnNames par.Node.Attr[`types`] = &types par.Node.Attr[`data`] = &data newSource(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func compositeTag(par parFunc) string { setAllAttr(par) if len((*par.Pars)[`Name`]) == 0 { return `` } if par.Owner.Attr[`composites`] == nil { par.Owner.Attr[`composites`] = make([]string, 0) par.Owner.Attr[`compositedata`] = make([]string, 0) } par.Owner.Attr[`composites`] = append(par.Owner.Attr[`composites`].([]string), macro((*par.Pars)[`Name`], par.Workspace.Vars)) par.Owner.Attr[`compositedata`] = append(par.Owner.Attr[`compositedata`].([]string), macro((*par.Pars)[`Data`], par.Workspace.Vars)) return `` } func errredirTag(par parFunc) string { setAllAttr(par) if len((*par.Pars)[`ErrorID`]) == 0 { return `` } if par.Owner.Attr[`errredirect`] == nil { par.Owner.Attr[`errredirect`] = make(map[string]map[string]any) } par.Owner.Attr[`errredirect`].(map[string]map[string]any)[(*par.Pars)[`ErrorID`]] = par.Node.Attr return `` } func popupTag(par parFunc) string { setAllAttr(par) width := converter.StrToInt((*par.Pars)[`Width`]) if width < 1 || width > 100 { return `` } par.Owner.Attr[`popup`] = par.Node.Attr return `` } func customTag(par parFunc) string { setAllAttr(par) if len((*par.Pars)[`Column`]) == 0 || len((*par.Pars)[`Body`]) == 0 { return `` } if par.Owner.Attr[`customs`] == nil { par.Owner.Attr[`customs`] = make([]string, 0) par.Owner.Attr[`custombody`] = make([]string, 0) } par.Owner.Attr[`customs`] = append(par.Owner.Attr[`customs`].([]string), par.Node.Attr[`column`].(string)) par.Owner.Attr[`custombody`] = append(par.Owner.Attr[`custombody`].([]string), (*par.Pars)[`Body`]) return `` } func customTagFull(par parFunc) string { setAllAttr(par) process((*par.Pars)[`Body`], par.Node, par.Workspace) par.Owner.Tail = append(par.Owner.Tail, par.Node) return `` } func tailTag(par parFunc) string { setAllAttr(par) for key, v := range par.Node.Attr { switch v.(type) { case string: par.Owner.Attr[key] = macro(v.(string), par.Workspace.Vars) default: par.Owner.Attr[key] = v } } return `` } func showHideTag(par parFunc, action string) string { setAllAttr(par) cond := par.Node.Attr[`condition`] if v, ok := cond.(string); ok { val := make(map[string]string) items := strings.Split(v, `,`) for _, item := range items { lr := strings.SplitN(strings.TrimSpace(item), `=`, 2) key := strings.TrimSpace(lr[0]) if len(lr) == 2 { val[key] = macro(strings.TrimSpace(lr[1]), par.Workspace.Vars) } else { val[key] = `` } } if _, ok := par.Owner.Attr[action]; ok { par.Owner.Attr[action] = append(par.Owner.Attr[action].([]map[string]string), val) } else { par.Owner.Attr[action] = []map[string]string{val} } } return `` } func showTag(par parFunc) string { return showHideTag(par, `show`) } func hideTag(par parFunc) string { return showHideTag(par, `hide`) } func includeTag(par parFunc) string { if len((*par.Pars)[`Name`]) >= 0 && len(getVar(par.Workspace, `_include`)) < 5 { bi := &sqldb.Snippet{} name := macro((*par.Pars)[`Name`], par.Workspace.Vars) ecosystem, tblname := converter.ParseName(name) prefix := getVar(par.Workspace, `ecosystem_id`) if ecosystem != 0 { prefix = converter.Int64ToStr(ecosystem) name = tblname } bi.SetTablePrefix(prefix) found, err := bi.Get(name) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting snippet by name") return err.Error() } if !found { log.WithFields(log.Fields{"type": consts.NotFound, "name": (*par.Pars)[`Name`]}).Error("include snippet not found") return fmt.Sprintf("Inlcude %s has not been found", (*par.Pars)[`Name`]) } if len(bi.Value) > 0 { root := node{} setVar(par.Workspace, `_include`, getVar(par.Workspace, `_include`)+`1`) process(bi.Value, &root, par.Workspace) include := getVar(par.Workspace, `_include`) setVar(par.Workspace, `_include`, include[:len(include)-1]) for _, item := range root.Children { par.Owner.Children = append(par.Owner.Children, item) } } } return `` } func setvarTag(par parFunc) string { if len((*par.Pars)[`Name`]) > 0 { if strings.ContainsAny((*par.Pars)[`Value`], `({`) { (*par.Pars)[`Value`] = processToText(par, (*par.Pars)[`Value`]) } setVar(par.Workspace, (*par.Pars)[`Name`], macroReplace((*par.Pars)[`Value`], par.Workspace.Vars)) } return `` } func varasisTag(par parFunc) string { key := (*par.Pars)[`Name`] if len(key) > 0 { value := (*par.Pars)[`Value`] if strings.HasPrefix(value, `#`) { if v, ok := (*par.Workspace.Vars)[strings.Trim(value, `#`)]; ok { value = v.Value } } else if v, ok := (*par.Workspace.Vars)[value]; ok { value = v.Value } (*par.Workspace.Vars)[key] = Var{Value: value, AsIs: true} } return `` } func getvarTag(par parFunc) string { if len((*par.Pars)[`Name`]) > 0 { return macro(getVar(par.Workspace, (*par.Pars)[`Name`]), par.Workspace.Vars) } return `` } func tableTag(par parFunc) string { defaultTag(par) defaultTail(par, `table`) if len((*par.Pars)[`Columns`]) > 0 { imap := make([]map[string]string, 0) for _, v := range strings.Split((*par.Pars)[`Columns`], `,`) { v = macro(strings.TrimSpace(v), par.Workspace.Vars) if off := strings.IndexByte(v, '='); off == -1 { imap = append(imap, map[string]string{`Title`: v, `Name`: v}) } else { imap = append(imap, map[string]string{`Title`: strings.TrimSpace(v[:off]), `Name`: strings.TrimSpace(v[off+1:])}) } } if len(imap) > 0 { par.Node.Attr[`columns`] = imap } } return `` } func validateTag(par parFunc) string { setAllAttr(par) par.Owner.Attr[`validate`] = par.Node.Attr return `` } func validateFull(par parFunc) string { setAllAttr(par) par.Owner.Tail = append(par.Owner.Tail, par.Node) return `` } func defaultTail(par parFunc, tag string) { if par.Tails != nil { for _, v := range *par.Tails { name := (*v)[len(*v)-1] curFunc := tails[tag].Tails[string(name)].tplFunc pars := (*v)[:len(*v)-1] callFunc(&curFunc, par.Node, par.Workspace, &pars, nil) } } } func defaultTailTag(par parFunc) string { defaultTag(par) defaultTail(par, par.Node.Tag) return `` } func buttonTag(par parFunc) string { defaultTag(par) defaultTail(par, `button`) defer func() { delete(par.Node.Attr, `composites`) delete(par.Node.Attr, `compositedata`) }() if par.Node.Attr[`composites`] != nil { composites := make([]Composite, 0) for i, name := range par.Node.Attr[`composites`].([]string) { var data any input := par.Node.Attr[`compositedata`].([]string)[i] if len(input) > 0 { if err := json.Unmarshal([]byte(input), &data); err != nil { log.WithFields(log.Fields{"type": consts.JSONUnmarshallError, "source": input}).Error("on button tag unmarshaling content") return err.Error() } } composites = append(composites, Composite{Name: name, Data: data}) } par.Node.Attr[`composite`] = &composites } return `` } func ifTag(par parFunc) string { cond := ifValue((*par.Pars)[`Condition`], par.Workspace) if cond { process((*par.Pars)[`Body`], par.Node, par.Workspace) for _, item := range par.Node.Children { par.Owner.Children = append(par.Owner.Children, item) } } if !cond && par.Tails != nil { for _, v := range *par.Tails { name := (*v)[len(*v)-1] curFunc := tails[`if`].Tails[string(name)].tplFunc pars := (*v)[:len(*v)-1] callFunc(&curFunc, par.Owner, par.Workspace, &pars, nil) if getVar(par.Workspace, `_cond`) == `1` { setVar(par.Workspace, `_cond`, `0`) break } } } return `` } func ifFull(par parFunc) string { setAttr(par, `Condition`) par.Owner.Children = append(par.Owner.Children, par.Node) if par.Tails != nil { for _, v := range *par.Tails { name := (*v)[len(*v)-1] curFunc := tails[`if`].Tails[string(name)].tplFunc pars := (*v)[:len(*v)-1] callFunc(&curFunc, par.Node, par.Workspace, &pars, nil) } } return `` } func elseifTag(par parFunc) string { cond := ifValue((*par.Pars)[`Condition`], par.Workspace) if cond { process((*par.Pars)[`Body`], par.Node, par.Workspace) for _, item := range par.Node.Children { par.Owner.Children = append(par.Owner.Children, item) } setVar(par.Workspace, `_cond`, `1`) } return `` } func elseifFull(par parFunc) string { setAttr(par, `Condition`) par.Owner.Tail = append(par.Owner.Tail, par.Node) return `` } func elseTag(par parFunc) string { for _, item := range par.Node.Children { par.Owner.Children = append(par.Owner.Children, item) } return `` } func elseFull(par parFunc) string { par.Owner.Tail = append(par.Owner.Tail, par.Node) return `` } func dateTimeTag(par parFunc) string { datetime := par.ParamWithMacros("DateTime") if len(datetime) == 0 || datetime[0] < '0' || datetime[0] > '9' { return `` } value := datetime defTime := `1970-01-01T00:00:00` lenTime := len(datetime) if lenTime < len(defTime) { datetime += defTime[lenTime:] } itime, err := time.Parse(`2006-01-02T15:04:05`, strings.Replace(datetime[:19], ` `, `T`, -1)) if err != nil { unix := converter.StrToInt64(value) if unix > 0 { itime = time.Unix(unix, 0) } else { return err.Error() } } format := par.ParamWithMacros("Format") if len(format) == 0 { format, _ = language.LangText(nil, `timeformat`, converter.StrToInt(getVar(par.Workspace, `ecosystem_id`)), getVar(par.Workspace, `lang`)) if format == `timeformat` { format = `2006-01-02 15:04:05` } } else { format = macro(format, par.Workspace.Vars) } format = strings.Replace(format, `YYYY`, `2006`, -1) format = strings.Replace(format, `YY`, `06`, -1) format = strings.Replace(format, `MM`, `01`, -1) format = strings.Replace(format, `DD`, `02`, -1) format = strings.Replace(format, `HH`, `15`, -1) format = strings.Replace(format, `MI`, `04`, -1) format = strings.Replace(format, `SS`, `05`, -1) locationName := par.ParamWithMacros("Location") if len(locationName) > 0 { loc, err := time.LoadLocation(locationName) if err != nil { return err.Error() } itime = itime.In(loc) } return itime.Format(format) } func cmpTimeTag(par parFunc) string { prepare := func(val string) string { val = strings.Replace(macro(val, par.Workspace.Vars), `T`, ` `, -1) if len(val) > 19 { val = val[:19] } return val } left := prepare((*par.Pars)[`Time1`]) right := prepare((*par.Pars)[`Time2`]) if left == right { return `0` } if left < right { return `-1` } return `1` } type byFirst [][]string func (s byFirst) Len() int { return len(s) } func (s byFirst) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byFirst) Less(i, j int) bool { return strings.Compare(s[i][0], s[j][0]) < 0 } func jsontosourceTag(par parFunc) string { setAllAttr(par) var prefix string if par.Node.Attr[`prefix`] != nil { prefix = par.Node.Attr[`prefix`].(string) + `_` } data := make([][]string, 0, 16) cols := []string{prefix + `key`, prefix + `value`} types := []string{`text`, `text`} var out map[string]any dataVal := macro((*par.Pars)[`Data`], par.Workspace.Vars) if len(dataVal) > 0 { json.Unmarshal([]byte(macro((*par.Pars)[`Data`], par.Workspace.Vars)), &out) } for key, item := range out { if item == nil { item = `` } var value string switch v := item.(type) { case map[string]any: var keys, values []string for k := range v { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { values = append(values, fmt.Sprintf(`%q:%q`, k, v[k])) } value = `{` + strings.Join(values, ",\r\n") + `}` default: value = fmt.Sprint(item) } data = append(data, []string{key, value}) } sort.Sort(byFirst(data)) setAllAttr(par) par.Node.Attr[`columns`] = &cols par.Node.Attr[`types`] = &types par.Node.Attr[`data`] = &data newSource(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func arraytosourceTag(par parFunc) string { setAllAttr(par) var prefix string if par.Node.Attr[`prefix`] != nil { prefix = par.Node.Attr[`prefix`].(string) + `_` } data := make([][]string, 0, 16) cols := []string{prefix + `key`, prefix + `value`} types := []string{`text`, `text`} for key, item := range splitArray([]rune(macro((*par.Pars)[`Data`], par.Workspace.Vars))) { data = append(data, []string{fmt.Sprint(key), item}) } setAllAttr(par) par.Node.Attr[`columns`] = &cols par.Node.Attr[`types`] = &types par.Node.Attr[`data`] = &data newSource(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func chartTag(par parFunc) string { defaultTag(par) defaultTail(par, "chart") if len((*par.Pars)["Colors"]) > 0 { colors := strings.Split(macro((*par.Pars)["Colors"], par.Workspace.Vars), ",") for i, v := range colors { colors[i] = strings.TrimSpace(v) } par.Node.Attr["colors"] = colors } return "" } func rangeTag(par parFunc) string { setAllAttr(par) step := int64(1) data := make([][]string, 0, 32) from := converter.StrToInt64(macro((*par.Pars)["From"], par.Workspace.Vars)) to := converter.StrToInt64(macro((*par.Pars)["To"], par.Workspace.Vars)) if len((*par.Pars)["Step"]) > 0 { step = converter.StrToInt64(macro((*par.Pars)["Step"], par.Workspace.Vars)) } if step > 0 && from < to { for i := from; i < to; i += step { data = append(data, []string{converter.Int64ToStr(i)}) } } else if step < 0 && from > to { for i := from; i > to; i += step { data = append(data, []string{converter.Int64ToStr(i)}) } } delete(par.Node.Attr, `from`) delete(par.Node.Attr, `to`) delete(par.Node.Attr, `step`) par.Node.Attr[`columns`] = &[]string{"id"} par.Node.Attr[`data`] = &data newSource(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } func imageTag(par parFunc) string { (*par.Pars)["Src"] = parseArg((*par.Pars)["Src"], par.Workspace) defaultTag(par) defaultTail(par, par.Node.Tag) return `` } func binaryTag(par parFunc) string { var ecosystemID string defaultTail(par, `binary`) if par.Node.Attr[`ecosystem`] != nil { ecosystemID = par.Node.Attr[`ecosystem`].(string) } else { ecosystemID = getVar(par.Workspace, `ecosystem_id`) } binary := &sqldb.Binary{} binary.SetTablePrefix(ecosystemID) var ( ok bool err error ) if par.Node.Attr["id"] != nil { ok, err = binary.GetByID(converter.StrToInt64(macro(par.Node.Attr["id"].(string), par.Workspace.Vars))) } else { ok, err = binary.Get( converter.StrToInt64(par.ParamWithMacros("AppID")), par.ParamWithMacros("Account"), par.ParamWithMacros("Name"), ) } if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting record from db") return err.Error() } if ok { return binary.Link() } return "" } func columntypeTag(par parFunc) string { if len((*par.Pars)["Table"]) > 0 && len((*par.Pars)["Column"]) > 0 { tableName := macro((*par.Pars)[`Table`], par.Workspace.Vars) columnName := macro((*par.Pars)[`Column`], par.Workspace.Vars) tblname := qb.GetTableName(par.Workspace.SmartContract.TxSmart.EcosystemID, tableName) colType, err := par.Workspace.SmartContract.DbTransaction.GetColumnType(tblname, columnName) if err == nil { return colType } return err.Error() } return `` } func getHistoryTag(par parFunc) string { setAllAttr(par) var rollID int64 if len((*par.Pars)["RollbackId"]) > 0 { rollID = converter.StrToInt64(macro((*par.Pars)[`RollbackId`], par.Workspace.Vars)) } if len((*par.Pars)["Name"]) == 0 { return `` } table := macro((*par.Pars)["Name"], par.Workspace.Vars) list, err := smart.GetHistoryRaw(nil, converter.StrToInt64(getVar(par.Workspace, `ecosystem_id`)), table, converter.StrToInt64(macro((*par.Pars)[`Id`], par.Workspace.Vars)), rollID) if err != nil { return err.Error() } colsList, err := par.Workspace.SmartContract.DbTransaction.GetAllColumnTypes(getVar(par.Workspace, `ecosystem_id`) + "_" + table) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting column types from db") return err.Error() } cols := make([]string, 0, len(colsList)) typesCol := make([]string, 0, len(colsList)) for _, v := range colsList { cols = append(cols, v[columnNameKey]) typesCol = append(typesCol, `text`) } data := make([][]string, 0) if len(list) > 0 { for i := range list { item := list[i].(*types.Map) items := make([]string, len(cols)) for ind, key := range cols { var val string if v, found := item.Get(key); found { val = v.(string) } if val == `NULL` { val = `` } items[ind] = val } data = append(data, items) } } par.Node.Attr[`columns`] = &cols par.Node.Attr[`types`] = &typesCol par.Node.Attr[`data`] = &data newSource(par) par.Owner.Children = append(par.Owner.Children, par.Node) return `` } ================================================ FILE: packages/template/template.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package template import ( "encoding/json" "regexp" "strconv" "strings" "unicode/utf8" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/language" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) const ( tagText = `text` maxDeep = 16 ) type node struct { Tag string `json:"tag"` Attr map[string]any `json:"attr,omitempty"` Text string `json:"text,omitempty"` Children []*node `json:"children,omitempty"` Tail []*node `json:"tail,omitempty"` } // Source describes dbfind or data source type Source struct { Columns *[]string Data *[][]string } // Var stores value and additional parameter of variable type Var struct { Value string AsIs bool } // Workspace represents a workspace of executable template type Workspace struct { Sources *map[string]Source Vars *map[string]Var SmartContract *smart.SmartContract Timeout *bool } // SetSource sets source to workspace func (w *Workspace) SetSource(name string, source *Source) { if w.Sources == nil { sources := make(map[string]Source) w.Sources = &sources } (*w.Sources)[name] = *source } type parFunc struct { Owner *node Node *node Workspace *Workspace Pars *map[string]string RawPars *map[string]string Tails *[]*[][]rune } func (p *parFunc) Param(key string) string { return (*p.Pars)[key] } func (p *parFunc) ParamWithMacros(key string) string { v := p.Param(key) return macro(v, p.Workspace.Vars) } type nodeFunc func(par parFunc) string type tplFunc struct { Func nodeFunc // process function Full nodeFunc // full process function Tag string // HTML tag Params string // names of parameters } type tailInfo struct { tplFunc Last bool } type forTails struct { Tails map[string]tailInfo } func newSource(par parFunc) { if par.Node.Attr[`source`] == nil { return } par.Workspace.SetSource(par.Node.Attr[`source`].(string), &Source{ Columns: par.Node.Attr[`columns`].(*[]string), Data: par.Node.Attr[`data`].(*[][]string), }) } func setAttr(par parFunc, name string) { if len((*par.Pars)[name]) > 0 { par.Node.Attr[strings.ToLower(name)] = (*par.Pars)[name] } } func setAllAttr(par parFunc) { for key, v := range *par.Pars { if key == `Params` || key == `PageParams` { imap := make(map[string]any) re := regexp.MustCompile(`(?is)(.*)\((.*)\)`) parList := make([]string, 0, 10) curPar := make([]rune, 0, 256) stack := make([]rune, 0, 256) for _, ch := range v { switch ch { case '"': if len(stack) > 0 && stack[len(stack)-1] == '"' { stack = stack[:len(stack)-1] } else { stack = append(stack, '"') } case '(': stack = append(stack, ')') case '{': stack = append(stack, '}') case '[': stack = append(stack, ']') case ')', '}', ']': if len(stack) > 0 && stack[len(stack)-1] == ch { stack = stack[:len(stack)-1] } case ',': if len(stack) == 0 { parList = append(parList, string(curPar)) curPar = curPar[:0] continue } } curPar = append(curPar, ch) } if len(curPar) > 0 { parList = append(parList, string(curPar)) } for _, parval := range parList { parval = strings.TrimSpace(parval) if len(parval) > 0 { if off := strings.IndexByte(parval, '='); off == -1 { imap[parval] = map[string]any{ `type`: `text`, `text`: parval} } else { val := strings.TrimSpace(parval[off+1:]) if ret := re.FindStringSubmatch(val); len(ret) == 3 { plist := strings.Split(ret[2], `,`) for i, ilist := range plist { plist[i] = strings.TrimSpace(ilist) } imap[strings.TrimSpace(parval[:off])] = map[string]any{ `type`: ret[1], `params`: plist} } else { imap[strings.TrimSpace(parval[:off])] = map[string]any{ `type`: `text`, `text`: val} } } } } if len(imap) > 0 { par.Node.Attr[strings.ToLower(key)] = imap } } else if key != `Body` && (key != `Data` || getVar(par.Workspace, `_full`) == `1`) && len(v) > 0 { par.Node.Attr[strings.ToLower(key)] = v } } for key := range *par.Pars { if len(key) == 0 || key[0] != '@' { continue } key = strings.ToLower(key[1:]) if par.Node.Attr[key] == nil { continue } par.Node.Attr[key] = processToText(par, par.Node.Attr[key].(string)) } } func processToText(par parFunc, input string) (out string) { root := node{} process(input, &root, par.Workspace) for _, item := range root.Children { if item.Tag == `text` { out += item.Text } } return } func ifValue(val string, workspace *Workspace) bool { var sep string val = parseArg(val, workspace) if strings.Index(val, `;base64`) < 0 { for _, item := range []string{`==`, `!=`, `<=`, `>=`, `<`, `>`} { if strings.Index(val, item) >= 0 { sep = item break } } } cond := []string{val} if len(sep) > 0 { cond = strings.SplitN(val, sep, 2) cond[0], cond[1] = macro(strings.Trim(strings.TrimSpace(cond[0]), `"`), workspace.Vars), macro(strings.Trim(strings.TrimSpace(cond[1]), `"`), workspace.Vars) } else { val = macro(val, workspace.Vars) } switch sep { case ``: return len(val) > 0 && val != `0` && val != `false` case `==`: return len(cond) == 2 && strings.TrimSpace(cond[0]) == strings.TrimSpace(cond[1]) case `!=`: return len(cond) == 2 && strings.TrimSpace(cond[0]) != strings.TrimSpace(cond[1]) case `>`, `<`, `<=`, `>=`: ret0, _ := decimal.NewFromString(strings.TrimSpace(cond[0])) ret1, _ := decimal.NewFromString(strings.TrimSpace(cond[1])) if len(cond) == 2 { var bin bool if sep == `>` || sep == `<=` { bin = ret0.Cmp(ret1) > 0 } else { bin = ret0.Cmp(ret1) < 0 } if sep == `<=` || sep == `>=` { bin = !bin } return bin } } return false } func replace(input string, level *[]string, vars *map[string]Var) string { if len(input) == 0 { return input } result := make([]rune, 0, utf8.RuneCountInString(input)) isName := false name := make([]rune, 0, 128) syschar := '#' clearname := func() { result = append(append(result, syschar), name...) isName = false name = name[:0] } for _, r := range input { if r != syschar { if isName { name = append(name, r) if len(name) > 64 || r <= ' ' { clearname() } } else { result = append(result, r) } continue } if isName { if varValue, ok := (*vars)[string(name)]; ok { value := varValue.Value var loop bool if len(*level) < maxDeep { for _, item := range *level { if item == string(name) { loop = true break } } } else { loop = true } if !loop { if !varValue.AsIs { *level = append(*level, string(name)) value = replace(value, level, vars) *level = (*level)[:len(*level)-1] } result = append(result, []rune(value)...) } else { result = append(append(result, syschar), append(name, syschar)...) } } isName = false name = name[:0] } else { isName = true } } if isName { result = append(append(result, syschar), name...) } return string(result) } func macro(input string, vars *map[string]Var) string { if (*vars)[`_full`].Value == `1` || strings.IndexByte(input, '#') == -1 { return input } return macroReplace(input, vars) } func macroReplace(input string, vars *map[string]Var) string { level := make([]string, 0, maxDeep) return replace(input, &level, vars) } func appendText(owner *node, text string) { if len(strings.TrimSpace(text)) == 0 { return } if len(text) > 0 { owner.Children = append(owner.Children, &node{Tag: tagText, Text: text}) } } func callFunc(curFunc *tplFunc, owner *node, workspace *Workspace, params *[][]rune, tailpars *[]*[][]rune) { var ( out string curNode node ) pars := make(map[string]string) parFunc := parFunc{ Workspace: workspace, } if *workspace.Timeout { return } trim := func(input string, quotes bool) string { result := strings.Trim(input, "\t\r\n ") if quotes && len(result) > 0 { for _, ch := range "\"`" { if rune(result[0]) == ch { result = strings.Trim(result, string([]rune{ch})) break } } } return result } if curFunc.Params == `*` { for i, v := range *params { val := strings.TrimSpace(string(v)) off := strings.IndexByte(val, ':') if off != -1 { pars[val[:off]] = macro(trim(val[off+1:], true), workspace.Vars) } else { pars[strconv.Itoa(i)] = val } } } else { for i, v := range strings.Split(curFunc.Params, `,`) { if i < len(*params) { val := strings.TrimSpace(string((*params)[i])) off := strings.IndexByte(val, ':') if off != -1 && strings.Contains(curFunc.Params, `#`+val[:off]) { pars[`#`+val[:off]] = trim(val[off+1:], val[:off] != `Data`) } else if off != -1 && strings.Contains(curFunc.Params, val[:off]) { pars[val[:off]] = trim(val[off+1:], val[:off] != `Data`) } else { pars[v] = val } } else if _, ok := pars[v]; !ok { pars[v] = `` } } } state := int(converter.StrToInt64(getVar(workspace, `ecosystem_id`))) if getVar(workspace, `_full`) != `1` { for i, v := range pars { pars[i] = language.LangMacro(v, state, getVar(workspace, `lang`)) if pars[i] != v { if parFunc.RawPars == nil { rawpars := make(map[string]string) parFunc.RawPars = &rawpars } (*parFunc.RawPars)[i] = v } } } if len(curFunc.Tag) > 0 { curNode.Tag = curFunc.Tag curNode.Attr = make(map[string]any) if len(pars[`Body`]) > 0 && curFunc.Tag != `custom` { if (curFunc.Tag != `if` && curFunc.Tag != `elseif`) || getVar(workspace, `_full`) == `1` { process(pars[`Body`], &curNode, workspace) } } parFunc.Owner = owner parFunc.Node = &curNode parFunc.Tails = tailpars } if *workspace.Timeout { return } parFunc.Pars = &pars if getVar(workspace, `_full`) == `1` { out = curFunc.Full(parFunc) } else { out = curFunc.Func(parFunc) } for key, v := range parFunc.Node.Attr { switch attr := v.(type) { case string: if !strings.HasPrefix(key, `#`) { parFunc.Node.Attr[key] = macro(attr, workspace.Vars) } case map[string]any: for parkey, parval := range attr { switch parmap := parval.(type) { case map[string]any: for textkey, textval := range parmap { var result any switch val := textval.(type) { case string: result = macro(val, workspace.Vars) case []string: for i, ival := range val { val[i] = macro(ival, workspace.Vars) } result = val } if result != nil { parFunc.Node.Attr[key].(map[string]any)[parkey].(map[string]any)[textkey] = result } } } } } } for key, v := range parFunc.Node.Attr { switch attr := v.(type) { case string: if strings.HasPrefix(key, `#`) { parFunc.Node.Attr[key[1:]] = attr delete(parFunc.Node.Attr, key) } } } parFunc.Node.Text = macro(parFunc.Node.Text, workspace.Vars) for inode, node := range parFunc.Node.Children { parFunc.Node.Children[inode].Text = macro(node.Text, workspace.Vars) } if len(out) > 0 { if len(owner.Children) > 0 && owner.Children[len(owner.Children)-1].Tag == tagText { owner.Children[len(owner.Children)-1].Text += out } else { appendText(owner, out) } } } func getFunc(input string, curFunc tplFunc) (*[][]rune, int, *[]*[][]rune) { var ( curp, skip, off, mode, lenParams int quote bool pair, ch rune tailpar *[]*[][]rune ) var params [][]rune sizeParam := 32 + len(input)/2 params = append(params, make([]rune, 0, sizeParam)) if curFunc.Params == `*` { lenParams = 0xff } else { lenParams = len(strings.Split(curFunc.Params, `,`)) } objLevel := 0 objMode := 0 level := 1 if input[0] == '{' { mode = 1 } skip = 1 main: for off, ch = range input { if skip > 0 { skip-- continue } if objLevel > 0 { params[curp] = append(params[curp], ch) switch ch { case modes[objMode][0]: objLevel++ case modes[objMode][1]: objLevel-- } continue } if pair > 0 { if ch != pair { params[curp] = append(params[curp], ch) } else { if off+1 == len(input) || rune(input[off+1]) != pair { pair = 0 if quote { params[curp] = append(params[curp], ch) quote = false } } else { params[curp] = append(params[curp], ch) skip = 1 } } continue } if len(params[curp]) == 0 && mode == 0 && ch != modes[mode][1] && ch != ',' { if ch >= '!' { if ch == '"' || ch == '`' { pair = ch } else if ch == '[' || ch == '{' { objMode = 2 if ch == '{' { objMode = 1 } objLevel = 1 params[curp] = append(params[curp], ch) } else { if ch == modes[mode][0] { level++ } params[curp] = append(params[curp], ch) } } continue } switch ch { case '"', '`': if mode == 0 { pair = ch quote = true } case ',': if mode == 0 && level == 1 && len(params) < lenParams { params = append(params, make([]rune, 0, sizeParam)) curp++ continue } case modes[mode][0]: level++ case modes[mode][1]: if level > 0 { level-- } if level == 0 { if mode == 0 && (strings.Contains(curFunc.Params, `Body`) || strings.Contains(curFunc.Params, `Data`)) { var isBody bool next := off + 1 for next < len(input) { if rune(input[next]) == modes[1][0] { isBody = true break } if rune(input[next]) == ' ' || rune(input[next]) == '\t' { next++ continue } break } if isBody { mode = 1 for _, keyp := range []string{`Body`, `Data`} { if strings.Contains(curFunc.Params, keyp) { irune := make([]rune, 0, sizeParam) s := keyp + `:` params = append(params, append(irune, []rune(s)...)) break } } curp++ skip = next - off level = 1 continue } } for tail, ok := tails[curFunc.Tag]; ok && off+2 < len(input) && input[off+1] == '.'; { var found bool for key, tailFunc := range tail.Tails { next := off + 2 if next < len(input) && strings.HasPrefix(input[next:], key) { var isTail bool next += len(key) for next < len(input) { if rune(input[next]) == '(' || rune(input[next]) == '{' { isTail = true break } if rune(input[next]) == ' ' || rune(input[next]) == '\t' { next++ continue } break } if isTail { parTail, shift, _ := getFunc(input[next:], tailFunc.tplFunc) off = next for ; shift > 0; shift-- { _, size := utf8.DecodeRuneInString(input[off:]) off += size } if tailpar == nil { fortail := make([]*[][]rune, 0) tailpar = &fortail } *parTail = append(*parTail, []rune(key)) *tailpar = append(*tailpar, parTail) found = true if tailFunc.Last { break main } break } } } if !found { break } } break main } } params[curp] = append(params[curp], ch) continue } return ¶ms, utf8.RuneCountInString(input[:off]), tailpar } func process(input string, owner *node, workspace *Workspace) { var ( nameOff, shift int curFunc tplFunc isFunc bool params *[][]rune tailpars *[]*[][]rune ) inrune := []rune(input) name := make([]rune, 0, 128) for off, ch := range inrune { if shift > 0 { shift-- continue } if ch == '(' { if curFunc, isFunc = funcs[string(name[nameOff:])]; isFunc { if *workspace.Timeout { return } appendText(owner, macro(string(name[:nameOff]), workspace.Vars)) name = name[:0] nameOff = 0 params, shift, tailpars = getFunc(string(inrune[off:]), curFunc) callFunc(&curFunc, owner, workspace, params, tailpars) for off+shift+3 < len([]rune(input)) && string(inrune[off+shift+1:off+shift+3]) == `.(` { var next int params, next, tailpars = getFunc(string(inrune[off+shift+2:]), curFunc) callFunc(&curFunc, owner, workspace, params, tailpars) shift += next + 2 } continue } } if (ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') { nameOff = len(name) + 1 } name = append(name, ch) } appendText(owner, string(name)) } func parseArg(arg string, workspace *Workspace) (val string) { if strings.IndexByte(arg, '(') == -1 { return arg } var owner node process(arg, &owner, workspace) for _, inode := range owner.Children { if inode.Tag == tagText { val += inode.Text } } return } // Template2JSON converts templates to JSON data func Template2JSON(input string, timeout *bool, vars *map[string]string) []byte { root := node{} isclb := (*vars)[`clb`] == `true` || (*vars)[`clb`] == `1` keyID := converter.StrToInt64((*vars)["key_id"]) accountID := (*vars)["account_id"] sc := smart.SmartContract{ CLB: isclb, VM: script.GetVM(), TxSmart: &types.SmartTransaction{ Header: &types.Header{ EcosystemID: converter.StrToInt64((*vars)[`ecosystem_id`]), KeyID: keyID, NetworkID: conf.Config.LocalConf.NetworkID, }, }, Key: &sqldb.Key{ ID: keyID, AccountID: accountID, }, DbTransaction: sqldb.NewDbTransaction(sqldb.DBConn), } toVars := mapToVar(*vars) process(input, &root, &Workspace{Vars: toVars, Timeout: timeout, SmartContract: &sc}) if root.Children == nil || *timeout { return []byte(`[]`) } for i, v := range root.Children { if v.Tag == `text` { root.Children[i].Text = macro(v.Text, toVars) } } out, err := json.Marshal(root.Children) if err != nil { log.WithFields(log.Fields{"type": consts.JSONMarshallError, "error": err}).Error("marshalling template data to json") return []byte(err.Error()) } return out } func splitArray(in []rune) []string { var quote, trim rune var off int ret := make([]string, 0, 32) brace := make([]rune, 0, 32) if len(in) == 0 { return ret } if in[0] == '[' && in[len(in)-1] == ']' { in = in[1 : len(in)-1] } newPar := func(cur int) { par := strings.TrimSpace(string(in[off:cur])) if rune(par[len(par)-1]) == trim { par = par[:len(par)-1] } ret = append(ret, par) } for i, ch := range in { if ch == '[' { brace = append(brace, ']') } if ch == '{' { brace = append(brace, '}') } if len(brace) > 0 { if ch == brace[len(brace)-1] { brace = brace[:len(brace)-1] } continue } if ch == quote { quote = 0 continue } if quote != 0 { continue } if ch == ' ' && off == i { off++ continue } if ch == '"' || ch == '`' || ch == '\'' { quote = ch if off == i { trim = ch off++ } } if ch == ',' { newPar(i) off = i + 1 trim = 0 } } if off < len(in) { newPar(len(in)) } return ret } func setVar(par *Workspace, key, value string) { (*par.Vars)[key] = Var{Value: value} } func getVar(par *Workspace, key string) string { return (*par.Vars)[key].Value } func mapToVar(in map[string]string) *map[string]Var { ret := make(map[string]Var) for key, v := range in { ret[key] = Var{Value: v} } return &ret } ================================================ FILE: packages/transaction/ban.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "sync" "time" "github.com/IBAX-io/go-ibax/packages/conf" ) type banKey struct { Time time.Time // banned till Bad []time.Time // time of bad tx } var ( banList = make(map[int64]banKey) mutex = &sync.RWMutex{} ) // IsKeyBanned returns true if the key has been banned func IsKeyBanned(keyID int64) bool { mutex.RLock() if ban, ok := banList[keyID]; ok { mutex.RUnlock() now := time.Now() if now.Before(ban.Time) { return true } for i := 0; i < conf.Config.BanKey.BadTx; i++ { if ban.Bad[i].Add(time.Duration(conf.Config.BanKey.BadTime) * time.Minute).After(now) { return false } } // Delete if time of all bad tx is old mutex.Lock() delete(banList, keyID) mutex.Unlock() } else { mutex.RUnlock() } return false } // BannedTill returns the time that the user has been banned till func BannedTill(keyID int64) string { mutex.RLock() defer mutex.RUnlock() if ban, ok := banList[keyID]; ok { return ban.Time.Format(`2006-01-02 15:04:05`) } return `` } // BadTxForBan adds info about bad tx of the key func BadTxForBan(keyID int64) { var ( ban banKey ok bool ) mutex.Lock() defer mutex.Unlock() now := time.Now() if ban, ok = banList[keyID]; ok { var bMin, count int for i := 0; i < conf.Config.BanKey.BadTx; i++ { if ban.Bad[i].Add(time.Duration(conf.Config.BanKey.BadTime) * time.Minute).After(now) { count++ } if i > bMin && ban.Bad[i].Before(ban.Bad[bMin]) { bMin = i } } ban.Bad[bMin] = now if count >= conf.Config.BanKey.BadTx-1 { ban.Time = now.Add(time.Duration(conf.Config.BanKey.BanTime) * time.Minute) } } else { ban = banKey{Bad: make([]time.Time, conf.Config.BanKey.BadTx)} ban.Bad[0] = time.Now() } banList[keyID] = ban } ================================================ FILE: packages/transaction/builder.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) func newTransaction(smartTx types.SmartTransaction, privateKey []byte, internal bool) (data, hash []byte, err error) { stp := &SmartTransactionParser{ SmartContract: &smart.SmartContract{TxSmart: new(types.SmartTransaction)}, } data, err = stp.BinMarshalWithPrivate(&smartTx, privateKey, internal) if err != nil { log.WithFields(log.Fields{"type": consts.MarshallingError, "error": err}).Error("marshalling smart contract to msgpack") return } hash = stp.Hash return } func NewInternalTransaction(smartTx types.SmartTransaction, privateKey []byte) (data, hash []byte, err error) { return newTransaction(smartTx, privateKey, true) } func NewTransactionInProc(smartTx types.SmartTransaction, privateKey []byte) (data, hash []byte, err error) { return newTransaction(smartTx, privateKey, false) } // CreateTransaction creates transaction func CreateTransaction(data, hash []byte, keyID, tnow int64) error { tx := &sqldb.Transaction{ Hash: hash, Data: data[:], Type: types.SmartContractTxType, KeyID: keyID, HighRate: sqldb.TransactionRateOnBlock, Time: tnow, } if err := tx.Create(nil); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating new transaction") return err } return nil } // CreateDelayTransactionHighRate creates transaction func CreateDelayTransactionHighRate(data, hash []byte, keyID, highRate int64) *sqldb.Transaction { t := int8(highRate) tx := &sqldb.Transaction{ Hash: hash, Data: data[:], Type: getTxTxType(t), KeyID: keyID, HighRate: sqldb.GetTxRateByTxType(t), } return tx } func getTxTxType(rate int8) int8 { ret := int8(1) switch rate { case types.SmartContractTxType, types.StopNetworkTxType: ret = rate default: } return ret } ================================================ FILE: packages/transaction/cache.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "fmt" "sync" ) type transactionCache struct { mutex sync.RWMutex cache map[string]*Transaction } var txCache = &transactionCache{cache: make(map[string]*Transaction)} // CleanCache cleans cache of transaction parsers func CleanCache() { txCache.Clean() } func (tc *transactionCache) Get(hash string) (t *Transaction, ok bool) { tc.mutex.RLock() defer tc.mutex.RUnlock() t, ok = tc.cache[hash] return } func (tc *transactionCache) Set(t *Transaction) { tc.mutex.Lock() defer tc.mutex.Unlock() tc.cache[fmt.Sprintf("%x", t.Hash())] = t } func (tc *transactionCache) Clean() { tc.mutex.Lock() defer tc.mutex.Unlock() tc.cache = make(map[string]*Transaction) } ================================================ FILE: packages/transaction/contract.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "bytes" "fmt" "time" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) const ( errUnknownContract = `Cannot find %s contract` ) func CreateContract(contractName string, keyID int64, params map[string]any, privateKey []byte) error { ecosysID, _ := converter.ParseName(contractName) if ecosysID == 0 { ecosysID = 1 } contract := smart.GetContract(contractName, uint32(ecosysID)) if contract == nil { return fmt.Errorf(errUnknownContract, contractName) } sc := types.SmartTransaction{ Header: &types.Header{ ID: int(contract.Info().ID), EcosystemID: ecosysID, KeyID: keyID, Time: time.Now().Unix(), NetworkID: conf.Config.LocalConf.NetworkID, }, Params: params, } txData, _, err := NewTransactionInProc(sc, privateKey) if err == nil { rtx := &Transaction{} if err = rtx.Unmarshall(bytes.NewBuffer(txData), true); err == nil { //err = sqldb.SendTx(rtx, sc.KeyId) err = sqldb.SendTxBatches([]*sqldb.RawTx{rtx.SetRawTx()}) } } return err } ================================================ FILE: packages/transaction/db.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "fmt" "gorm.io/gorm" "github.com/pkg/errors" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/utils" log "github.com/sirupsen/logrus" ) var ( ErrDuplicatedTx = errors.New("Duplicated transaction") ErrNotComeTime = errors.New("Transaction processing time has not come") ErrExpiredTime = errors.New("Transaction processing time is expired") ErrEarlyTime = utils.WithBan(errors.New("Early transaction time")) ErrEmptyKey = utils.WithBan(errors.New("KeyID is empty")) ) // InsertInLogTx is inserting tx in log //func InsertInLogTx(t *Transaction, blockID int64) error { // ltx := &sqldb.LogTransaction{Hash: t.TxHash, Block: blockID} // if err := ltx.Create(t.DbTransaction); err != nil { // log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("insert logged transaction") // return utils.ErrInfo(err) // } // return nil //} // CheckLogTx checks if this transaction exists // And it would have successfully passed a frontal test func CheckLogTx(txHash []byte, logger *log.Entry) error { logTx := &sqldb.LogTransaction{} found, err := logTx.GetByHash(nil, txHash) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting log transaction by hash") return err } if found { logger.WithFields(log.Fields{"tx_hash": txHash, "type": consts.DuplicateObject}).Warning("double tx in log transactions") return ErrDuplicatedTx } return nil } // DeleteQueueTx deletes a transaction from the queue func DeleteQueueTx(dbTx *sqldb.DbTransaction, hash []byte) error { delQueueTx := &sqldb.QueueTx{Hash: hash} err := delQueueTx.DeleteTx(dbTx) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("deleting transaction from queue") return err } // Because we process transactions with verified=0 in queue_parser_tx, after processing we need to delete them err = sqldb.DeleteTransactionByHash(dbTx, hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("deleting transaction if unused") return err } //err = sqldb.DeleteTransactionsAttemptsByHash(dbTx, hash) //if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("deleting DeleteTransactionsAttemptsByHash") // return err //} return nil } func MarkTransactionBad(hash []byte, errText string) error { if hash == nil { return nil } if len(errText) > 255 { errText = errText[:255] + "..." } log.WithFields(log.Fields{"type": consts.BadTxError, "tx_hash": hash, "error": errText}).Debug("tx marked as bad") return sqldb.NewDbTransaction(sqldb.DBConn).Connection().Transaction(func(tx *gorm.DB) error { // looks like there is no hash in queue_tx at this moment qtx := &sqldb.QueueTx{} _, err := qtx.GetByHash(sqldb.NewDbTransaction(tx), hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("getting tx by hash from queue") return err } if qtx.FromGate == 0 { m := &sqldb.TransactionStatus{} err = m.SetError(sqldb.NewDbTransaction(tx), errText, hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("setting transaction status error") return err } } err = DeleteQueueTx(sqldb.NewDbTransaction(tx), hash) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Debug("deleting transaction from queue") return err } return nil }) } // ProcessQueueTransaction writes transactions into the queue //func ProcessQueueTransaction(dbTx *sqldb.DbTransaction, hash, binaryTx []byte, myTx bool) error { // t, err := UnmarshallTransaction(bytes.NewBuffer(binaryTx), true) // if err != nil { // return err // } // // if err = t.Check(time.Now().Unix(), true); err != nil { // if err != ErrEarlyTime { // return err // } // return nil // } // // if t.TxKeyID == 0 { // errStr := "undefined keyID" // return errors.New(errStr) // } // var found bool // tx := &sqldb.Transaction{} // found, err = tx.Get(hash) // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting transaction by hash") // return utils.ErrInfo(err) // } // if found { // err = sqldb.DeleteTransactionByHash(dbTx, hash) // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting transaction by hash") // return utils.ErrInfo(err) // } // } // // put with verified=1 // var expedite decimal.Decimal // if len(t.TxSmart.GetExpedite) > 0 { // expedite, err = decimal.NewFromString(t.TxSmart.GetExpedite) // if err != nil { // return utils.ErrInfo(err) // } // } // newTx := &sqldb.Transaction{ // Hash: hash, // Data: binaryTx, // Type: int8(t.TxType), // KeyID: t.TxKeyID, // GetExpedite: expedite, // Time: t.TxTime, // Verified: 1, // } // err = newTx.Create() // if err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("creating new transaction") // return utils.ErrInfo(err) // } // // delQueueTx := &sqldb.QueueTx{Hash: hash} // if err = delQueueTx.DeleteTx(dbTx); err != nil { // log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("deleting transaction from queue") // return utils.ErrInfo(err) // } // // return nil //} // ProcessTransactionsQueue parses new transactions func ProcessTransactionsQueue(dbTx *sqldb.DbTransaction) error { all, err := sqldb.GetAllUnverifiedAndUnusedTransactions(dbTx, syspar.GetMaxTxCount()) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all unverified and unused transactions") return err } //for i := 0; i < len(all); i++ { // err := ProcessQueueTransaction(dbTx, all[i].Hash, all[i].Data, false) // if err != nil { // MarkTransactionBad(dbTx, all[i].Hash, err.Error()) // return utils.ErrInfo(err) // } // log.Debug("transaction parsed successfully") //} return ProcessQueueTransactionBatches(dbTx, all) } // AllTxParser parses new transactions func ProcessTransactionsAttempt(dbTx *sqldb.DbTransaction) error { all, err := sqldb.FindTxAttemptCount(dbTx, consts.MaxTXAttempt) if err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("getting all transactions attempt > consts.MaxTXAttempt") return err } for _, data := range all { err := MarkTransactionBad(data.Hash, fmt.Sprintf("The limit of %d attempts has been reached", consts.MaxTXAttempt)) if err != nil { return utils.ErrInfo(err) } log.Debug("transaction attempt deal successfully") } return nil } ================================================ FILE: packages/transaction/deliver.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package transaction import ( "math/rand" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" ) type DeliverProvider interface { SysUpdateWorker(*sqldb.DbTransaction) error SysTableColByteaWorker(*sqldb.DbTransaction) error FlushVM() } type InToCxt struct { SqlDbSavePoint string GenBlock bool DbTransaction *sqldb.DbTransaction BlockHeader *types.BlockHeader PreBlockHeader *types.BlockHeader Notifications types.Notifications Rand *rand.Rand TxCheckLimits *Limits OutputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo PrevSysPar map[string]string EcoParams []sqldb.EcoParam } type OutCtx struct { SysUpdate bool RollBackTx []*types.RollbackTx TxResult *pbgo.TxResult TxOutputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo TxInputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo } type OutCtxOption func(b *OutCtx) func (tr *OutCtx) Apply(opts ...OutCtxOption) { for _, opt := range opts { if opt == nil { continue } opt(tr) } return } func WithOutCtxTxResult(ret *pbgo.TxResult) OutCtxOption { return func(b *OutCtx) { b.TxResult = ret } } func WithOutCtxSysUpdate(ret bool) OutCtxOption { return func(b *OutCtx) { b.SysUpdate = ret } } func WithOutCtxRollBackTx(ret []*types.RollbackTx) OutCtxOption { return func(b *OutCtx) { b.RollBackTx = ret } } func WithOutCtxTxOutputs(txOutputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo) OutCtxOption { return func(b *OutCtx) { b.TxOutputsMap = txOutputsMap } } func WithOutCtxTxInputs(txInputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo) OutCtxOption { return func(b *OutCtx) { b.TxInputsMap = txInputsMap } } ================================================ FILE: packages/transaction/first_block.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "bytes" "strconv" "time" "github.com/IBAX-io/go-ibax/packages/migration" "github.com/pkg/errors" "github.com/vmihailenco/msgpack/v5" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) const ( firstEcosystemID = 1 firstAppID = 1 ) // FirstBlockParser is parser wrapper type FirstBlockParser struct { Logger *log.Entry DbTransaction *sqldb.DbTransaction Data *types.FirstBlock Timestamp int64 TxHash []byte Payload []byte // transaction binary data } func (f *FirstBlockParser) txType() byte { return f.Data.TxType() } func (f *FirstBlockParser) txHash() []byte { return f.TxHash } func (f *FirstBlockParser) txPayload() []byte { return f.Payload } func (f *FirstBlockParser) txTime() int64 { return f.Timestamp } func (f *FirstBlockParser) txKeyID() int64 { return f.Data.KeyID } func (f *FirstBlockParser) txExpedite() decimal.Decimal { return decimal.Decimal{} } func (s *FirstBlockParser) setTimestamp() { s.Timestamp = time.Now().UnixMilli() } func (f *FirstBlockParser) TxRollback() error { return nil } func (f *FirstBlockParser) SysUpdateWorker(dbTx *sqldb.DbTransaction) error { return nil } func (f *FirstBlockParser) SysTableColByteaWorker(dbTx *sqldb.DbTransaction) error { return nil } func (f *FirstBlockParser) FlushVM() {} func (f *FirstBlockParser) Init(in *InToCxt) error { f.Logger = log.WithFields(log.Fields{}) f.DbTransaction = in.DbTransaction return nil } func (f *FirstBlockParser) Validate() error { return nil } func (f *FirstBlockParser) Action(in *InToCxt, out *OutCtx) (err error) { if in.BlockHeader.BlockId > 1 { return nil } logger := f.Logger data := f.Data dbTx := in.DbTransaction id := int64(0) keyID := crypto.Address(data.PublicKey) nodeKeyID := crypto.Address(data.NodePublicKey) err = sqldb.ExecSchemaEcosystem(dbTx, migration.SqlData{ Ecosystem: firstEcosystemID, Wallet: keyID, Name: consts.DefaultEcosystemName, Founder: keyID, AppID: firstAppID, Account: converter.AddressToString(keyID), Digits: consts.MoneyDigits, TokenSymbol: consts.DefaultTokenSymbol, TokenName: consts.DefaultTokenName, }) if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("executing ecosystem schema") return err } amount := decimal.New(consts.FounderAmount, int32(consts.MoneyDigits)).String() taxes := &sqldb.PlatformParameter{Name: `taxes_wallet`} if err = taxes.SaveArray(dbTx, [][]string{{"1", converter.Int64ToStr(keyID)}}); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("saving taxes_wallet array") return err } err = sqldb.GetDB(dbTx).Exec(`update "1_platform_parameters" SET value = ? where name = 'test'`, strconv.FormatInt(data.Test, 10)).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating test parameter") return err } err = sqldb.GetDB(dbTx).Exec(`Update "1_platform_parameters" SET value = ? where name = 'private_blockchain'`, strconv.FormatUint(data.PrivateBlockchain, 10)).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating private_blockchain") return err } if err = syspar.SysUpdate(dbTx); err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating syspar") return err } err = sqldb.GetDB(dbTx).Exec(`insert into "1_keys" (id,account,pub,amount) values(?,?,?,?),(?,?,?,?)`, keyID, converter.AddressToString(keyID), data.PublicKey, 0, nodeKeyID, converter.AddressToString(nodeKeyID), data.NodePublicKey, 0).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("inserting key") return err } err = sqldb.GetDB(dbTx).Exec(`insert into "spent_info" (output_index,output_tx_hash,output_key_id,output_value,ecosystem,block_id,type) values(?,?,?,?,?,?,?)`, 0, f.TxHash, keyID, amount, 1, 1, consts.UTXO_Type_First_Block).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("inserting spent info") return err } id, err = dbTx.GetNextID("1_pages") if err != nil { return err } err = sqldb.GetDB(dbTx).Exec(`insert into "1_pages" (id,name,menu,value,conditions) values(?, 'default_page', 'default_menu', ?, 'ContractConditions("@1DeveloperCondition")')`, id, syspar.SysString(`default_ecosystem_page`)).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("inserting default page") return err } id, err = dbTx.GetNextID("1_menu") if err != nil { return err } err = sqldb.GetDB(dbTx).Exec(`insert into "1_menu" (id,name,value,title,conditions) values(?, 'default_menu', ?, ?, 'ContractAccess("@1EditMenu")')`, id, syspar.SysString(`default_ecosystem_menu`), `default`).Error if err != nil { logger.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("inserting default menu") return err } err = smart.LoadContract(dbTx, 1) if err != nil { return err } if err := syspar.SysTableColType(dbTx); err != nil { return err } syspar.SetFirstBlockData(data) syspar.SetFirstBlockTimestamp(time.UnixMilli(f.Timestamp).Unix()) return nil } func (s *FirstBlockParser) BinMarshal(data *types.FirstBlock) ([]byte, error) { s.Data = data var buf []byte var err error buf, err = msgpack.Marshal(data) if err != nil { return nil, err } s.setTimestamp() s.Payload = buf s.TxHash = crypto.DoubleHash(s.Payload) buf, err = msgpack.Marshal(s) if err != nil { return nil, err } buf = append([]byte{s.txType()}, buf...) return buf, nil } func (f *FirstBlockParser) Unmarshal(buffer *bytes.Buffer) error { buffer.UnreadByte() if err := msgpack.Unmarshal(buffer.Bytes()[1:], f); err != nil { return errors.Wrap(err, "first block Unmarshal err") } return nil } ================================================ FILE: packages/transaction/limits.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "fmt" "time" "github.com/pkg/errors" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/script" log "github.com/sirupsen/logrus" ) type LimitMode int const ( letPreprocess LimitMode = 0x0001 // checking before generating block letGenBlock LimitMode = 0x0002 // checking during generating block letParsing LimitMode = 0x0004 // common checking during parsing block ) func GetLetPreprocess() LimitMode { return letPreprocess } func GetLetGenBlock() LimitMode { return letGenBlock } func GetLetParsing() LimitMode { return letParsing } // Limits is used for saving current limit information type Limits struct { Mode LimitMode Limiters []Limiter // the list of limiters } // Limiter describes interface functions for limits type Limiter interface { init() check(TransactionCaller, LimitMode) error } type limiterModes struct { limiter Limiter modes LimitMode // combination of letPreprocess letGenBlock letParsing } var ( // ErrLimitSkip returns when tx should be skipped during generating block ErrLimitSkip = errors.New(`skip tx`) // ErrLimitStop returns when the generation of the block should be stopped ErrLimitStop = errors.New(`stop generating block`) ) // NewLimits initializes Limits structure. func NewLimits(b LimitMode) (limits *Limits) { limits = &Limits{Limiters: make([]Limiter, 0, 8)} limits.Mode = b allLimiters := []limiterModes{ {limiter: &txMaxSize{}, modes: letPreprocess | letParsing}, {limiter: &txUserLimit{}, modes: letPreprocess | letParsing}, {limiter: &txMaxLimit{}, modes: letPreprocess | letParsing}, {limiter: &txUserEcosysLimit{}, modes: letPreprocess | letParsing}, {limiter: &timeBlockLimit{}, modes: letGenBlock}, {limiter: &txMaxFuel{}, modes: letGenBlock | letParsing}, } for _, limiter := range allLimiters { if limiter.modes&limits.Mode == 0 { continue } limiter.limiter.init() limits.Limiters = append(limits.Limiters, limiter.limiter) } return } // CheckLimit calls each limiter func (limits *Limits) CheckLimit(t TransactionCaller) error { for _, limiter := range limits.Limiters { if err := limiter.check(t, limits.Mode); err != nil { return err } } return nil } func limitError(limitName, msg string, args ...any) error { err := fmt.Errorf(msg, args...) log.WithFields(log.Fields{"type": consts.BlockError, "error": err}).Error(limitName) return script.SetVMError(`panic`, err) } // Checking the max tx in the block type txMaxLimit struct { Count int // the current count Limit int // max count of tx in the block } func (bl *txMaxLimit) init() { bl.Limit = syspar.GetMaxTxCount() } func (bl *txMaxLimit) check(t TransactionCaller, mode LimitMode) error { bl.Count++ if bl.Count+1 > bl.Limit && mode == letPreprocess { return errors.WithMessage(ErrLimitStop, "txMaxLimit") } if bl.Count > bl.Limit { return limitError(`txMaxLimit`, `Max tx in the block`) } return nil } // Checking the time of the start of generating block type timeBlockLimit struct { Start time.Time // the time of the start of generating block Limit time.Duration // the maximum time } func (bl *timeBlockLimit) init() { bl.Start = time.Now() bl.Limit = time.Millisecond * time.Duration(syspar.GetMaxBlockGenerationTime()) } func (bl *timeBlockLimit) check(t TransactionCaller, mode LimitMode) error { if time.Since(bl.Start) < bl.Limit { return nil } if mode == letGenBlock { return errors.WithMessage(ErrLimitStop, "timeBlockLimit") } return limitError("txBlockTimeLimit", "Block generation time exceeded") } // Checking the max tx from one user in the block type txUserLimit struct { TxUsers map[int64]int // the counter of tx from one user Limit int // the value of max tx from one user } func (bl *txUserLimit) init() { bl.TxUsers = make(map[int64]int) bl.Limit = syspar.GetMaxBlockUserTx() } func (bl *txUserLimit) check(t TransactionCaller, mode LimitMode) error { var ( count int ok bool ) keyID := t.txKeyID() if count, ok = bl.TxUsers[keyID]; ok { if count+1 > bl.Limit && mode == letPreprocess { return ErrLimitSkip } if count > bl.Limit { return limitError(`txUserLimit`, `Max tx from one user %d`, keyID) } } bl.TxUsers[keyID] = count + 1 return nil } // Checking the max tx from one user in the ecosystem contracts type ecosysLimit struct { TxUsers map[int64]int // the counter of tx from one user in the ecosystem Limit int // the value of max tx from one user in the ecosystem } type txUserEcosysLimit struct { TxEcosys map[int64]ecosysLimit // the counter of tx from one user in ecosystems } func (bl *txUserEcosysLimit) init() { bl.TxEcosys = make(map[int64]ecosysLimit) } func (bl *txUserEcosysLimit) check(t TransactionCaller, mode LimitMode) error { keyID := t.txKeyID() smart, ok := t.(*SmartTransactionParser) if !ok { return nil } ecosystemID := smart.TxSmart.EcosystemID if val, ok := bl.TxEcosys[ecosystemID]; ok { if user, ok := val.TxUsers[keyID]; ok { if user+1 > val.Limit && mode == letPreprocess { return ErrLimitSkip } if user > val.Limit { return limitError(`txUserEcosysLimit`, `Max tx from one user %d in ecosystem %d`, keyID, ecosystemID) } val.TxUsers[keyID] = user + 1 } else { val.TxUsers[keyID] = 1 } } else { limit := syspar.GetMaxBlockUserTx() bl.TxEcosys[ecosystemID] = ecosysLimit{TxUsers: make(map[int64]int), Limit: limit} bl.TxEcosys[ecosystemID].TxUsers[keyID] = 1 } return nil } // Checking the max tx & block size type txMaxSize struct { Size int64 // the current size of the block LimitBlock int64 // max size of the block LimitTx int64 // max size of tx } func (bl *txMaxSize) init() { bl.LimitBlock = syspar.GetMaxBlockSize() bl.LimitTx = syspar.GetMaxTxSize() } func (bl *txMaxSize) check(t TransactionCaller, mode LimitMode) error { size := int64(len(append([]byte{t.txType()}, t.txPayload()...))) if size > bl.LimitTx { return limitError(`txMaxSize`, `Max size of tx`) } bl.Size += size if bl.Size > bl.LimitBlock { if mode == letPreprocess { return errors.WithMessage(ErrLimitStop, "txMaxSize") } return limitError(`txMaxSize`, `Max size of the block`) } return nil } // Checking the max tx & block size type txMaxFuel struct { Fuel int64 // the current fuel of the block LimitBlock int64 // max fuel of the block LimitTx int64 // max fuel of tx } func (bl *txMaxFuel) init() { bl.LimitBlock = syspar.GetMaxBlockFuel() bl.LimitTx = syspar.GetMaxTxFuel() } func (bl *txMaxFuel) check(t TransactionCaller, mode LimitMode) error { smart, ok := t.(*SmartTransactionParser) if !ok { return nil } fuel := smart.TxFuel if fuel > bl.LimitTx { return limitError(`txMaxFuel`, `Max fuel of tx %d > %d`, fuel, bl.LimitTx) } bl.Fuel += fuel if bl.Fuel > bl.LimitBlock { if mode == letGenBlock { return errors.WithMessage(ErrLimitStop, "txMaxFuel") } return limitError(`txMaxFuel`, `Max fuel of the block`) } return nil } ================================================ FILE: packages/transaction/play.go ================================================ package transaction import ( "encoding/hex" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" ) // GetLogger returns logger func (t *Transaction) GetLogger() *log.Entry { var logger *log.Entry if t.Inner != nil { logger = log.WithFields(log.Fields{"tx_type": t.Type(), "tx_time": t.Timestamp(), "tx_wallet_id": t.KeyID()}) } if t.BlockHeader != nil { logger = logger.WithFields(log.Fields{"block_id": t.BlockHeader.BlockId, "block_time": t.BlockHeader.Timestamp, "block_wallet_id": t.BlockHeader.KeyId, "block_state_id": t.BlockHeader.EcosystemId, "block_hash": t.BlockHeader.BlockHash, "block_version": t.BlockHeader.Version}) } if t.PreBlockHeader != nil { logger = logger.WithFields(log.Fields{"pre_block_id": t.PreBlockHeader.BlockId, "pre_block_time": t.PreBlockHeader.Timestamp, "pre_block_wallet_id": t.PreBlockHeader.KeyId, "pre_block_state_id": t.PreBlockHeader.EcosystemId, "pre_block_hash": t.PreBlockHeader.BlockHash, "pre_block_version": t.PreBlockHeader.Version}) } return logger } func (t *Transaction) Play() error { if err := t.Inner.Init(t.InToCxt); err != nil { return err } return t.Inner.Action(t.InToCxt, t.OutCtx) } func (t *Transaction) Check(checkTime int64) error { if t.KeyID() == 0 { return ErrEmptyKey } logger := log.WithFields(log.Fields{"tx_hash": hex.EncodeToString(t.Hash()), "tx_time": t.Timestamp(), "check_time": checkTime, "type": consts.ParameterExceeded}) if time.UnixMilli(t.Timestamp()).Unix() > checkTime { //if time.UnixMilli(t.Timestamp()).Unix()-consts.MaxTxForw > checkTime { // logger.WithFields(log.Fields{"tx_max_forw": consts.MaxTxForw}).Errorf("time in the tx cannot be more than %d seconds of block time ", consts.MaxTxForw) // return ErrNotComeTime //} logger.Error("time in the tx cannot be more than of block time ") return ErrEarlyTime } if t.Type() != types.StopNetworkTxType { if time.UnixMilli(t.Timestamp()).Unix() < checkTime-consts.MaxTxBack { logger.WithFields(log.Fields{"tx_max_back": consts.MaxTxBack, "tx_type": t.Type()}).Errorf("time in the tx cannot be less then %d seconds of block time", consts.MaxTxBack) return ErrExpiredTime } } err := CheckLogTx(t.Hash(), logger) if err != nil { return err } return nil } // CallContract calls the contract functions according to the specified flags //func (t *Transaction) CallContract(point int) error { // // var err error // t.TxSize = int64(len(t.Raw.payload)) // t.VM = smart.GetVM() // t.CLB = false // t.Rollback = true // t.SysUpdate = false // t.RollBackTx = make([]*sqldb.RollbackTx, 0) // if t.GenBlock { // t.TimeLimit = syspar.GetMaxBlockGenerationTime() // } // // t.TxResult, err = t.SmartContract.CallContract(point) // if err == nil && t.TxSmart != nil { // if t.Penalty { // if t.FlushRollback != nil { // flush := t.FlushRollback // for i := len(flush) - 1; i >= 0; i-- { // flush[i].FlushVM() // } // } // } // err = t.TxCheckLimits.CheckLimit(t) // } // if err != nil { // if t.FlushRollback != nil { // flush := t.FlushRollback // for i := len(flush) - 1; i >= 0; i-- { // flush[i].FlushVM() // } // } // } // return err //} /* func (t *Transaction) CallCLBContract() (resultContract string, flushRollback []smart.FlushInfo, err error) { t.TxSize = int64(len(t.Inner.TxPayload())) t.VM = smart.GetVM() t.CLB = true t.Rollback = false t.SysUpdate = false resultContract, err = t.SmartContract.CallContract(0) return } */ ================================================ FILE: packages/transaction/process.go ================================================ package transaction import ( "bytes" "time" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" ) func ProcessQueueTransactionBatches(dbTx *sqldb.DbTransaction, qs []*sqldb.QueueTx) error { var ( checkTime = time.Now().Unix() hashes [][]byte trxs []*sqldb.Transaction err error ) type badTxStruct struct { hash []byte msg string keyID int64 } processBadTx := func(dbTx *sqldb.DbTransaction) chan badTxStruct { ch := make(chan badTxStruct) go func() { for badTxItem := range ch { BadTxForBan(badTxItem.keyID) _ = MarkTransactionBad(badTxItem.hash, badTxItem.msg) } }() return ch } txBadChan := processBadTx(dbTx) defer func() { close(txBadChan) }() for i := 0; i < len(qs); i++ { tx := &Transaction{} tx, err = UnmarshallTransaction(bytes.NewBuffer(qs[i].Data), true) if err != nil { if tx != nil { txBadChan <- badTxStruct{hash: tx.Hash(), msg: err.Error(), keyID: tx.KeyID()} } continue } err = tx.Check(checkTime) if err != nil { txBadChan <- badTxStruct{hash: tx.Hash(), msg: err.Error(), keyID: tx.KeyID()} continue } newTx := &sqldb.Transaction{ Hash: tx.Hash(), Data: tx.FullData, Type: int8(tx.Type()), KeyID: tx.KeyID(), Expedite: tx.Expedite(), Time: tx.Timestamp(), Verified: 1, Used: 0, Sent: 0, } trxs = append(trxs, newTx) hashes = append(hashes, qs[i].Hash) } if len(trxs) > 0 { errTx := sqldb.CreateTransactionBatches(dbTx, trxs) if errTx != nil { return errTx } } if len(hashes) > 0 { errQTx := sqldb.DeleteQueueTxs(dbTx, hashes) if errQTx != nil { return errQTx } } return nil } ================================================ FILE: packages/transaction/raw.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "bytes" "fmt" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" log "github.com/sirupsen/logrus" "github.com/vmihailenco/msgpack/v5" ) func (rtx *Transaction) Unmarshall(buffer *bytes.Buffer, fill bool) error { if buffer.Len() == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("empty transaction buffer") return fmt.Errorf("empty transaction buffer") } rtx.FullData = buffer.Bytes() rtx.InToCxt = &InToCxt{ DbTransaction: new(sqldb.DbTransaction), } txT, err := buffer.ReadByte() if err != nil { return err } var inner TransactionCaller switch txT { case types.SmartContractTxType, types.TransferSelfTxType, types.UtxoTxType: itx := &SmartTransactionParser{ SmartContract: &smart.SmartContract{TxSmart: new(types.SmartTransaction)}, } inner = itx if err = itx.Unmarshal(buffer, fill); err != nil { return err } case byte(128): //reset unmarshal client buf itx := &SmartTransactionParser{ SmartContract: &smart.SmartContract{TxSmart: new(types.SmartTransaction)}, } inner = itx if err := converter.BinUnmarshalBuff(buffer, &itx.Payload); err != nil { return err } itx.Hash = crypto.DoubleHash(itx.Payload) itx.TxSignature = buffer.Bytes() if err := msgpack.Unmarshal(itx.Payload, &itx.TxSmart); err != nil { return err } var newbuf []byte newbuf, err = itx.Marshal() if err != nil { return err } if err = itx.Unmarshal(bytes.NewBuffer(newbuf), fill); err != nil { return err } rtx.FullData = newbuf case types.FirstBlockTxType: var itx FirstBlockParser inner = &itx if err := itx.Unmarshal(buffer); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.UnmarshallingError, "tx_type": itx.txType()}).Error("getting parser for tx type") return err } case types.StopNetworkTxType: var itx = StopNetworkParser{} inner = &itx if err := itx.Unmarshal(buffer); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.UnmarshallingError, "tx_type": rtx.Type()}).Error("getting parser for tx type") return err } default: return fmt.Errorf("unsupported tx type %d", txT) } rtx.Inner = inner if cache, ok := txCache.Get(fmt.Sprintf("%x", rtx.Hash())); ok { rtx = cache return nil } txCache.Set(rtx) return nil } func (rtx *Transaction) SetRawTx() *sqldb.RawTx { return &sqldb.RawTx{ Hash: rtx.Hash(), Time: rtx.Timestamp(), TxType: rtx.Type(), Data: rtx.FullData, Expedite: rtx.Expedite().String(), WalletID: rtx.KeyID(), } } ================================================ FILE: packages/transaction/smart_contract.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "bytes" "fmt" "time" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/script" "github.com/IBAX-io/go-ibax/packages/smart" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/IBAX-io/go-ibax/packages/utils" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" "github.com/vmihailenco/msgpack/v5" ) type SmartTransactionParser struct { *smart.SmartContract } func (s *SmartTransactionParser) txType() byte { return s.TxSmart.TxType() } func (s *SmartTransactionParser) txHash() []byte { return s.Hash } func (s *SmartTransactionParser) txPayload() []byte { return s.Payload } func (s *SmartTransactionParser) txTime() int64 { return s.Timestamp } func (s *SmartTransactionParser) txKeyID() int64 { return s.TxSmart.KeyID } func (s *SmartTransactionParser) txExpedite() decimal.Decimal { dec, _ := decimal.NewFromString(s.TxSmart.Expedite) return dec } func (s *SmartTransactionParser) setTimestamp() { s.Timestamp = time.Now().UnixMilli() } func (s *SmartTransactionParser) Init(t *InToCxt) error { s.Rand = t.Rand s.GenBlock = t.GenBlock s.BlockHeader = t.BlockHeader s.PreBlockHeader = t.PreBlockHeader s.Notifications = t.Notifications s.DbTransaction = t.DbTransaction s.TxSize = int64(len(s.Payload)) s.VM = script.GetVM() s.CLB = false s.Rollback = true s.SysUpdate = false s.OutputsMap = t.OutputsMap s.PrevSysPar = t.PrevSysPar s.EcoParams = t.EcoParams s.TxInputsMap = make(map[sqldb.KeyUTXO][]sqldb.SpentInfo) s.TxOutputsMap = make(map[sqldb.KeyUTXO][]sqldb.SpentInfo) s.RollBackTx = make([]*types.RollbackTx, 0) if s.GenBlock { s.TimeLimit = syspar.GetMaxBlockGenerationTime() } s.Key = &sqldb.Key{} return nil } func (s *SmartTransactionParser) Validate() error { if err := s.TxSmart.Validate(); err != nil { return err } _, err := utils.CheckSign([][]byte{crypto.CutPub(s.TxSmart.PublicKey)}, s.Hash, s.TxSignature, false) if err != nil { return err } return nil } func (s *SmartTransactionParser) Action(in *InToCxt, out *OutCtx) (err error) { var res string defer func() { if len(res) > 255 { res = res[:252] + "..." } ret := &pbgo.TxResult{ Result: res, Hash: out.TxResult.Hash, } if s.Penalty { ret.Code = pbgo.TxInvokeStatusCode_PENALTY ret.BlockId = s.BlockHeader.BlockId } out.Apply( WithOutCtxTxResult(ret), WithOutCtxSysUpdate(s.SysUpdate), WithOutCtxRollBackTx(s.RollBackTx), ) if err != nil || s.Penalty { if s.FlushRollback != nil { flush := s.FlushRollback for i := len(flush) - 1; i >= 0; i-- { flush[i].FlushVM() } } return } ret.Code = pbgo.TxInvokeStatusCode_SUCCESS ret.BlockId = s.BlockHeader.BlockId out.Apply( WithOutCtxTxResult(ret), WithOutCtxTxOutputs(s.TxOutputsMap), WithOutCtxTxInputs(s.TxInputsMap), ) //in.DbTransaction.BinLogSql = s.DbTransaction.BinLogSql }() _transferSelf := s.TxSmart.TransferSelf if _transferSelf != nil { _, err = smart.TransferSelf(s.SmartContract, _transferSelf.Value, _transferSelf.Source, _transferSelf.Target) if err != nil { return err } err = in.TxCheckLimits.CheckLimit(s) if err != nil { return } return } _utxo := s.TxSmart.UTXO if _utxo != nil { _, err = smart.UtxoToken(s.SmartContract, _utxo.ToID, _utxo.Value) if err != nil { return err } err = in.TxCheckLimits.CheckLimit(s) if err != nil { return } return } res, err = s.CallContract(in.SqlDbSavePoint) if err == nil && s.TxSmart != nil { err = in.TxCheckLimits.CheckLimit(s) } if err != nil { return } return } func (s *SmartTransactionParser) TxRollback() error { return syspar.SysUpdate(s.DbTransaction) } func (s *SmartTransactionParser) Marshal() ([]byte, error) { s.setTimestamp() if err := s.Validate(); err != nil { return nil, err } buf, err := msgpack.Marshal(s) if err != nil { return nil, err } buf = append([]byte{s.txType()}, buf...) return buf, nil } func (s *SmartTransactionParser) setSig(privateKey []byte) error { signature, err := crypto.Sign(privateKey, s.Hash) if err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("signing by node private key") return err } s.TxSignature = converter.EncodeLengthPlusData(signature) return nil } func (s *SmartTransactionParser) BinMarshalWithPrivate(smartTx *types.SmartTransaction, privateKey []byte, internal bool) ([]byte, error) { var ( buf []byte err error ) if err = smartTx.WithPrivate(privateKey, internal); err != nil { return nil, err } s.TxSmart = smartTx buf, err = s.TxSmart.Marshal() if err != nil { return nil, err } s.Payload = buf s.Hash = crypto.DoubleHash(s.Payload) err = s.setSig(privateKey) if err != nil { return nil, err } return s.Marshal() } func (s *SmartTransactionParser) Unmarshal(buffer *bytes.Buffer, fill bool) error { buffer.UnreadByte() if err := msgpack.Unmarshal(buffer.Bytes()[1:], s); err != nil { return err } if s.SmartContract.TxSmart.UTXO != nil || s.SmartContract.TxSmart.TransferSelf != nil { return nil } if err := s.parseFromContract(fill); err != nil { return err } return nil } func (s *SmartTransactionParser) parseFromContract(fillData bool) error { var err error smartTx := s.TxSmart contract := smart.GetContractByID(int32(smartTx.ID)) if contract == nil { log.WithFields(log.Fields{"contract_id": smartTx.ID, "type": consts.NotFound}).Error("unknown contract") return fmt.Errorf(`unknown contract %d`, smartTx.ID) } s.TxContract = contract s.TxData = make(map[string]any) txInfo := contract.Info().Tx if txInfo != nil { if fillData { for k := range smartTx.Params { if _, ok := contract.Info().TxMap()[k]; !ok { return fmt.Errorf("'%s' parameter is not required", k) } } if s.TxData, err = smart.FillTxData(*txInfo, smartTx.Params); err != nil { return errors.Wrap(err, fmt.Sprintf("contract '%s'", contract.Name)) } } else { s.TxData = smartTx.Params for key, item := range s.TxData { if v, ok := item.(map[any]any); ok { imap := make(map[string]any) for ikey, ival := range v { imap[fmt.Sprint(ikey)] = ival } s.TxData[key] = imap } } } } return nil } func (s *SmartTransactionParser) SysUpdateWorker(dbTx *sqldb.DbTransaction) error { if !s.SysUpdate { return nil } if err := syspar.SysUpdate(dbTx); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating syspar") return err } return nil } func (s *SmartTransactionParser) SysTableColByteaWorker(dbTx *sqldb.DbTransaction) error { if err := syspar.SysTableColType(dbTx); err != nil { log.WithFields(log.Fields{"type": consts.DBError, "error": err}).Error("updating syspar") return err } return nil } func (s *SmartTransactionParser) FlushVM() { if s.FlushRollback == nil { return } flush := s.FlushRollback for i := len(flush) - 1; i >= 0; i-- { flush[i].FlushVM() } return } type TxOutCtx struct { SysUpdate bool SysTableColBytea bool Flush bool FlushRollback []*smart.FlushInfo VM *script.VM VM2 *script.VM } ================================================ FILE: packages/transaction/stop_network.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "bytes" "errors" "time" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/vmihailenco/msgpack/v5" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/common/crypto/x509" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" ) var ( messageNetworkStopping = "Attention! The network is stopped!" ErrNetworkStopping = errors.New("network is stopping") ) type StopNetworkParser struct { Logger *log.Entry Data *types.StopNetwork Cert *x509.Cert Timestamp int64 TxHash []byte Payload []byte // transaction binary data } func (s *StopNetworkParser) txType() byte { return s.Data.TxType() } func (s *StopNetworkParser) txHash() []byte { return s.TxHash } func (s *StopNetworkParser) txPayload() []byte { return s.Payload } func (s *StopNetworkParser) txTime() int64 { return s.Timestamp } func (s *StopNetworkParser) txKeyID() int64 { return s.Data.KeyID } func (s *StopNetworkParser) txExpedite() decimal.Decimal { return decimal.Decimal{} } func (s *StopNetworkParser) setTimestamp() { s.Timestamp = time.Now().UnixMilli() } func (s *StopNetworkParser) Init(in *InToCxt) error { return nil } func (s *StopNetworkParser) Validate() error { if err := s.validate(); err != nil { s.Logger.WithError(err).Error("validating tx") return err } return nil } func (s *StopNetworkParser) validate() error { data := s.Data cert, err := x509.ParseCert(data.StopNetworkCert) if err != nil { return err } fbdata, err := syspar.GetFirstBlockData() if err != nil { return err } if err = cert.Validate(fbdata.StopNetworkCertBundle); err != nil { return err } s.Cert = cert return nil } func (s *StopNetworkParser) Action(in *InToCxt, out *OutCtx) (err error) { // Allow execute transaction, if the certificate was used if s.Cert.EqualBytes(consts.UsedStopNetworkCerts...) { return nil } // Set the node in a pause state //node.PauseNodeActivity(node.PauseTypeStopingNetwork) s.Logger.Warn(messageNetworkStopping) return ErrNetworkStopping } func (s *StopNetworkParser) TxRollback() error { return nil } func (s *StopNetworkParser) SysUpdateWorker(dbTx *sqldb.DbTransaction) error { return nil } func (s *StopNetworkParser) SysTableColByteaWorker(dbTx *sqldb.DbTransaction) error { return nil } func (s *StopNetworkParser) FlushVM() {} func (s *StopNetworkParser) BinMarshal(data *types.StopNetwork) ([]byte, error) { s.setTimestamp() s.Data = data var ( buf []byte err error ) buf, err = msgpack.Marshal(data) if err != nil { return nil, err } s.Payload = buf s.TxHash = crypto.DoubleHash(s.Payload) err = s.validate() if err != nil { return nil, err } buf, err = msgpack.Marshal(s) if err != nil { return nil, err } buf = append([]byte{s.txType()}, buf...) return buf, nil } func (s *StopNetworkParser) Unmarshal(buffer *bytes.Buffer) error { buffer.UnreadByte() if err := msgpack.Unmarshal(buffer.Bytes()[1:], s); err != nil { return err } return nil } ================================================ FILE: packages/transaction/transaction.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package transaction import ( "bytes" "fmt" "math/rand" "github.com/IBAX-io/go-ibax/packages/pbgo" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" "github.com/IBAX-io/go-ibax/packages/types" "github.com/shopspring/decimal" ) // Transaction is a structure for parsing transactions type Transaction struct { FullData []byte // full transaction, with type and data *InToCxt *OutCtx Inner TransactionCaller } // TransactionCaller is parsing transactions type TransactionCaller interface { Init(*InToCxt) error Validate() error Action(*InToCxt, *OutCtx) error TxRollback() error txType() byte txHash() []byte txPayload() []byte txTime() int64 txKeyID() int64 txExpedite() decimal.Decimal } func (t *Transaction) Type() byte { return t.Inner.txType() } func (t *Transaction) Hash() []byte { return t.Inner.txHash() } func (t *Transaction) Payload() []byte { return t.Inner.txPayload() } func (t *Transaction) Timestamp() int64 { return t.Inner.txTime() } func (t *Transaction) KeyID() int64 { return t.Inner.txKeyID() } func (t *Transaction) Expedite() decimal.Decimal { return t.Inner.txExpedite() } func (t *Transaction) IsSmartContract() bool { _, ok := t.Inner.(*SmartTransactionParser) return ok } func (t *Transaction) SmartContract() *SmartTransactionParser { return t.Inner.(*SmartTransactionParser) } // UnmarshallTransaction is unmarshalling transaction func UnmarshallTransaction(buffer *bytes.Buffer, fill bool) (*Transaction, error) { tx := &Transaction{} var err error defer func() { if err != nil && tx != nil { if tx.Inner == nil { return } _ = MarkTransactionBad(tx.Hash(), err.Error()) } }() err = tx.Unmarshall(buffer, fill) if err != nil { return nil, fmt.Errorf("parse transaction error: %w", err) } return tx, nil } func (tr *Transaction) WithOption( notifications types.Notifications, genBlock bool, blockHeader, preBlockHeader *types.BlockHeader, dbTransaction *sqldb.DbTransaction, rand *rand.Rand, txCheckLimits *Limits, sqlDbSavePoint string, outputsMap map[sqldb.KeyUTXO][]sqldb.SpentInfo, prevSysPar map[string]string, ecoParams []sqldb.EcoParam, opts ...TransactionOption) error { in := &InToCxt{ SqlDbSavePoint: sqlDbSavePoint, TxCheckLimits: txCheckLimits, Rand: rand, DbTransaction: dbTransaction, PreBlockHeader: preBlockHeader, BlockHeader: blockHeader, GenBlock: genBlock, Notifications: notifications, OutputsMap: outputsMap, PrevSysPar: prevSysPar, EcoParams: ecoParams, } in.DbTransaction.BinLogSql = nil tr.InToCxt = in tr.OutCtx = &OutCtx{ TxResult: &pbgo.TxResult{Hash: tr.Hash()}, } return tr.Apply(opts...) } type TransactionOption func(b *Transaction) error func (tr *Transaction) Apply(opts ...TransactionOption) error { for _, opt := range opts { if opt == nil { continue } if err := opt(tr); err != nil { return err } } return nil } ================================================ FILE: packages/types/api.go ================================================ package types import log "github.com/sirupsen/logrus" type EcosystemGetter interface { GetEcosystemLookup() ([]int64, []string, error) ValidateId(id, clientID int64, le *log.Entry) (int64, error) GetEcosystemName(id int64) (string, error) } ================================================ FILE: packages/types/block.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: block.proto package types import ( fmt "fmt" proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // BlockSyncMethod define block sync method. type BlockSyncMethod int32 const ( BlockSyncMethod_CONTRACTVM BlockSyncMethod = 0 BlockSyncMethod_SQLDML BlockSyncMethod = 1 ) var BlockSyncMethod_name = map[int32]string{ 0: "CONTRACTVM", 1: "SQLDML", } var BlockSyncMethod_value = map[string]int32{ "CONTRACTVM": 0, "SQLDML": 1, } func (x BlockSyncMethod) String() string { return proto.EnumName(BlockSyncMethod_name, int32(x)) } func (BlockSyncMethod) EnumDescriptor() ([]byte, []int) { return fileDescriptor_8e550b1f5926e92d, []int{0} } //BlockHeader is a structure of the block's header type BlockHeader struct { BlockId int64 `protobuf:"varint,1,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` EcosystemId int64 `protobuf:"varint,3,opt,name=ecosystem_id,json=ecosystemId,proto3" json:"ecosystem_id,omitempty"` KeyId int64 `protobuf:"varint,4,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` NodePosition int64 `protobuf:"varint,5,opt,name=node_position,json=nodePosition,proto3" json:"node_position,omitempty"` Sign []byte `protobuf:"bytes,6,opt,name=sign,proto3" json:"sign,omitempty"` BlockHash []byte `protobuf:"bytes,7,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` //differences with before and after in tx modification table RollbacksHash []byte `protobuf:"bytes,8,opt,name=rollbacks_hash,json=rollbacksHash,proto3" json:"rollbacks_hash,omitempty"` Version int32 `protobuf:"varint,9,opt,name=version,proto3" json:"version,omitempty"` ConsensusMode int32 `protobuf:"varint,10,opt,name=consensus_mode,json=consensusMode,proto3" json:"consensus_mode,omitempty"` CandidateNodes []byte `protobuf:"bytes,11,opt,name=candidate_nodes,json=candidateNodes,proto3" json:"candidate_nodes,omitempty"` NetworkId int64 `protobuf:"varint,12,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"` } func (m *BlockHeader) Reset() { *m = BlockHeader{} } func (m *BlockHeader) String() string { return proto.CompactTextString(m) } func (*BlockHeader) ProtoMessage() {} func (*BlockHeader) Descriptor() ([]byte, []int) { return fileDescriptor_8e550b1f5926e92d, []int{0} } func (m *BlockHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *BlockHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_BlockHeader.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *BlockHeader) XXX_Merge(src proto.Message) { xxx_messageInfo_BlockHeader.Merge(m, src) } func (m *BlockHeader) XXX_Size() int { return m.Size() } func (m *BlockHeader) XXX_DiscardUnknown() { xxx_messageInfo_BlockHeader.DiscardUnknown(m) } var xxx_messageInfo_BlockHeader proto.InternalMessageInfo func (m *BlockHeader) GetBlockId() int64 { if m != nil { return m.BlockId } return 0 } func (m *BlockHeader) GetTimestamp() int64 { if m != nil { return m.Timestamp } return 0 } func (m *BlockHeader) GetEcosystemId() int64 { if m != nil { return m.EcosystemId } return 0 } func (m *BlockHeader) GetKeyId() int64 { if m != nil { return m.KeyId } return 0 } func (m *BlockHeader) GetNodePosition() int64 { if m != nil { return m.NodePosition } return 0 } func (m *BlockHeader) GetSign() []byte { if m != nil { return m.Sign } return nil } func (m *BlockHeader) GetBlockHash() []byte { if m != nil { return m.BlockHash } return nil } func (m *BlockHeader) GetRollbacksHash() []byte { if m != nil { return m.RollbacksHash } return nil } func (m *BlockHeader) GetVersion() int32 { if m != nil { return m.Version } return 0 } func (m *BlockHeader) GetConsensusMode() int32 { if m != nil { return m.ConsensusMode } return 0 } func (m *BlockHeader) GetCandidateNodes() []byte { if m != nil { return m.CandidateNodes } return nil } func (m *BlockHeader) GetNetworkId() int64 { if m != nil { return m.NetworkId } return 0 } // BlockData is a structure of the block's type BlockData struct { Header *BlockHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` PrevHeader *BlockHeader `protobuf:"bytes,2,opt,name=prev_header,json=prevHeader,proto3" json:"prev_header,omitempty"` MerkleRoot []byte `protobuf:"bytes,3,opt,name=merkle_root,json=merkleRoot,proto3" json:"merkle_root,omitempty"` BinData []byte `protobuf:"bytes,4,opt,name=bin_data,json=binData,proto3" json:"bin_data,omitempty"` TxFullData [][]byte `protobuf:"bytes,5,rep,name=tx_full_data,json=txFullData,proto3" json:"tx_full_data,omitempty"` AfterTxs *AfterTxs `protobuf:"bytes,6,opt,name=after_txs,json=afterTxs,proto3" json:"after_txs,omitempty"` SysUpdate bool `protobuf:"varint,7,opt,name=sys_update,json=sysUpdate,proto3" json:"sys_update,omitempty"` } func (m *BlockData) Reset() { *m = BlockData{} } func (m *BlockData) String() string { return proto.CompactTextString(m) } func (*BlockData) ProtoMessage() {} func (*BlockData) Descriptor() ([]byte, []int) { return fileDescriptor_8e550b1f5926e92d, []int{1} } func (m *BlockData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *BlockData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_BlockData.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *BlockData) XXX_Merge(src proto.Message) { xxx_messageInfo_BlockData.Merge(m, src) } func (m *BlockData) XXX_Size() int { return m.Size() } func (m *BlockData) XXX_DiscardUnknown() { xxx_messageInfo_BlockData.DiscardUnknown(m) } var xxx_messageInfo_BlockData proto.InternalMessageInfo func (m *BlockData) GetHeader() *BlockHeader { if m != nil { return m.Header } return nil } func (m *BlockData) GetPrevHeader() *BlockHeader { if m != nil { return m.PrevHeader } return nil } func (m *BlockData) GetMerkleRoot() []byte { if m != nil { return m.MerkleRoot } return nil } func (m *BlockData) GetBinData() []byte { if m != nil { return m.BinData } return nil } func (m *BlockData) GetTxFullData() [][]byte { if m != nil { return m.TxFullData } return nil } func (m *BlockData) GetAfterTxs() *AfterTxs { if m != nil { return m.AfterTxs } return nil } func (m *BlockData) GetSysUpdate() bool { if m != nil { return m.SysUpdate } return false } func init() { proto.RegisterEnum("types.BlockSyncMethod", BlockSyncMethod_name, BlockSyncMethod_value) proto.RegisterType((*BlockHeader)(nil), "types.BlockHeader") proto.RegisterType((*BlockData)(nil), "types.BlockData") } func init() { proto.RegisterFile("block.proto", fileDescriptor_8e550b1f5926e92d) } var fileDescriptor_8e550b1f5926e92d = []byte{ // 554 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x93, 0xcd, 0x6e, 0xd3, 0x40, 0x14, 0x85, 0xe3, 0xa6, 0xf9, 0xf1, 0xb5, 0xfb, 0xa3, 0x91, 0x90, 0x0c, 0x02, 0x13, 0x8a, 0x10, 0xa5, 0xa2, 0x89, 0xd4, 0x3e, 0x41, 0x9b, 0x0a, 0x35, 0x52, 0x53, 0xc0, 0x0d, 0x08, 0xb1, 0xb1, 0xc6, 0x9e, 0x69, 0x6c, 0xf9, 0x67, 0x2c, 0xcf, 0xa4, 0xc4, 0x6f, 0xc0, 0x92, 0x1d, 0xaf, 0xc4, 0xb2, 0x4b, 0x96, 0x28, 0x79, 0x11, 0x34, 0xd7, 0x26, 0xb0, 0x61, 0x37, 0xf3, 0x9d, 0x73, 0x33, 0x27, 0xf7, 0x24, 0x60, 0x05, 0xa9, 0x08, 0x93, 0x61, 0x51, 0x0a, 0x25, 0x48, 0x47, 0x55, 0x05, 0x97, 0x8f, 0xa0, 0x48, 0x69, 0x55, 0xa3, 0x83, 0xaf, 0x6d, 0xb0, 0xce, 0xb5, 0xe5, 0x92, 0x53, 0xc6, 0x4b, 0xf2, 0x10, 0xfa, 0x38, 0xe1, 0xc7, 0xcc, 0x31, 0x06, 0xc6, 0x61, 0xdb, 0xeb, 0xe1, 0x7d, 0xc2, 0xc8, 0x63, 0x30, 0x55, 0x9c, 0x71, 0xa9, 0x68, 0x56, 0x38, 0x5b, 0xa8, 0xfd, 0x05, 0xe4, 0x19, 0xd8, 0x3c, 0x14, 0xb2, 0x92, 0x8a, 0x67, 0x7a, 0xb8, 0x8d, 0x06, 0x6b, 0xc3, 0x26, 0x8c, 0x3c, 0x80, 0x6e, 0xc2, 0x2b, 0x2d, 0x6e, 0xa3, 0xd8, 0x49, 0x78, 0x35, 0x61, 0xe4, 0x39, 0xec, 0xe4, 0x82, 0x71, 0xbf, 0x10, 0x32, 0x56, 0xb1, 0xc8, 0x9d, 0x0e, 0xaa, 0xb6, 0x86, 0xef, 0x1a, 0x46, 0x08, 0x6c, 0xcb, 0x78, 0x9e, 0x3b, 0xdd, 0x81, 0x71, 0x68, 0x7b, 0x78, 0x26, 0x4f, 0x00, 0xea, 0xac, 0x11, 0x95, 0x91, 0xd3, 0x43, 0xc5, 0x44, 0x72, 0x49, 0x65, 0x44, 0x5e, 0xc0, 0x6e, 0x29, 0xd2, 0x34, 0xa0, 0x61, 0x22, 0x6b, 0x4b, 0x1f, 0x2d, 0x3b, 0x1b, 0x8a, 0x36, 0x07, 0x7a, 0x77, 0xbc, 0x94, 0xfa, 0x61, 0x73, 0x60, 0x1c, 0x76, 0xbc, 0x3f, 0x57, 0xfd, 0x01, 0xa1, 0xc8, 0x25, 0xcf, 0xe5, 0x42, 0xfa, 0x99, 0x60, 0xdc, 0x01, 0x34, 0xec, 0x6c, 0xe8, 0x54, 0x30, 0x4e, 0x5e, 0xc2, 0x5e, 0x48, 0x73, 0x16, 0x33, 0xaa, 0xb8, 0xaf, 0x43, 0x4b, 0xc7, 0xc2, 0x87, 0x76, 0x37, 0xf8, 0x5a, 0x53, 0x9d, 0x37, 0xe7, 0xea, 0x8b, 0x28, 0x71, 0xbb, 0x76, 0xbd, 0xc1, 0x86, 0x4c, 0xd8, 0xc1, 0xf7, 0x2d, 0x30, 0xb1, 0x8a, 0x0b, 0xaa, 0x28, 0x39, 0x82, 0x6e, 0x84, 0x95, 0x60, 0x0d, 0xd6, 0x09, 0x19, 0x62, 0x79, 0xc3, 0x7f, 0xca, 0xf2, 0x1a, 0x07, 0x39, 0x05, 0xab, 0x28, 0xf9, 0x9d, 0xdf, 0x0c, 0x6c, 0xfd, 0x77, 0x00, 0xb4, 0xad, 0x69, 0xfa, 0x29, 0x58, 0x19, 0x2f, 0x93, 0x94, 0xfb, 0xa5, 0x10, 0x0a, 0xfb, 0xb2, 0x3d, 0xa8, 0x91, 0x27, 0x84, 0xc2, 0x9f, 0x42, 0x9c, 0xfb, 0x8c, 0x2a, 0x8a, 0x85, 0xd9, 0x5e, 0x2f, 0x88, 0x73, 0x0c, 0x37, 0x00, 0x5b, 0x2d, 0xfd, 0xdb, 0x45, 0x9a, 0xd6, 0x72, 0x67, 0xd0, 0xd6, 0xc3, 0x6a, 0xf9, 0x66, 0x91, 0xa6, 0xe8, 0x78, 0x0d, 0x26, 0xbd, 0x55, 0xbc, 0xf4, 0xd5, 0x52, 0x62, 0x69, 0xd6, 0xc9, 0x5e, 0x13, 0xe8, 0x4c, 0xf3, 0xd9, 0x52, 0x7a, 0x7d, 0xda, 0x9c, 0xf4, 0x66, 0x64, 0x25, 0xfd, 0x45, 0xa1, 0x97, 0x85, 0x4d, 0xf6, 0x3d, 0x53, 0x56, 0xf2, 0x03, 0x82, 0xa3, 0x63, 0xd8, 0xc3, 0x6f, 0x71, 0x53, 0xe5, 0xe1, 0x94, 0xab, 0x48, 0x30, 0xb2, 0x0b, 0x30, 0x7e, 0x7b, 0x3d, 0xf3, 0xce, 0xc6, 0xb3, 0x8f, 0xd3, 0xfd, 0x16, 0x01, 0xe8, 0xde, 0xbc, 0xbf, 0xba, 0x98, 0x5e, 0xed, 0x1b, 0xe7, 0xe3, 0x1f, 0x2b, 0xd7, 0xb8, 0x5f, 0xb9, 0xc6, 0xaf, 0x95, 0x6b, 0x7c, 0x5b, 0xbb, 0xad, 0xfb, 0xb5, 0xdb, 0xfa, 0xb9, 0x76, 0x5b, 0x9f, 0x5f, 0xcd, 0x63, 0x15, 0x2d, 0x82, 0x61, 0x28, 0xb2, 0xd1, 0xe4, 0xfc, 0xec, 0xd3, 0x71, 0x2c, 0x46, 0x73, 0x71, 0x1c, 0x07, 0x74, 0x39, 0x2a, 0x68, 0x98, 0xd0, 0x39, 0x97, 0x23, 0x4c, 0x19, 0x74, 0xf1, 0xff, 0x71, 0xfa, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x6d, 0xf2, 0x4f, 0x4d, 0x41, 0x03, 0x00, 0x00, } func (m *BlockHeader) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *BlockHeader) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *BlockHeader) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.NetworkId != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.NetworkId)) i-- dAtA[i] = 0x60 } if len(m.CandidateNodes) > 0 { i -= len(m.CandidateNodes) copy(dAtA[i:], m.CandidateNodes) i = encodeVarintBlock(dAtA, i, uint64(len(m.CandidateNodes))) i-- dAtA[i] = 0x5a } if m.ConsensusMode != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.ConsensusMode)) i-- dAtA[i] = 0x50 } if m.Version != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.Version)) i-- dAtA[i] = 0x48 } if len(m.RollbacksHash) > 0 { i -= len(m.RollbacksHash) copy(dAtA[i:], m.RollbacksHash) i = encodeVarintBlock(dAtA, i, uint64(len(m.RollbacksHash))) i-- dAtA[i] = 0x42 } if len(m.BlockHash) > 0 { i -= len(m.BlockHash) copy(dAtA[i:], m.BlockHash) i = encodeVarintBlock(dAtA, i, uint64(len(m.BlockHash))) i-- dAtA[i] = 0x3a } if len(m.Sign) > 0 { i -= len(m.Sign) copy(dAtA[i:], m.Sign) i = encodeVarintBlock(dAtA, i, uint64(len(m.Sign))) i-- dAtA[i] = 0x32 } if m.NodePosition != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.NodePosition)) i-- dAtA[i] = 0x28 } if m.KeyId != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.KeyId)) i-- dAtA[i] = 0x20 } if m.EcosystemId != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.EcosystemId)) i-- dAtA[i] = 0x18 } if m.Timestamp != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.Timestamp)) i-- dAtA[i] = 0x10 } if m.BlockId != 0 { i = encodeVarintBlock(dAtA, i, uint64(m.BlockId)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *BlockData) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *BlockData) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *BlockData) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.SysUpdate { i-- if m.SysUpdate { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x38 } if m.AfterTxs != nil { { size, err := m.AfterTxs.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintBlock(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x32 } if len(m.TxFullData) > 0 { for iNdEx := len(m.TxFullData) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.TxFullData[iNdEx]) copy(dAtA[i:], m.TxFullData[iNdEx]) i = encodeVarintBlock(dAtA, i, uint64(len(m.TxFullData[iNdEx]))) i-- dAtA[i] = 0x2a } } if len(m.BinData) > 0 { i -= len(m.BinData) copy(dAtA[i:], m.BinData) i = encodeVarintBlock(dAtA, i, uint64(len(m.BinData))) i-- dAtA[i] = 0x22 } if len(m.MerkleRoot) > 0 { i -= len(m.MerkleRoot) copy(dAtA[i:], m.MerkleRoot) i = encodeVarintBlock(dAtA, i, uint64(len(m.MerkleRoot))) i-- dAtA[i] = 0x1a } if m.PrevHeader != nil { { size, err := m.PrevHeader.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintBlock(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } if m.Header != nil { { size, err := m.Header.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintBlock(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintBlock(dAtA []byte, offset int, v uint64) int { offset -= sovBlock(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *BlockHeader) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.BlockId != 0 { n += 1 + sovBlock(uint64(m.BlockId)) } if m.Timestamp != 0 { n += 1 + sovBlock(uint64(m.Timestamp)) } if m.EcosystemId != 0 { n += 1 + sovBlock(uint64(m.EcosystemId)) } if m.KeyId != 0 { n += 1 + sovBlock(uint64(m.KeyId)) } if m.NodePosition != 0 { n += 1 + sovBlock(uint64(m.NodePosition)) } l = len(m.Sign) if l > 0 { n += 1 + l + sovBlock(uint64(l)) } l = len(m.BlockHash) if l > 0 { n += 1 + l + sovBlock(uint64(l)) } l = len(m.RollbacksHash) if l > 0 { n += 1 + l + sovBlock(uint64(l)) } if m.Version != 0 { n += 1 + sovBlock(uint64(m.Version)) } if m.ConsensusMode != 0 { n += 1 + sovBlock(uint64(m.ConsensusMode)) } l = len(m.CandidateNodes) if l > 0 { n += 1 + l + sovBlock(uint64(l)) } if m.NetworkId != 0 { n += 1 + sovBlock(uint64(m.NetworkId)) } return n } func (m *BlockData) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.Header != nil { l = m.Header.Size() n += 1 + l + sovBlock(uint64(l)) } if m.PrevHeader != nil { l = m.PrevHeader.Size() n += 1 + l + sovBlock(uint64(l)) } l = len(m.MerkleRoot) if l > 0 { n += 1 + l + sovBlock(uint64(l)) } l = len(m.BinData) if l > 0 { n += 1 + l + sovBlock(uint64(l)) } if len(m.TxFullData) > 0 { for _, b := range m.TxFullData { l = len(b) n += 1 + l + sovBlock(uint64(l)) } } if m.AfterTxs != nil { l = m.AfterTxs.Size() n += 1 + l + sovBlock(uint64(l)) } if m.SysUpdate { n += 2 } return n } func sovBlock(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozBlock(x uint64) (n int) { return sovBlock(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *BlockHeader) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: BlockHeader: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: BlockHeader: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field BlockId", wireType) } m.BlockId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.BlockId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) } m.Timestamp = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Timestamp |= int64(b&0x7F) << shift if b < 0x80 { break } } case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field EcosystemId", wireType) } m.EcosystemId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.EcosystemId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field KeyId", wireType) } m.KeyId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.KeyId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field NodePosition", wireType) } m.NodePosition = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.NodePosition |= int64(b&0x7F) << shift if b < 0x80 { break } } case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Sign", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.Sign = append(m.Sign[:0], dAtA[iNdEx:postIndex]...) if m.Sign == nil { m.Sign = []byte{} } iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BlockHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.BlockHash = append(m.BlockHash[:0], dAtA[iNdEx:postIndex]...) if m.BlockHash == nil { m.BlockHash = []byte{} } iNdEx = postIndex case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RollbacksHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.RollbacksHash = append(m.RollbacksHash[:0], dAtA[iNdEx:postIndex]...) if m.RollbacksHash == nil { m.RollbacksHash = []byte{} } iNdEx = postIndex case 9: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) } m.Version = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Version |= int32(b&0x7F) << shift if b < 0x80 { break } } case 10: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ConsensusMode", wireType) } m.ConsensusMode = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.ConsensusMode |= int32(b&0x7F) << shift if b < 0x80 { break } } case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CandidateNodes", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.CandidateNodes = append(m.CandidateNodes[:0], dAtA[iNdEx:postIndex]...) if m.CandidateNodes == nil { m.CandidateNodes = []byte{} } iNdEx = postIndex case 12: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field NetworkId", wireType) } m.NetworkId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.NetworkId |= int64(b&0x7F) << shift if b < 0x80 { break } } default: iNdEx = preIndex skippy, err := skipBlock(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthBlock } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *BlockData) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: BlockData: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: BlockData: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } if m.Header == nil { m.Header = &BlockHeader{} } if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PrevHeader", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } if m.PrevHeader == nil { m.PrevHeader = &BlockHeader{} } if err := m.PrevHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field MerkleRoot", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.MerkleRoot = append(m.MerkleRoot[:0], dAtA[iNdEx:postIndex]...) if m.MerkleRoot == nil { m.MerkleRoot = []byte{} } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BinData", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.BinData = append(m.BinData[:0], dAtA[iNdEx:postIndex]...) if m.BinData == nil { m.BinData = []byte{} } iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TxFullData", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } m.TxFullData = append(m.TxFullData, make([]byte, postIndex-iNdEx)) copy(m.TxFullData[len(m.TxFullData)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AfterTxs", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthBlock } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBlock } if postIndex > l { return io.ErrUnexpectedEOF } if m.AfterTxs == nil { m.AfterTxs = &AfterTxs{} } if err := m.AfterTxs.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field SysUpdate", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBlock } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.SysUpdate = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBlock(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthBlock } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipBlock(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowBlock } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowBlock } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowBlock } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthBlock } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupBlock } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthBlock } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthBlock = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowBlock = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupBlock = fmt.Errorf("proto: unexpected end of group") ) ================================================ FILE: packages/types/block_data.go ================================================ package types import ( "bytes" "fmt" "github.com/gogo/protobuf/proto" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/pkg/errors" ) const ( minBlockSize = 9 ) var ( ErrMaxBlockSize = func(max int64, size int) error { return fmt.Errorf("block size exceeds maximum %d limit, size is %d", max, size) } ErrMinBlockSize = func(min int, size int) error { return fmt.Errorf("block size exceeds minimum %d limit, size is %d", min, size) } ErrZeroBlockSize = errors.New("Block size is zero") ErrUnmarshallBlock = errors.New("Unmarshall block") ) func blockVer(cur, prev *BlockHeader) (ret string) { if cur.Version >= consts.BvRollbackHash { ret = fmt.Sprintf(",%x", prev.RollbacksHash) } return } func (b *BlockHeader) GenHash(prev *BlockHeader, mrklRoot []byte) []byte { return crypto.DoubleHash([]byte(b.ForSha(prev, mrklRoot))) } func (b *BlockHeader) ForSha(prev *BlockHeader, mrklRoot []byte) string { return fmt.Sprintf("%d,%x,%s,%d,%d,%d,%d,%d", b.BlockId, prev.BlockHash, mrklRoot, b.Timestamp, b.EcosystemId, b.KeyId, b.NodePosition, b.NetworkId) + blockVer(b, prev) } // ForSign from 128 bytes to 512 bytes. Signature of TYPE, BLOCK_ID, PREV_BLOCK_HASH, TIME, WALLET_ID, state_id, MRKL_ROOT func (b *BlockHeader) ForSign(prev *BlockHeader, mrklRoot []byte) string { return fmt.Sprintf("0,%v,%x,%v,%v,%v,%v,%s,%d", b.BlockId, prev.BlockHash, b.Timestamp, b.EcosystemId, b.KeyId, b.NodePosition, mrklRoot, b.NetworkId) + blockVer(b, prev) } // ParseBlockHeader is parses block header func ParseBlockHeader(buf *bytes.Buffer, maxBlockSize int64) (header *BlockHeader, err error) { if int64(buf.Len()) > maxBlockSize { err = ErrMaxBlockSize(maxBlockSize, buf.Len()) return } blo := &BlockData{} if err := blo.UnmarshallBlock(buf.Bytes()); err != nil { return nil, err } return blo.Header, nil } type BlockDataOption func(b *BlockData) error func (b *BlockData) Apply(opts ...BlockDataOption) error { for _, opt := range opts { if opt == nil { continue } if err := opt(b); err != nil { return err } } return nil } func WithCurHeader(cur *BlockHeader) BlockDataOption { return func(b *BlockData) error { b.Header = cur return nil } } func WithPrevHeader(pre *BlockHeader) BlockDataOption { return func(b *BlockData) error { b.PrevHeader = pre return nil } } func WithTxFullData(data [][]byte) BlockDataOption { return func(b *BlockData) error { b.TxFullData = data return nil } } func WithAfterTxs(a *AfterTxs) BlockDataOption { return func(b *BlockData) error { b.AfterTxs = a return nil } } func WithSysUpdate(a bool) BlockDataOption { return func(b *BlockData) error { b.SysUpdate = a return nil } } func (b BlockData) ForSign() string { return b.Header.ForSign(b.PrevHeader, b.MerkleRoot) } func (b *BlockData) GenMerkleRoot() []byte { var mrklArray [][]byte for _, tr := range b.TxFullData { mrklArray = append(mrklArray, converter.BinToHex(crypto.DoubleHash(tr))) } if len(mrklArray) == 0 { mrklArray = append(mrklArray, []byte("0")) } return MerkleTreeRoot(mrklArray) } func (b *BlockData) GetSign(key []byte) ([]byte, error) { forSign := b.ForSign() signed, err := crypto.Sign(key, []byte(forSign)) if err != nil { return nil, errors.Wrap(err, "signing block") } return signed, nil } // MarshallBlock is marshalling block func (b *BlockData) MarshallBlock(key []byte) ([]byte, error) { //if b.AfterTxs != nil { // for i := 0; i < len(b.AfterTxs.TxBinLogSql); i++ { // b.AfterTxs.TxBinLogSql[i] = DoZlibCompress(b.AfterTxs.TxBinLogSql[i]) // } //} for i := 0; i < len(b.TxFullData); i++ { b.TxFullData[i] = DoZlibCompress(b.TxFullData[i]) } b.MerkleRoot = b.GenMerkleRoot() signed, err := b.GetSign(key) if err != nil { return nil, err } b.Header.Sign = signed b.Header.BlockHash = b.Header.GenHash(b.PrevHeader, b.MerkleRoot) return proto.Marshal(b) } func (b *BlockData) UnmarshallBlock(data []byte) error { if len(data) == 0 { return ErrZeroBlockSize } if len(data) < minBlockSize { return ErrMinBlockSize(len(data), minBlockSize) } if err := proto.Unmarshal(data, b); err != nil { return errors.Wrap(err, "unmarshalling block") } //if b.AfterTxs != nil { // for i := 0; i < len(b.AfterTxs.TxBinLogSql); i++ { // b.AfterTxs.TxBinLogSql[i] = DoZlibUnCompress(b.AfterTxs.TxBinLogSql[i]) // } //} for i := 0; i < len(b.TxFullData); i++ { b.TxFullData[i] = DoZlibUnCompress(b.TxFullData[i]) } b.BinData = data return nil } // MerkleTreeRoot return Merkle value func MerkleTreeRoot(dataArray [][]byte) []byte { result := make(map[int32][][]byte) for _, v := range dataArray { hash := converter.BinToHex(crypto.DoubleHash(v)) result[0] = append(result[0], hash) } var j int32 for len(result[j]) > 1 { for i := 0; i < len(result[j]); i = i + 2 { if len(result[j]) <= (i + 1) { if _, ok := result[j+1]; !ok { result[j+1] = [][]byte{result[j][i]} } else { result[j+1] = append(result[j+1], result[j][i]) } } else { if _, ok := result[j+1]; !ok { hash := crypto.DoubleHash(append(result[j][i], result[j][i+1]...)) hash = converter.BinToHex(hash) result[j+1] = [][]byte{hash} } else { hash := crypto.DoubleHash([]byte(append(result[j][i], result[j][i+1]...))) hash = converter.BinToHex(hash) result[j+1] = append(result[j+1], hash) } } } j++ } ret := result[int32(len(result)-1)] return ret[0] } type BlockCandidateNode struct { ID int64 ReplyCount int64 } ================================================ FILE: packages/types/compress.go ================================================ /*---------------------------------------------------------------- - Copyright (c) IBAX. All rights reserved. - See LICENSE in the project root for license information. ---------------------------------------------------------------*/ package types import ( "bytes" "compress/zlib" "io" ) func DoZlibCompress(src []byte) []byte { var in bytes.Buffer w := zlib.NewWriter(&in) w.Write(src) w.Close() return in.Bytes() } func DoZlibUnCompress(compressSrc []byte) []byte { b := bytes.NewReader(compressSrc) var out bytes.Buffer r, _ := zlib.NewReader(b) io.Copy(&out, r) r.Close() return out.Bytes() } ================================================ FILE: packages/types/custom_tx.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package types import ( "errors" "fmt" "regexp" "strings" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" "github.com/vmihailenco/msgpack/v5" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" ) // Transaction types. const ( FirstBlockTxType = iota + 1 StopNetworkTxType SmartContractTxType DelayTxType UtxoTxType TransferSelfTxType ) // FirstBlock is the header of first block transaction type FirstBlock struct { KeyID int64 Time int64 PublicKey []byte NodePublicKey []byte StopNetworkCertBundle []byte Test int64 PrivateBlockchain uint64 } func (t *FirstBlock) TxType() byte { return FirstBlockTxType } type StopNetwork struct { KeyID int64 Time int64 StopNetworkCert []byte } func (t *StopNetwork) TxType() byte { return StopNetworkTxType } // Header is contain header data type Header struct { ID int EcosystemID int64 KeyID int64 Time int64 NetworkID int64 PublicKey []byte } type TransferSelf struct { Value string Source string Target string } // UTXO Transfer type UTXO struct { ToID int64 Value string Comment string } // SmartTransaction is storing smart contract data type SmartTransaction struct { *Header MaxSum string PayOver string Lang string Expedite string SignedBy int64 TransferSelf *TransferSelf UTXO *UTXO Params map[string]any } func (s *SmartTransaction) TxType() byte { if s.TransferSelf != nil { return TransferSelfTxType } if s.UTXO != nil { return UtxoTxType } return SmartContractTxType } func (s *SmartTransaction) WithPrivate(privateKey []byte, internal bool) error { var ( publicKey []byte err error ) if publicKey, err = crypto.PrivateToPublic(privateKey); err != nil { log.WithFields(log.Fields{"type": consts.CryptoError, "error": err}).Error("converting node private key to public") return err } s.PublicKey = publicKey if internal { s.SignedBy = crypto.Address(publicKey) } if s.NetworkID != conf.Config.LocalConf.NetworkID { return fmt.Errorf("error networkid invalid") } return nil } func (s *SmartTransaction) Unmarshal(buffer []byte) error { return msgpack.Unmarshal(buffer, s) } func (s *SmartTransaction) Marshal() ([]byte, error) { return msgpack.Marshal(s) } func (t SmartTransaction) Hash() ([]byte, error) { b, err := t.Marshal() if err != nil { return nil, err } return crypto.DoubleHash(b), nil } func (txSmart *SmartTransaction) Validate() error { if len(txSmart.Expedite) > 0 { expedite, err := decimal.NewFromString(txSmart.Expedite) if err != nil { return fmt.Errorf("wrong expedite format") } if expedite.LessThan(decimal.Zero) { return fmt.Errorf("expedite fee must be greater than 0") } } if len(strings.TrimSpace(txSmart.Lang)) > 2 { return fmt.Errorf(`localization size is greater than 2`) } if len(txSmart.MaxSum) > 0 && converter.StrToInt64(txSmart.MaxSum) <= 0 { return fmt.Errorf(`maxsum must be greater than 0`) } if txSmart.NetworkID != conf.Config.LocalConf.NetworkID { return fmt.Errorf("error networkid invalid") } if txSmart.TransferSelf != nil { if ok, _ := regexp.MatchString("^\\d+$", txSmart.TransferSelf.Value); !ok { return errors.New("error TransferSelf Value must be a positive integer") } if value, err := decimal.NewFromString(txSmart.TransferSelf.Value); err != nil || value.LessThanOrEqual(decimal.Zero) { return errors.New("error TransferSelf Value must be greater than zero") } return nil } if txSmart.UTXO != nil { if converter.IDToAddress(txSmart.UTXO.ToID) == `invalid` { return errors.New("error UTXO ToID must be a valid address") } if ok, _ := regexp.MatchString("^\\d+$", txSmart.UTXO.Value); !ok { return errors.New("error UTXO Value must be a positive integer") } if value, err := decimal.NewFromString(txSmart.UTXO.Value); err != nil || value.LessThanOrEqual(decimal.Zero) { return errors.New("error UTXO Value must be greater than zero") } return nil } return nil } ================================================ FILE: packages/types/file.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package types //type File *Map func NewFile() *Map { return LoadMap(map[string]any{ "Name": "", "MimeType": "", "Body": []byte{}, }) } func NewFileFromMap(m map[string]any) (f *Map, ok bool) { var v any f = NewFile() if v, ok = m["Name"].(string); !ok { return } f.Set("Name", v) if v, ok = m["MimeType"].(string); !ok { return } f.Set("MimeType", v) if v, ok = m["Body"].([]byte); !ok { return } f.Set("Body", v) return } ================================================ FILE: packages/types/map.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package types import ( "encoding/json" "fmt" "sort" "strings" ) // This is the modified version of https://github.com/jabong/florest-core/ // https://github.com/jabong/florest-core/blob/master/src/common/collections/maps/linkedhashmap/linkedhashmap.go // Link represents a node of doubly linked list type Link struct { key string value any next *Link prev *Link } // Map holds the elements in go's native map, also maintains the head and tail link // to keep the elements in insertion order type Map struct { m map[string]*Link head *Link tail *Link } func newLink(key string, value any) *Link { return &Link{key: key, value: value, next: nil, prev: nil} } // NewMap instantiates a linked hash map. func NewMap() *Map { return &Map{m: make(map[string]*Link), head: nil, tail: nil} } func ConvertMap(in any) any { switch v := in.(type) { case map[string]any: out := NewMap() keys := make([]string, 0, len(v)) for key := range v { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { switch val := v[key].(type) { case json.Number: if n, err := val.Int64(); err == nil { v[key] = n } else if f, err := val.Float64(); err == nil { v[key] = f } } out.Set(key, ConvertMap(v[key])) } return out case []any: for i, item := range v { v[i] = ConvertMap(item) } } return in } // LoadMap instantiates a linked hash map and initializing it from map[string]interface{}. func LoadMap(init map[string]any) (ret *Map) { ret = NewMap() keys := make([]string, 0, len(init)) for key := range init { keys = append(keys, key) } sort.Strings(keys) for _, v := range keys { ret.Set(v, ConvertMap(init[v])) } return } // Put inserts an element into the map. func (m *Map) Set(key string, value any) { link, found := m.m[key] if !found { link = newLink(key, value) if m.tail == nil { m.head = link m.tail = link } else { m.tail.next = link link.prev = m.tail m.tail = link } m.m[key] = link } else { link.value = value } } // Get searches the element in the map by key and returns its value or nil if key doesn't exists. // Second return parameter is true if key was found, otherwise false. func (m *Map) Get(key string) (value any, found bool) { var link *Link link, found = m.m[key] if found { value = link.value } else { value = nil } return } // Remove removes the element from the map by key. func (m *Map) Remove(key string) { link, found := m.m[key] if found { delete(m.m, key) if m.head == link && m.tail == link { m.head = nil m.tail = nil } else if m.tail == link { m.tail = link.prev link.prev.next = nil } else if m.head == link { m.head = link.next link.next.prev = nil } else { link.prev.next = link.next link.next.prev = link.prev } } } // IsEmpty returns true if map does not contain any elements func (m *Map) IsEmpty() bool { return m == nil || m.Size() == 0 } // Size returns number of elements in the map. func (m *Map) Size() int { return len(m.m) } // Keys returns all keys of the map (insertion order). func (m *Map) Keys() []string { keys := make([]string, m.Size()) count := 0 for current := m.head; current != nil; current = current.next { keys[count] = current.key count++ } return keys } // Values returns all values of the map (insertion order). func (m *Map) Values() []any { values := make([]any, m.Size()) count := 0 for current := m.head; current != nil; current = current.next { values[count] = current.value count++ } return values } // Clear removes all elements from the map. func (m *Map) Clear() { m.m = make(map[string]*Link) m.head = nil m.tail = nil } // String returns a string representation of container func (m *Map) String() string { str := "map[" for current := m.head; current != nil; current = current.next { str += fmt.Sprintf("%v:%v ", current.key, current.value) } return strings.TrimRight(str, " ") + "]" } func (m *Map) MarshalJSON() ([]byte, error) { s := "{" for current := m.head; current != nil; current = current.next { k := current.key escaped := strings.Replace(k, `"`, `\"`, -1) s = s + `"` + escaped + `":` v := current.value vBytes, err := json.Marshal(v) if err != nil { return []byte{}, err } s = s + string(vBytes) + "," } if len(s) > 1 { s = s[0 : len(s)-1] } s = s + "}" return []byte(s), nil } ================================================ FILE: packages/types/mode_interfaces.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package types import ( "context" log "github.com/sirupsen/logrus" ) // ClientTxPreprocessor procees tx from client type ClientTxPreprocessor interface { //ProcessClientTranstaction([]byte, int64, *log.Entry) (string, error) ProcessClientTxBatches([][]byte, int64, *log.Entry) ([]string, error) } // SmartContractRunner run serialized contract type SmartContractRunner interface { RunContract(data, hash []byte, keyID, tnow int64, le *log.Entry) error } type DaemonFactory interface { GetDaemonsList() []string Load(context.Context) error } ================================================ FILE: packages/types/notifications.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package types type Notifications interface { AddAccounts(ecosystem int64, accounts ...string) AddRoles(ecosystem int64, roles ...int64) Size() int Send() } ================================================ FILE: packages/types/play.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: play.proto package types import ( fmt "fmt" pbgo "github.com/IBAX-io/go-ibax/packages/pbgo" proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package //AfterTxs defined block batch process tx for sql DML type AfterTxs struct { Txs []*AfterTx `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"` //TxBinLogSql defined contract exec sql for tx DML // repeated bytes tx_bin_log_sql = 2; Rts []*RollbackTx `protobuf:"bytes,3,rep,name=rts,proto3" json:"rts,omitempty"` } func (m *AfterTxs) Reset() { *m = AfterTxs{} } func (m *AfterTxs) String() string { return proto.CompactTextString(m) } func (*AfterTxs) ProtoMessage() {} func (*AfterTxs) Descriptor() ([]byte, []int) { return fileDescriptor_e999501ad2a3bf5d, []int{0} } func (m *AfterTxs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *AfterTxs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_AfterTxs.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *AfterTxs) XXX_Merge(src proto.Message) { xxx_messageInfo_AfterTxs.Merge(m, src) } func (m *AfterTxs) XXX_Size() int { return m.Size() } func (m *AfterTxs) XXX_DiscardUnknown() { xxx_messageInfo_AfterTxs.DiscardUnknown(m) } var xxx_messageInfo_AfterTxs proto.InternalMessageInfo func (m *AfterTxs) GetTxs() []*AfterTx { if m != nil { return m.Txs } return nil } func (m *AfterTxs) GetRts() []*RollbackTx { if m != nil { return m.Rts } return nil } type AfterTx struct { UsedTx []byte `protobuf:"bytes,1,opt,name=used_tx,json=usedTx,proto3" json:"used_tx,omitempty"` Lts *LogTransaction `protobuf:"bytes,2,opt,name=lts,proto3" json:"lts,omitempty"` UpdTxStatus *pbgo.TxResult `protobuf:"bytes,3,opt,name=upd_tx_status,json=updTxStatus,proto3" json:"upd_tx_status,omitempty"` } func (m *AfterTx) Reset() { *m = AfterTx{} } func (m *AfterTx) String() string { return proto.CompactTextString(m) } func (*AfterTx) ProtoMessage() {} func (*AfterTx) Descriptor() ([]byte, []int) { return fileDescriptor_e999501ad2a3bf5d, []int{1} } func (m *AfterTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *AfterTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_AfterTx.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *AfterTx) XXX_Merge(src proto.Message) { xxx_messageInfo_AfterTx.Merge(m, src) } func (m *AfterTx) XXX_Size() int { return m.Size() } func (m *AfterTx) XXX_DiscardUnknown() { xxx_messageInfo_AfterTx.DiscardUnknown(m) } var xxx_messageInfo_AfterTx proto.InternalMessageInfo func (m *AfterTx) GetUsedTx() []byte { if m != nil { return m.UsedTx } return nil } func (m *AfterTx) GetLts() *LogTransaction { if m != nil { return m.Lts } return nil } func (m *AfterTx) GetUpdTxStatus() *pbgo.TxResult { if m != nil { return m.UpdTxStatus } return nil } type RollbackTx struct { Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` BlockId int64 `protobuf:"varint,2,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` TxHash []byte `protobuf:"bytes,3,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` NameTable string `protobuf:"bytes,4,opt,name=name_table,json=nameTable,proto3" json:"name_table,omitempty"` TableId string `protobuf:"bytes,5,opt,name=table_id,json=tableId,proto3" json:"table_id,omitempty"` Data string `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` DataHash []byte `protobuf:"bytes,7,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` } func (m *RollbackTx) Reset() { *m = RollbackTx{} } func (m *RollbackTx) String() string { return proto.CompactTextString(m) } func (*RollbackTx) ProtoMessage() {} func (*RollbackTx) Descriptor() ([]byte, []int) { return fileDescriptor_e999501ad2a3bf5d, []int{2} } func (m *RollbackTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *RollbackTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_RollbackTx.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *RollbackTx) XXX_Merge(src proto.Message) { xxx_messageInfo_RollbackTx.Merge(m, src) } func (m *RollbackTx) XXX_Size() int { return m.Size() } func (m *RollbackTx) XXX_DiscardUnknown() { xxx_messageInfo_RollbackTx.DiscardUnknown(m) } var xxx_messageInfo_RollbackTx proto.InternalMessageInfo func (m *RollbackTx) GetId() int64 { if m != nil { return m.Id } return 0 } func (m *RollbackTx) GetBlockId() int64 { if m != nil { return m.BlockId } return 0 } func (m *RollbackTx) GetTxHash() []byte { if m != nil { return m.TxHash } return nil } func (m *RollbackTx) GetNameTable() string { if m != nil { return m.NameTable } return "" } func (m *RollbackTx) GetTableId() string { if m != nil { return m.TableId } return "" } func (m *RollbackTx) GetData() string { if m != nil { return m.Data } return "" } func (m *RollbackTx) GetDataHash() []byte { if m != nil { return m.DataHash } return nil } type LogTransaction struct { Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` Block int64 `protobuf:"varint,2,opt,name=block,proto3" json:"block,omitempty"` // bytes tx_data = 3; Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Address int64 `protobuf:"varint,5,opt,name=address,proto3" json:"address,omitempty"` EcosystemId int64 `protobuf:"varint,6,opt,name=ecosystem_id,json=ecosystemId,proto3" json:"ecosystem_id,omitempty"` ContractName string `protobuf:"bytes,7,opt,name=contract_name,json=contractName,proto3" json:"contract_name,omitempty"` InvokeStatus pbgo.TxInvokeStatusCode `protobuf:"varint,8,opt,name=invoke_status,json=invokeStatus,proto3,enum=pbgo.TxInvokeStatusCode" json:"invoke_status,omitempty"` } func (m *LogTransaction) Reset() { *m = LogTransaction{} } func (m *LogTransaction) String() string { return proto.CompactTextString(m) } func (*LogTransaction) ProtoMessage() {} func (*LogTransaction) Descriptor() ([]byte, []int) { return fileDescriptor_e999501ad2a3bf5d, []int{3} } func (m *LogTransaction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *LogTransaction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_LogTransaction.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *LogTransaction) XXX_Merge(src proto.Message) { xxx_messageInfo_LogTransaction.Merge(m, src) } func (m *LogTransaction) XXX_Size() int { return m.Size() } func (m *LogTransaction) XXX_DiscardUnknown() { xxx_messageInfo_LogTransaction.DiscardUnknown(m) } var xxx_messageInfo_LogTransaction proto.InternalMessageInfo func (m *LogTransaction) GetHash() []byte { if m != nil { return m.Hash } return nil } func (m *LogTransaction) GetBlock() int64 { if m != nil { return m.Block } return 0 } func (m *LogTransaction) GetTimestamp() int64 { if m != nil { return m.Timestamp } return 0 } func (m *LogTransaction) GetAddress() int64 { if m != nil { return m.Address } return 0 } func (m *LogTransaction) GetEcosystemId() int64 { if m != nil { return m.EcosystemId } return 0 } func (m *LogTransaction) GetContractName() string { if m != nil { return m.ContractName } return "" } func (m *LogTransaction) GetInvokeStatus() pbgo.TxInvokeStatusCode { if m != nil { return m.InvokeStatus } return pbgo.TxInvokeStatusCode_SUCCESS } func init() { proto.RegisterType((*AfterTxs)(nil), "types.AfterTxs") proto.RegisterType((*AfterTx)(nil), "types.AfterTx") proto.RegisterType((*RollbackTx)(nil), "types.RollbackTx") proto.RegisterType((*LogTransaction)(nil), "types.LogTransaction") } func init() { proto.RegisterFile("play.proto", fileDescriptor_e999501ad2a3bf5d) } var fileDescriptor_e999501ad2a3bf5d = []byte{ // 515 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x52, 0x3d, 0x8f, 0xd3, 0x40, 0x10, 0xbd, 0x3d, 0xdf, 0xc5, 0xc9, 0x24, 0x17, 0x89, 0x15, 0x08, 0xf3, 0x65, 0x99, 0x5c, 0x41, 0x28, 0x2e, 0x91, 0x42, 0x4d, 0x71, 0x77, 0x0d, 0x91, 0x10, 0x12, 0x8b, 0x0b, 0x44, 0x63, 0xad, 0xbd, 0x4b, 0x62, 0xc5, 0xce, 0x5a, 0xde, 0x31, 0xf2, 0x55, 0xfc, 0x05, 0xfe, 0x0f, 0x7f, 0x80, 0xf2, 0x4a, 0x4a, 0x94, 0xfc, 0x0a, 0x3a, 0xb4, 0x93, 0x84, 0x83, 0xca, 0x3b, 0xef, 0x3d, 0xcf, 0x7b, 0xb3, 0xb3, 0x00, 0x55, 0x21, 0x6f, 0x26, 0x55, 0x6d, 0xd0, 0xf0, 0x53, 0xbc, 0xa9, 0xb4, 0x7d, 0xdc, 0xc5, 0x76, 0x07, 0x8c, 0xde, 0x43, 0xf7, 0xf2, 0x33, 0xea, 0x3a, 0x6e, 0x2d, 0x8f, 0xc0, 0xc3, 0xd6, 0x06, 0x2c, 0xf2, 0xc6, 0xfd, 0xd9, 0x70, 0x42, 0xd2, 0xc9, 0x9e, 0x15, 0x8e, 0xe2, 0xe7, 0xe0, 0xd5, 0x68, 0x03, 0x8f, 0x14, 0xf7, 0xf6, 0x0a, 0x61, 0x8a, 0x22, 0x95, 0xd9, 0xca, 0x89, 0x6a, 0xb4, 0xa3, 0xaf, 0xe0, 0xef, 0x7f, 0xe2, 0x0f, 0xc1, 0x6f, 0xac, 0x56, 0x09, 0xb6, 0x01, 0x8b, 0xd8, 0x78, 0x20, 0x3a, 0xae, 0x8c, 0x5b, 0xfe, 0x02, 0xbc, 0x02, 0x6d, 0x70, 0x1c, 0xb1, 0x71, 0x7f, 0xf6, 0x60, 0xdf, 0xe8, 0xad, 0x59, 0xc4, 0xb5, 0x5c, 0x5b, 0x99, 0x61, 0x6e, 0xd6, 0xc2, 0x29, 0xf8, 0x0c, 0xce, 0x9a, 0xca, 0x35, 0x48, 0x2c, 0x4a, 0x6c, 0x9c, 0x37, 0xa3, 0x74, 0x55, 0xba, 0x30, 0x93, 0xb8, 0x15, 0xda, 0x36, 0x05, 0x8a, 0x7e, 0x53, 0xa9, 0xb8, 0xfd, 0x40, 0x92, 0xd1, 0x77, 0x06, 0x70, 0x17, 0x8a, 0x0f, 0xe1, 0x38, 0x57, 0xe4, 0xef, 0x89, 0xe3, 0x5c, 0xf1, 0x47, 0xd0, 0x4d, 0x0b, 0x93, 0xad, 0x92, 0x5c, 0x51, 0x00, 0x4f, 0xf8, 0x54, 0xcf, 0x95, 0xcb, 0x8b, 0x6d, 0xb2, 0x94, 0x76, 0x49, 0x3e, 0x03, 0xd1, 0xc1, 0xf6, 0x8d, 0xb4, 0x4b, 0xfe, 0x0c, 0x60, 0x2d, 0x4b, 0x9d, 0xa0, 0x4c, 0x0b, 0x1d, 0x9c, 0x44, 0x6c, 0xdc, 0x13, 0x3d, 0x87, 0xc4, 0x0e, 0x70, 0x2d, 0x89, 0x71, 0x2d, 0x4f, 0x89, 0xf4, 0xa9, 0x9e, 0x2b, 0xce, 0xe1, 0x44, 0x49, 0x94, 0x41, 0x87, 0x60, 0x3a, 0xf3, 0x27, 0xd0, 0x73, 0xdf, 0x9d, 0x91, 0x4f, 0x46, 0x5d, 0x07, 0x38, 0xab, 0xd1, 0x6f, 0x06, 0xc3, 0xff, 0x6f, 0xc2, 0xf5, 0x20, 0xe9, 0xee, 0x0e, 0xe9, 0xcc, 0xef, 0xc3, 0x29, 0xa5, 0xde, 0x8f, 0xb0, 0x2b, 0xf8, 0x53, 0xe8, 0x61, 0x5e, 0x6a, 0x8b, 0xb2, 0xac, 0x28, 0xa6, 0x27, 0xee, 0x00, 0x1e, 0x80, 0x2f, 0x95, 0xaa, 0xb5, 0xb5, 0x94, 0xd2, 0x13, 0x87, 0x92, 0x3f, 0x87, 0x81, 0xce, 0x8c, 0xbd, 0xb1, 0xa8, 0x4b, 0x37, 0x44, 0x87, 0xe8, 0xfe, 0x5f, 0x6c, 0xae, 0xf8, 0x39, 0x9c, 0x65, 0x66, 0x8d, 0xb5, 0xcc, 0x30, 0x71, 0x93, 0x53, 0xf0, 0x9e, 0x18, 0x1c, 0xc0, 0x77, 0xb2, 0xd4, 0xfc, 0x35, 0x9c, 0xe5, 0xeb, 0x2f, 0x66, 0xa5, 0x0f, 0xeb, 0xea, 0x46, 0x6c, 0x3c, 0x9c, 0x05, 0x87, 0x75, 0xcd, 0x89, 0xdc, 0xed, 0xe9, 0xda, 0x28, 0x2d, 0x06, 0xf9, 0x3f, 0xc8, 0xd5, 0xf5, 0x8f, 0x4d, 0xc8, 0x6e, 0x37, 0x21, 0xfb, 0xb5, 0x09, 0xd9, 0xb7, 0x6d, 0x78, 0x74, 0xbb, 0x0d, 0x8f, 0x7e, 0x6e, 0xc3, 0xa3, 0x4f, 0x2f, 0x17, 0x39, 0x2e, 0x9b, 0x74, 0x92, 0x99, 0x72, 0x3a, 0xbf, 0xba, 0xfc, 0x78, 0x91, 0x9b, 0xe9, 0xc2, 0x5c, 0xe4, 0xa9, 0x6c, 0xa7, 0x95, 0xcc, 0x56, 0x72, 0xa1, 0xed, 0x94, 0x9e, 0x51, 0xda, 0xa1, 0x97, 0xfd, 0xea, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9b, 0x9f, 0x46, 0xe8, 0xf8, 0x02, 0x00, 0x00, } func (m *AfterTxs) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *AfterTxs) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *AfterTxs) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Rts) > 0 { for iNdEx := len(m.Rts) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Rts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintPlay(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } } if len(m.Txs) > 0 { for iNdEx := len(m.Txs) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Txs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintPlay(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *AfterTx) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *AfterTx) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *AfterTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.UpdTxStatus != nil { { size, err := m.UpdTxStatus.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintPlay(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } if m.Lts != nil { { size, err := m.Lts.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintPlay(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } if len(m.UsedTx) > 0 { i -= len(m.UsedTx) copy(dAtA[i:], m.UsedTx) i = encodeVarintPlay(dAtA, i, uint64(len(m.UsedTx))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *RollbackTx) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *RollbackTx) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *RollbackTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.DataHash) > 0 { i -= len(m.DataHash) copy(dAtA[i:], m.DataHash) i = encodeVarintPlay(dAtA, i, uint64(len(m.DataHash))) i-- dAtA[i] = 0x3a } if len(m.Data) > 0 { i -= len(m.Data) copy(dAtA[i:], m.Data) i = encodeVarintPlay(dAtA, i, uint64(len(m.Data))) i-- dAtA[i] = 0x32 } if len(m.TableId) > 0 { i -= len(m.TableId) copy(dAtA[i:], m.TableId) i = encodeVarintPlay(dAtA, i, uint64(len(m.TableId))) i-- dAtA[i] = 0x2a } if len(m.NameTable) > 0 { i -= len(m.NameTable) copy(dAtA[i:], m.NameTable) i = encodeVarintPlay(dAtA, i, uint64(len(m.NameTable))) i-- dAtA[i] = 0x22 } if len(m.TxHash) > 0 { i -= len(m.TxHash) copy(dAtA[i:], m.TxHash) i = encodeVarintPlay(dAtA, i, uint64(len(m.TxHash))) i-- dAtA[i] = 0x1a } if m.BlockId != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.BlockId)) i-- dAtA[i] = 0x10 } if m.Id != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.Id)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *LogTransaction) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *LogTransaction) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *LogTransaction) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.InvokeStatus != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.InvokeStatus)) i-- dAtA[i] = 0x40 } if len(m.ContractName) > 0 { i -= len(m.ContractName) copy(dAtA[i:], m.ContractName) i = encodeVarintPlay(dAtA, i, uint64(len(m.ContractName))) i-- dAtA[i] = 0x3a } if m.EcosystemId != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.EcosystemId)) i-- dAtA[i] = 0x30 } if m.Address != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.Address)) i-- dAtA[i] = 0x28 } if m.Timestamp != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.Timestamp)) i-- dAtA[i] = 0x20 } if m.Block != 0 { i = encodeVarintPlay(dAtA, i, uint64(m.Block)) i-- dAtA[i] = 0x10 } if len(m.Hash) > 0 { i -= len(m.Hash) copy(dAtA[i:], m.Hash) i = encodeVarintPlay(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintPlay(dAtA []byte, offset int, v uint64) int { offset -= sovPlay(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *AfterTxs) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.Txs) > 0 { for _, e := range m.Txs { l = e.Size() n += 1 + l + sovPlay(uint64(l)) } } if len(m.Rts) > 0 { for _, e := range m.Rts { l = e.Size() n += 1 + l + sovPlay(uint64(l)) } } return n } func (m *AfterTx) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.UsedTx) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } if m.Lts != nil { l = m.Lts.Size() n += 1 + l + sovPlay(uint64(l)) } if m.UpdTxStatus != nil { l = m.UpdTxStatus.Size() n += 1 + l + sovPlay(uint64(l)) } return n } func (m *RollbackTx) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.Id != 0 { n += 1 + sovPlay(uint64(m.Id)) } if m.BlockId != 0 { n += 1 + sovPlay(uint64(m.BlockId)) } l = len(m.TxHash) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } l = len(m.NameTable) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } l = len(m.TableId) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } l = len(m.Data) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } l = len(m.DataHash) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } return n } func (m *LogTransaction) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Hash) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } if m.Block != 0 { n += 1 + sovPlay(uint64(m.Block)) } if m.Timestamp != 0 { n += 1 + sovPlay(uint64(m.Timestamp)) } if m.Address != 0 { n += 1 + sovPlay(uint64(m.Address)) } if m.EcosystemId != 0 { n += 1 + sovPlay(uint64(m.EcosystemId)) } l = len(m.ContractName) if l > 0 { n += 1 + l + sovPlay(uint64(l)) } if m.InvokeStatus != 0 { n += 1 + sovPlay(uint64(m.InvokeStatus)) } return n } func sovPlay(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozPlay(x uint64) (n int) { return sovPlay(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *AfterTxs) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: AfterTxs: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: AfterTxs: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Txs", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.Txs = append(m.Txs, &AfterTx{}) if err := m.Txs[len(m.Txs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Rts", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.Rts = append(m.Rts, &RollbackTx{}) if err := m.Rts[len(m.Rts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPlay(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthPlay } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *AfterTx) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: AfterTx: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: AfterTx: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UsedTx", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.UsedTx = append(m.UsedTx[:0], dAtA[iNdEx:postIndex]...) if m.UsedTx == nil { m.UsedTx = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Lts", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } if m.Lts == nil { m.Lts = &LogTransaction{} } if err := m.Lts.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UpdTxStatus", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } if m.UpdTxStatus == nil { m.UpdTxStatus = &pbgo.TxResult{} } if err := m.UpdTxStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPlay(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthPlay } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *RollbackTx) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: RollbackTx: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: RollbackTx: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } m.Id = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Id |= int64(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field BlockId", wireType) } m.BlockId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.BlockId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TxHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.TxHash = append(m.TxHash[:0], dAtA[iNdEx:postIndex]...) if m.TxHash == nil { m.TxHash = []byte{} } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NameTable", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.NameTable = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TableId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.TableId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.Data = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DataHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.DataHash = append(m.DataHash[:0], dAtA[iNdEx:postIndex]...) if m.DataHash == nil { m.DataHash = []byte{} } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPlay(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthPlay } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *LogTransaction) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: LogTransaction: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: LogTransaction: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) if m.Hash == nil { m.Hash = []byte{} } iNdEx = postIndex case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Block", wireType) } m.Block = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Block |= int64(b&0x7F) << shift if b < 0x80 { break } } case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) } m.Timestamp = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Timestamp |= int64(b&0x7F) << shift if b < 0x80 { break } } case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) } m.Address = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Address |= int64(b&0x7F) << shift if b < 0x80 { break } } case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field EcosystemId", wireType) } m.EcosystemId = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.EcosystemId |= int64(b&0x7F) << shift if b < 0x80 { break } } case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ContractName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthPlay } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthPlay } if postIndex > l { return io.ErrUnexpectedEOF } m.ContractName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 8: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field InvokeStatus", wireType) } m.InvokeStatus = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowPlay } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.InvokeStatus |= pbgo.TxInvokeStatusCode(b&0x7F) << shift if b < 0x80 { break } } default: iNdEx = preIndex skippy, err := skipPlay(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthPlay } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipPlay(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowPlay } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowPlay } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowPlay } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthPlay } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupPlay } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthPlay } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthPlay = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowPlay = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupPlay = fmt.Errorf("proto: unexpected end of group") ) ================================================ FILE: packages/utils/ban_error.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import ( "github.com/pkg/errors" ) type BanError struct { err error } func (b *BanError) Error() string { return b.err.Error() } func WithBan(err error) error { return &BanError{ err: err, } } func IsBanError(err error) bool { err = errors.Cause(err) if _, ok := err.(*BanError); ok { return true } return false } ================================================ FILE: packages/utils/ban_error_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import ( "testing" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) func TestBanError(t *testing.T) { cases := map[error]bool{ errors.New("case 1"): false, WithBan(errors.New("case 2")): true, errors.Wrap(errors.New("case 3"), "message"): false, errors.Wrap(WithBan(errors.New("case 4")), "message"): true, } for err, ok := range cases { assert.Equal(t, ok, IsBanError(err), err.Error()) } } ================================================ FILE: packages/utils/clock.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import "time" // Clock represents interface of clock type Clock interface { Now() time.Time } // ClockWrapper represents wrapper of clock type ClockWrapper struct { } // Now returns current time func (cw *ClockWrapper) Now() time.Time { return time.Now() } ================================================ FILE: packages/utils/clock_mock.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import ( "time" "github.com/stretchr/testify/mock" ) // MockClock is an autogenerated mock type for the Clock type type MockClock struct { mock.Mock } // Now provides a mock function with given fields: func (_m *MockClock) Now() time.Time { ret := _m.Called() var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() } else { r0 = ret.Get(0).(time.Time) } return r0 } ================================================ FILE: packages/utils/metric/collector.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package metric import ( "github.com/IBAX-io/go-ibax/packages/types" ) // CollectorFunc represents function for collects values of metrics type CollectorFunc func(int64) ([]*Value, error) // Value represents value of metrics type Value struct { Time int64 Metric string Key string Value int64 } // ToMap returns values as map func (v *Value) ToMap() *types.Map { return types.LoadMap(map[string]any{ "time": v.Time, "metric": v.Metric, "key": v.Key, "value": v.Value, }) } // Collector represents struct that works with the collection of metrics type Collector struct { funcs []CollectorFunc } // Values returns values of all metrics func (c *Collector) Values(timeBlock int64) []any { values := make([]any, 0) for _, fn := range c.funcs { result, err := fn(timeBlock) if err != nil { continue } for _, v := range result { values = append(values, v.ToMap()) } } return values } // NewCollector creates new collector func NewCollector(funcs ...CollectorFunc) *Collector { c := &Collector{} c.funcs = make([]CollectorFunc, 0, len(funcs)) c.funcs = append(c.funcs, funcs...) return c } ================================================ FILE: packages/utils/metric/collector_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package metric import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func MockValue(v int64) *Value { return &Value{Time: 1, Metric: "test_metric", Key: "ecosystem_1", Value: v} } func MockCollectorFunc(v int64, err error) CollectorFunc { return func() ([]*Value, error) { if err != nil { return nil, err } return []*Value{MockValue(v)}, nil } } func TestValue(t *testing.T) { value := MockValue(100) result := map[string]any{"time": int64(1), "metric": "test_metric", "key": "ecosystem_1", "value": int64(100)} assert.Equal(t, result, value.ToMap()) } func TestCollector(t *testing.T) { c := NewCollector( MockCollectorFunc(100, nil), MockCollectorFunc(0, errors.New("Test")), MockCollectorFunc(200, nil), ) result := []any{ map[string]any{"time": int64(1), "metric": "test_metric", "key": "ecosystem_1", "value": int64(100)}, map[string]any{"time": int64(1), "metric": "test_metric", "key": "ecosystem_1", "value": int64(200)}, } assert.Equal(t, result, c.Values()) } ================================================ FILE: packages/utils/metric/metrics.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package metric import ( "strconv" "time" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/storage/sqldb" log "github.com/sirupsen/logrus" ) const ( metricEcosystemPages = "ecosystem_pages" metricEcosystemMembers = "ecosystem_members" metricEcosystemTx = "ecosystem_tx" ) // CollectMetricDataForEcosystemTables returns metrics for some tables of ecosystems func CollectMetricDataForEcosystemTables(timeBlock int64) (metricValues []*Value, err error) { stateIDs, _, err := sqldb.GetAllSystemStatesIDs() if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("get all system states ids") return nil, err } now := time.Unix(timeBlock, 0) unixDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix() for _, stateID := range stateIDs { var pagesCount, membersCount int64 tablePrefix := strconv.FormatInt(stateID, 10) p := &sqldb.Page{} p.SetTablePrefix(tablePrefix) if pagesCount, err = p.Count(); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("get count of pages") return nil, err } metricValues = append(metricValues, &Value{ Time: unixDate, Metric: metricEcosystemPages, Key: tablePrefix, Value: pagesCount, }) m := &sqldb.Member{} m.SetTablePrefix(tablePrefix) if membersCount, err = m.Count(); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("get count of members") return nil, err } metricValues = append(metricValues, &Value{ Time: unixDate, Metric: metricEcosystemMembers, Key: tablePrefix, Value: membersCount, }) } return metricValues, nil } // CollectMetricDataForEcosystemTx returns metrics for transactions of ecosystems func CollectMetricDataForEcosystemTx(timeBlock int64) (metricValues []*Value, err error) { ecosystemTx, err := sqldb.GetEcosystemTxPerDay(timeBlock) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.DBError}).Error("get ecosystem transactions by period") return nil, err } for _, item := range ecosystemTx { if len(item.Ecosystem) == 0 { continue } metricValues = append(metricValues, &Value{ Time: item.UnixTime, Metric: metricEcosystemTx, Key: item.Ecosystem, Value: item.Count, }) } return metricValues, nil } ================================================ FILE: packages/utils/ntp.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import ( "net" "sort" "time" ) const ( ntpPool = "pool.ntp.org" // ntpPool is the NTP server to query for the current time ntpChecks = 3 // Number of measurements to do against the NTP server driftThreshold = 1 * time.Second // Allowed clock drift before warning user ) // durationSlice attaches the methods of sort.Interface to []time.Duration, // sorting in increasing order. type durationSlice []time.Duration func (s durationSlice) Len() int { return len(s) } func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] } func (s durationSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // CheckClockDrift queries an NTP server for clock drifts and warns the user if // one large enough is detected. func CheckClockDrift() (bool, error) { drift, err := sntpDrift(ntpChecks) if err != nil { return false, err } //fmt.Println("drift:"+ strconv.FormatInt(drift.Milliseconds(),10)) if drift < -driftThreshold || drift > driftThreshold { return false, nil } return true, nil } // sntpDrift does a naive time resolution against an NTP server and returns the // measured drift. This method uses the simple version of NTP. It's not precise // but should be fine for these purposes. // // Note, it executes two extra measurements compared to the number of requested // ones to be able to discard the two extremes as outliers. func sntpDrift(measurements int) (time.Duration, error) { // Resolve the address of the NTP server addr, err := net.ResolveUDPAddr("udp", ntpPool+":123") if err != nil { return 0, err } // Construct the time request (empty package with only 2 fields set): // Bits 3-5: Protocol version, 3 // Bits 6-8: Mode of operation, client, 3 request := make([]byte, 48) request[0] = 3<<3 | 3 // Execute each of the measurements var drifts []time.Duration for i := 0; i < measurements+2; i++ { // Dial the NTP server and send the time retrieval request conn, err := net.DialUDP("udp", nil, addr) if err != nil { return 0, err } defer conn.Close() sent := time.Now() if _, err = conn.Write(request); err != nil { return 0, err } // Retrieve the reply and calculate the elapsed time conn.SetDeadline(time.Now().Add(5 * time.Second)) reply := make([]byte, 48) if _, err = conn.Read(reply); err != nil { return 0, err } elapsed := time.Since(sent) // Reconstruct the time from the reply data sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24 frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24 nanosec := sec*1e9 + (frac*1e9)>>32 t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local() // Calculate the drift based on an assumed answer time of RRT/2 drifts = append(drifts, sent.Sub(t)+elapsed/2) } // Calculate average drif (drop two extremities to avoid outliers) sort.Sort(durationSlice(drifts)) drift := time.Duration(0) for i := 1; i < len(drifts)-1; i++ { drift += drifts[i] } return drift / time.Duration(measurements), nil } ================================================ FILE: packages/utils/utils.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import ( "context" "encoding/hex" "fmt" "io" "math/rand" "net/http" "os" "os/exec" "path/filepath" "reflect" "runtime" "strings" "time" "unicode" "github.com/IBAX-io/go-ibax/packages/common/crypto" "github.com/IBAX-io/go-ibax/packages/conf" "github.com/IBAX-io/go-ibax/packages/conf/syspar" "github.com/IBAX-io/go-ibax/packages/consts" "github.com/IBAX-io/go-ibax/packages/converter" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/theckman/go-flock" ) var ( // ReturnCh is chan for returns ReturnCh chan string // CancelFunc is represents cancel func CancelFunc context.CancelFunc // DaemonsCount is number of daemons DaemonsCount int ) // GetHTTPTextAnswer returns HTTP answer as a string func GetHTTPTextAnswer(url string) (string, error) { resp, err := http.Get(url) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "url": url}).Error("cannot get url") return "", err } defer resp.Body.Close() htmlData, err := io.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError}).Error("cannot read response body") return "", err } if resp.StatusCode == http.StatusNotFound { err = fmt.Errorf(`404`) } return string(htmlData), err } // ErrInfoFmt fomats the error message func ErrInfoFmt(err string, a ...any) error { return fmt.Errorf("%s (%s)", fmt.Sprintf(err, a...), Caller(1)) } // ErrInfo formats the error message func ErrInfo(verr any, additionally ...string) error { var err error switch verr.(type) { case error: err = verr.(error) case string: err = errors.New(verr.(string)) } if err != nil { if len(additionally) > 0 { return fmt.Errorf("%s # %s (%s)", err, additionally, Caller(1)) } return fmt.Errorf("%s (%s)", err, Caller(1)) } return err } // CallMethod calls the function by its name func CallMethod(i any, methodName string) any { var ptr reflect.Value var value reflect.Value var finalMethod reflect.Value value = reflect.ValueOf(i) // if we start with a pointer, we need to get value pointed to // if we start with a value, we need to get a pointer to that value if value.Type().Kind() == reflect.Ptr { ptr = value value = ptr.Elem() } else { ptr = reflect.New(reflect.TypeOf(i)) temp := ptr.Elem() temp.Set(value) } // check for method on value method := value.MethodByName(methodName) if method.IsValid() { finalMethod = method } // check for method on pointer method = ptr.MethodByName(methodName) if method.IsValid() { finalMethod = method } if finalMethod.IsValid() { return finalMethod.Call([]reflect.Value{})[0].Interface() } // return or panic, method not found of either type log.WithFields(log.Fields{"method_name": methodName, "type": consts.NotFound}).Error("method not found") return fmt.Errorf("method %s not found", methodName) } // Caller returns the name of the latest function func Caller(steps int) string { name := "?" if pc, _, num, ok := runtime.Caller(steps + 1); ok { name = fmt.Sprintf("%s : %d", filepath.Base(runtime.FuncForPC(pc).Name()), num) } return name } // CopyFileContents copy files func CopyFileContents(src, dst string) error { in, err := os.Open(src) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "file_name": src}).Error("opening file") return ErrInfo(err) } defer in.Close() out, err := os.Create(dst) if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "file_name": dst}).Error("creating file") return ErrInfo(err) } defer func() { cerr := out.Close() if err == nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "file_name": dst}).Error("closing file") err = cerr } }() if _, err = io.Copy(out, in); err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "from_file": src, "to_file": dst}).Error("copying from to") return ErrInfo(err) } err = out.Sync() if err != nil { log.WithFields(log.Fields{"error": err, "type": consts.IOError, "file_name": dst}).Error("syncing file") } return ErrInfo(err) } // CheckSign checks the signature func CheckSign(publicKeys [][]byte, forSign []byte, signs []byte, nodeKeyOrLogin bool) (bool, error) { defer func() { if r := recover(); r != nil { log.WithFields(log.Fields{"type": consts.PanicRecoveredError, "error": r}).Error("recovered panic in check sign") } }() var signsSlice [][]byte if len(forSign) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("for sign is empty") return false, ErrInfoFmt("len(forSign) == 0") } if len(publicKeys) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("public keys is empty") return false, ErrInfoFmt("len(publicKeys) == 0") } if len(signs) == 0 { log.WithFields(log.Fields{"type": consts.EmptyObject}).Error("signs is empty") return false, ErrInfoFmt("len(signs) == 0") } // node always has only one signature if nodeKeyOrLogin { signsSlice = append(signsSlice, signs) } else { length, err := converter.DecodeLength(&signs) if err != nil { log.WithFields(log.Fields{"type": consts.UnmarshallingError, "error": err}).Error("decoding signs length") return false, err } if length > 0 { signsSlice = append(signsSlice, converter.BytesShift(&signs, length)) } if len(publicKeys) != len(signsSlice) { log.WithFields(log.Fields{"public_keys_length": len(publicKeys), "signs_length": len(signsSlice), "type": consts.SizeDoesNotMatch}).Error("public keys and signs slices lengths does not match") return false, fmt.Errorf("sign error publicKeys length %d != signsSlice length %d", len(publicKeys), len(signsSlice)) } } return crypto.Verify(publicKeys[0], forSign, signsSlice[0]) } // GetCurrentDir returns the current directory func GetCurrentDir() string { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Warning("getting current dir") return "." } return dir } // ShellExecute runs cmdline func ShellExecute(cmdline string) { time.Sleep(500 * time.Millisecond) switch runtime.GOOS { case "linux": exec.Command("xdg-open", cmdline).Start() case "windows": exec.Command(`rundll32.exe`, `url.dll,FileProtocolHandler`, cmdline).Start() case "darwin": exec.Command("open", cmdline).Start() } } // GetParent returns the information where the call of function happened func GetParent() string { parent := "" for i := 2; ; i++ { var name string if pc, _, num, ok := runtime.Caller(i); ok { name = filepath.Base(runtime.FuncForPC(pc).Name()) file, line := runtime.FuncForPC(pc).FileLine(pc) if i > 5 || name == "runtime.goexit" { break } else { parent += fmt.Sprintf("%s:%d -> %s:%d / ", filepath.Base(file), line, name, num) } } } return parent } // GetNodeKeys returns node private key and public key func GetNodeKeys() (string, string) { return hex.EncodeToString(syspar.GetNodePrivKey()), hex.EncodeToString(syspar.GetNodePubKey()) } func GetNodePrivateKey() ([]byte, error) { data, err := os.ReadFile(filepath.Join(conf.Config.DirPathConf.KeysDir, consts.NodePrivateKeyFilename)) if err != nil { log.WithFields(log.Fields{"type": consts.IOError, "error": err}).Error("reading node private key from file") return nil, err } privateKey, err := hex.DecodeString(string(data)) if err != nil { log.WithFields(log.Fields{"type": consts.ConversionError, "error": err}).Error("decoding private key from hex") return nil, err } return privateKey, nil } func GetHostPort(h string) string { if strings.Contains(h, ":") { return h } return fmt.Sprintf("%s:%d", h, consts.DefaultTcpPort) } func CreateDirIfNotExists(dir string, mode os.FileMode) error { if _, err := os.Stat(dir); os.IsNotExist(err) { err := os.Mkdir(dir, mode) if err != nil { return errors.Wrapf(err, "creating dir %s", dir) } } return nil } func LockOrDie(dir string) *flock.Flock { f := flock.New(dir) success, err := f.TryLock() if err != nil { log.WithError(err).Fatal("Locking go-ibax") } if !success { log.Fatal("Go-ibax is locked") } return f } func ShuffleSlice(slice []string) { for i := range slice { j := rand.Intn(i + 1) slice[i], slice[j] = slice[j], slice[i] } } // MakeDirectory makes directory if is not exists func MakeDirectory(dir string) error { if _, err := os.Stat(dir); err != nil { if os.IsNotExist(err) { return os.Mkdir(dir, 0775) } return err } return nil } func StringInSlice(slice []string, v string) bool { for _, item := range slice { if v == item { return true } } return false } func ToSnakeCase(s string) string { var ( in = []rune(s) out = make([]rune, 0, len(in)) ) for i, c := range in { if unicode.IsUpper(c) { if i > 0 && ((i+1 < len(in) && unicode.IsLower(in[i+1])) || unicode.IsLower(in[i-1])) { out = append(out, '_') } c = unicode.ToLower(c) } out = append(out, c) } return string(out) } ================================================ FILE: packages/utils/utils_test.go ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) IBAX. All rights reserved. * See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ package utils import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" ) func TestToSnakeCase(t *testing.T) { cases := []struct { arg, expected string }{ {"Contains", "contains"}, {"AddressToId", "address_to_id"}, {"HMac", "h_mac"}, {"JSONEncode", "json_encode"}, {"Hash", "hash"}, {"PubToID", "pub_to_id"}, } for _, tt := range cases { assert.Equal(t, tt.expected, ToSnakeCase(tt.arg)) } } func TestNtp(t *testing.T) { for i := 0; i < 1000; i++ { st := time.Now() b, err := CheckClockDrift() et := time.Now() dr := et.Sub(st) fmt.Println("dr:" + dr.String()) assert.Error(t, err, nil) if b { fmt.Println("time ok") } else { fmt.Println("time not ok") } time.Sleep(2 * time.Second) } } ================================================ FILE: tools/desync_monitor/config/config.go ================================================ package config import ( "github.com/BurntSushi/toml" ) type Daemon struct { DaemonMode bool `toml:"daemon"` QueryingPeriod int `toml:"querying_period"` } type Config struct { Daemon Daemon `toml:"daemon"` NodesList []string `toml:"nodes_list"` } func (c *Config) Read(fileName string) error { _, err := toml.DecodeFile(fileName, c) return err } ================================================ FILE: tools/desync_monitor/config.toml.temp ================================================ nodes_list = [ # "http://127.0.0.1:7079", # "http://127.0.0.1:2079", # "http://127.0.0.1:3079", ] [Daemon] Daemon = true querying_period = 4 ================================================ FILE: tools/desync_monitor/main.go ================================================ package main import ( "flag" "fmt" "math" "strings" "time" "github.com/IBAX-io/go-ibax/tools/desync_monitor/config" "github.com/IBAX-io/go-ibax/tools/desync_monitor/query" log "github.com/sirupsen/logrus" ) const confPathFlagName = "confPath" const nodesListFlagName = "nodesList" const daemonModeFlagName = "daemonMode" const queryingPeriodFlagName = "queryingPeriod" var configPath = flag.String(confPathFlagName, "config.toml", "path to desync monitor config") var nodesList = flag.String(nodesListFlagName, "127.0.0.1:7079", "which nodes to query, in format url1,url2,url3") var daemonMode = flag.Bool(daemonModeFlagName, false, "start as daemon") var queryingPeriod = flag.Int(queryingPeriodFlagName, 1, "period of querying nodes in seconds, if started as daemon") func minElement(slice []int64) int64 { var min int64 = math.MaxInt64 for _, blockID := range slice { if blockID < min { min = blockID } } return min } func flagsOverrideConfig(conf *config.Config) { flag.Visit(func(flag *flag.Flag) { switch flag.Name { case nodesListFlagName: nodesList := strings.Split(*nodesList, ",") conf.NodesList = nodesList case daemonModeFlagName: conf.Daemon.DaemonMode = *daemonMode case queryingPeriodFlagName: conf.Daemon.QueryingPeriod = *queryingPeriod } }) } func monitor(conf *config.Config) { maxBlockIDs, err := query.MaxBlockIDs(conf.NodesList) if err != nil { log.WithFields(log.Fields{"err": err}).Error("on sending max block request") return } log.Infoln("max blocks ", maxBlockIDs) blockInfos, err := query.BlockInfo(conf.NodesList, minElement(maxBlockIDs)) if err != nil { log.WithFields(log.Fields{"err": err}).Error("on sending block info request") return } hash2Node := map[string][]string{} for node, blockInfo := range blockInfos { rollbacksHash := fmt.Sprintf("%d: %x", blockInfo.BlockID, blockInfo.RollbacksHash) if _, ok := hash2Node[rollbacksHash]; !ok { hash2Node[rollbacksHash] = []string{} } hash2Node[rollbacksHash] = append(hash2Node[rollbacksHash], node) } log.Infof("requested nodes: %v", conf.NodesList) if len(hash2Node) <= 1 { log.Infoln("nodes synced") return } hash2NodeStrResults := []string{} for k, v := range hash2Node { hash2NodeStrResults = append(hash2NodeStrResults, fmt.Sprintf("%s: %s", k, v)) } log.Infof("nodes unsynced. Rollback hashes are: %s", strings.Join(hash2NodeStrResults, ", ")) } func main() { flag.Parse() conf := &config.Config{} if err := conf.Read(*configPath); err != nil { log.WithError(err).Fatal("reading config") } flagsOverrideConfig(conf) if conf.Daemon.DaemonMode { log.Infoln("MODE: daemon") ticker := time.NewTicker(time.Second * time.Duration(conf.Daemon.QueryingPeriod)) for range ticker.C { monitor(conf) } } else { log.Println("MODE: single request") monitor(conf) } } ================================================ FILE: tools/desync_monitor/query/query.go ================================================ package query import ( "fmt" "sync" ) const maxBlockIDEndpoint = "/api/v2/maxblockid" const blockInfoEndpoint = "/api/v2/block/%d" type MaxBlockID struct { MaxBlockID int64 `json:"max_block_id"` } type blockInfoResult struct { BlockID int64 `json:"block_id"` Hash []byte `json:"hash"` EcosystemID int64 `json:"ecosystem_id"` KeyID int64 `json:"key_id"` Time int64 `json:"time"` Tx int32 `json:"tx_count"` RollbacksHash []byte `json:"rollbacks_hash"` } func MaxBlockIDs(nodesList []string) ([]int64, error) { wg := sync.WaitGroup{} workResults := ConcurrentMap{m: map[string]any{}} for _, nodeUrl := range nodesList { wg.Add(1) go func(url string) { defer wg.Done() maxBlockID := &MaxBlockID{} if err := sendGetRequest(url+maxBlockIDEndpoint, maxBlockID); err != nil { workResults.Set(url, err) return } workResults.Set(url, maxBlockID.MaxBlockID) }(nodeUrl) } wg.Wait() maxBlockIds := []int64{} for _, result := range workResults.m { switch res := result.(type) { case int64: maxBlockIds = append(maxBlockIds, res) case error: return nil, res } } return maxBlockIds, nil } func BlockInfo(nodesList []string, blockID int64) (map[string]*blockInfoResult, error) { wg := sync.WaitGroup{} workResults := ConcurrentMap{m: map[string]any{}} for _, nodeUrl := range nodesList { wg.Add(1) go func(url string) { defer wg.Done() blockInfo := &blockInfoResult{BlockID: blockID} if err := sendGetRequest(url+fmt.Sprintf(blockInfoEndpoint, blockID), blockInfo); err != nil { workResults.Set(url, err) return } workResults.Set(url, blockInfo) }(nodeUrl) } wg.Wait() result := map[string]*blockInfoResult{} for nodeUrl, blockInfoOrError := range workResults.m { switch res := blockInfoOrError.(type) { case error: return nil, res case *blockInfoResult: result[nodeUrl] = res } } return result, nil } ================================================ FILE: tools/desync_monitor/query/utils.go ================================================ package query import ( "encoding/json" "fmt" "io" "net/http" "sync" log "github.com/sirupsen/logrus" ) type ConcurrentMap struct { m map[string]any mu sync.RWMutex } func (c *ConcurrentMap) Set(key string, value any) { c.mu.Lock() defer c.mu.Unlock() c.m[key] = value } func (c *ConcurrentMap) Get(key string) (bool, any) { c.mu.RLock() defer c.mu.RUnlock() res, ok := c.m[key] return ok, res } func sendGetRequest(url string, v any) error { resp, err := http.Get(url) if err != nil { log.WithFields(log.Fields{"url": url, "error": err}).Error("get requesting url") return err } if resp.StatusCode != http.StatusOK { err := fmt.Errorf("status code is not OK %d", resp.StatusCode) log.WithFields(log.Fields{"url": url, "error": err}).Error("incorrect status code") return err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{"url": url, "error": err}).Error("reading response body") return err } if err := json.Unmarshal(data, v); err != nil { log.WithFields(log.Fields{"data": string(data), "error": err}).Error("unmarshalling json to struct") return err } return nil }