From 240ad0a33c11f567807d365d6fc6e0ffeef20fc7 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 12:46:57 +0300 Subject: [PATCH 01/34] add staking contracts abi --- .../explorer/validator/metadata_retriever.ex | 2 +- .../poa}/metadata.json | 0 .../poa}/validators.json | 0 .../priv/contracts_abi/pos/staking.json | 925 ++++++++++++++++++ .../priv/contracts_abi/pos/validators.json | 492 ++++++++++ 5 files changed, 1418 insertions(+), 1 deletion(-) rename apps/explorer/priv/{validator_contracts_abi => contracts_abi/poa}/metadata.json (100%) rename apps/explorer/priv/{validator_contracts_abi => contracts_abi/poa}/validators.json (100%) create mode 100644 apps/explorer/priv/contracts_abi/pos/staking.json create mode 100644 apps/explorer/priv/contracts_abi/pos/validators.json diff --git a/apps/explorer/lib/explorer/validator/metadata_retriever.ex b/apps/explorer/lib/explorer/validator/metadata_retriever.ex index 682b1e906e..4d262fca61 100644 --- a/apps/explorer/lib/explorer/validator/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/validator/metadata_retriever.ex @@ -69,7 +69,7 @@ defmodule Explorer.Validator.MetadataRetriever do # sobelow_skip ["Traversal"] defp contract_abi(file_name) do :explorer - |> Application.app_dir("priv/validator_contracts_abi/#{file_name}") + |> Application.app_dir("priv/contracts_abi/poa/#{file_name}") |> File.read!() |> Jason.decode!() end diff --git a/apps/explorer/priv/validator_contracts_abi/metadata.json b/apps/explorer/priv/contracts_abi/poa/metadata.json similarity index 100% rename from apps/explorer/priv/validator_contracts_abi/metadata.json rename to apps/explorer/priv/contracts_abi/poa/metadata.json diff --git a/apps/explorer/priv/validator_contracts_abi/validators.json b/apps/explorer/priv/contracts_abi/poa/validators.json similarity index 100% rename from apps/explorer/priv/validator_contracts_abi/validators.json rename to apps/explorer/priv/contracts_abi/poa/validators.json diff --git a/apps/explorer/priv/contracts_abi/pos/staking.json b/apps/explorer/priv/contracts_abi/pos/staking.json new file mode 100644 index 0000000000..7bcbcfb18c --- /dev/null +++ b/apps/explorer/priv/contracts_abi/pos/staking.json @@ -0,0 +1,925 @@ +[ + { + "constant": true, + "inputs": [], + "name": "STAKE_UNIT", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MAX_DELEGATORS_PER_POOL", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MAX_CANDIDATES", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "fromPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "toPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Staked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "fromPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "toPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "StakeMoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "fromPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "int256" + } + ], + "name": "WithdrawalOrdered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "fromPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_unremovableStakingAddress", + "type": "address" + } + ], + "name": "clearUnremovableValidator", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "incrementStakingEpoch", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "removePool", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "removePool", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_fromPoolStakingAddress", + "type": "address" + }, + { + "name": "_toPoolStakingAddress", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "moveStake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_toPoolStakingAddress", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "stake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_fromPoolStakingAddress", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_amount", + "type": "int256" + } + ], + "name": "orderWithdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + } + ], + "name": "claimOrderedWithdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_erc20TokenContract", + "type": "address" + } + ], + "name": "setErc20TokenContract", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_minStake", + "type": "uint256" + } + ], + "name": "setCandidateMinStake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_minStake", + "type": "uint256" + } + ], + "name": "setDelegatorMinStake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPools", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPoolsInactive", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPoolsLikelihood", + "outputs": [ + { + "name": "likelihoods", + "type": "int256[]" + }, + { + "name": "sum", + "type": "int256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPoolsToBeElected", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPoolsToBeRemoved", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "areStakeAndWithdrawAllowed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "erc20TokenContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCandidateMinStake", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getDelegatorMinStake", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "isPoolActive", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "maxWithdrawAllowed", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "maxWithdrawOrderAllowed", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "bytes" + } + ], + "name": "onTokenTransfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "orderedWithdrawAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + } + ], + "name": "orderedWithdrawAmountTotal", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "orderWithdrawEpoch", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + } + ], + "name": "stakeAmountTotal", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + } + ], + "name": "poolDelegators", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_delegator", + "type": "address" + } + ], + "name": "poolDelegatorIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_delegator", + "type": "address" + } + ], + "name": "poolDelegatorInactiveIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "poolIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "poolInactiveIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "poolToBeElectedIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "poolToBeRemovedIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "stakeAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "stakeAmountByCurrentEpoch", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "stakeAmountMinusOrderedWithdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + } + ], + "name": "stakeAmountTotalMinusOrderedWithdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stakingEpoch", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "validatorSetContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/apps/explorer/priv/contracts_abi/pos/validators.json b/apps/explorer/priv/contracts_abi/pos/validators.json new file mode 100644 index 0000000000..0f0fd038c6 --- /dev/null +++ b/apps/explorer/priv/contracts_abi/pos/validators.json @@ -0,0 +1,492 @@ +[ + { + "constant": false, + "inputs": [], + "name": "newValidatorSet", + "outputs": [ + { + "name": "", + "type": "bool" + }, + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MAX_VALIDATORS", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "indexed": true, + "name": "parentHash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "newSet", + "type": "address[]" + } + ], + "name": "InitiateChange", + "type": "event", + "anonymous": false + }, + { + "inputs": [], + "name": "clearUnremovableValidator", + "type": "function", + "constant": false, + "outputs": [], + "payable": false, + "stateMutability": "nonpayable" + }, + { + "inputs": [], + "name": "emitInitiateChange", + "type": "function", + "constant": false, + "outputs": [], + "payable": false, + "stateMutability": "nonpayable" + }, + { + "inputs": [], + "name": "finalizeChange", + "type": "function", + "constant": false, + "outputs": [], + "payable": false, + "stateMutability": "nonpayable" + }, + { + "inputs": [ + { + "name": "_blockRewardContract", + "type": "address" + }, + { + "name": "_randomContract", + "type": "address" + }, + { + "name": "_stakingContract", + "type": "address" + }, + { + "name": "_initialMiningAddresses", + "type": "address[]" + }, + { + "name": "_initialStakingAddresses", + "type": "address[]" + }, + { + "name": "_firstValidatorIsUnremovable", + "type": "bool" + } + ], + "name": "initialize", + "type": "function", + "constant": false, + "outputs": [], + "payable": false, + "stateMutability": "nonpayable" + }, + { + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + }, + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "setStakingAddress", + "type": "function", + "constant": false, + "outputs": [], + "payable": false, + "stateMutability": "nonpayable" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "banCounter", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "bannedUntil", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "blockRewardContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "changeRequestCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "emitInitiateChangeCallable", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPreviousValidators", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPendingValidators", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getQueueValidators", + "outputs": [ + { + "name": "", + "type": "address[]" + }, + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getValidators", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "initiateChangeAllowed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "isReportValidatorValid", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "isValidator", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "isValidatorOnPreviousEpoch", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "isValidatorBanned", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "miningByStakingAddress", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "randomContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "stakingByMiningAddress", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stakingContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "unremovableValidator", + "outputs": [ + { + "name": "stakingAddress", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "validatorCounter", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "validatorIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "validatorSetApplyBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file From 2be2577e825081f353239df83322a150b9ef3d72 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 12:50:41 +0300 Subject: [PATCH 02/34] add staking pools reader --- .../lib/explorer/staking/pools_reader.ex | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 apps/explorer/lib/explorer/staking/pools_reader.ex diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex new file mode 100644 index 0000000000..0aaffaf8e9 --- /dev/null +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -0,0 +1,84 @@ +defmodule Explorer.Staking.PoolsReader do + @moduledoc """ + Reads staking pools using Smart Contract functions from the blockchain. + """ + alias Explorer.SmartContract.Reader + + @spec get_pools() :: [String.t()] + def get_pools do + get_active_pools() ++ get_inactive_pools() + end + + @spec get_active_pools() :: [String.t()] + def get_active_pools do + {:ok, [active_pools]} = call_staking_method("getPools", []) + active_pools + end + + @spec get_inactive_pools() :: [String.t()] + def get_inactive_pools do + {:ok, [inactive_pools]} = call_staking_method("getPoolsInactive", []) + inactive_pools + end + + @spec pool_data(String.t()) :: {:ok, map()} | :error + def pool_data(staking_address) do + with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]), + {:ok, [is_active]} <- call_staking_method("isPoolActive", [staking_address]), + {:ok, [delegator_addresses]} <- call_staking_method("poolDelegators", [staking_address]), + {:ok, [staked_amount]} <- call_staking_method("stakeAmountTotalMinusOrderedWithdraw", [staking_address]), + {:ok, [is_validator]} <- call_validators_method("isValidator", [mining_address]), + {:ok, [was_validator_count]} <- call_validators_method("validatorCounter", [mining_address]), + {:ok, [is_banned]} <- call_validators_method("isValidatorBanned", [mining_address]), + {:ok, [banned_unitil]} <- call_validators_method("bannedUntil", [mining_address]), + {:ok, [was_banned_count]} <- call_validators_method("banCounter", [mining_address]) do + { + :ok, + %{ + staking_address: staking_address, + mining_address: mining_address, + is_active: is_active, + delegator_addresses: delegator_addresses, + staked_amount: staked_amount, + is_validator: is_validator, + was_validator_count: was_validator_count, + is_banned: is_banned, + banned_unitil: banned_unitil, + was_banned_count: was_banned_count + } + } + else + _ -> + :error + end + end + + defp call_staking_method(method, params) do + %{^method => resp} = + Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{ + method => params + }) + + resp + end + + defp call_validators_method(method, params) do + %{^method => resp} = + Reader.query_contract(config(:validators_contract_address), abi("validators.json"), %{ + method => params + }) + + resp + end + + defp config(key) do + Application.get_env(:explorer, __MODULE__, [])[key] + end + + defp abi(file_name) do + :explorer + |> Application.app_dir("priv/contracts_abi/pos/#{file_name}") + |> File.read!() + |> Jason.decode!() + end +end From e9c61945e6cc65e70ae190d32534398731053ec1 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 12:51:13 +0300 Subject: [PATCH 03/34] add staking pools fetcher --- apps/indexer/config/config.exs | 1 + apps/indexer/lib/indexer/block/fetcher.ex | 7 +- .../lib/indexer/block/realtime/fetcher.ex | 4 +- .../lib/indexer/fetcher/staking_pools.ex | 135 ++++++++++++++++++ apps/indexer/lib/indexer/supervisor.ex | 4 +- 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/staking_pools.ex diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 181323c0a3..04c2edf7c8 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -38,6 +38,7 @@ config :indexer, # config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true # config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true +config :indexer, Indexer.Fetcher.StakingPools.Supervisor, disabled?: true config :indexer, Indexer.Tracer, service: :indexer, diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 241142f4f6..9f9a9474f4 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -22,7 +22,8 @@ defmodule Indexer.Block.Fetcher do ReplacedTransaction, Token, TokenBalance, - UncleBlock + UncleBlock, + StakingPools } alias Indexer.Tracer @@ -280,6 +281,10 @@ defmodule Indexer.Block.Fetcher do def async_import_token_balances(_), do: :ok + def async_import_staking_pools do + StakingPools.async_fetch() + end + def async_import_uncles(%{block_second_degree_relations: block_second_degree_relations}) do block_second_degree_relations |> Enum.map(& &1.uncle_hash) diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 9fce118964..8778677f33 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -20,7 +20,8 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_tokens: 1, async_import_token_balances: 1, async_import_uncles: 1, - fetch_and_import_range: 2 + fetch_and_import_range: 2, + async_import_staking_pools: 0 ] alias Ecto.Changeset @@ -350,6 +351,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_token_balances(imported) async_import_uncles(imported) async_import_replaced_transactions(imported) + async_import_staking_pools() end defp balances( diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex new file mode 100644 index 0000000000..246f53bdf7 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -0,0 +1,135 @@ +defmodule Indexer.Fetcher.StakingPools do + @moduledoc """ + Fetches staking pools and send to be imported in `Address.Name` table + """ + + use Indexer.Fetcher + use Spandex.Decorators + + require Logger + + alias Explorer.Chain + alias Indexer.BufferedTask + alias Explorer.Staking.PoolsReader + + @behaviour BufferedTask + + @defaults [ + flush_interval: 300, + max_batch_size: 100, + max_concurrency: 10, + task_supervisor: Indexer.Fetcher.StakingPools.TaskSupervisor + ] + + @max_retries 3 + + @spec async_fetch() :: :ok + def async_fetch() do + pools = + PoolsReader.get_pools() + |> Enum.map(&entry/1) + + pid = GenServer.whereis(__MODULE__) + + if pid && Process.alive?(pid) do + BufferedTask.buffer(__MODULE__, pools, :infinity) + end + end + + @doc false + def child_spec([init_options, gen_server_options]) do + merged_init_opts = + @defaults + |> Keyword.merge(init_options) + |> Keyword.put(:state, {0, []}) + + Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__) + end + + @impl BufferedTask + def init(_initial, reducer, acc) do + PoolsReader.get_pools() + |> Enum.map(&entry/1) + |> Enum.reduce(acc, &reducer.(&1, &2)) + end + + @impl BufferedTask + def run(pools, _json_rpc_named_arguments) do + failed_list = + pools + |> Enum.map(&Map.put(&1, :retries_count, &1.retries_count + 1)) + |> fetch_from_blockchain() + |> import_pools() + + if failed_list == [] do + :ok + else + {:retry, [failed_list]} + end + end + + defp entry(pool_address) do + %{ + staking_address: pool_address, + retries_count: 0 + } + end + + defp fetch_from_blockchain(addresses) do + addresses + |> Enum.filter(&(&1.retries_count <= @max_retries)) + |> Enum.map(fn %{staking_address: staking_address} = pool -> + case PoolsReader.pool_data(staking_address) do + {:ok, data} -> + Map.merge(pool, data) + + error -> + Map.put(pool, :error, error) + end + end) + end + + defp import_pools(pools) do + {failed, success} = + Enum.reduce(pools, {[], []}, fn + %{error: _error, staking_address: address}, {failed, success} -> + {[address | failed], success} + + pool, {failed, success} -> + {failed, [changeset(pool) | success]} + end) + + import_params = %{ + staking_pools: %{params: success}, + timeout: :infinity + } + + case Chain.import(import_params) do + {:ok, _} -> + :ok + + {:error, reason} -> + Logger.debug(fn -> ["failed to import staking pools: ", inspect(reason)] end, + error_count: Enum.count(pools) + ) + end + + failed + end + + defp changeset(%{staking_address: staking_address} = pool) do + {:ok, mining_address} = Chain.Hash.Address.cast(pool[:mining_address]) + + data = + pool + |> Map.delete(:staking_address) + |> Map.put(:mining_address, mining_address) + + %{ + name: "anonymous", + primary: true, + address_hash: staking_address, + metadata: data + } + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 113aa56576..c84a1b4460 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -19,7 +19,8 @@ defmodule Indexer.Supervisor do Token, TokenBalance, TokenUpdater, - UncleBlock + UncleBlock, + StakingPools } alias Indexer.Temporary.{ @@ -121,6 +122,7 @@ defmodule Indexer.Supervisor do {TokenBalance.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, + {StakingPools.Supervisor, [[memory_monitor: memory_monitor]]}, # Out-of-band fetchers {CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]}, From 5acda9df9851c24b202909d804493dd733633d21 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 12:51:35 +0300 Subject: [PATCH 04/34] add staking pools importer --- apps/explorer/config/config.exs | 4 + .../chain/import/runner/staking_pools.ex | 88 +++++++++++++++++++ .../chain/import/stage/address_referencing.ex | 3 +- 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index dea1be2b46..07e6224f15 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -51,6 +51,10 @@ else config :explorer, Explorer.Validator.MetadataProcessor, enabled: false end +config :explorer, Explorer.Staking.PoolsReader, + validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"), + staking_contract_address: System.get_env("POS_STAKING_CONTRACT") + if System.get_env("SUPPLY_MODULE") == "TokenBridge" do config :explorer, supply: Explorer.Chain.Supply.TokenBridge end diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex new file mode 100644 index 0000000000..aaf5d7242e --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -0,0 +1,88 @@ +defmodule Explorer.Chain.Import.Runner.StakingPools do + @moduledoc """ + Bulk imports staking pools to Address.Name tabe. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.{Address, Import} + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [Address.Name.t()] + + @impl Import.Runner + def ecto_schema_module, do: Address.Name + + @impl Import.Runner + def option_key, do: :staking_pools + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + multi + |> Multi.run(:insert_staking_pools, fn repo, _ -> + insert(repo, changes_list, insert_options) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: + {:ok, [Address.Name.t()]} + | {:error, [Changeset.t()]} + defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + {:ok, _} = + Import.insert_changes_list( + repo, + changes_list, + conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"}, + on_conflict: on_conflict, + for: Address.Name, + returning: [:address_hash], + timeout: timeout, + timestamps: timestamps + ) + end + + defp default_on_conflict do + from( + name in Address.Name, + update: [ + set: [ + name: fragment("EXCLUDED.name"), + metadata: fragment("EXCLUDED.metadata"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", name.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", name.updated_at) + ] + ] + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex index f6aac010b0..0e42bdc1f9 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex @@ -24,7 +24,8 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do Runner.Tokens, Runner.TokenTransfers, Runner.Address.CurrentTokenBalances, - Runner.Address.TokenBalances + Runner.Address.TokenBalances, + Runner.StakingPools ] @impl Stage From 40e03b7e5ccbd877b037b48b73a44a0cba117a15 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 14:18:50 +0300 Subject: [PATCH 05/34] add pools reader test --- .../explorer/staking/pools_reader_test.exs | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 apps/explorer/test/explorer/staking/pools_reader_test.exs diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs new file mode 100644 index 0000000000..10b9644e60 --- /dev/null +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -0,0 +1,238 @@ +defmodule Explorer.Token.PoolsReaderTest do + use EthereumJSONRPC.Case + use Explorer.DataCase + + alias Explorer.Staking.PoolsReader + + import Mox + + setup :verify_on_exit! + setup :set_mox_global + + describe "get_pools_list" do + test "get_active_pools success" do + get_pools_from_blockchain() + + result = PoolsReader.get_active_pools() + + assert Enum.count(result) == 3 + end + + test "get_active_pools error" do + fetch_from_blockchain_with_error() + + assert_raise MatchError, fn -> + PoolsReader.get_active_pools() + end + end + end + + describe "get_pools_data" do + test "get_pool_data success" do + get_pool_data_from_blockchain() + + address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> + + response = { + :ok, + %{ + banned_unitil: 0, + delegator_addresses: [], + is_active: true, + is_banned: false, + is_validator: true, + mining_address: + <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>, + staked_amount: 0, + staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>, + was_banned_count: 0, + was_validator_count: 2 + } + } + + assert response = PoolsReader.pool_data(address) + end + + test "get_pool_data error" do + fetch_from_blockchain_with_error() + + address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> + + assert :error = PoolsReader.pool_data(address) + end + end + + defp get_pools_from_blockchain() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: "eth_call", params: _}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000aa94b687d3f9552a453b81b2834ca53778980dc0000000000000000000000000312c230e7d6db05224f60208a656e3541c5c42ba" + } + ]} + end + ) + end + + defp fetch_from_blockchain_with_error() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: "eth_call", params: _}], _options -> + {:ok, + [ + %{ + error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."}, + id: id, + jsonrpc: "2.0" + } + ]} + end + ) + end + + defp get_pool_data_from_blockchain() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + 9, + fn requests, _opts -> + {:ok, + Enum.map(requests, fn + # miningByStakingAddress + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" + } + + # isPoolActive + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000001" + } + + # poolDelegators + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + } + + # stakeAmountTotalMinusOrderedWithdraw + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # isValidator + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000001" + } + + # validatorCounter + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000002" + } + + # isValidatorBanned + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # bannedUntil + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # banCounter + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + end)} + end + ) + end +end From fb522efd578fc3b376e0a691c7244c66efc4de4a Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 14:36:53 +0300 Subject: [PATCH 06/34] add staking pools importer test --- .../lib/explorer/staking/pools_reader.ex | 3 +- .../import/runner/staking_pools_test.exs | 94 +++++++++++++++++++ .../explorer/staking/pools_reader_test.exs | 4 +- 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index 0aaffaf8e9..198730d183 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -26,6 +26,7 @@ defmodule Explorer.Staking.PoolsReader do with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]), {:ok, [is_active]} <- call_staking_method("isPoolActive", [staking_address]), {:ok, [delegator_addresses]} <- call_staking_method("poolDelegators", [staking_address]), + delegators_count = Enum.count(delegator_addresses), {:ok, [staked_amount]} <- call_staking_method("stakeAmountTotalMinusOrderedWithdraw", [staking_address]), {:ok, [is_validator]} <- call_validators_method("isValidator", [mining_address]), {:ok, [was_validator_count]} <- call_validators_method("validatorCounter", [mining_address]), @@ -38,7 +39,7 @@ defmodule Explorer.Staking.PoolsReader do staking_address: staking_address, mining_address: mining_address, is_active: is_active, - delegator_addresses: delegator_addresses, + delegators_count: delegators_count, staked_amount: staked_amount, is_validator: is_validator, was_validator_count: was_validator_count, diff --git a/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs new file mode 100644 index 0000000000..d5bc6ecfca --- /dev/null +++ b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs @@ -0,0 +1,94 @@ +defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do + use Explorer.DataCase + + alias Ecto.Multi + alias Explorer.Chain.Import.Runner.StakingPools + + describe "run/1" do + test "insert new pools list" do + pools = [ + %{ + address_hash: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> + }, + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>> + }, + retries_count: 1, + staked_amount: 0, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true + }, + %{ + address_hash: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<170, 148, 182, 135, 211, 249, 85, 42, 69, 59, 129, 178, 131, 76, 165, 55, 120, 152, 13, 192>> + }, + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<117, 223, 66, 56, 58, 254, 107, 245, 25, 74, 168, 250, 14, 155, 61, 95, 158, 134, 148, 65>> + }, + retries_count: 1, + staked_amount: 0, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true + }, + %{ + address_hash: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<49, 44, 35, 14, 125, 109, 176, 82, 36, 246, 2, 8, 166, 86, 227, 84, 28, 92, 66, 186>> + }, + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<82, 45, 243, 150, 174, 112, 160, 88, 189, 105, 119, 132, 8, 99, 15, 219, 2, 51, 137, 178>> + }, + retries_count: 1, + staked_amount: 0, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true + } + ] + + assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) + assert Enum.count(list) == Enum.count(pools) + end + end + + defp run_changes(changes) do + Multi.new() + |> StakingPools.run(changes, %{ + timeout: :infinity, + timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()} + }) + |> Repo.transaction() + end +end diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs index 10b9644e60..7783b34648 100644 --- a/apps/explorer/test/explorer/staking/pools_reader_test.exs +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -37,7 +37,7 @@ defmodule Explorer.Token.PoolsReaderTest do :ok, %{ banned_unitil: 0, - delegator_addresses: [], + delegators_count: 0, is_active: true, is_banned: false, is_validator: true, @@ -50,7 +50,7 @@ defmodule Explorer.Token.PoolsReaderTest do } } - assert response = PoolsReader.pool_data(address) + assert PoolsReader.pool_data(address) == response end test "get_pool_data error" do From 009a54466e04bb0a5b24f0dc74d01f74acb8e010 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 16:02:47 +0300 Subject: [PATCH 07/34] fix staking pools fetcher --- apps/indexer/lib/indexer/fetcher/staking_pools.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex index 246f53bdf7..4086c5c576 100644 --- a/apps/indexer/lib/indexer/fetcher/staking_pools.ex +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -64,11 +64,11 @@ defmodule Indexer.Fetcher.StakingPools do if failed_list == [] do :ok else - {:retry, [failed_list]} + {:retry, failed_list} end end - defp entry(pool_address) do + def entry(pool_address) do %{ staking_address: pool_address, retries_count: 0 From b158d71219f8c6a716cc1139c2f624093b23ee20 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 16:09:30 +0300 Subject: [PATCH 08/34] add staking pools fetcher test --- .../lib/indexer/fetcher/staking_pools.ex | 8 +- .../indexer/fetcher/staking_pools_test.exs | 205 ++++++++++++++++++ 2 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 apps/indexer/test/indexer/fetcher/staking_pools_test.exs diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex index 4086c5c576..f380dc0e17 100644 --- a/apps/indexer/lib/indexer/fetcher/staking_pools.ex +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -25,13 +25,13 @@ defmodule Indexer.Fetcher.StakingPools do @spec async_fetch() :: :ok def async_fetch() do - pools = - PoolsReader.get_pools() - |> Enum.map(&entry/1) - pid = GenServer.whereis(__MODULE__) if pid && Process.alive?(pid) do + pools = + PoolsReader.get_pools() + |> Enum.map(&entry/1) + BufferedTask.buffer(__MODULE__, pools, :infinity) end end diff --git a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs new file mode 100644 index 0000000000..2172fca188 --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs @@ -0,0 +1,205 @@ +defmodule Indexer.Fetcher.StakingPoolsTest do + use EthereumJSONRPC.Case + use Explorer.DataCase + + import Mox + + alias Indexer.Fetcher.StakingPools + alias Explorer.Staking.PoolsReader + alias Explorer.Chain.Address + + @moduletag :capture_log + + setup :verify_on_exit! + + describe "init/3" do + test "returns pools addresses" do + get_pools_from_blockchain(2) + + list = StakingPools.init([], &[&1 | &2], []) + + assert Enum.count(list) == 6 + end + end + + describe "run/3" do + test "one success import from pools" do + get_pools_from_blockchain(1) + + list = + PoolsReader.get_active_pools() + |> Enum.map(&StakingPools.entry/1) + + success_address = + list + |> List.first() + |> Map.get(:staking_address) + + get_pool_data_from_blockchain() + + assert {:retry, retry_list} = StakingPools.run(list, nil) + assert Enum.count(retry_list) == 2 + + pool = Explorer.Repo.get_by(Address.Name, address_hash: success_address) + assert pool.name == "anonymous" + end + end + + defp get_pools_from_blockchain(n) do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + n, + fn [%{id: id, method: "eth_call", params: _}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000aa94b687d3f9552a453b81b2834ca53778980dc0000000000000000000000000312c230e7d6db05224f60208a656e3541c5c42ba" + } + ]} + end + ) + end + + defp get_pool_data_from_blockchain() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + 11, + fn requests, _opts -> + {:ok, + Enum.map(requests, fn + # miningByStakingAddress + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" + } + + # isPoolActive + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000001" + } + + # poolDelegators + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + } + + # stakeAmountTotalMinusOrderedWithdraw + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # isValidator + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000001" + } + + # validatorCounter + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000002" + } + + # isValidatorBanned + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # bannedUntil + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # banCounter + %{ + id: id, + method: "eth_call", + params: [ + %{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + end)} + end + ) + end +end From fc79dfb9ebf7502f3b9364ef075e5e2d8e103c66 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 16:13:41 +0300 Subject: [PATCH 09/34] fix credo warnings --- apps/indexer/lib/indexer/block/fetcher.ex | 4 ++-- apps/indexer/lib/indexer/fetcher/staking_pools.ex | 4 ++-- apps/indexer/lib/indexer/supervisor.ex | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 9f9a9474f4..299bf8f9e0 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -20,10 +20,10 @@ defmodule Indexer.Block.Fetcher do ContractCode, InternalTransaction, ReplacedTransaction, + StakingPools, Token, TokenBalance, - UncleBlock, - StakingPools + UncleBlock } alias Indexer.Tracer diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex index f380dc0e17..e5affe0177 100644 --- a/apps/indexer/lib/indexer/fetcher/staking_pools.ex +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -9,8 +9,8 @@ defmodule Indexer.Fetcher.StakingPools do require Logger alias Explorer.Chain - alias Indexer.BufferedTask alias Explorer.Staking.PoolsReader + alias Indexer.BufferedTask @behaviour BufferedTask @@ -24,7 +24,7 @@ defmodule Indexer.Fetcher.StakingPools do @max_retries 3 @spec async_fetch() :: :ok - def async_fetch() do + def async_fetch do pid = GenServer.whereis(__MODULE__) if pid && Process.alive?(pid) do diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index c84a1b4460..c6b8c4fff8 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -16,11 +16,11 @@ defmodule Indexer.Supervisor do InternalTransaction, PendingTransaction, ReplacedTransaction, + StakingPools, Token, TokenBalance, TokenUpdater, - UncleBlock, - StakingPools + UncleBlock } alias Indexer.Temporary.{ From 657958b0c99dc50718719952705ec112f182d73a Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 16:22:47 +0300 Subject: [PATCH 10/34] fix sobelow --- apps/explorer/lib/explorer/staking/pools_reader.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index 198730d183..ed13408caa 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -76,6 +76,7 @@ defmodule Explorer.Staking.PoolsReader do Application.get_env(:explorer, __MODULE__, [])[key] end + # sobelow_skip ["Traversal"] defp abi(file_name) do :explorer |> Application.app_dir("priv/contracts_abi/pos/#{file_name}") From d18116f00ef029921b534755cd688fabe0a76b4c Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 22 Apr 2019 18:41:10 +0300 Subject: [PATCH 11/34] update readme and changelog --- CHANGELOG.md | 1 + apps/indexer/README.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aab08669f8..28c57149e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#1777](https://github.com/poanetwork/blockscout/pull/1777) - show ERC-20 token transfer info on transaction page - [#1770](https://github.com/poanetwork/blockscout/pull/1770) - set a websocket keepalive from config - [#1789](https://github.com/poanetwork/blockscout/pull/1789) - add ERC-721 info to transaction overview page +- [#1801](https://github.com/poanetwork/blockscout/pull/1801) - Staking pools fetching ### Fixes diff --git a/apps/indexer/README.md b/apps/indexer/README.md index ceb4ce9d0f..1c8507843b 100644 --- a/apps/indexer/README.md +++ b/apps/indexer/README.md @@ -57,6 +57,7 @@ The following async fetchers are launched for importing missing data: - `token_balance` - `token` - `contract_code` +- `staking_pools` ### Async fetchers @@ -78,6 +79,7 @@ Most of them are based off `BufferedTask`, and the basic algorithm goes like thi - `token_balance`: for `address_token_balances` with null `value_fetched_at`. Also upserts `address_current_token_balances` - `token`: for `tokens` with `cataloged == false` - `contract_code`: for `transactions` with non-null `created_contract_address_hash` and null `created_contract_code_indexed_at` +- `staking_pools`: for fetching staking pools Additionally: - `token_updater` is run every 2 days to update token metadata From 4404645e0f73dbe79a54e923ac5c97c0e33998cc Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 23 Apr 2019 19:20:52 +0300 Subject: [PATCH 12/34] add style to every line of decompiled contract --- .../views/address_decompiled_contract_view.ex | 58 +++++++++++++++++-- .../address_decompiled_contract_view_test.exs | 16 ++++- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex index 148d82b818..3382f681a6 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex @@ -18,13 +18,59 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do } def highlight_decompiled_code(code) do - @colors - |> Enum.reduce(code, fn {symbol, rgb}, acc -> - String.replace(acc, symbol, "") + {_, result} = + @colors + |> Enum.reduce(code, fn {symbol, rgb}, acc -> + String.replace(acc, symbol, "") + end) + |> String.replace("\e[1m", "") + |> String.replace("»", "»") + |> String.replace("\e[0m", "") + |> String.split(~r/\|\<\/span\>/, include_captures: true, trim: true) + |> Enum.reduce({"", []}, fn part, {style, acc} -> + new_style = + cond do + String.contains?(part, " part + part == "" -> "" + true -> style + end + + new_part = + cond do + part == "" -> + "" + + part == "" -> + "" + + part == new_style -> + "" + + new_style == "" -> + part + + true -> + result = + part + |> String.split("\n") + |> Enum.reduce("", fn p, a -> + a <> new_style <> p <> "\n" + end) + + if String.ends_with?(part, "\n") do + result + else + String.slice(result, 0..-2) + end + end + + {new_style, [new_part | acc]} + end) + + result + |> Enum.reduce("", fn part, acc -> + part <> acc end) - |> String.replace("\e[1m", "") - |> String.replace("»", "»") - |> String.replace("\e[0m", "") |> add_line_numbers() end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs index 9e12503c3a..c3ff123584 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs @@ -56,7 +56,21 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do result = AddressDecompiledContractView.highlight_decompiled_code(code) assert result == - " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: \n require (calldata.size - 4) >= 64\n return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: \n if (calldata.size - 4) < 32:\n revert\n else:\n if not (320 - 1) or not cd[4]:\n revert\n else:\n mem[0] = (320 - 1) and (320 - 1) and cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n return bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n" + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: 64\n return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: not cd[4]:\n revert\n else:\n mem[0]cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n return bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n" + end + + test "adds style span to every line" do + code = """ + # + # eveem.org 6 Feb 2019 + # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875 + # + # Let's make the world open source + #  + """ + + assert AddressDecompiledContractView.highlight_decompiled_code(code) == + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n\n" end end From 326ea32a775173af054aa5f5571c8ddb810a8f7c Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 23 Apr 2019 19:26:08 +0300 Subject: [PATCH 13/34] fix credo --- .../views/address_decompiled_contract_view.ex | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex index 3382f681a6..1418255594 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex @@ -35,34 +35,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do true -> style end - new_part = - cond do - part == "" -> - "" - - part == "" -> - "" - - part == new_style -> - "" - - new_style == "" -> - part - - true -> - result = - part - |> String.split("\n") - |> Enum.reduce("", fn p, a -> - a <> new_style <> p <> "\n" - end) - - if String.ends_with?(part, "\n") do - result - else - String.slice(result, 0..-2) - end - end + new_part = new_part(part, new_style) {new_style, [new_part | acc]} end) @@ -87,4 +60,34 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do acc <> "#{line}\n" end) end + + defp new_part(part, new_style) do + cond do + part == "" -> + "" + + part == "" -> + "" + + part == new_style -> + "" + + new_style == "" -> + part + + true -> + result = + part + |> String.split("\n") + |> Enum.reduce("", fn p, a -> + a <> new_style <> p <> "\n" + end) + + if String.ends_with?(part, "\n") do + result + else + String.slice(result, 0..-2) + end + end + end end From 32498b5b298a7bfc0f1d29b43fb8760eb26a9ad4 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 24 Apr 2019 10:22:07 +0300 Subject: [PATCH 14/34] fix typo --- apps/explorer/lib/explorer/staking/pools_reader.ex | 4 ++-- apps/explorer/test/explorer/staking/pools_reader_test.exs | 2 +- apps/indexer/lib/indexer/fetcher/staking_pools.ex | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index ed13408caa..d0fd8ee679 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -31,7 +31,7 @@ defmodule Explorer.Staking.PoolsReader do {:ok, [is_validator]} <- call_validators_method("isValidator", [mining_address]), {:ok, [was_validator_count]} <- call_validators_method("validatorCounter", [mining_address]), {:ok, [is_banned]} <- call_validators_method("isValidatorBanned", [mining_address]), - {:ok, [banned_unitil]} <- call_validators_method("bannedUntil", [mining_address]), + {:ok, [banned_until]} <- call_validators_method("bannedUntil", [mining_address]), {:ok, [was_banned_count]} <- call_validators_method("banCounter", [mining_address]) do { :ok, @@ -44,7 +44,7 @@ defmodule Explorer.Staking.PoolsReader do is_validator: is_validator, was_validator_count: was_validator_count, is_banned: is_banned, - banned_unitil: banned_unitil, + banned_until: banned_until, was_banned_count: was_banned_count } } diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs index 7783b34648..f1d5164c3c 100644 --- a/apps/explorer/test/explorer/staking/pools_reader_test.exs +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -36,7 +36,7 @@ defmodule Explorer.Token.PoolsReaderTest do response = { :ok, %{ - banned_unitil: 0, + banned_until: 0, delegators_count: 0, is_active: true, is_banned: false, diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex index e5affe0177..68794d9ee4 100644 --- a/apps/indexer/lib/indexer/fetcher/staking_pools.ex +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -11,6 +11,7 @@ defmodule Indexer.Fetcher.StakingPools do alias Explorer.Chain alias Explorer.Staking.PoolsReader alias Indexer.BufferedTask + alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor @behaviour BufferedTask @@ -25,9 +26,9 @@ defmodule Indexer.Fetcher.StakingPools do @spec async_fetch() :: :ok def async_fetch do - pid = GenServer.whereis(__MODULE__) - - if pid && Process.alive?(pid) do + if StakingPoolsSupervisor.disabled?() do + :ok + else pools = PoolsReader.get_pools() |> Enum.map(&entry/1) From 718c1411848374ebe5e9d8ceb459c8f5294eb17e Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 24 Apr 2019 10:42:03 +0300 Subject: [PATCH 15/34] fix line numbers --- apps/block_scout_web/assets/css/_code.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/assets/css/_code.scss b/apps/block_scout_web/assets/css/_code.scss index a3c4f5823c..510352ad6f 100644 --- a/apps/block_scout_web/assets/css/_code.scss +++ b/apps/block_scout_web/assets/css/_code.scss @@ -14,7 +14,7 @@ pre { .pre-decompiled code::before { content: counter(line); display: inline-block; - width: flex; + width: 3em; border-right: 1px solid #ddd; padding: 0 .5em; margin-right: .5em; From eb4154f14e93357b7034214c08141aedc6084f93 Mon Sep 17 00:00:00 2001 From: saneery Date: Thu, 25 Apr 2019 11:52:53 +0300 Subject: [PATCH 16/34] batching pool data requests --- .../lib/explorer/staking/pools_reader.ex | 51 ++++++++++++++++--- .../explorer/staking/pools_reader_test.exs | 2 +- .../indexer/fetcher/staking_pools_test.exs | 2 +- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index d0fd8ee679..de03ff10b5 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -24,15 +24,16 @@ defmodule Explorer.Staking.PoolsReader do @spec pool_data(String.t()) :: {:ok, map()} | :error def pool_data(staking_address) do with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]), - {:ok, [is_active]} <- call_staking_method("isPoolActive", [staking_address]), - {:ok, [delegator_addresses]} <- call_staking_method("poolDelegators", [staking_address]), + data = fetch_data(staking_address, mining_address), + {:ok, [is_active]} <- data["isPoolActive"], + {:ok, [delegator_addresses]} <- data["poolDelegators"], delegators_count = Enum.count(delegator_addresses), - {:ok, [staked_amount]} <- call_staking_method("stakeAmountTotalMinusOrderedWithdraw", [staking_address]), - {:ok, [is_validator]} <- call_validators_method("isValidator", [mining_address]), - {:ok, [was_validator_count]} <- call_validators_method("validatorCounter", [mining_address]), - {:ok, [is_banned]} <- call_validators_method("isValidatorBanned", [mining_address]), - {:ok, [banned_until]} <- call_validators_method("bannedUntil", [mining_address]), - {:ok, [was_banned_count]} <- call_validators_method("banCounter", [mining_address]) do + {:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"], + {:ok, [is_validator]} <- data["isValidator"], + {:ok, [was_validator_count]} <- data["validatorCounter"], + {:ok, [is_banned]} <- data["isValidatorBanned"], + {:ok, [banned_until]} <- data["bannedUntil"], + {:ok, [was_banned_count]} <- data["banCounter"] do { :ok, %{ @@ -72,6 +73,40 @@ defmodule Explorer.Staking.PoolsReader do resp end + defp fetch_data(staking_address, mining_address) do + contract_abi = abi("staking.json") ++ abi("validators.json") + + methods = [ + {:staking, "isPoolActive", staking_address}, + {:staking, "poolDelegators", staking_address}, + {:staking, "stakeAmountTotalMinusOrderedWithdraw", staking_address}, + {:validators, "isValidator", mining_address}, + {:validators, "validatorCounter", mining_address}, + {:validators, "isValidatorBanned", mining_address}, + {:validators, "bannedUntil", mining_address}, + {:validators, "banCounter", mining_address} + ] + + methods + |> Enum.map(&format_request/1) + |> Reader.query_contracts(contract_abi) + |> Enum.zip(methods) + |> Enum.into(%{}, fn {response, {_, function_name, _}} -> + {function_name, response} + end) + end + + defp format_request({contract_name, function_name, param}) do + %{ + contract_address: contract(contract_name), + function_name: function_name, + args: [param] + } + end + + defp contract(:staking), do: config(:staking_contract_address) + defp contract(:validators), do: config(:validators_contract_address) + defp config(key) do Application.get_env(:explorer, __MODULE__, [])[key] end diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs index f1d5164c3c..ac6a600722 100644 --- a/apps/explorer/test/explorer/staking/pools_reader_test.exs +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -101,7 +101,7 @@ defmodule Explorer.Token.PoolsReaderTest do expect( EthereumJSONRPC.Mox, :json_rpc, - 9, + 2, fn requests, _opts -> {:ok, Enum.map(requests, fn diff --git a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs index 2172fca188..8f985537bf 100644 --- a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs +++ b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs @@ -68,7 +68,7 @@ defmodule Indexer.Fetcher.StakingPoolsTest do expect( EthereumJSONRPC.Mox, :json_rpc, - 11, + 4, fn requests, _opts -> {:ok, Enum.map(requests, fn From 1bd25dcc45d64ba25cdf222ff54086eac4f7d1b0 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 29 Apr 2019 16:18:54 +0300 Subject: [PATCH 17/34] don't lookup at table when table isn't inited --- .../lib/explorer/exchange_rates/exchange_rates.ex | 8 +++++++- .../lib/explorer/known_tokens/known_tokens.ex | 14 ++++++++++++-- .../exchange_rates/exchange_rates_test.exs | 7 +++++++ .../explorer/known_tokens/known_tokens_test.exs | 7 +++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index 514751d8d6..f867e9b0ae 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -90,7 +90,7 @@ defmodule Explorer.ExchangeRates do """ @spec lookup(String.t()) :: Token.t() | nil def lookup(symbol) do - if store() == :ets do + if store() == :ets and enabled?() do case :ets.lookup(table_name(), symbol) do [tuple | _] when is_tuple(tuple) -> Token.from_tuple(tuple) _ -> nil @@ -133,4 +133,10 @@ defmodule Explorer.ExchangeRates do defp store do config(:store) || :ets end + + defp enabled? do + :explorer + |> Application.fetch_env!(__MODULE__) + |> Keyword.fetch!(:enabled) + end end diff --git a/apps/explorer/lib/explorer/known_tokens/known_tokens.ex b/apps/explorer/lib/explorer/known_tokens/known_tokens.ex index baa09784ba..4ef6c62df8 100644 --- a/apps/explorer/lib/explorer/known_tokens/known_tokens.ex +++ b/apps/explorer/lib/explorer/known_tokens/known_tokens.ex @@ -81,7 +81,11 @@ defmodule Explorer.KnownTokens do """ @spec list :: [{String.t(), Hash.Address.t()}] def list do - list_from_store(store()) + if enabled?() do + list_from_store(store()) + else + [] + end end @doc """ @@ -89,7 +93,7 @@ defmodule Explorer.KnownTokens do """ @spec lookup(String.t()) :: {:ok, Hash.Address.t()} | :error | nil def lookup(symbol) do - if store() == :ets do + if store() == :ets && enabled?()do case :ets.lookup(table_name(), symbol) do [{_symbol, address} | _] -> Hash.Address.cast(address) _ -> nil @@ -128,4 +132,10 @@ defmodule Explorer.KnownTokens do defp store do config(:store) || :ets end + + defp enabled? do + :explorer + |> Application.fetch_env!(__MODULE__) + |> Keyword.fetch!(:enabled) + end end diff --git a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs index 1d44a7adce..ff1510937a 100644 --- a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs @@ -19,6 +19,7 @@ defmodule Explorer.ExchangeRatesTest do Application.put_env(:explorer, Explorer.ExchangeRates.Source, source: TestSource) Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) on_exit(fn -> Application.put_env(:explorer, Explorer.ExchangeRates.Source, source_configuration) @@ -135,4 +136,10 @@ defmodule Explorer.ExchangeRatesTest do assert z == ExchangeRates.lookup("z") assert nil == ExchangeRates.lookup("nope") end + + test "lookup when desibled" do + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: false) + + assert nil == ExchangeRates.lookup("z") + end end diff --git a/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs b/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs index 7d5cb21407..a33d3c85e7 100644 --- a/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs +++ b/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs @@ -21,6 +21,7 @@ defmodule Explorer.KnownTokensTest do Application.put_env(:explorer, Explorer.KnownTokens.Source, source: TestSource) Application.put_env(:explorer, Explorer.KnownTokens, table_name: :known_tokens) + Application.put_env(:explorer, Explorer.KnownTokens, enabled: true) on_exit(fn -> Application.put_env(:explorer, Explorer.KnownTokens.Source, source_configuration) @@ -128,4 +129,10 @@ defmodule Explorer.KnownTokensTest do assert Hash.Address.cast("0x0000000000000000000000000000000000000001") == KnownTokens.lookup("TEST1") assert nil == KnownTokens.lookup("nope") end + + test "lookup when desibled" do + Application.put_env(:explorer, Explorer.KnownTokens, enabled: false) + + assert nil == KnownTokens.lookup("z") + end end From c4583bdb8b0a94fd0f828b5f13306f1d109dbac0 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 29 Apr 2019 16:28:52 +0300 Subject: [PATCH 18/34] mix format --- apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex | 2 +- apps/explorer/lib/explorer/known_tokens/known_tokens.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index f867e9b0ae..b4dc0229f9 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -90,7 +90,7 @@ defmodule Explorer.ExchangeRates do """ @spec lookup(String.t()) :: Token.t() | nil def lookup(symbol) do - if store() == :ets and enabled?() do + if store() == :ets && enabled?() do case :ets.lookup(table_name(), symbol) do [tuple | _] when is_tuple(tuple) -> Token.from_tuple(tuple) _ -> nil diff --git a/apps/explorer/lib/explorer/known_tokens/known_tokens.ex b/apps/explorer/lib/explorer/known_tokens/known_tokens.ex index 4ef6c62df8..caca671540 100644 --- a/apps/explorer/lib/explorer/known_tokens/known_tokens.ex +++ b/apps/explorer/lib/explorer/known_tokens/known_tokens.ex @@ -93,7 +93,7 @@ defmodule Explorer.KnownTokens do """ @spec lookup(String.t()) :: {:ok, Hash.Address.t()} | :error | nil def lookup(symbol) do - if store() == :ets && enabled?()do + if store() == :ets && enabled?() do case :ets.lookup(table_name(), symbol) do [{_symbol, address} | _] -> Hash.Address.cast(address) _ -> nil From 6db7dbfefe7e0f83db15b9f422bd417f8fe37223 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 29 Apr 2019 16:58:41 +0300 Subject: [PATCH 19/34] update exchange rates test in block_scout_web app --- .../test/block_scout_web/channels/exchange_rate_channel_test.exs | 1 + .../controllers/api/rpc/stats_controller_test.exs | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs index acdf441750..f41a764c6e 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs @@ -16,6 +16,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do configuration = Application.get_env(:explorer, Explorer.ExchangeRates) Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) ExchangeRates.init([]) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs index 7c47ed622e..9a7c26e9ee 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs @@ -107,6 +107,7 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do configuration = Application.get_env(:explorer, Explorer.ExchangeRates) Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) ExchangeRates.init([]) From 1c39e0a2ed1a34138585080d03082e571c0d6719 Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 30 Apr 2019 11:11:41 +0300 Subject: [PATCH 20/34] fix typo --- .../test/explorer/exchange_rates/exchange_rates_test.exs | 2 +- apps/explorer/test/explorer/known_tokens/known_tokens_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs index ff1510937a..d1bed67de1 100644 --- a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs @@ -137,7 +137,7 @@ defmodule Explorer.ExchangeRatesTest do assert nil == ExchangeRates.lookup("nope") end - test "lookup when desibled" do + test "lookup when disabled" do Application.put_env(:explorer, Explorer.ExchangeRates, enabled: false) assert nil == ExchangeRates.lookup("z") diff --git a/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs b/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs index a33d3c85e7..9076cc1d13 100644 --- a/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs +++ b/apps/explorer/test/explorer/known_tokens/known_tokens_test.exs @@ -130,7 +130,7 @@ defmodule Explorer.KnownTokensTest do assert nil == KnownTokens.lookup("nope") end - test "lookup when desibled" do + test "lookup when disabled" do Application.put_env(:explorer, Explorer.KnownTokens, enabled: false) assert nil == KnownTokens.lookup("z") From 1aeea2b9cad20ad0b2a79dad2c8e26c1eb1658c3 Mon Sep 17 00:00:00 2001 From: goodsoft Date: Mon, 29 Apr 2019 17:40:20 +0300 Subject: [PATCH 21/34] Re-implement Geth JS internal transaction tracer in Elixir Currently in Geth variant raw traces are converted into internal_transactions using a custom JS tracer in [1]. cpp-ethereum nodes don't support custom JS tracers and return raw traces regardless of provided parameters. This PR adds an Elixir implementation of the tracer, which is utilized in case raw traces are detected in the response from the node. It was cross-checked for correctness with Geth builtin `callTracer`[2]. In the process some bugs were discovered in the our JS tracer, which will be fixed with a separate PR. Though, unlike JS tracer, we don't have access to EVM internal state during tracing, so additional requests to node are required: transactions and receipts are fetched before tracing, created contract codes and pre-selfdestruct balances are fetched after. [1] priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js [2] https://github.com/ethereum/go-ethereum/blob/master/eth/tracers/internal/tracers/call_tracer.js --- .../lib/ethereum_jsonrpc/geth.ex | 88 +++++- .../lib/ethereum_jsonrpc/geth/call.ex | 36 +-- .../lib/ethereum_jsonrpc/geth/tracer.ex | 296 ++++++++++++++++++ 3 files changed, 398 insertions(+), 22 deletions(-) create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index b4a4e7c922..ab725c9c73 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -3,9 +3,10 @@ defmodule EthereumJSONRPC.Geth do Ethereum JSONRPC methods that are only supported by [Geth](https://github.com/ethereum/go-ethereum/wiki/geth). """ - import EthereumJSONRPC, only: [id_to_params: 1, json_rpc: 2, request: 1] + import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] - alias EthereumJSONRPC.Geth.Calls + alias EthereumJSONRPC.{FetchedBalance, FetchedCode} + alias EthereumJSONRPC.Geth.{Calls, Tracer} @behaviour EthereumJSONRPC.Variant @@ -28,7 +29,11 @@ defmodule EthereumJSONRPC.Geth do id_to_params |> debug_trace_transaction_requests() |> json_rpc(json_rpc_named_arguments) do - debug_trace_transaction_responses_to_internal_transactions_params(responses, id_to_params) + debug_trace_transaction_responses_to_internal_transactions_params( + responses, + id_to_params, + json_rpc_named_arguments + ) end end @@ -62,13 +67,88 @@ defmodule EthereumJSONRPC.Geth do request(%{id: id, method: "debug_traceTransaction", params: [hash_data, %{tracer: @tracer}]}) end - defp debug_trace_transaction_responses_to_internal_transactions_params(responses, id_to_params) + defp debug_trace_transaction_responses_to_internal_transactions_params( + [%{result: %{"structLogs" => _}} | _] = responses, + id_to_params, + json_rpc_named_arguments + ) + when is_map(id_to_params) do + with {:ok, receipts} <- + id_to_params + |> Enum.map(fn {id, %{hash_data: hash_data}} -> + request(%{id: id, method: "eth_getTransactionReceipt", params: [hash_data]}) + end) + |> json_rpc(json_rpc_named_arguments), + {:ok, txs} <- + id_to_params + |> Enum.map(fn {id, %{hash_data: hash_data}} -> + request(%{id: id, method: "eth_getTransactionByHash", params: [hash_data]}) + end) + |> json_rpc(json_rpc_named_arguments) do + receipts_map = Enum.into(receipts, %{}, fn %{id: id, result: receipt} -> {id, receipt} end) + txs_map = Enum.into(txs, %{}, fn %{id: id, result: tx} -> {id, tx} end) + + responses + |> Enum.map(fn %{id: id, result: %{"structLogs" => _} = result} -> + debug_trace_transaction_response_to_internal_transactions_params( + %{id: id, result: Tracer.replay(result, Map.fetch!(receipts_map, id), Map.fetch!(txs_map, id))}, + id_to_params + ) + end) + |> reduce_internal_transactions_params() + |> fetch_missing_data(json_rpc_named_arguments) + end + end + + defp debug_trace_transaction_responses_to_internal_transactions_params( + responses, + id_to_params, + _json_rpc_named_arguments + ) when is_list(responses) and is_map(id_to_params) do responses |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1, id_to_params)) |> reduce_internal_transactions_params() end + defp fetch_missing_data({:ok, transactions}, json_rpc_named_arguments) when is_list(transactions) do + id_to_params = id_to_params(transactions) + + with {:ok, responses} <- + id_to_params + |> Enum.map(fn + {id, %{created_contract_address_hash: address, block_number: block_number}} -> + FetchedCode.request(%{id: id, block_quantity: integer_to_quantity(block_number), address: address}) + + {id, %{type: "selfdestruct", from: hash_data, block_number: block_number}} -> + FetchedBalance.request(%{id: id, block_quantity: integer_to_quantity(block_number), hash_data: hash_data}) + + _ -> + nil + end) + |> Enum.reject(&is_nil/1) + |> json_rpc(json_rpc_named_arguments) do + results = Enum.into(responses, %{}, fn %{id: id, result: result} -> {id, result} end) + + transactions = + id_to_params + |> Enum.map(fn + {id, %{created_contract_address_hash: _} = transaction} -> + %{transaction | created_contract_code: Map.fetch!(results, id)} + + {id, %{type: "selfdestruct"} = transaction} -> + %{transaction | value: Map.fetch!(results, id)} + + {_, transaction} -> + transaction + end) + + {:ok, transactions} + end + end + + defp fetch_missing_data(result, _json_rpc_named_arguments), do: result + defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, result: calls}, id_to_params) when is_map(id_to_params) do %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex index 88a32c704f..434cc7b34d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex @@ -328,9 +328,8 @@ defmodule EthereumJSONRPC.Geth.Call do "from" => from_address_hash, "to" => to_address_hash, "gas" => gas, - "gasUsed" => gas_used, "input" => input, - "output" => output, + "error" => error, "value" => value }) when call_type in ~w(call callcode delegatecall) do @@ -345,9 +344,8 @@ defmodule EthereumJSONRPC.Geth.Call do from_address_hash: from_address_hash, to_address_hash: to_address_hash, gas: gas, - gas_used: gas_used, input: input, - output: output, + error: error, value: value } end @@ -363,8 +361,9 @@ defmodule EthereumJSONRPC.Geth.Call do "from" => from_address_hash, "to" => to_address_hash, "gas" => gas, + "gasUsed" => gas_used, "input" => input, - "error" => error, + "output" => output, "value" => value }) when call_type in ~w(call callcode delegatecall) do @@ -379,8 +378,9 @@ defmodule EthereumJSONRPC.Geth.Call do from_address_hash: from_address_hash, to_address_hash: to_address_hash, gas: gas, + gas_used: gas_used, input: input, - error: error, + output: output, value: value } end @@ -425,13 +425,11 @@ defmodule EthereumJSONRPC.Geth.Call do "transactionHash" => transaction_hash, "index" => index, "traceAddress" => trace_address, - "type" => "create", + "type" => "create" = type, "from" => from_address_hash, - "createdContractAddressHash" => created_contract_address_hash, + "error" => error, "gas" => gas, - "gasUsed" => gas_used, "init" => init, - "createdContractCode" => created_contract_code, "value" => value }) do %{ @@ -440,13 +438,11 @@ defmodule EthereumJSONRPC.Geth.Call do transaction_hash: transaction_hash, index: index, trace_address: trace_address, - type: "create", + type: type, from_address_hash: from_address_hash, gas: gas, - gas_used: gas_used, - created_contract_address_hash: created_contract_address_hash, + error: error, init: init, - created_contract_code: created_contract_code, value: value } end @@ -457,11 +453,13 @@ defmodule EthereumJSONRPC.Geth.Call do "transactionHash" => transaction_hash, "index" => index, "traceAddress" => trace_address, - "type" => "create" = type, + "type" => "create", "from" => from_address_hash, - "error" => error, + "createdContractAddressHash" => created_contract_address_hash, "gas" => gas, + "gasUsed" => gas_used, "init" => init, + "createdContractCode" => created_contract_code, "value" => value }) do %{ @@ -470,11 +468,13 @@ defmodule EthereumJSONRPC.Geth.Call do transaction_hash: transaction_hash, index: index, trace_address: trace_address, - type: type, + type: "create", from_address_hash: from_address_hash, gas: gas, - error: error, + gas_used: gas_used, + created_contract_address_hash: created_contract_address_hash, init: init, + created_contract_code: created_contract_code, value: value } end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex new file mode 100644 index 0000000000..98fe2c6719 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex @@ -0,0 +1,296 @@ +defmodule EthereumJSONRPC.Geth.Tracer do + @moduledoc """ + Elixir implementation of a custom tracer (`priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js`) + for variants that don't support specifying tracer in [debug_traceTransaction](https://github.com/ethereum/go-ethereum/wiki/Management-APIs#debug_tracetransaction) calls. + """ + + import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] + + def replay(%{"structLogs" => logs} = result, receipt, tx) when is_list(logs) do + %{"from" => from, "to" => to, "contractAddress" => contract_address, "gasUsed" => gas_used} = receipt + %{"value" => value, "input" => input, "gas" => gas} = tx + + top = + to + |> if do + %{ + "type" => "call", + "callType" => "call", + "to" => to, + "input" => input, + "output" => Map.get(result, "return", "0x" <> Map.get(result, "returnValue", "")) + } + else + %{ + "type" => "create", + "init" => input, + "createdContractAddressHash" => contract_address, + "createdContractCode" => "0x" + } + end + |> Map.merge(%{ + "from" => from, + "traceAddress" => [], + "value" => value, + "gas" => 0, + "gasUsed" => quantity_to_integer(gas_used) - quantity_to_integer(gas) + }) + + ctx = %{ + depth: 1, + stack: [top], + trace_address: [0], + calls: [[]] + } + + logs + |> Enum.reduce(ctx, &step/2) + |> finalize() + end + + defp step(%{"error" => _}, %{stack: [%{"error" => _} | _]} = ctx), do: ctx + + defp step( + %{"error" => _} = log, + %{ + depth: stack_depth, + stack: [call | stack], + trace_address: [_, trace_index | trace_address], + calls: [subsubcalls, subcalls | calls] + } = ctx + ) do + call = process_return(log, Map.put(call, "error", "error")) + + subsubcalls = + subsubcalls + |> Enum.reverse() + |> Enum.map(fn + subcalls when is_list(subcalls) -> subcalls + subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]} + end) + + %{ + ctx + | depth: stack_depth - 1, + stack: stack, + trace_address: [trace_index + 1 | trace_address], + calls: [[subsubcalls, call | subcalls] | calls] + } + end + + defp step( + %{"gas" => log_gas} = log, + %{stack: [%{"type" => "create", "gas" => 0, "gasUsed" => 0} = call | stack]} = ctx + ) do + step(log, %{ctx | stack: [%{call | "gas" => log_gas, "gasUsed" => log_gas} | stack]}) + end + + defp step(%{"gas" => log_gas} = log, %{stack: [%{"type" => "create", "gas" => 0} = call | stack]} = ctx) do + step(log, %{ctx | stack: [%{call | "gas" => log_gas} | stack]}) + end + + defp step(%{"gas" => log_gas} = log, %{stack: [%{"gas" => 0, "gasUsed" => gas_used} = call | stack]} = ctx) do + step(log, %{ctx | stack: [%{call | "gas" => log_gas, "gasUsed" => gas_used + log_gas} | stack]}) + end + + defp step( + %{"depth" => log_depth, "gas" => log_gas} = log, + %{ + depth: stack_depth, + stack: [call | stack], + trace_address: [_, trace_index | trace_address], + calls: [subsubcalls, subcalls | calls] + } = ctx + ) + when log_depth == stack_depth - 1 do + call = process_return(log, %{call | "gasUsed" => call["gasUsed"] - log_gas}) + + subsubcalls = + subsubcalls + |> Enum.reverse() + |> Enum.map(fn + subcalls when is_list(subcalls) -> subcalls + subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]} + end) + + step(log, %{ + ctx + | depth: stack_depth - 1, + stack: stack, + trace_address: [trace_index + 1 | trace_address], + calls: [[subsubcalls, call | subcalls] | calls] + }) + end + + defp step(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx) + defp step(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx) + defp step(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx) + defp step(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx) + defp step(%{"op" => "DELEGATECALL"} = log, ctx), do: call_op(log, "delegatecall", ctx) + defp step(%{"op" => "STATICCALL"} = log, ctx), do: call_op(log, "staticcall", ctx) + defp step(%{"op" => "REVERT"}, ctx), do: revert_op(ctx) + defp step(_, ctx), do: ctx + + defp process_return(%{"stack" => log_stack}, %{"type" => "create"} = call) do + [ret | _] = Enum.reverse(log_stack) + + case quantity_to_integer(ret) do + 0 -> Map.put(call, "error", call["error"] || "internal failure") + _ -> %{call | "createdContractAddressHash" => "0x" <> String.slice(ret, 24, 40)} + end + end + + defp process_return( + %{"stack" => log_stack, "memory" => log_memory}, + %{"outputOffset" => out_off, "outputLength" => out_len} = call + ) do + [ret | _] = Enum.reverse(log_stack) + + ret + |> quantity_to_integer() + |> case do + 0 -> + Map.put(call, "error", call["error"] || "internal failure") + + _ -> + output = + log_memory + |> IO.iodata_to_binary() + |> String.slice(out_off, out_len) + + %{call | "output" => "0x" <> output} + end + |> Map.drop(["outputOffset", "outputLength"]) + end + + defp create_op( + %{"stack" => log_stack, "memory" => log_memory, "gas" => log_gas, "gasCost" => log_gas_cost}, + %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx + ) do + [value, input_offset, input_length | _] = Enum.reverse(log_stack) + + init = + log_memory + |> IO.iodata_to_binary() + |> String.slice(quantity_to_integer("0x" <> input_offset) * 2, quantity_to_integer("0x" <> input_length) * 2) + + call = %{ + "type" => "create", + "from" => nil, + "traceAddress" => Enum.reverse(trace_address), + "init" => "0x" <> init, + "gas" => 0, + "gasUsed" => log_gas - log_gas_cost, + "value" => "0x" <> value, + "createdContractAddressHash" => nil, + "createdContractCode" => "0x" + } + + %{ + ctx + | depth: stack_depth + 1, + stack: [call | stack], + trace_address: [0 | trace_address], + calls: [[] | calls] + } + end + + defp self_destruct_op( + %{"stack" => log_stack, "gas" => log_gas, "gasCost" => log_gas_cost}, + %{trace_address: [trace_index | trace_address], calls: [subcalls | calls]} = ctx + ) do + [to | _] = Enum.reverse(log_stack) + + if quantity_to_integer(to) in 1..8 do + ctx + else + call = %{ + "type" => "selfdestruct", + "from" => nil, + "to" => "0x" <> String.slice(to, 24, 40), + "traceAddress" => Enum.reverse([trace_index | trace_address]), + "gas" => log_gas, + "gasUsed" => log_gas_cost, + "value" => "0x0" + } + + %{ctx | trace_address: [trace_index + 1 | trace_address], calls: [[call | subcalls] | calls]} + end + end + + defp call_op( + %{"stack" => log_stack, "memory" => log_memory, "gas" => log_gas, "gasCost" => log_gas_cost}, + call_type, + %{ + depth: stack_depth, + stack: [%{"value" => parent_value} = parent | stack], + trace_address: trace_address, + calls: calls + } = ctx + ) do + [_, to | log_stack] = Enum.reverse(log_stack) + + {value, [input_offset, input_length, output_offset, output_length | _]} = + case call_type do + "delegatecall" -> + {parent_value, log_stack} + + "staticcall" -> + {"0x0", log_stack} + + _ -> + [value | rest] = log_stack + {"0x" <> value, rest} + end + + input = + log_memory + |> IO.iodata_to_binary() + |> String.slice(quantity_to_integer("0x" <> input_offset) * 2, quantity_to_integer("0x" <> input_length) * 2) + + call = %{ + "type" => "call", + "callType" => call_type, + "from" => nil, + "to" => "0x" <> String.slice(to, 24, 40), + "traceAddress" => Enum.reverse(trace_address), + "input" => "0x" <> input, + "output" => "0x", + "outputOffset" => quantity_to_integer("0x" <> output_offset) * 2, + "outputLength" => quantity_to_integer("0x" <> output_length) * 2, + "gas" => 0, + "gasUsed" => log_gas - log_gas_cost, + "value" => value + } + + %{ + ctx + | depth: stack_depth + 1, + stack: [call, parent | stack], + trace_address: [0 | trace_address], + calls: [[] | calls] + } + end + + defp revert_op(%{stack: [last | stack]} = ctx) do + %{ctx | stack: [Map.put(last, "error", "execution reverted") | stack]} + end + + defp finalize(%{stack: [top], calls: [calls]}) do + calls = + Enum.map(calls, fn + subcalls when is_list(subcalls) -> subcalls + subcall when is_map(subcall) -> %{subcall | "from" => top["createdContractAddressHash"] || top["to"]} + end) + + [top | Enum.reverse(calls)] + |> List.flatten() + |> Enum.map(fn + %{"gas" => gas, "gasUsed" => gas_used} = call when gas_used < 0 -> + %{call | "gas" => integer_to_quantity(gas - gas_used), "gasUsed" => "0x0"} + + %{"gas" => gas, "gasUsed" => gas_used} = call -> + %{call | "gas" => integer_to_quantity(gas), "gasUsed" => integer_to_quantity(gas_used)} + end) + end +end From 89f29e67bc23f54c1aa1a717e9c86e2fe4af8729 Mon Sep 17 00:00:00 2001 From: Paul Tsupikoff Date: Mon, 29 Apr 2019 20:12:00 +0300 Subject: [PATCH 22/34] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d61f4975a..60a2051e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#1815](https://github.com/poanetwork/blockscout/pull/1815) - able to search without prefix "0x" - [#1813](https://github.com/poanetwork/blockscout/pull/1813) - add total blocks counter to the main page - [#1806](https://github.com/poanetwork/blockscout/pull/1806) - verify contracts with a post request +- [#1857](https://github.com/poanetwork/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir ### Fixes From c6086646015eb3282dd7c3d1148a58b2fe6dd62c Mon Sep 17 00:00:00 2001 From: goodsoft Date: Mon, 29 Apr 2019 21:00:52 +0300 Subject: [PATCH 23/34] Use from/to fields from transaction instead of receipt Transaction receipt from cpp-ethereum doesn't contain these fields. --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex index 98fe2c6719..a47fc22e3a 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex @@ -7,8 +7,8 @@ defmodule EthereumJSONRPC.Geth.Tracer do import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] def replay(%{"structLogs" => logs} = result, receipt, tx) when is_list(logs) do - %{"from" => from, "to" => to, "contractAddress" => contract_address, "gasUsed" => gas_used} = receipt - %{"value" => value, "input" => input, "gas" => gas} = tx + %{"contractAddress" => contract_address, "gasUsed" => gas_used} = receipt + %{"from" => from, "to" => to, "value" => value, "input" => input, "gas" => gas} = tx top = to From daa2e85a601c3bf63d7703bf572e08b1b2289f1e Mon Sep 17 00:00:00 2001 From: goodsoft Date: Wed, 1 May 2019 00:40:18 +0300 Subject: [PATCH 24/34] Fix internal transaction output in JS tracer That was most probably a typo. --- .../js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js index 23dc279edc..c4cb9afe61 100644 --- a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js +++ b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js @@ -124,7 +124,7 @@ call.createdContractAddressHash = toHex(toAddress(ret.toString(16))); call.createdContractCode = toHex(db.getCode(toAddress(ret.toString(16)))); } else { - call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen)); + call.output = toHex(log.memory.slice(call.outputOffset, call.outputOffset + call.outputLength)); } } else if (call.error === undefined) { call.error = 'internal failure'; From e61a156dcd8122d28d00c9ca97649578e6f120b7 Mon Sep 17 00:00:00 2001 From: goodsoft Date: Wed, 1 May 2019 00:41:38 +0300 Subject: [PATCH 25/34] Simplify gas calculations in JS tracer Now `gas` is the gas available in the first opcode of a call, while `gas_used` is the gas available _after_ the last opcode of the call. --- .../geth/debug_traceTransaction/tracer.js | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js index c4cb9afe61..15db7d8ff9 100644 --- a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js +++ b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js @@ -73,6 +73,15 @@ topCall.calls.push(childCall); }, + pushGasToTopCall(log) { + const topCall = this.topCall(); + + if (topCall.gasBigInt === undefined) { + topCall.gasBigInt = log.getGas(); + } + topCall.gasUsedBigInt = topCall.gasBigInt - log.getGas() - log.getCost(); + }, + success(log, db) { const op = log.op.toString(); @@ -115,8 +124,6 @@ // Pop off the last call and get the execution results const call = this.callStack.pop(); - call.gasUsedBigInt = call.gasBigInt.subtract(log.getGas()); - const ret = log.stack.peek(0); if (!ret.equals(0)) { @@ -135,6 +142,9 @@ this.pushChildCall(call); } + else { + this.pushGasToTopCall(log); + } }, createOp(log) { @@ -147,7 +157,6 @@ type: 'create', from: toHex(log.contract.getAddress()), init: toHex(log.memory.slice(inputOffset, inputEnd)), - gasBigInt: bigInt(log.getGas()), valueBigInt: bigInt(stackValue.toString(10)) }; this.callStack.push(call); @@ -160,7 +169,8 @@ type: 'selfdestruct', from: toHex(contractAddress), to: toHex(toAddress(log.stack.peek(0).toString(16))), - gasBigInt: bigInt(log.getGas()), + gasBigInt: log.getGas(), + gasUsedBigInt: log.getCost(), valueBigInt: db.getBalance(contractAddress) }); }, @@ -186,7 +196,6 @@ callType: op.toLowerCase(), from: toHex(log.contract.getAddress()), to: toHex(to), - gasBigInt: bigInt(log.getGas()), input: toHex(log.memory.slice(inputOffset, inputEnd)), outputOffset: log.stack.peek(4 + stackOffset).valueOf(), outputLength: log.stack.peek(5 + stackOffset).valueOf() @@ -220,7 +229,7 @@ result(ctx, db) { const result = this.ctxToResult(ctx, db); const filtered = this.filterNotUndefined(result); - const callSequence = this.sequence(filtered, [], filtered.valueBigInt, filtered.gasUsedBigInt, []).callSequence; + const callSequence = this.sequence(filtered, [], filtered.valueBigInt, []).callSequence; return this.encodeCallSequence(callSequence); }, @@ -339,7 +348,7 @@ }, // sequence converts the finalized calls from a call tree to a call sequence - sequence(call, callSequence, availableValueBigInt, availableGasBigInt, traceAddress) { + sequence(call, callSequence, availableValueBigInt, traceAddress) { const subcalls = call.calls; delete call.calls; @@ -347,38 +356,24 @@ if (call.type === 'call' && call.callType === 'delegatecall') { call.valueBigInt = availableValueBigInt; - } else if (call.type === 'selfdestruct') { - call.gasUsedBigInt = availableGasBigInt } var newCallSequence = callSequence.concat([call]); if (subcalls !== undefined) { - var nestedAvailableValueBigInt = availableValueBigInt; - var nestedAvailableGasBigInt = availableGasBigInt; - for (var i = 0; i < subcalls.length; i++) { const nestedSequenced = this.sequence( subcalls[i], newCallSequence, - nestedAvailableValueBigInt, - availableGasBigInt, + call.valueBigInt, traceAddress.concat([i]) ); newCallSequence = nestedSequenced.callSequence; - nestedAvailableValueBigInt = nestedSequenced.availableValueBigInt; - nestedAvailableGasBigInt = nestedSequenced.availableGasBigInt; } } - const newAvailableValueBigInt = availableValueBigInt.subtract(call.valueBigInt); - - const newAvailableGasUsedBigInt = availableGasBigInt.subtract(call.gasUsedBigInt); - return { - callSequence: newCallSequence, - availableValueBigInt: newAvailableValueBigInt, - availableGasBigInt: newAvailableGasUsedBigInt + callSequence: newCallSequence }; }, @@ -410,7 +405,7 @@ delete call.gasBigInt; if (gasBigInt === undefined) { - throw "gasBigInt undefined in " + JSON.stringify(call); + gasBigInt = bigInt.zero; } call.gas = '0x' + gasBigInt.toString(16); @@ -421,7 +416,7 @@ delete call.gasUsedBigInt; if (gasUsedBigInt === undefined) { - throw "gasUsedBigInt undefined in " + JSON.stringify(call); + gasUsedBigInt = bigInt.zero; } call.gasUsed = '0x' + gasUsedBigInt.toString(16); From e02194c738317cb268ac908e2970a3a2d46e02ed Mon Sep 17 00:00:00 2001 From: goodsoft Date: Wed, 1 May 2019 00:35:44 +0300 Subject: [PATCH 26/34] Simplify gas calculation Now `gas` is the amount of gas given to the first opcode in a call, and `gas_used` is the amount of gas left after the last opcode in the call. --- .../lib/ethereum_jsonrpc/geth/tracer.ex | 62 +++++++------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex index a47fc22e3a..6545250b4a 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex @@ -7,8 +7,8 @@ defmodule EthereumJSONRPC.Geth.Tracer do import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] def replay(%{"structLogs" => logs} = result, receipt, tx) when is_list(logs) do - %{"contractAddress" => contract_address, "gasUsed" => gas_used} = receipt - %{"from" => from, "to" => to, "value" => value, "input" => input, "gas" => gas} = tx + %{"contractAddress" => contract_address} = receipt + %{"from" => from, "to" => to, "value" => value, "input" => input} = tx top = to @@ -33,7 +33,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do "traceAddress" => [], "value" => value, "gas" => 0, - "gasUsed" => quantity_to_integer(gas_used) - quantity_to_integer(gas) + "gasUsed" => 0 }) ctx = %{ @@ -79,22 +79,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do end defp step( - %{"gas" => log_gas} = log, - %{stack: [%{"type" => "create", "gas" => 0, "gasUsed" => 0} = call | stack]} = ctx - ) do - step(log, %{ctx | stack: [%{call | "gas" => log_gas, "gasUsed" => log_gas} | stack]}) - end - - defp step(%{"gas" => log_gas} = log, %{stack: [%{"type" => "create", "gas" => 0} = call | stack]} = ctx) do - step(log, %{ctx | stack: [%{call | "gas" => log_gas} | stack]}) - end - - defp step(%{"gas" => log_gas} = log, %{stack: [%{"gas" => 0, "gasUsed" => gas_used} = call | stack]} = ctx) do - step(log, %{ctx | stack: [%{call | "gas" => log_gas, "gasUsed" => gas_used + log_gas} | stack]}) - end - - defp step( - %{"depth" => log_depth, "gas" => log_gas} = log, + %{"depth" => log_depth} = log, %{ depth: stack_depth, stack: [call | stack], @@ -103,7 +88,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do } = ctx ) when log_depth == stack_depth - 1 do - call = process_return(log, %{call | "gasUsed" => call["gasUsed"] - log_gas}) + call = process_return(log, call) subsubcalls = subsubcalls @@ -122,14 +107,19 @@ defmodule EthereumJSONRPC.Geth.Tracer do }) end - defp step(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx) - defp step(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx) - defp step(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx) - defp step(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx) - defp step(%{"op" => "DELEGATECALL"} = log, ctx), do: call_op(log, "delegatecall", ctx) - defp step(%{"op" => "STATICCALL"} = log, ctx), do: call_op(log, "staticcall", ctx) - defp step(%{"op" => "REVERT"}, ctx), do: revert_op(ctx) - defp step(_, ctx), do: ctx + defp step(%{"gas" => log_gas, "gasCost" => log_gas_cost} = log, %{stack: [%{"gas" => call_gas} = call | stack]} = ctx) do + gas = max(call_gas, log_gas) + op(log, %{ctx | stack: [%{call | "gas" => gas, "gasUsed" => gas - log_gas - log_gas_cost} | stack]}) + end + + defp op(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx) + defp op(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx) + defp op(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx) + defp op(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx) + defp op(%{"op" => "DELEGATECALL"} = log, ctx), do: call_op(log, "delegatecall", ctx) + defp op(%{"op" => "STATICCALL"} = log, ctx), do: call_op(log, "staticcall", ctx) + defp op(%{"op" => "REVERT"}, ctx), do: revert_op(ctx) + defp op(_, ctx), do: ctx defp process_return(%{"stack" => log_stack}, %{"type" => "create"} = call) do [ret | _] = Enum.reverse(log_stack) @@ -164,7 +154,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do end defp create_op( - %{"stack" => log_stack, "memory" => log_memory, "gas" => log_gas, "gasCost" => log_gas_cost}, + %{"stack" => log_stack, "memory" => log_memory}, %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx ) do [value, input_offset, input_length | _] = Enum.reverse(log_stack) @@ -180,7 +170,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do "traceAddress" => Enum.reverse(trace_address), "init" => "0x" <> init, "gas" => 0, - "gasUsed" => log_gas - log_gas_cost, + "gasUsed" => 0, "value" => "0x" <> value, "createdContractAddressHash" => nil, "createdContractCode" => "0x" @@ -219,7 +209,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do end defp call_op( - %{"stack" => log_stack, "memory" => log_memory, "gas" => log_gas, "gasCost" => log_gas_cost}, + %{"stack" => log_stack, "memory" => log_memory}, call_type, %{ depth: stack_depth, @@ -259,7 +249,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do "outputOffset" => quantity_to_integer("0x" <> output_offset) * 2, "outputLength" => quantity_to_integer("0x" <> output_length) * 2, "gas" => 0, - "gasUsed" => log_gas - log_gas_cost, + "gasUsed" => 0, "value" => value } @@ -285,12 +275,8 @@ defmodule EthereumJSONRPC.Geth.Tracer do [top | Enum.reverse(calls)] |> List.flatten() - |> Enum.map(fn - %{"gas" => gas, "gasUsed" => gas_used} = call when gas_used < 0 -> - %{call | "gas" => integer_to_quantity(gas - gas_used), "gasUsed" => "0x0"} - - %{"gas" => gas, "gasUsed" => gas_used} = call -> - %{call | "gas" => integer_to_quantity(gas), "gasUsed" => integer_to_quantity(gas_used)} + |> Enum.map(fn %{"gas" => gas, "gasUsed" => gas_used} = call -> + %{call | "gas" => integer_to_quantity(gas), "gasUsed" => integer_to_quantity(gas_used)} end) end end From 4e28620a72eea19740bc6efa5fc8a9c6e4446837 Mon Sep 17 00:00:00 2001 From: Paul Tsupikoff Date: Wed, 1 May 2019 00:53:37 +0300 Subject: [PATCH 27/34] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d61f4975a..0790d8e2e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#1840](https://github.com/poanetwork/blockscout/pull/1840) - Handle case when total supply is nil - [#1838](https://github.com/poanetwork/blockscout/pull/1838) - Block counter calculates only consensus blocks - [#1849](https://github.com/poanetwork/blockscout/pull/1849) - Improve chains menu +- [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth ### Chore From e3fe93b1285e083687470820b5edc0908f34fade Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 1 May 2019 12:50:28 +0300 Subject: [PATCH 28/34] transacion importer return created_contract_address_hash --- apps/explorer/lib/explorer/chain/import/runner/transactions.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index f652309c4d..f46f8a1a9b 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -86,7 +86,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do conflict_target: :hash, on_conflict: on_conflict, for: Transaction, - returning: ~w(block_number index hash internal_transactions_indexed_at block_hash nonce from_address_hash)a, + returning: ~w(block_number index hash internal_transactions_indexed_at block_hash nonce from_address_hash created_contract_address_hash)a, timeout: timeout, timestamps: timestamps ) From b9ec39c12bfa74a9e104e5593437e405cb050f4c Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 1 May 2019 12:58:18 +0300 Subject: [PATCH 29/34] mix format --- apps/explorer/lib/explorer/chain/import/runner/transactions.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index f46f8a1a9b..4f1d2d6fe5 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -86,7 +86,8 @@ defmodule Explorer.Chain.Import.Runner.Transactions do conflict_target: :hash, on_conflict: on_conflict, for: Transaction, - returning: ~w(block_number index hash internal_transactions_indexed_at block_hash nonce from_address_hash created_contract_address_hash)a, + returning: + ~w(block_number index hash internal_transactions_indexed_at block_hash nonce from_address_hash created_contract_address_hash)a, timeout: timeout, timestamps: timestamps ) From 7e0f3cba33384414f4cdfb78b249b0a11de260f1 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 1 May 2019 13:16:53 +0300 Subject: [PATCH 30/34] fetch env without raise --- apps/explorer/lib/explorer/application.ex | 4 +--- apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex | 4 +--- apps/explorer/lib/explorer/known_tokens/known_tokens.ex | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 080b880211..47b30cfa2b 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -56,9 +56,7 @@ defmodule Explorer.Application do end defp should_start?(process) do - :explorer - |> Application.fetch_env!(process) - |> Keyword.fetch!(:enabled) + Application.get_env(:explorer, process, [])[:enabled] == true end defp configure(process) do diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index b4dc0229f9..f0ed6f0f54 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -135,8 +135,6 @@ defmodule Explorer.ExchangeRates do end defp enabled? do - :explorer - |> Application.fetch_env!(__MODULE__) - |> Keyword.fetch!(:enabled) + Application.get_env(:explorer, __MODULE__, [])[:enabled] == true end end diff --git a/apps/explorer/lib/explorer/known_tokens/known_tokens.ex b/apps/explorer/lib/explorer/known_tokens/known_tokens.ex index caca671540..aff9dea74b 100644 --- a/apps/explorer/lib/explorer/known_tokens/known_tokens.ex +++ b/apps/explorer/lib/explorer/known_tokens/known_tokens.ex @@ -134,8 +134,6 @@ defmodule Explorer.KnownTokens do end defp enabled? do - :explorer - |> Application.fetch_env!(__MODULE__) - |> Keyword.fetch!(:enabled) + Application.get_env(:explorer, __MODULE__, [])[:enabled] == true end end From 8683b66250401805d09d17674fff201c4b3e4400 Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Mon, 29 Apr 2019 13:17:55 -0400 Subject: [PATCH 31/34] feat: show raw transaction traces --- CHANGELOG.md | 1 + .../raw_trace/code_highlighting.js | 7 + .../transaction_raw_trace_controller.ex | 55 ++++++ .../lib/block_scout_web/router.ex | 7 + .../templates/transaction/_tabs.html.eex | 5 + .../transaction_raw_trace/_metatags.html.eex | 1 + .../transaction_raw_trace/index.html.eex | 18 ++ .../views/transaction_raw_trace_view.ex | 18 ++ .../block_scout_web/views/transaction_view.ex | 3 +- apps/block_scout_web/priv/gettext/default.pot | 7 + .../priv/gettext/en/LC_MESSAGES/default.po | 7 + .../explorer/chain/internal_transaction.ex | 119 ++++++++++- .../chain/internal_transaction/action.ex | 42 ++++ .../chain/internal_transaction/result.ex | 32 +++ .../chain/internal_transaction_test.exs | 187 +++++++++++++++++- mix.lock | 2 +- 16 files changed, 507 insertions(+), 4 deletions(-) create mode 100644 apps/block_scout_web/assets/js/view_specific/raw_trace/code_highlighting.js create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_metatags.html.eex create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex create mode 100644 apps/explorer/lib/explorer/chain/internal_transaction/action.ex create mode 100644 apps/explorer/lib/explorer/chain/internal_transaction/result.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 66676df93b..18716d9ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#1815](https://github.com/poanetwork/blockscout/pull/1815) - able to search without prefix "0x" - [#1813](https://github.com/poanetwork/blockscout/pull/1813) - add total blocks counter to the main page - [#1806](https://github.com/poanetwork/blockscout/pull/1806) - verify contracts with a post request +- [#1859](https://github.com/poanetwork/blockscout/pull/1859) - feat: show raw transaction traces ### Fixes diff --git a/apps/block_scout_web/assets/js/view_specific/raw_trace/code_highlighting.js b/apps/block_scout_web/assets/js/view_specific/raw_trace/code_highlighting.js new file mode 100644 index 0000000000..2ffb1efa6f --- /dev/null +++ b/apps/block_scout_web/assets/js/view_specific/raw_trace/code_highlighting.js @@ -0,0 +1,7 @@ +import $ from 'jquery' +import hljs from 'highlight.js' + +// only activate highlighting on pages with this selector +if ($('[data-activate-highlight]').length > 0) { + hljs.initHighlightingOnLoad() +} diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex new file mode 100644 index 0000000000..a73290faab --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex @@ -0,0 +1,55 @@ +defmodule BlockScoutWeb.TransactionRawTraceController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.TransactionView + alias Explorer.{Chain, Market} + alias Explorer.ExchangeRates.Token + + def index(conn, %{"transaction_id" => hash_string}) do + with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), + {:ok, transaction} <- + Chain.hash_to_transaction( + hash, + necessity_by_association: %{ + :block => :optional, + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [to_address: :smart_contract] => :optional, + :token_transfers => :optional + } + ) do + options = [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional + } + ] + + internal_transactions = Chain.transaction_to_internal_transactions(transaction, options) + + render( + conn, + "index.html", + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + internal_transactions: internal_transactions, + block_height: Chain.block_height(), + show_token_transfers: Chain.transaction_has_token_transfers?(hash), + transaction: transaction + ) + else + :error -> + conn + |> put_status(422) + |> put_view(TransactionView) + |> render("invalid.html", transaction_hash: hash_string) + + {:error, :not_found} -> + conn + |> put_status(404) + |> put_view(TransactionView) + |> render("not_found.html", transaction_hash: hash_string) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index a6e5a4b968..c0e975793f 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -99,6 +99,13 @@ defmodule BlockScoutWeb.Router do as: :internal_transaction ) + resources( + "/raw_trace", + TransactionRawTraceController, + only: [:index], + as: :raw_trace + ) + resources("/logs", TransactionLogController, only: [:index], as: :log) resources("/token_transfers", TransactionTokenTransferController, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex index fb60b05e19..83bdb3372f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex @@ -20,4 +20,9 @@ "data-test": "transaction_logs_link" ) %> + <%= link( + gettext("Raw Trace"), + class: "nav-link #{tab_status("raw_trace", @conn.request_path)}", + to: transaction_raw_trace_path(@conn, :index, @transaction) + ) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_metatags.html.eex new file mode 100644 index 0000000000..85c3d6675f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.TransactionView, "_metatags.html", conn: @conn, transaction: @transaction %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex new file mode 100644 index 0000000000..2695dea13b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex @@ -0,0 +1,18 @@ +
+ <%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> + +
+
+ <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> +
+ +
+

<%= gettext "Raw Trace" %>

+ <%= if Enum.count(@internal_transactions) > 0 do %> +
<%= for {line, number} <- raw_traces_with_lines(@internal_transactions) do %>
<%= line %>
<% end %>
+ <% else %> + No trace entries found. + <% end %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex new file mode 100644 index 0000000000..3f56e4e948 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex @@ -0,0 +1,18 @@ +defmodule BlockScoutWeb.TransactionRawTraceView do + use BlockScoutWeb, :view + @dialyzer :no_match + + alias Explorer.Chain.InternalTransaction + + def render("scripts.html", %{conn: conn}) do + render_scripts(conn, "raw_trace/code_highlighting.js") + end + + def raw_traces_with_lines(internal_transactions) do + internal_transactions + |> InternalTransaction.internal_transactions_to_raw() + |> Jason.encode!(pretty: true) + |> String.split("\n") + |> Enum.with_index() + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 8fa133550f..418a7161de 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -13,7 +13,7 @@ defmodule BlockScoutWeb.TransactionView do import BlockScoutWeb.Gettext import BlockScoutWeb.Tokens.Helpers - @tabs ["token_transfers", "internal_transactions", "logs"] + @tabs ["token_transfers", "internal_transactions", "logs", "raw_trace"] defguardp is_transaction_type(mod) when mod in [InternalTransaction, Transaction] @@ -338,6 +338,7 @@ defmodule BlockScoutWeb.TransactionView do defp tab_name(["token_transfers"]), do: gettext("Token Transfers") defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions") defp tab_name(["logs"]), do: gettext("Logs") + defp tab_name(["raw_trace"]), do: gettext("Raw Trace") defp decode_params(params, types) do params diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index eb4adbee6d..6f198f8793 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1732,3 +1732,10 @@ msgstr "" #: lib/block_scout_web/templates/transaction/overview.html.eex:225 msgid "Gas" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:10 +#: lib/block_scout_web/views/transaction_view.ex:341 +msgid "Raw Trace" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 9d15643bdc..64201bee65 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1732,3 +1732,10 @@ msgstr "" #: lib/block_scout_web/templates/transaction/overview.html.eex:225 msgid "Gas" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:10 +#: lib/block_scout_web/views/transaction_view.ex:341 +msgid "Raw Trace" +msgstr "" diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 6a8fa7b6bf..60a0f3ed08 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.InternalTransaction do use Explorer.Schema alias Explorer.Chain.{Address, Data, Gas, Hash, Transaction, Wei} - alias Explorer.Chain.InternalTransaction.{CallType, Type} + alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type} @typedoc """ * `block_number` - the `t:Explorer.Chain.Block.t/0` `number` that the `transaction` is collated into. @@ -497,4 +497,121 @@ defmodule Explorer.Chain.InternalTransaction do def where_block_number_is_not_null(query) do where(query, [t], not is_nil(t.block_number)) end + + def internal_transactions_to_raw(internal_transactions) when is_list(internal_transactions) do + internal_transactions + |> Enum.map(&internal_transaction_to_raw/1) + |> add_subtraces() + end + + defp internal_transaction_to_raw(%{type: :call} = transaction) do + %{ + call_type: call_type, + to_address_hash: to_address_hash, + from_address_hash: from_address_hash, + input: input, + gas: gas, + value: value, + trace_address: trace_address + } = transaction + + action = %{ + "callType" => call_type, + "to" => to_address_hash, + "from" => from_address_hash, + "input" => input, + "gas" => gas, + "value" => value + } + + %{ + "type" => "call", + "action" => Action.to_raw(action), + "traceAddress" => trace_address + } + |> put_raw_call_error_or_result(transaction) + end + + defp internal_transaction_to_raw(%{type: :create} = transaction) do + %{ + from_address_hash: from_address_hash, + gas: gas, + init: init, + trace_address: trace_address, + value: value + } = transaction + + action = %{"from" => from_address_hash, "gas" => gas, "init" => init, "value" => value} + + %{ + "type" => "create", + "action" => Action.to_raw(action), + "traceAddress" => trace_address + } + |> put_raw_create_error_or_result(transaction) + end + + defp internal_transaction_to_raw(%{type: :selfdestruct} = transaction) do + %{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash, + trace_address: trace_address, + value: value + } = transaction + + action = %{ + "address" => from_address_hash, + "balance" => value, + "refundAddress" => to_address_hash + } + + %{ + "type" => "suicide", + "action" => Action.to_raw(action), + "traceAddress" => trace_address + } + end + + defp add_subtraces(traces) do + Enum.map(traces, fn trace -> + Map.put(trace, "subtraces", count_subtraces(trace, traces)) + end) + end + + defp count_subtraces(%{"traceAddress" => trace_address}, traces) do + Enum.count(traces, fn %{"traceAddress" => trace_address_candidate} -> + direct_descendant?(trace_address, trace_address_candidate) + end) + end + + defp direct_descendant?([], [_]), do: true + + defp direct_descendant?([elem | remaining_left], [elem | remaining_right]), + do: direct_descendant?(remaining_left, remaining_right) + + defp direct_descendant?(_, _), do: false + + defp put_raw_call_error_or_result(raw, %{error: error}) when not is_nil(error) do + Map.put(raw, "error", error) + end + + defp put_raw_call_error_or_result(raw, %{gas_used: gas_used, output: output}) do + Map.put(raw, "result", Result.to_raw(%{"gasUsed" => gas_used, "output" => output})) + end + + defp put_raw_create_error_or_result(raw, %{error: error}) when not is_nil(error) do + Map.put(raw, "error", error) + end + + defp put_raw_create_error_or_result(raw, %{ + created_contract_code: code, + created_contract_address_hash: created_contract_address_hash, + gas_used: gas_used + }) do + Map.put( + raw, + "result", + Result.to_raw(%{"gasUsed" => gas_used, "code" => code, "address" => created_contract_address_hash}) + ) + end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/action.ex b/apps/explorer/lib/explorer/chain/internal_transaction/action.ex new file mode 100644 index 0000000000..663e306515 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/internal_transaction/action.ex @@ -0,0 +1,42 @@ +defmodule Explorer.Chain.InternalTransaction.Action do + @moduledoc """ + The action that was performed in a `t:EthereumJSONRPC.Parity.Trace.t/0` + """ + + import EthereumJSONRPC, only: [integer_to_quantity: 1] + alias Explorer.Chain.{Data, Hash, Wei} + + def to_raw(action) when is_map(action) do + Enum.into(action, %{}, &entry_to_raw/1) + end + + defp entry_to_raw({key, %Data{} = data}) when key in ~w(init input) do + {key, Data.to_string(data)} + end + + defp entry_to_raw({key, %Hash{} = address}) when key in ~w(address from refundAddress to) do + {key, to_string(address)} + end + + defp entry_to_raw({"callType", type}) do + {"callType", Atom.to_string(type)} + end + + defp entry_to_raw({"gas" = key, %Decimal{} = decimal}) do + value = + decimal + |> Decimal.round() + |> Decimal.to_integer() + + {key, integer_to_quantity(value)} + end + + defp entry_to_raw({key, %Wei{value: value}}) when key in ~w(balance value) do + rounded = + value + |> Decimal.round() + |> Decimal.to_integer() + + {key, integer_to_quantity(rounded)} + end +end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/result.ex b/apps/explorer/lib/explorer/chain/internal_transaction/result.ex new file mode 100644 index 0000000000..5b4e3102fc --- /dev/null +++ b/apps/explorer/lib/explorer/chain/internal_transaction/result.ex @@ -0,0 +1,32 @@ +defmodule Explorer.Chain.InternalTransaction.Result do + @moduledoc """ + The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`. + """ + + import EthereumJSONRPC, only: [integer_to_quantity: 1] + + alias Explorer.Chain.{Data, Hash} + + def to_raw(result) when is_map(result) do + Enum.into(result, %{}, &entry_to_raw/1) + end + + defp entry_to_raw({"output" = key, %Data{} = data}) do + {key, Data.to_string(data)} + end + + defp entry_to_raw({"address" = key, %Hash{} = hash}) do + {key, to_string(hash)} + end + + defp entry_to_raw({"code", _} = entry), do: entry + + defp entry_to_raw({key, decimal}) when key in ~w(gasUsed) do + integer = + decimal + |> Decimal.round() + |> Decimal.to_integer() + + {key, integer_to_quantity(integer)} + end +end diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index 2b92339515..54ace519bd 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -1,7 +1,10 @@ defmodule Explorer.Chain.InternalTransactionTest do use Explorer.DataCase - alias Explorer.Chain.InternalTransaction + alias Explorer.Chain.{InternalTransaction, Wei} + alias Explorer.Factory + + import EthereumJSONRPC, only: [integer_to_quantity: 1] doctest InternalTransaction @@ -54,4 +57,186 @@ defmodule Explorer.Chain.InternalTransactionTest do assert Repo.insert(changeset) end end + + defp call_type(opts) do + defaults = [ + type: :call, + call_type: :call, + to_address_hash: Factory.address_hash(), + from_address_hash: Factory.address_hash(), + input: Factory.transaction_input(), + output: Factory.transaction_input(), + gas: Decimal.new(50_000), + gas_used: Decimal.new(25_000), + value: %Wei{value: 100}, + index: 0, + trace_address: [] + ] + + struct!(InternalTransaction, Keyword.merge(defaults, opts)) + end + + defp create_type(opts) do + defaults = [ + type: :create, + from_address_hash: Factory.address_hash(), + gas: Decimal.new(50_000), + gas_used: Decimal.new(25_000), + value: %Wei{value: 100}, + index: 0, + init: Factory.transaction_input(), + trace_address: [] + ] + + struct!(InternalTransaction, Keyword.merge(defaults, opts)) + end + + defp selfdestruct_type(opts) do + defaults = [ + type: :selfdestruct, + from_address_hash: Factory.address_hash(), + to_address_hash: Factory.address_hash(), + gas: Decimal.new(50_000), + gas_used: Decimal.new(25_000), + value: %Wei{value: 100}, + index: 0, + trace_address: [] + ] + + struct!(InternalTransaction, Keyword.merge(defaults, opts)) + end + + describe "internal_transactions_to_raw" do + test "it adds subtrace count" do + transactions = [ + call_type(trace_address: []), + call_type(trace_address: [0]), + call_type(trace_address: [1]), + call_type(trace_address: [2]), + call_type(trace_address: [0, 0]), + call_type(trace_address: [0, 1]), + call_type(trace_address: [1, 0]), + call_type(trace_address: [0, 0, 0]), + call_type(trace_address: [0, 0, 1]), + call_type(trace_address: [0, 0, 2]), + call_type(trace_address: [0, 1, 0]), + call_type(trace_address: [0, 1, 1]) + ] + + subtraces = + transactions + |> InternalTransaction.internal_transactions_to_raw() + |> Enum.map(&Map.get(&1, "subtraces")) + + assert subtraces == [3, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0] + end + + test "it correctly formats a call" do + from = Factory.address_hash() + to = Factory.address_hash() + gas = 50_000 + gas_used = 25_000 + input = Factory.transaction_input() + value = 50 + output = Factory.transaction_input() + + call_transaction = + call_type( + from_address_hash: from, + to_address_hash: to, + gas: Decimal.new(gas), + gas_used: Decimal.new(gas_used), + input: input, + value: %Wei{value: value}, + output: output + ) + + [call] = InternalTransaction.internal_transactions_to_raw([call_transaction]) + + assert call == %{ + "action" => %{ + "callType" => "call", + "from" => to_string(from), + "gas" => integer_to_quantity(gas), + "input" => to_string(input), + "to" => to_string(to), + "value" => integer_to_quantity(value) + }, + "result" => %{ + "gasUsed" => integer_to_quantity(gas_used), + "output" => to_string(output) + }, + "subtraces" => 0, + "traceAddress" => [], + "type" => "call" + } + end + + test "it correctly formats a create" do + contract_code = Factory.contract_code_info().bytecode + contract_address = Factory.address_hash() + from = Factory.address_hash() + gas = 50_000 + gas_used = 25_000 + init = Factory.transaction_input() + value = 50 + + create_transaction = + create_type( + from_address_hash: from, + created_contract_code: contract_code, + created_contract_address_hash: contract_address, + gas: Decimal.new(gas), + gas_used: Decimal.new(gas_used), + init: init, + value: %Wei{value: value} + ) + + [create] = InternalTransaction.internal_transactions_to_raw([create_transaction]) + + assert create == %{ + "action" => %{ + "from" => to_string(from), + "gas" => integer_to_quantity(gas), + "init" => to_string(init), + "value" => integer_to_quantity(value) + }, + "result" => %{ + "address" => to_string(contract_address), + "code" => to_string(contract_code), + "gasUsed" => integer_to_quantity(gas_used) + }, + "subtraces" => 0, + "traceAddress" => [], + "type" => "create" + } + end + + test "it correctly formats a selfdestruct" do + from_address = Factory.address_hash() + to_address = Factory.address_hash() + + value = 50 + + selfdestruct_transaction = + selfdestruct_type( + to_address_hash: to_address, + from_address_hash: from_address, + value: %Wei{value: value} + ) + + [selfdestruct] = InternalTransaction.internal_transactions_to_raw([selfdestruct_transaction]) + + assert selfdestruct == %{ + "action" => %{ + "address" => to_string(from_address), + "balance" => integer_to_quantity(value), + "refundAddress" => to_string(to_address) + }, + "subtraces" => 0, + "traceAddress" => [], + "type" => "suicide" + } + end + end end diff --git a/mix.lock b/mix.lock index e9066554b1..4ed50a45fe 100644 --- a/mix.lock +++ b/mix.lock @@ -33,7 +33,7 @@ "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.0.4", "e7a0feb0b2484b90981c56d5cd03c52122c1c31ded0b95ed213b7c5c07ae6737", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, - "ex_abi": {:hex, :ex_abi, "0.1.18", "19db9bffdd201edbdff97d7dd5849291218b17beda045c1b76bff5248964f37d", [:mix], [{:exth_crypto, "~> 0.1.4", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_abi": {:hex, :ex_abi, "0.2.1", "e8224ef22782b58d535bf3e29e73379af48df52cc9a941746980d14b32e793c2", [:mix], [{:exth_crypto, "~> 0.1.6", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"}, "ex_cldr": {:hex, :ex_cldr, "1.3.2", "8f4a00c99d1c537b8e8db7e7903f4bd78d82a7289502d080f70365392b13921b", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, optional: true]}]}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.2.0", "ef27299922da913ffad1ed296cacf28b6452fc1243b77301dc17c03276c6ee34", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, optional: false]}, {:ex_cldr, "~> 1.3", [hex: :ex_cldr, optional: false]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, optional: false]}]}, "ex_cldr_units": {:hex, :ex_cldr_units, "1.1.1", "b3c7256709bdeb3740a5f64ce2bce659eb9cf4cc1afb4cf94aba033b4a18bc5f", [:mix], [{:ex_cldr, "~> 1.0", [hex: :ex_cldr, optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, optional: false]}]}, From 27375bdb4d5c6be76fec00ea4b5452bbad7998af Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Tue, 30 Apr 2019 16:06:29 -0400 Subject: [PATCH 32/34] undo mix.lock change --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 4ed50a45fe..e9066554b1 100644 --- a/mix.lock +++ b/mix.lock @@ -33,7 +33,7 @@ "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.0.4", "e7a0feb0b2484b90981c56d5cd03c52122c1c31ded0b95ed213b7c5c07ae6737", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, - "ex_abi": {:hex, :ex_abi, "0.2.1", "e8224ef22782b58d535bf3e29e73379af48df52cc9a941746980d14b32e793c2", [:mix], [{:exth_crypto, "~> 0.1.6", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_abi": {:hex, :ex_abi, "0.1.18", "19db9bffdd201edbdff97d7dd5849291218b17beda045c1b76bff5248964f37d", [:mix], [{:exth_crypto, "~> 0.1.4", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"}, "ex_cldr": {:hex, :ex_cldr, "1.3.2", "8f4a00c99d1c537b8e8db7e7903f4bd78d82a7289502d080f70365392b13921b", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, optional: true]}]}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.2.0", "ef27299922da913ffad1ed296cacf28b6452fc1243b77301dc17c03276c6ee34", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, optional: false]}, {:ex_cldr, "~> 1.3", [hex: :ex_cldr, optional: false]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, optional: false]}]}, "ex_cldr_units": {:hex, :ex_cldr_units, "1.1.1", "b3c7256709bdeb3740a5f64ce2bce659eb9cf4cc1afb4cf94aba033b4a18bc5f", [:mix], [{:ex_cldr, "~> 1.0", [hex: :ex_cldr, optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, optional: false]}]}, From 1d22acd4791fa71609e9db91fea89c1109ce9f20 Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Wed, 1 May 2019 12:02:32 -0400 Subject: [PATCH 33/34] start the index from one --- .../lib/block_scout_web/views/transaction_raw_trace_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex index 3f56e4e948..9d4a438582 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex @@ -13,6 +13,6 @@ defmodule BlockScoutWeb.TransactionRawTraceView do |> InternalTransaction.internal_transactions_to_raw() |> Jason.encode!(pretty: true) |> String.split("\n") - |> Enum.with_index() + |> Enum.with_index(1) end end From 9a480f8db69c0429c8044cd425d3938e4d75334e Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 3 May 2019 11:13:58 +0300 Subject: [PATCH 34/34] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd047fd5c..de28200d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [#1849](https://github.com/poanetwork/blockscout/pull/1849) - Improve chains menu - [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth - [#1868](https://github.com/poanetwork/blockscout/pull/1868) - fix: logs list endpoint performance +- [#1822](https://github.com/poanetwork/blockscout/pull/1822) - Fix style breaks in decompiled contract code view ### Chore