From 87863a24d741016207fcbe4f765f291a2144c7f8 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 30 Aug 2018 14:14:49 +0200 Subject: [PATCH 01/21] add tvm contracts --- .../api/models/TransactionSerializer.scala | 123 +++++- app/org/tronscan/utils/ContractUtils.scala | 39 +- app/org/tronscan/utils/ProtoUtils.scala | 51 +++ app/protobuf/api/api.proto | 350 +++++++++++++++++- app/protobuf/core/Contract.proto | 100 ++++- app/protobuf/core/Tron.proto | 202 ++++++++-- 6 files changed, 805 insertions(+), 60 deletions(-) diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index 003098b..0670567 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -7,7 +7,7 @@ import io.circe.{Decoder, Encoder, HCursor, Json => Js} import org.joda.time.DateTime import org.tron.common.crypto.ECKey import org.tron.common.utils.{Base58, ByteArray, Crypto, Sha256Hash} -import org.tron.protos.Tron.Transaction.Contract.ContractType.{AccountCreateContract, AccountUpdateContract, AssetIssueContract, DeployContract, FreezeBalanceContract, ParticipateAssetIssueContract, TransferAssetContract, TransferContract, UnfreezeAssetContract, UnfreezeBalanceContract, UpdateAssetContract, VoteAssetContract, VoteWitnessContract, WithdrawBalanceContract, WitnessCreateContract, WitnessUpdateContract} +import org.tron.protos.Tron.Transaction.Contract.ContractType.{AccountCreateContract, AccountUpdateContract, AssetIssueContract, CreateSmartContract, ExchangeCreateContract, ExchangeInjectContract, ExchangeTransactionContract, ExchangeWithdrawContract, FreezeBalanceContract, ParticipateAssetIssueContract, ProposalApproveContract, ProposalCreateContract, ProposalDeleteContract, TransferAssetContract, TransferContract, TriggerSmartContract, UnfreezeAssetContract, UnfreezeBalanceContract, UpdateAssetContract, VoteAssetContract, VoteWitnessContract, WithdrawBalanceContract, WitnessCreateContract, WitnessUpdateContract} import org.tron.protos.Tron.{AccountType, Transaction} import org.tronscan.Extensions._ import org.tronscan.protocol.MainNetFormatter @@ -216,14 +216,82 @@ object TransactionSerializer { implicit val encodeUnfreezeAssetContract = new Encoder[org.tron.protos.Contract.UnfreezeAssetContract] { def apply(contract: org.tron.protos.Contract.UnfreezeAssetContract): Js = Js.obj( - "ownerAddress" -> Base58.encode58Check(contract.ownerAddress.toByteArray).asJson, + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, ) } - implicit val encodeDeployContract = new Encoder[org.tron.protos.Contract.DeployContract] { - def apply(contract: org.tron.protos.Contract.DeployContract): Js = Js.obj( - "ownerAddress" -> Base58.encode58Check(contract.ownerAddress.toByteArray).asJson, - "script" -> ByteArray.toHexString(contract.script.toByteArray).asJson, + + implicit val encodeProposalCreateContract = new Encoder[org.tron.protos.Contract.ProposalCreateContract] { + def apply(contract: org.tron.protos.Contract.ProposalCreateContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "parameters" -> contract.parameters.asJson, + ) + } + + implicit val encodeProposalApproveContract = new Encoder[org.tron.protos.Contract.ProposalApproveContract] { + def apply(contract: org.tron.protos.Contract.ProposalApproveContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "proposalId" -> contract.proposalId.asJson, + "isAddApproval" -> contract.isAddApproval.asJson, + ) + } + + implicit val encodeProposalDeleteContract = new Encoder[org.tron.protos.Contract.ProposalDeleteContract] { + def apply(contract: org.tron.protos.Contract.ProposalDeleteContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "proposalId" -> contract.proposalId.asJson, + ) + } + + implicit val encodeCreateSmartContract = new Encoder[org.tron.protos.Contract.CreateSmartContract] { + def apply(contract: org.tron.protos.Contract.CreateSmartContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + ) + } + + implicit val encodeTriggerSmartContract = new Encoder[org.tron.protos.Contract.TriggerSmartContract] { + def apply(contract: org.tron.protos.Contract.TriggerSmartContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "contractAddress" -> contract.contractAddress.encodeAddress.asJson, + "callValue" -> contract.callValue.asJson, + "data" -> ByteArray.toHexString(contract.data.toByteArray).asJson, + ) + } + + implicit val encodeExchangeCreateContract = new Encoder[org.tron.protos.Contract.ExchangeCreateContract] { + def apply(contract: org.tron.protos.Contract.ExchangeCreateContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "firstTokenId" -> contract.firstTokenId.decodeString.asJson, + "firstTokenBalance" -> contract.firstTokenBalance.asJson, + "secondTokenId" -> contract.secondTokenId.decodeString.asJson, + "secondTokenBalance" -> contract.secondTokenBalance.asJson, + ) + } + + implicit val encodeExchangeInjectContract = new Encoder[org.tron.protos.Contract.ExchangeInjectContract] { + def apply(contract: org.tron.protos.Contract.ExchangeInjectContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "exchangeId" -> contract.exchangeId.asJson, + "tokenId" -> contract.tokenId.decodeString.asJson, + "quant" -> contract.quant.asJson, + ) + } + + implicit val encodeExchangeWithdrawContract = new Encoder[org.tron.protos.Contract.ExchangeWithdrawContract] { + def apply(contract: org.tron.protos.Contract.ExchangeWithdrawContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "exchangeId" -> contract.exchangeId.asJson, + "tokenId" -> contract.tokenId.decodeString.asJson, + "quant" -> contract.quant.asJson, + ) + } + + implicit val encodeExchangeTransactionContract = new Encoder[org.tron.protos.Contract.ExchangeTransactionContract] { + def apply(contract: org.tron.protos.Contract.ExchangeTransactionContract): Js = Js.obj( + "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, + "exchangeId" -> contract.exchangeId.asJson, + "tokenId" -> contract.tokenId.decodeString.asJson, + "quant" -> contract.quant.asJson, ) } @@ -247,9 +315,6 @@ object TransactionSerializer { case AssetIssueContract => org.tron.protos.Contract.AssetIssueContract.parseFrom(contract.getParameter.value.toByteArray).asJson - case DeployContract => - org.tron.protos.Contract.DeployContract.parseFrom(contract.getParameter.value.toByteArray).asJson - case ParticipateAssetIssueContract => org.tron.protos.Contract.ParticipateAssetIssueContract.parseFrom(contract.getParameter.value.toByteArray).asJson @@ -277,10 +342,44 @@ object TransactionSerializer { case UpdateAssetContract => org.tron.protos.Contract.UpdateAssetContract.parseFrom(contract.getParameter.value.toByteArray).asJson + case ProposalCreateContract => + org.tron.protos.Contract.ProposalCreateContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case ProposalApproveContract => + org.tron.protos.Contract.ProposalApproveContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case ProposalDeleteContract => + org.tron.protos.Contract.ProposalDeleteContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case CreateSmartContract => + org.tron.protos.Contract.CreateSmartContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case TriggerSmartContract => + org.tron.protos.Contract.TriggerSmartContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + // case BuyStorageBytesContract => + // org.tron.protos.Contract.BuyStorageBytesContract.parseFrom(any.value.toByteArray) + + // case BuyStorageContract => + // org.tron.protos.Contract.BuyStorageContract.parseFrom(any.value.toByteArray) + + // case SellStorageContract => + // org.tron.protos.Contract.SellStorageContract.parseFrom(any.value.toByteArray) + + case ExchangeCreateContract => + org.tron.protos.Contract.ExchangeCreateContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case ExchangeInjectContract => + org.tron.protos.Contract.ExchangeInjectContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case ExchangeWithdrawContract => + org.tron.protos.Contract.ExchangeWithdrawContract.parseFrom(contract.getParameter.value.toByteArray).asJson + + case ExchangeTransactionContract => + org.tron.protos.Contract.ExchangeTransactionContract.parseFrom(contract.getParameter.value.toByteArray).asJson + case _ => - Js.obj( - "type" -> contract.`type`.toString().asJson - ) + Js.obj() } diff --git a/app/org/tronscan/utils/ContractUtils.scala b/app/org/tronscan/utils/ContractUtils.scala index e3aba87..756a9aa 100644 --- a/app/org/tronscan/utils/ContractUtils.scala +++ b/app/org/tronscan/utils/ContractUtils.scala @@ -27,9 +27,6 @@ object ContractUtils { case c: AssetIssueContract => c.ownerAddress.encodeAddress - case c: DeployContract => - c.ownerAddress.encodeAddress - case c: ParticipateAssetIssueContract => c.ownerAddress.encodeAddress @@ -57,6 +54,42 @@ object ContractUtils { case c: UpdateAssetContract => c.ownerAddress.encodeAddress + case c: ProposalCreateContract => + c.ownerAddress.encodeAddress + + case c: ProposalApproveContract => + c.ownerAddress.encodeAddress + + case c: ProposalDeleteContract => + c.ownerAddress.encodeAddress + + case c: CreateSmartContract => + c.ownerAddress.encodeAddress + + case c: TriggerSmartContract => + c.ownerAddress.encodeAddress + + case c: BuyStorageBytesContract => + c.ownerAddress.encodeAddress + + case c: BuyStorageContract => + c.ownerAddress.encodeAddress + + case c: SellStorageContract => + c.ownerAddress.encodeAddress + + case c: ExchangeCreateContract => + c.ownerAddress.encodeAddress + + case c: ExchangeInjectContract => + c.ownerAddress.encodeAddress + + case c: ExchangeWithdrawContract => + c.ownerAddress.encodeAddress + + case c: ExchangeTransactionContract => + c.ownerAddress.encodeAddress + case _ => "" } diff --git a/app/org/tronscan/utils/ProtoUtils.scala b/app/org/tronscan/utils/ProtoUtils.scala index c72994d..95791d5 100644 --- a/app/org/tronscan/utils/ProtoUtils.scala +++ b/app/org/tronscan/utils/ProtoUtils.scala @@ -1,5 +1,6 @@ package org.tronscan.utils +import org.tron.protos.Contract.{BuyStorageBytesContract, BuyStorageContract, SellStorageContract} import org.tron.protos.Tron.Transaction object ProtoUtils { @@ -14,32 +15,82 @@ object ProtoUtils { contract.`type` match { case TransferContract => org.tron.protos.Contract.TransferContract.parseFrom(any.value.toByteArray) + case TransferAssetContract => org.tron.protos.Contract.TransferAssetContract.parseFrom(any.value.toByteArray) + case VoteWitnessContract => org.tron.protos.Contract.VoteWitnessContract.parseFrom(any.value.toByteArray) + case AssetIssueContract => org.tron.protos.Contract.AssetIssueContract.parseFrom(any.value.toByteArray) + case UpdateAssetContract => org.tron.protos.Contract.UpdateAssetContract.parseFrom(any.value.toByteArray) + case ParticipateAssetIssueContract => org.tron.protos.Contract.ParticipateAssetIssueContract.parseFrom(any.value.toByteArray) + case WitnessCreateContract => org.tron.protos.Contract.WitnessCreateContract.parseFrom(any.value.toByteArray) + case WitnessUpdateContract => org.tron.protos.Contract.WitnessUpdateContract.parseFrom(any.value.toByteArray) + case UnfreezeBalanceContract => org.tron.protos.Contract.UnfreezeBalanceContract.parseFrom(any.value.toByteArray) + case FreezeBalanceContract => org.tron.protos.Contract.FreezeBalanceContract.parseFrom(any.value.toByteArray) + case WithdrawBalanceContract => org.tron.protos.Contract.WithdrawBalanceContract.parseFrom(any.value.toByteArray) + case AccountUpdateContract => org.tron.protos.Contract.AccountUpdateContract.parseFrom(any.value.toByteArray) + case UnfreezeAssetContract => org.tron.protos.Contract.UnfreezeAssetContract.parseFrom(any.value.toByteArray) + case AccountCreateContract => org.tron.protos.Contract.AccountCreateContract.parseFrom(any.value.toByteArray) + + case ProposalCreateContract => + org.tron.protos.Contract.ProposalCreateContract.parseFrom(any.value.toByteArray) + + case ProposalApproveContract => + org.tron.protos.Contract.ProposalApproveContract.parseFrom(any.value.toByteArray) + + case ProposalDeleteContract => + org.tron.protos.Contract.ProposalDeleteContract.parseFrom(any.value.toByteArray) + + case CreateSmartContract => + org.tron.protos.Contract.CreateSmartContract.parseFrom(any.value.toByteArray) + + case TriggerSmartContract => + org.tron.protos.Contract.TriggerSmartContract.parseFrom(any.value.toByteArray) + +// case BuyStorageBytesContract => +// org.tron.protos.Contract.BuyStorageBytesContract.parseFrom(any.value.toByteArray) + +// case BuyStorageContract => +// org.tron.protos.Contract.BuyStorageContract.parseFrom(any.value.toByteArray) + +// case SellStorageContract => +// org.tron.protos.Contract.SellStorageContract.parseFrom(any.value.toByteArray) + + case ExchangeCreateContract => + org.tron.protos.Contract.ExchangeCreateContract.parseFrom(any.value.toByteArray) + + case ExchangeInjectContract => + org.tron.protos.Contract.ExchangeInjectContract.parseFrom(any.value.toByteArray) + + case ExchangeWithdrawContract => + org.tron.protos.Contract.ExchangeWithdrawContract.parseFrom(any.value.toByteArray) + + case ExchangeTransactionContract => + org.tron.protos.Contract.ExchangeTransactionContract.parseFrom(any.value.toByteArray) + case _ => throw new Exception("Unknown Contract") } diff --git a/app/protobuf/api/api.proto b/app/protobuf/api/api.proto index 1266a7d..a84e460 100644 --- a/app/protobuf/api/api.proto +++ b/app/protobuf/api/api.proto @@ -22,6 +22,17 @@ service Wallet { }; }; + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccountbyid" + body: "*" + additional_bindings { + get: "/wallet/getaccountbyid" + } + }; + }; + + //Please use CreateTransaction2 instead of this function. rpc CreateTransaction (TransferContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/createtransaction" @@ -31,6 +42,9 @@ service Wallet { } }; }; + //Use this function instead of CreateTransaction. + rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { + }; rpc BroadcastTransaction (Transaction) returns (Return) { option (google.api.http) = { @@ -41,7 +55,7 @@ service Wallet { } }; }; - + //Please use UpdateAccount2 instead of this function. rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/updateaccount" @@ -52,6 +66,22 @@ service Wallet { }; }; + + rpc SetAccountId (SetAccountIdContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/setaccountid" + body: "*" + additional_bindings { + get: "/wallet/setaccountid" + } + }; + }; + + //Use this function instead of UpdateAccount. + rpc UpdateAccount2 (AccountUpdateContract) returns (TransactionExtention) { + }; + + //Please use VoteWitnessAccount2 instead of this function. rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/votewitnessaccount" @@ -62,6 +92,14 @@ service Wallet { }; }; + //modify the consume_user_resource_percent + rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { + }; + + //Use this function instead of VoteWitnessAccount. + rpc VoteWitnessAccount2 (VoteWitnessContract) returns (TransactionExtention) { + }; + //Please use CreateAssetIssue2 instead of this function. rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/createassetissue" @@ -71,7 +109,10 @@ service Wallet { } }; }; - + //Use this function instead of CreateAssetIssue. + rpc CreateAssetIssue2 (AssetIssueContract) returns (TransactionExtention) { + }; + //Please use UpdateWitness2 instead of this function. rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/updatewitness" @@ -81,7 +122,10 @@ service Wallet { } }; }; - + //Use this function instead of UpdateWitness. + rpc UpdateWitness2 (WitnessUpdateContract) returns (TransactionExtention) { + }; + //Please use CreateAccount2 instead of this function. rpc CreateAccount (AccountCreateContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/createaccount" @@ -91,7 +135,10 @@ service Wallet { } }; }; - + //Use this function instead of CreateAccount. + rpc CreateAccount2 (AccountCreateContract) returns (TransactionExtention) { + } + //Please use CreateWitness2 instead of this function. rpc CreateWitness (WitnessCreateContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/createwitness" @@ -101,7 +148,10 @@ service Wallet { } }; }; - + //Use this function instead of CreateWitness. + rpc CreateWitness2 (WitnessCreateContract) returns (TransactionExtention) { + } + //Please use TransferAsset2 instead of this function. rpc TransferAsset (TransferAssetContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/transferasset" @@ -111,7 +161,10 @@ service Wallet { } }; } - + //Use this function instead of TransferAsset. + rpc TransferAsset2 (TransferAssetContract) returns (TransactionExtention) { + } + //Please use ParticipateAssetIssue2 instead of this function. rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/participateassetissue" @@ -121,7 +174,10 @@ service Wallet { } }; } - + //Use this function instead of ParticipateAssetIssue. + rpc ParticipateAssetIssue2 (ParticipateAssetIssueContract) returns (TransactionExtention) { + } + //Please use FreezeBalance2 instead of this function. rpc FreezeBalance (FreezeBalanceContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/freezebalance" @@ -131,7 +187,10 @@ service Wallet { } }; } - + //Use this function instead of FreezeBalance. + rpc FreezeBalance2 (FreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeBalance2 instead of this function. rpc UnfreezeBalance (UnfreezeBalanceContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/unfreezebalance" @@ -141,7 +200,10 @@ service Wallet { } }; } - + //Use this function instead of UnfreezeBalance. + rpc UnfreezeBalance2 (UnfreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeAsset2 instead of this function. rpc UnfreezeAsset (UnfreezeAssetContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/unfreezeasset" @@ -151,7 +213,10 @@ service Wallet { } }; } - + //Use this function instead of UnfreezeAsset. + rpc UnfreezeAsset2 (UnfreezeAssetContract) returns (TransactionExtention) { + } + //Please use WithdrawBalance2 instead of this function. rpc WithdrawBalance (WithdrawBalanceContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/withdrawbalance" @@ -161,7 +226,10 @@ service Wallet { } }; } - + //Use this function instead of WithdrawBalance. + rpc WithdrawBalance2 (WithdrawBalanceContract) returns (TransactionExtention) { + } + //Please use UpdateAsset2 instead of this function. rpc UpdateAsset (UpdateAssetContract) returns (Transaction) { option (google.api.http) = { post: "/wallet/updateasset" @@ -171,6 +239,39 @@ service Wallet { } }; } + //Use this function instead of UpdateAsset. + rpc UpdateAsset2 (UpdateAssetContract) returns (TransactionExtention) { + } + + rpc ProposalCreate (ProposalCreateContract) returns (TransactionExtention) { + } + + rpc ProposalApprove (ProposalApproveContract) returns (TransactionExtention) { + } + + rpc ProposalDelete (ProposalDeleteContract) returns (TransactionExtention) { + } + + rpc BuyStorage (BuyStorageContract) returns (TransactionExtention) { + } + + rpc BuyStorageBytes (BuyStorageBytesContract) returns (TransactionExtention) { + } + + rpc SellStorage (SellStorageContract) returns (TransactionExtention) { + } + + rpc ExchangeCreate (ExchangeCreateContract) returns (TransactionExtention) { + } + + rpc ExchangeInject (ExchangeInjectContract) returns (TransactionExtention) { + } + + rpc ExchangeWithdraw (ExchangeWithdrawContract) returns (TransactionExtention) { + } + + rpc ExchangeTransaction (ExchangeTransactionContract) returns (TransactionExtention) { + } rpc ListNodes (EmptyMessage) returns (NodeList) { option (google.api.http) = { @@ -200,6 +301,8 @@ service Wallet { } }; }; + rpc GetAccountResource (Account) returns (AccountResourceMessage) { + }; rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { option (google.api.http) = { post: "/wallet/getassetissuebyname" @@ -209,6 +312,7 @@ service Wallet { } }; } + //Please use GetNowBlock2 instead of this function. rpc GetNowBlock (EmptyMessage) returns (Block) { option (google.api.http) = { post: "/wallet/getnowblock" @@ -218,6 +322,10 @@ service Wallet { } }; } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. rpc GetBlockByNum (NumberMessage) returns (Block) { option (google.api.http) = { post: "/wallet/getblockbynum" @@ -227,6 +335,12 @@ service Wallet { } }; } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } rpc GetBlockById (BytesMessage) returns (Block) { option (google.api.http) = { @@ -237,6 +351,7 @@ service Wallet { } }; } + //Please use GetBlockByLimitNext2 instead of this function. rpc GetBlockByLimitNext (BlockLimit) returns (BlockList) { option (google.api.http) = { post: "/wallet/getblockbylimitnext" @@ -246,6 +361,10 @@ service Wallet { } }; } + //Use this function instead of GetBlockByLimitNext. + rpc GetBlockByLimitNext2 (BlockLimit) returns (BlockListExtention) { + } + //Please use GetBlockByLatestNum2 instead of this function. rpc GetBlockByLatestNum (NumberMessage) returns (BlockList) { option (google.api.http) = { post: "/wallet/getblockbylatestnum" @@ -255,6 +374,9 @@ service Wallet { } }; } + //Use this function instead of GetBlockByLatestNum. + rpc GetBlockByLatestNum2 (NumberMessage) returns (BlockListExtention) { + } rpc GetTransactionById (BytesMessage) returns (Transaction) { option (google.api.http) = { post: "/wallet/gettransactionbyid" @@ -265,6 +387,15 @@ service Wallet { }; } + rpc DeployContract (CreateSmartContract) returns (TransactionExtention) { + } + + rpc GetContract (BytesMessage) returns (SmartContract) { + } + + rpc TriggerContract (TriggerSmartContract) returns (TransactionExtention) { + } + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { option (google.api.http) = { post: "/wallet/listwitnesses" @@ -274,6 +405,57 @@ service Wallet { } }; }; + + rpc ListProposals (EmptyMessage) returns (ProposalList) { + option (google.api.http) = { + post: "/wallet/listproposals" + body: "*" + additional_bindings { + get: "/wallet/listproposals" + } + }; + }; + + rpc GetProposalById (BytesMessage) returns (Proposal) { + option (google.api.http) = { + post: "/wallet/getproposalbyid" + body: "*" + additional_bindings { + get: "/wallet/getproposalbyid" + } + }; + }; + + rpc ListExchanges (EmptyMessage) returns (ExchangeList) { + option (google.api.http) = { + post: "/wallet/listexchanges" + body: "*" + additional_bindings { + get: "/wallet/listexchanges" + } + }; + }; + + rpc GetExchangeById (BytesMessage) returns (Exchange) { + option (google.api.http) = { + post: "/wallet/getexchangebyid" + body: "*" + additional_bindings { + get: "/wallet/getexchangebyid" + } + }; + }; + + rpc GetChainParameters (EmptyMessage) returns (ChainParameters) { + option (google.api.http) = { + post: "/wallet/getchainparameters" + body: "*" + additional_bindings { + get: "/wallet/getchainparameters" + } + }; + }; + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { option (google.api.http) = { post: "/wallet/getassetissuelist" @@ -311,6 +493,7 @@ service Wallet { }; } //Warning: do not invoke this interface provided by others. + //Please use GetTransactionSign2 instead of this function. rpc GetTransactionSign (TransactionSign) returns (Transaction) { option (google.api.http) = { post: "/wallet/gettransactionsign" @@ -321,12 +504,16 @@ service Wallet { }; }; //Warning: do not invoke this interface provided by others. - rpc CreateAdresss (BytesMessage) returns (BytesMessage) { + //Use this function instead of GetTransactionSign. + rpc GetTransactionSign2 (TransactionSign) returns (TransactionExtention) { + }; + //Warning: do not invoke this interface provided by others. + rpc CreateAddress (BytesMessage) returns (BytesMessage) { option (google.api.http) = { - post: "/wallet/createadresss" + post: "/wallet/createaddress" body: "*" additional_bindings { - get: "/wallet/createadresss" + get: "/wallet/createaddress" } }; }; @@ -340,6 +527,37 @@ service Wallet { } }; }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransferByPrivate (EasyTransferByPrivateMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransferbyprivate" + body: "*" + additional_bindings { + get: "/wallet/easytransferbyprivate" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + + option (google.api.http) = { + post: "/wallet/generateaddress" + body: "*" + additional_bindings { + get: "/wallet/generateaddress" + } + }; + } + + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/wallet/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactioninfobyid" + } + }; + } }; @@ -354,6 +572,16 @@ service WalletSolidity { } }; }; + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccountbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccountbyid" + } + }; + }; + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { option (google.api.http) = { post: "/walletsolidity/listwitnesses" @@ -381,6 +609,7 @@ service WalletSolidity { } }; } + //Please use GetNowBlock2 instead of this function. rpc GetNowBlock (EmptyMessage) returns (Block) { option (google.api.http) = { post: "/walletsolidity/getnowblock" @@ -390,6 +619,10 @@ service WalletSolidity { } }; } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. rpc GetBlockByNum (NumberMessage) returns (Block) { option (google.api.http) = { post: "/walletsolidity/getblockbynum" @@ -399,6 +632,13 @@ service WalletSolidity { } }; } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + rpc GetTransactionById (BytesMessage) returns (Transaction) { option (google.api.http) = { post: "/walletsolidity/gettransactionbyid" @@ -408,7 +648,6 @@ service WalletSolidity { } }; } - rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { option (google.api.http) = { post: "/walletsolidity/gettransactioninfobyid" @@ -418,10 +657,20 @@ service WalletSolidity { } }; } + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + option (google.api.http) = { + post: "/walletsolidity/generateaddress" + body: "*" + additional_bindings { + get: "/walletsolidity/generateaddress" + } + }; + } }; service WalletExtension { - + //Please use GetTransactionsFromThis2 instead of this function. rpc GetTransactionsFromThis (AccountPaginated) returns (TransactionList) { option (google.api.http) = { post: "/walletextension/gettransactionsfromthis" @@ -431,6 +680,10 @@ service WalletExtension { } }; } + //Use this function instead of GetTransactionsFromThis. + rpc GetTransactionsFromThis2 (AccountPaginated) returns (TransactionListExtention) { + } + //Please use GetTransactionsToThis2 instead of this function. rpc GetTransactionsToThis (AccountPaginated) returns (TransactionList) { option (google.api.http) = { post: "/walletextension/gettransactionstothis" @@ -440,6 +693,9 @@ service WalletExtension { } }; } + //Use this function instead of GetTransactionsToThis. + rpc GetTransactionsToThis2 (AccountPaginated) returns (TransactionListExtention) { + } }; // the api of tron's db @@ -492,6 +748,12 @@ service Network { message WitnessList { repeated Witness witnesses = 1; } +message ProposalList { + repeated Proposal proposals = 1; +} +message ExchangeList { + repeated Exchange exchanges = 1; +} message AssetIssueList { repeated AssetIssueContract assetIssue = 1; } @@ -548,6 +810,7 @@ message TimePaginatedMessage { int64 offset = 2; int64 limit = 3; } +//deprecated message AccountNetMessage { int64 freeNetUsed = 1; int64 freeNetLimit = 2; @@ -558,19 +821,70 @@ message AccountNetMessage { int64 TotalNetLimit = 7; int64 TotalNetWeight = 8; } +message AccountResourceMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; + + int64 EnergyUsed = 13; + int64 EnergyLimit = 14; + int64 TotalEnergyLimit = 15; + int64 TotalEnergyWeight = 16; + + int64 storageUsed = 21; + int64 storageLimit = 22; +} message PaginatedMessage { int64 offset = 1; int64 limit = 2; } -message EasyTransferMessage{ +message EasyTransferMessage { bytes passPhrase = 1; bytes toAddress = 2; int64 amount = 3; } -message EasyTransferResponse{ +message EasyTransferByPrivateMessage { + bytes privateKey = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferResponse { Transaction transaction = 1; Return result = 2; + bytes txid = 3; //transaction id = sha256(transaction.rowdata) +} + +message AddressPrKeyPairMessage { + string address = 1; + string privateKey = 2; +} + +message TransactionExtention { + Transaction transaction = 1; + bytes txid = 2; //transaction id = sha256(transaction.rowdata) + repeated bytes constant_result = 3; + Return result = 4; +} + +message BlockExtention { + repeated TransactionExtention transactions = 1; + BlockHeader block_header = 2; + bytes blockid = 3; +} + +message BlockListExtention { + repeated BlockExtention block = 1; +} + +message TransactionListExtention { + repeated TransactionExtention transaction = 1; } \ No newline at end of file diff --git a/app/protobuf/core/Contract.proto b/app/protobuf/core/Contract.proto index 2b6160d..5d5c142 100644 --- a/app/protobuf/core/Contract.proto +++ b/app/protobuf/core/Contract.proto @@ -29,12 +29,18 @@ message AccountCreateContract { AccountType type = 3; } -// update account name if the account has no name. +// Update account name. Account name is not unique now. message AccountUpdateContract { bytes account_name = 1; bytes owner_address = 2; } +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + message TransferContract { bytes owner_address = 1; bytes to_address = 2; @@ -66,6 +72,12 @@ message VoteWitnessContract { bool support = 3; } +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + message WitnessCreateContract { bytes owner_address = 1; bytes url = 2; @@ -90,6 +102,7 @@ message AssetIssueContract { int32 num = 8; int64 start_time = 9; int64 end_time = 10; + int64 order = 11; // the order of tokens of the same name int32 vote_score = 16; bytes description = 20; bytes url = 21; @@ -102,23 +115,28 @@ message AssetIssueContract { message ParticipateAssetIssueContract { bytes owner_address = 1; bytes to_address = 2; - bytes asset_name = 3; // the name of target asset + bytes asset_name = 3; // the namekey of target asset, include name and order int64 amount = 4; // the amount of drops } -message DeployContract { - bytes owner_address = 1; - bytes script = 2; + +enum ResourceCode { + BANDWIDTH = 0x00; + ENERGY = 0x01; } message FreezeBalanceContract { bytes owner_address = 1; int64 frozen_balance = 2; int64 frozen_duration = 3; + + ResourceCode resource = 10; } message UnfreezeBalanceContract { bytes owner_address = 1; + + ResourceCode resource = 10; } message UnfreezeAssetContract { @@ -135,4 +153,76 @@ message UpdateAssetContract { bytes url = 3; int64 new_limit = 4; int64 new_public_limit = 5; +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} + +message ExchangeCreateContract { + bytes owner_address = 1; + bytes first_token_id = 2; + int64 first_token_balance = 3; + bytes second_token_id = 4; + int64 second_token_balance = 5; +} + +message ExchangeInjectContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeWithdrawContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeTransactionContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; } \ No newline at end of file diff --git a/app/protobuf/core/Tron.proto b/app/protobuf/core/Tron.proto index a9d4160..ed9526c 100644 --- a/app/protobuf/core/Tron.proto +++ b/app/protobuf/core/Tron.proto @@ -30,16 +30,49 @@ message Vote { int64 vote_count = 2; } -// Account +// Proposal +message Proposal { + int64 proposal_id = 1; + bytes proposer_address = 2; + map parameters = 3; + int64 expiration_time = 4; + int64 create_time = 5; + repeated bytes approvals = 6; + enum State { + PENDING = 0; + DISAPPROVED = 1; + APPROVED = 2; + CANCELED = 3; + } + State state = 7; +} + +// Exchange +message Exchange { + int64 exchange_id = 1; + bytes creator_address = 2; + int64 create_time = 3; + bytes first_token_id = 6; + int64 first_token_balance = 7; + bytes second_token_id = 8; + int64 second_token_balance = 9; +} + +message ChainParameters { + repeated ChainParameter chainParameter = 1; + message ChainParameter { + string key = 1; + int64 value = 2; + } +} + +/* Account */ message Account { - // frozen balance + /* frozen balance */ message Frozen { - // the frozen trx balance - int64 frozen_balance = 1; - // the expire time - int64 expire_time = 2; + int64 frozen_balance = 1; // the frozen trx balance + int64 expire_time = 2; // the expire time } - bytes account_name = 1; AccountType type = 2; // the create address @@ -51,18 +84,20 @@ message Account { // the other asset owned by this account map asset = 6; // latest asset operation time + // the frozen balance repeated Frozen frozen = 7; // bandwidth, get from frozen int64 net_usage = 8; + // this account create time - int64 create_time = 9; + int64 create_time = 0x09; // this last operation time, including transfer, voting and so on. //FIXME fix grammar int64 latest_opration_time = 10; // witness block producing allowance - int64 allowance = 11; + int64 allowance = 0x0B; // last withdraw time - int64 latest_withdraw_time = 12; + int64 latest_withdraw_time = 0x0C; // not used so far bytes code = 13; bool is_witness = 14; @@ -72,20 +107,37 @@ message Account { // asset_issued_name bytes asset_issued_name = 17; map latest_asset_operation_time = 18; + int64 free_net_usage = 19; map free_asset_net_usage = 20; int64 latest_consume_time = 21; int64 latest_consume_free_time = 22; + + bytes account_id = 23; + + message AccountResource { + // energy resource, get from frozen + int64 energy_usage = 1; + // the frozen balance for energy + Frozen frozen_balance_for_energy = 2; + int64 latest_consume_time_for_energy = 3; + + // storage resource, get from market + int64 storage_limit = 6; + int64 storage_usage = 7; + int64 latest_exchange_storage_time = 8; + } + AccountResource account_resource = 26; + + bytes codeHash = 30; } -//FIXME authority? -message acuthrity { +message authority { AccountId account = 1; bytes permission_name = 2; } -//FIXME permission -message permision { +message permission { AccountId account = 1; } @@ -102,7 +154,6 @@ message Witness { bool isJobs = 9; } - // Vote Change message Votes { bytes address = 1; @@ -131,6 +182,15 @@ message TXOutputs { repeated TXOutput outputs = 1; } +message ResourceReceipt { + int64 energy_usage = 1; + int64 energy_fee = 2; + int64 origin_energy_usage = 3; + int64 energy_usage_total = 4; + int64 net_usage = 5; + int64 net_fee = 6; + Transaction.Result.contractResult result = 7; +} message Transaction { message Contract { @@ -142,7 +202,6 @@ message Transaction { VoteWitnessContract = 4; WitnessCreateContract = 5; AssetIssueContract = 6; - DeployContract = 7; WitnessUpdateContract = 8; ParticipateAssetIssueContract = 9; AccountUpdateContract = 10; @@ -151,13 +210,27 @@ message Transaction { WithdrawBalanceContract = 13; UnfreezeAssetContract = 14; UpdateAssetContract = 15; + ProposalCreateContract = 16; + ProposalApproveContract = 17; + ProposalDeleteContract = 18; + SetAccountIdContract = 19; CustomContract = 20; + // BuyStorageContract = 21; + // BuyStorageBytesContract = 22; + // SellStorageContract = 23; + CreateSmartContract = 30; + TriggerSmartContract = 31; + GetContract = 32; + UpdateSettingContract = 33; + ExchangeCreateContract = 41; + ExchangeInjectContract = 42; + ExchangeWithdrawContract = 43; + ExchangeTransactionContract = 44; } ContractType type = 1; google.protobuf.Any parameter = 2; bytes provider = 3; bytes ContractName = 4; - } message Result { @@ -165,8 +238,28 @@ message Transaction { SUCESS = 0; FAILED = 1; } + enum contractResult { + DEFAULT = 0; + SUCCESS = 1; + REVERT = 2; + BAD_JUMP_DESTINATION = 3; + OUT_OF_MEMORY = 4; + PRECOMPILED_CONTRACT = 5; + STACK_TOO_SMALL = 6; + STACK_TOO_LARGE = 7; + ILLEGAL_OPERATION = 8; + STACK_OVERFLOW = 9; + OUT_OF_ENERGY = 10; + OUT_OF_TIME = 11; + JVM_STACK_OVER_FLOW = 12; + UNKNOWN = 13; + } int64 fee = 1; code ret = 2; + contractResult contractRet = 3; + + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; } message raw { @@ -174,7 +267,7 @@ message Transaction { int64 ref_block_num = 3; bytes ref_block_hash = 4; int64 expiration = 8; - repeated acuthrity auths = 9; //FIXME authority + repeated authority auths = 9; // data not used bytes data = 10; //only support size = 1, repeated list here for extension @@ -182,6 +275,7 @@ message Transaction { // scripts not used bytes scripts = 12; int64 timestamp = 14; + int64 fee_limit = 18; } raw raw_data = 1; @@ -191,10 +285,28 @@ message Transaction { } message TransactionInfo { - bytes id = 1; - int64 fee = 2; - int64 blockNumber = 3; - int64 blockTimeStamp = 4; + enum code { + SUCESS = 0; + FAILED = 1; + } + message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + } + bytes id = 1; + int64 fee = 2; + int64 blockNumber = 3; + int64 blockTimeStamp = 4; + repeated bytes contractResult = 5; + bytes contract_address = 6; + ResourceReceipt receipt = 7; + repeated Log log = 8; + code result = 9; + bytes resMessage = 10; + + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; } message Transactions { @@ -216,6 +328,7 @@ message BlockHeader { int64 number = 7; int64 witness_id = 8; bytes witness_address = 9; + int32 version = 10; } raw raw_data = 1; bytes witness_signature = 2; @@ -324,3 +437,48 @@ message HelloMessage { BlockId solidBlockId = 5; BlockId headBlockId = 6; } + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + int64 consume_user_resource_percent = 6; + string name = 7; + +} \ No newline at end of file From d96d666e1831d6922bb56b1205c3a5f5efcd7c38 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 30 Aug 2018 14:39:46 +0200 Subject: [PATCH 02/21] cleanup --- app/org/tronscan/actions/StatsOverview.scala | 3 +++ app/org/tronscan/api/AccountApi.scala | 4 ++-- app/org/tronscan/api/BaseApi.scala | 3 +++ app/org/tronscan/api/BlockApi.scala | 6 +++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/org/tronscan/actions/StatsOverview.scala b/app/org/tronscan/actions/StatsOverview.scala index 792bed4..2b11082 100644 --- a/app/org/tronscan/actions/StatsOverview.scala +++ b/app/org/tronscan/actions/StatsOverview.scala @@ -5,6 +5,9 @@ import org.tron.common.repositories.StatsRepository import scala.concurrent.ExecutionContext +/** + * Reads the total stats + */ class StatsOverview @Inject()( statsRepository: StatsRepository) { diff --git a/app/org/tronscan/api/AccountApi.scala b/app/org/tronscan/api/AccountApi.scala index 6cc3246..1fdd2ff 100644 --- a/app/org/tronscan/api/AccountApi.scala +++ b/app/org/tronscan/api/AccountApi.scala @@ -149,7 +149,7 @@ class AccountApi @Inject()( "allowance" -> account.allowance, "url" -> witness.map(_.url), ), - "name" -> new String(account.accountName.toByteArray).toString, + "name" -> account.accountName.decodeString, "address" -> address, "bandwidth" -> Json.obj( "freeNetUsed" -> accountBandwidth.freeNetUsed, @@ -226,7 +226,7 @@ class AccountApi @Inject()( for { wallet <- walletClient.full account <- wallet.getAccount(Account( - address = ByteString.copyFrom(Base58.decode58Check(address)) + address = address.decodeAddress, )) } yield { diff --git a/app/org/tronscan/api/BaseApi.scala b/app/org/tronscan/api/BaseApi.scala index 469de0f..c986e45 100644 --- a/app/org/tronscan/api/BaseApi.scala +++ b/app/org/tronscan/api/BaseApi.scala @@ -11,6 +11,9 @@ trait BaseApi extends InjectedController { "count", ) + /** + * Strip query parameters which are related to paging and navigation of the results + */ def stripNav(params: Map[String, String], sortParams: List[String] = List.empty) = { val navs = navParams ++ sortParams params.filterKeys(x => !navs.contains(x)) diff --git a/app/org/tronscan/api/BlockApi.scala b/app/org/tronscan/api/BlockApi.scala index 7e2eec6..8e67264 100644 --- a/app/org/tronscan/api/BlockApi.scala +++ b/app/org/tronscan/api/BlockApi.scala @@ -73,7 +73,7 @@ class BlockApi @Inject() ( val queryParams = request.queryString.map(x => x._1.toLowerCase -> x._2.mkString) val queryHash = queryParams.map(x => x._1 + "-" + x._2).mkString val filterHash = stripNav(queryParams).map(x => x._1 + "-" + x._2).mkString - val includeCount = request.getQueryString("count").exists(x => true) + val includeCount = request.getQueryString("count").isDefined def getBlocks = { @@ -146,10 +146,10 @@ class BlockApi @Inject() ( size = block.toByteArray.length, hash = block.hash, timestamp = new DateTime(header.timestamp), - txTrieRoot = Base58.encode58Check(header.txTrieRoot.toByteArray), + txTrieRoot = ByteUtil.toHexString(header.txTrieRoot.toByteArray), parentHash = ByteUtil.toHexString(header.parentHash.toByteArray), witnessId = header.witnessId, - witnessAddress = Base58.encode58Check(header.witnessAddress.toByteArray), + witnessAddress = header.witnessAddress.encodeAddress, nrOfTrx = block.transactions.size, ).asJson) } From e31a266e7743833a4568a58e72bda6deacc2fc58 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Sat, 1 Sep 2018 16:29:11 +0200 Subject: [PATCH 03/21] add GetBlockByLimitNext fix import --- app/org/tronscan/api/GrpcFullApi.scala | 17 ++++++++++++++++- .../api/models/TransactionSerializer.scala | 2 +- app/org/tronscan/grpc/GrpcBalancer.scala | 13 ++++++------- app/org/tronscan/grpc/WalletClient.scala | 2 +- .../importer/BlockChainStreamBuilder.scala | 18 ++++++++++-------- app/org/tronscan/importer/BlockImporter.scala | 14 ++++++-------- .../tronscan/importer/FullNodeImporter.scala | 6 +++--- .../importer/ImportStreamFactory.scala | 6 +++--- .../importer/SolidityNodeImporter.scala | 6 +++--- app/org/tronscan/network/NetworkScanner.scala | 2 +- conf/application.conf | 4 ++-- conf/routes | 17 +++++++++-------- 12 files changed, 61 insertions(+), 46 deletions(-) diff --git a/app/org/tronscan/api/GrpcFullApi.scala b/app/org/tronscan/api/GrpcFullApi.scala index 11fb11b..dcd79f4 100644 --- a/app/org/tronscan/api/GrpcFullApi.scala +++ b/app/org/tronscan/api/GrpcFullApi.scala @@ -6,7 +6,7 @@ import io.circe.Json import io.circe.syntax._ import io.swagger.annotations.Api import javax.inject.Inject -import org.tron.api.api.{BytesMessage, EmptyMessage, NumberMessage, WalletGrpc} +import org.tron.api.api._ import org.tron.common.utils.{Base58, ByteArray, ByteUtil} import org.tron.protos.Tron.Account import org.tronscan.api.models.{TransactionSerializer, TronModelsSerializers} @@ -70,6 +70,21 @@ class GrpcFullApi @Inject() ( } } + def getBlockByLimitNext = Action.async { implicit req => + + val from = req.getQueryString("from").get.toLong + val to = req.getQueryString("to").get.toLong + + for { + client <- getClient + blocks <- client.getBlockByLimitNext(BlockLimit(from, to)) + } yield { + Ok(Json.obj( + "data" -> blocks.block.sortBy(_.getBlockHeader.getRawData.number).toList.asJson + )) + } + } + def getTransactionById(hash: String) = Action.async { implicit req => for { diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index 0670567..2ad19ec 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -403,7 +403,7 @@ object TransactionSerializer { "signatures" -> transaction.signature.map { signature => Js.obj( "bytes" -> Crypto.getBase64FromByteString(signature).asJson, - "address" -> ByteString.copyFrom(ECKey.signatureToAddress(Sha256Hash.of(transaction.getRawData.toByteArray).getBytes, Crypto.getBase64FromByteString(transaction.signature(0)))).encodeAddress.asJson, +// "address" -> ByteString.copyFrom(ECKey.signatureToAddress(Sha256Hash.of(transaction.getRawData.toByteArray).getBytes, Crypto.getBase64FromByteString(transaction.signature(0)))).encodeAddress.asJson, ) }.asJson, ) diff --git a/app/org/tronscan/grpc/GrpcBalancer.scala b/app/org/tronscan/grpc/GrpcBalancer.scala index 2ff57a2..1880085 100644 --- a/app/org/tronscan/grpc/GrpcBalancer.scala +++ b/app/org/tronscan/grpc/GrpcBalancer.scala @@ -17,7 +17,7 @@ import scala.concurrent.Future import scala.concurrent.duration._ case class GrpcRequest(request: WalletStub => Future[Any]) -case class GrpcRetry(request: GrpcRequest) +case class GrpcRetry(request: GrpcRequest, sender: ActorRef) case class GrpcResponse(response: Any) case class GrpcBlock(num: Long, hash: String) case class OptimizeNodes() @@ -120,8 +120,8 @@ class GrpcBalancer @Inject() (configurationProvider: ConfigurationProvider) exte def receive = { case w: GrpcRequest ⇒ router.route(w, sender()) - case GrpcRetry(request) => - router.route(request, sender()) + case GrpcRetry(request, s) => + router.route(request, s) case stats: GrpcStats => nodeStatuses = nodeStatuses ++ Map(stats.ip -> stats) case Terminated(a) ⇒ @@ -193,16 +193,15 @@ class GrpcClient(nodeAddress: NodeAddress) extends Actor { } } - def handleRequest(request: GrpcRequest) = { + def handleRequest(request: GrpcRequest, s: ActorRef) = { import context.dispatcher - val s = sender() request.request(walletStub.withDeadlineAfter(5, TimeUnit.SECONDS)).map { x => s ! GrpcResponse(x) requestsHandled += 1 }.recover { case _ => requestErrors += 1 - context.parent.tell(GrpcRetry(request), s) + context.parent.tell(GrpcRetry(request, s), s) } } @@ -217,7 +216,7 @@ class GrpcClient(nodeAddress: NodeAddress) extends Actor { def receive = { case c: GrpcRequest => - handleRequest(c) + handleRequest(c, sender()) case "ping" => ping() diff --git a/app/org/tronscan/grpc/WalletClient.scala b/app/org/tronscan/grpc/WalletClient.scala index 853d074..9eb7da1 100644 --- a/app/org/tronscan/grpc/WalletClient.scala +++ b/app/org/tronscan/grpc/WalletClient.scala @@ -30,7 +30,7 @@ class WalletClient @Inject() ( } def fullRequest[A](request: WalletStub => Future[A]) = { - implicit val timeout = Timeout(9.seconds) + implicit val timeout = Timeout(3.seconds) (grpcBalancer ? GrpcRequest(request)).mapTo[GrpcResponse].map(_.response.asInstanceOf[A]) } diff --git a/app/org/tronscan/importer/BlockChainStreamBuilder.scala b/app/org/tronscan/importer/BlockChainStreamBuilder.scala index 09c6caa..a61f855 100644 --- a/app/org/tronscan/importer/BlockChainStreamBuilder.scala +++ b/app/org/tronscan/importer/BlockChainStreamBuilder.scala @@ -14,6 +14,7 @@ import org.tronscan.grpc.WalletClient import org.tronscan.importer.StreamTypes.ContractFlow import org.tronscan.models._ import org.tronscan.utils.ModelUtils +import play.api.Logger import scala.concurrent.{ExecutionContext, Future} @@ -41,24 +42,25 @@ class BlockChainStreamBuilder { * Reads all the blocks using batch calls */ def readFullNodeBlocksBatched(from: Long, to: Long, batchSize: Int = 50)(client: WalletClient)(implicit executionContext: ExecutionContext): Source[Block, NotUsed] = { - Source.unfold(from) { prev => - if (prev < to) { + Source.unfold(from) { fromBlock => + if (fromBlock < to) { - val toBlock = if (prev + batchSize > to) to else prev + batchSize - - Some((toBlock, (prev, toBlock))) + val nextBlock = fromBlock + batchSize + val toBlock = if (nextBlock <= to) nextBlock else to + Some((toBlock, (fromBlock, toBlock))) } else { None } } .mapAsync(30) { case (fromBlock, toBlock) => - client.fullRequest(_.getBlockByLimitNext(BlockLimit(fromBlock, toBlock))).map { blocks => - blocks.block.filter(_.blockHeader.isDefined).sortBy(_.getBlockHeader.getRawData.number) + client.fullRequest(_.getBlockByLimitNext(BlockLimit(fromBlock, toBlock + 1))).map { blocks => + val bs = blocks.block.filter(_.blockHeader.isDefined).sortBy(_.getBlockHeader.getRawData.number) +// Logger.info(s"DOWNLOADED $fromBlock to $toBlock. GOT ${bs.head.getBlockHeader.getRawData.number} => ${bs.last.getBlockHeader.getRawData.number}") + bs } } .mapConcat(x => x.toList) - .buffer(50000, OverflowStrategy.backpressure) } def filterContracts(contractTypes: List[Transaction.Contract.ContractType]) = { diff --git a/app/org/tronscan/importer/BlockImporter.scala b/app/org/tronscan/importer/BlockImporter.scala index 804aa22..9720658 100644 --- a/app/org/tronscan/importer/BlockImporter.scala +++ b/app/org/tronscan/importer/BlockImporter.scala @@ -39,16 +39,14 @@ class BlockImporter @Inject() ( val header = block.getBlockHeader.getRawData val queries: ListBuffer[FixedSqlAction[_, NoStream, Effect.Write]] = ListBuffer() - if (header.number % 1000 == 0) { - Logger.info(s"FULL NODE BLOCK: ${header.number}, TX: ${block.transactions.size}, CONFIRM: $confirmBlocks") - } + Logger.info(s"FULL NODE BLOCK: ${header.number}, TX: ${block.transactions.size}, CONFIRM: $confirmBlocks") // Import Block - queries.append(blockModelRepository.buildInsert(BlockModel.fromProto(block).copy(confirmed = confirmBlocks))) + queries.append(blockModelRepository.buildInsertOrUpdate(BlockModel.fromProto(block).copy(confirmed = confirmBlocks))) // Import Transactions queries.appendAll(block.transactions.map { trx => - transactionModelRepository.buildInsert(ModelUtils.transactionToModel(trx, block).copy(confirmed = confirmBlocks)) + transactionModelRepository.buildInsertOrUpdate(ModelUtils.transactionToModel(trx, block).copy(confirmed = confirmBlocks)) }) // Import Contracts @@ -86,12 +84,12 @@ class BlockImporter @Inject() ( .mapAsync(12) { solidityBlock => for { databaseBlock <- blockModelRepository.findByNumber(solidityBlock.getBlockHeader.getRawData.number) - } yield (solidityBlock, databaseBlock.get) + } yield (solidityBlock, databaseBlock) } // Filter empty or confirmed blocks - .filter(x => x._1.blockHeader.isDefined && !x._2.confirmed) + .filter(x => x._1.blockHeader.isDefined && x._2.nonEmpty && !x._2.get.confirmed) .map { - case (solidityBlock, databaseBlock) => + case (solidityBlock, Some(databaseBlock)) => val queries = ListBuffer[FixedSqlAction[_, NoStream, Effect.Write]]() diff --git a/app/org/tronscan/importer/FullNodeImporter.scala b/app/org/tronscan/importer/FullNodeImporter.scala index b3f2095..8923a25 100644 --- a/app/org/tronscan/importer/FullNodeImporter.scala +++ b/app/org/tronscan/importer/FullNodeImporter.scala @@ -33,11 +33,11 @@ class FullNodeImporter @Inject()( def buildStream(implicit actorSystem: ActorSystem) = { async { val nodeState = await(synchronisationService.nodeState) - Logger.info("BuildStream::nodeState -> " + nodeState) +// Logger.info("BuildStream::nodeState -> " + nodeState) val importAction = await(importStreamFactory.buildImportActionFromImportStatus(nodeState)) - Logger.info("BuildStream::importAction -> " + importAction) +// Logger.info("BuildStream::importAction -> " + importAction) val importers = importersFactory.buildFullNodeImporters(importAction) - Logger.info("BuildStream::importers -> " + importers.debug) +// Logger.info("BuildStream::importers -> " + importers.debug) val synchronisationChecker = importStreamFactory.fullNodePreSynchronisationChecker val blockSource = importStreamFactory.buildBlockSource(walletClient) val blockSink = importStreamFactory.buildBlockSink(importers) diff --git a/app/org/tronscan/importer/ImportStreamFactory.scala b/app/org/tronscan/importer/ImportStreamFactory.scala index ec958b5..e6070a1 100644 --- a/app/org/tronscan/importer/ImportStreamFactory.scala +++ b/app/org/tronscan/importer/ImportStreamFactory.scala @@ -142,7 +142,7 @@ class ImportStreamFactory @Inject()( /** * A sync that extracts all the blocks, transactions, contracts, addresses from the blocks and passes them to streams */ - def buildBlockSink(importers: BlockchainImporters): Sink[Block, Future[Done]] = { + def buildBlockSink(importers: BlockchainImporters): Sink[Block, Future[Done]] = { Sink.fromGraph(GraphDSL.create(Sink.ignore) { implicit b => sink => import GraphDSL.Implicits._ val blocks = b.add(Broadcast[Block](3)) @@ -198,7 +198,7 @@ class ImportStreamFactory @Inject()( // Switch between batch or single depending how far the sync is behind if (status.fullNodeBlocksToSync < 100) blockChainBuilder.readFullNodeBlocks(fromBlock, toBlock)(fullNodeBlockChain.client) - else blockChainBuilder.readFullNodeBlocksBatched(fromBlock, toBlock, 100)(walletClient) + else blockChainBuilder.readFullNodeBlocksBatched(fromBlock, toBlock, 90)(walletClient) } } .flatMapConcat(blockStream => blockStream) @@ -227,7 +227,7 @@ class ImportStreamFactory @Inject()( .filter { // Stop if there are more then 100 blocks to sync for full node case status if status.fullNodeBlocksToSync > 0 => - Logger.info(s"START SYNC FROM ${status.dbLatestBlock} TO ${status.fullNodeBlock}. " + status.toString) + Logger.info(s"START SYNC FROM ${status.dbLatestBlock} TO ${status.fullNodeBlock}.") true case status => Logger.info("IGNORE FULL NODE SYNC: " + status.toString) diff --git a/app/org/tronscan/importer/SolidityNodeImporter.scala b/app/org/tronscan/importer/SolidityNodeImporter.scala index 4f688c5..56cf143 100644 --- a/app/org/tronscan/importer/SolidityNodeImporter.scala +++ b/app/org/tronscan/importer/SolidityNodeImporter.scala @@ -32,11 +32,11 @@ class SolidityNodeImporter @Inject()( async { val nodeState = await(synchronisationService.nodeState) - Logger.info("BuildStream::nodeState -> " + nodeState) +// Logger.info("BuildStream::nodeState -> " + nodeState) val importAction = await(importStreamFactory.buildImportActionFromImportStatus(nodeState)) - Logger.info("BuildStream::importAction -> " + importAction) +// Logger.info("BuildStream::importAction -> " + importAction) val importers = importersFactory.buildSolidityImporters(importAction) - Logger.info("BuildStream::importers -> " + importers.debug) +// Logger.info("BuildStream::importers -> " + importers.debug) val synchronisationChecker = importStreamFactory.solidityNodePreSynchronisationChecker val blockSource = importStreamFactory.buildSolidityBlockSource(walletClient) val blockSink = importStreamFactory.buildBlockSink(importers) diff --git a/app/org/tronscan/network/NetworkScanner.scala b/app/org/tronscan/network/NetworkScanner.scala index 1c5ea11..8c6c32f 100644 --- a/app/org/tronscan/network/NetworkScanner.scala +++ b/app/org/tronscan/network/NetworkScanner.scala @@ -244,7 +244,7 @@ class NetworkScanner @Inject()( case None => networkNodes = networkNodes + (node.ip -> node) case x => - println("ignoring update", x) + } } diff --git a/conf/application.conf b/conf/application.conf index e0f0366..4eb1dde 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -86,20 +86,20 @@ cache { } } - grpc { balancer { maxClients = 12 } } - # Synchronisation Settings sync { full = true full = ${?ENABLE_SYNC} solidity = true solidity = ${?ENABLE_SYNC} + addresses = true + addresses = ${?ENABLE_SYNC} } network { diff --git a/conf/routes b/conf/routes index 62b62c7..cf77890 100644 --- a/conf/routes +++ b/conf/routes @@ -95,14 +95,15 @@ POST /api/slack/command org.tronscan.slack.SlackApi.handleComman # GRPC Full Node Api -GET /api/grpc/full/getnowblock org.tronscan.api.GrpcFullApi.getNowBlock -GET /api/grpc/full/getblockbynum/:number org.tronscan.api.GrpcFullApi.getBlockByNum(number: Long) -GET /api/grpc/full/totaltransaction org.tronscan.api.GrpcFullApi.totalTransaction -GET /api/grpc/full/getaccount/:address org.tronscan.api.GrpcFullApi.getAccount(address: String) -GET /api/grpc/full/gettransactionbyid/:hash org.tronscan.api.GrpcFullApi.getTransactionById(hash: String) -GET /api/grpc/full/getaccountnet/:address org.tronscan.api.GrpcFullApi.getAccountNet(address: String) -GET /api/grpc/full/listnodes org.tronscan.api.GrpcFullApi.listNodes -GET /api/grpc/full/listwitnesses org.tronscan.api.GrpcFullApi.listWitnesses +GET /api/grpc/full/getnowblock org.tronscan.api.GrpcFullApi.getNowBlock +GET /api/grpc/full/getblockbynum/:number org.tronscan.api.GrpcFullApi.getBlockByNum(number: Long) +GET /api/grpc/full/getblockbylimitnext org.tronscan.api.GrpcFullApi.getBlockByLimitNext +GET /api/grpc/full/totaltransaction org.tronscan.api.GrpcFullApi.totalTransaction +GET /api/grpc/full/getaccount/:address org.tronscan.api.GrpcFullApi.getAccount(address: String) +GET /api/grpc/full/gettransactionbyid/:hash org.tronscan.api.GrpcFullApi.getTransactionById(hash: String) +GET /api/grpc/full/getaccountnet/:address org.tronscan.api.GrpcFullApi.getAccountNet(address: String) +GET /api/grpc/full/listnodes org.tronscan.api.GrpcFullApi.listNodes +GET /api/grpc/full/listwitnesses org.tronscan.api.GrpcFullApi.listWitnesses # GRPC Solidity API From 144ad8b8156c1f5f29d46893a8e84b74a45eecba Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Sat, 1 Sep 2018 23:10:17 +0200 Subject: [PATCH 04/21] cleanup change vote witness id --- app/org/tronscan/api/AccountApi.scala | 74 +++---------------- app/org/tronscan/api/GrpcFullApi.scala | 41 +++++----- app/org/tronscan/grpc/GrpcBalancer.scala | 2 +- .../models/VoteWitnessContractModel.scala | 4 +- app/org/tronscan/utils/ModelUtils.scala | 1 + build.sbt | 2 - conf/routes | 1 - schema/schema.sql | 4 +- 8 files changed, 32 insertions(+), 97 deletions(-) diff --git a/app/org/tronscan/api/AccountApi.scala b/app/org/tronscan/api/AccountApi.scala index 1fdd2ff..b20c612 100644 --- a/app/org/tronscan/api/AccountApi.scala +++ b/app/org/tronscan/api/AccountApi.scala @@ -34,6 +34,7 @@ import scala.async.Async._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.concurrent.duration._ +import org.tronscan.Extensions._ @Api( value = "Accounts", @@ -112,6 +113,9 @@ class AccountApi @Inject()( } } + /** + * Find account by the given address + */ @ApiOperation( value = "Find account by address", response = classOf[AccountModel]) @@ -180,6 +184,9 @@ class AccountApi @Inject()( } } + /** + * Retrieves the balances for the given address + */ @ApiOperation( value = "", hidden = true @@ -218,6 +225,9 @@ class AccountApi @Inject()( } } + /** + * Votes cast by the given address + */ @ApiOperation( value = "", hidden = true @@ -308,70 +318,6 @@ class AccountApi @Inject()( } } - @ApiOperation( - value = "", - hidden = true - ) - def resync = Action.async { req => - - throw new Exception("DISABLED") - - val decider: Supervision.Decider = { - case exc => - println("SOLIDITY STREAM ERROR", exc, ExceptionUtils.getStackTrace(exc)) - Supervision.Resume - } - - implicit val materializer = ActorMaterializer( - ActorMaterializerSettings(system) - .withSupervisionStrategy(decider))(system) - - async { - - val accounts = await(repo.findAll).toList - - val source = Source(accounts) - .mapAsync(8) { existingAccount => - async { - - val walletSolidity = await(walletClient.full) - - val account = await(walletSolidity.getAccount(Account( - address = ByteString.copyFrom(Base58.decode58Check(existingAccount.address)) - ))) - - if (account != null) { - - val accountModel = AccountModel( - address = existingAccount.address, - name = new String(account.accountName.toByteArray), - balance = account.balance, - power = account.frozen.map(_.frozenBalance).sum, - tokenBalances = Json.toJson(account.asset), - dateUpdated = DateTime.now, - ) - - List(repo.buildInsertOrUpdate(accountModel)) ++ addressBalanceModelRepository.buildUpdateBalance(accountModel) - } else { - List.empty - } - - } - } - .flatMapConcat(queries => Source(queries)) - .groupedWithin(150, 10.seconds) - .mapAsync(1) { queries => - blockModelRepository.executeQueries(queries) - } - .toMat(Sink.ignore)(Keep.right) - .run - - await(source) - - Ok("Done") - } - } - @ApiOperation( value = "", hidden = true) diff --git a/app/org/tronscan/api/GrpcFullApi.scala b/app/org/tronscan/api/GrpcFullApi.scala index dcd79f4..7ea25ce 100644 --- a/app/org/tronscan/api/GrpcFullApi.scala +++ b/app/org/tronscan/api/GrpcFullApi.scala @@ -7,12 +7,12 @@ import io.circe.syntax._ import io.swagger.annotations.Api import javax.inject.Inject import org.tron.api.api._ -import org.tron.common.utils.{Base58, ByteArray, ByteUtil} +import org.tron.common.utils.{Base58, ByteArray} import org.tron.protos.Tron.Account import org.tronscan.api.models.{TransactionSerializer, TronModelsSerializers} import org.tronscan.grpc.{GrpcService, WalletClient} import play.api.mvc.Request -import org.tronscan.api.models.TransactionSerializer._ +import org.tronscan.Extensions._ import scala.concurrent.Future @@ -30,6 +30,12 @@ class GrpcFullApi @Inject() ( val serializer = new TronModelsSerializers import serializer._ + /** + * Retrieves a GRPC client + * + * Uses the full node by default, but can be overridden by using the ?ip= parameter + * Uses the 50051 port by default but can be overridden by using the ?port= parameter + */ def getClient(implicit request: Request[_]): Future[WalletGrpc.WalletStub] = { request.getQueryString("ip") match { case Some(ip) => @@ -43,8 +49,7 @@ class GrpcFullApi @Inject() ( def getNowBlock = Action.async { implicit req => for { - client <- getClient - block <- client.getNowBlock(EmptyMessage()) + block <- walletClient.fullRequest(_.getNowBlock(EmptyMessage())) } yield { Ok(Json.obj( "data" -> block.asJson @@ -56,8 +61,7 @@ class GrpcFullApi @Inject() ( def getBlockByNum(number: Long) = Action.async { implicit req => for { - client <- getClient - block <- client.getBlockByNum(NumberMessage(number)) + block <- walletClient.fullRequest(_.getBlockByNum(NumberMessage(number))) } yield { block.blockHeader match { case Some(_) => @@ -76,8 +80,7 @@ class GrpcFullApi @Inject() ( val to = req.getQueryString("to").get.toLong for { - client <- getClient - blocks <- client.getBlockByLimitNext(BlockLimit(from, to)) + blocks <- walletClient.fullRequest(_.getBlockByLimitNext(BlockLimit(from, to))) } yield { Ok(Json.obj( "data" -> blocks.block.sortBy(_.getBlockHeader.getRawData.number).toList.asJson @@ -88,8 +91,7 @@ class GrpcFullApi @Inject() ( def getTransactionById(hash: String) = Action.async { implicit req => for { - client <- getClient - transaction <- client.getTransactionById(BytesMessage(ByteString.copyFrom(ByteArray.fromHexString(hash)))) + transaction <- walletClient.fullRequest(_.getTransactionById(BytesMessage(ByteString.copyFrom(ByteArray.fromHexString(hash))))) } yield { Ok(Json.obj( "data" -> TransactionSerializer.serialize(transaction) @@ -100,8 +102,7 @@ class GrpcFullApi @Inject() ( def totalTransaction = Action.async { implicit req => for { - client <- getClient - total <- client.totalTransaction(EmptyMessage()) + total <- walletClient.fullRequest(_.totalTransaction(EmptyMessage())) } yield { Ok(Json.obj( "data" -> total.num.asJson @@ -112,10 +113,7 @@ class GrpcFullApi @Inject() ( def getAccount(address: String) = Action.async { implicit req => for { - client <- getClient - account <- client.getAccount(Account( - address = ByteString.copyFrom(Base58.decode58Check(address)) - )) + account <- walletClient.fullRequest(_.getAccount(address.toAccount)) } yield { Ok(Json.obj( "data" -> account.asJson @@ -126,10 +124,7 @@ class GrpcFullApi @Inject() ( def getAccountNet(address: String) = Action.async { implicit req => for { - client <- getClient - accountNet <- client.getAccountNet(Account( - address = ByteString.copyFrom(Base58.decode58Check(address)) - )) + accountNet <- walletClient.fullRequest(_.getAccountNet(address.toAccount)) } yield { Ok(Json.obj( "data" -> accountNet.asJson @@ -141,8 +136,7 @@ class GrpcFullApi @Inject() ( def listNodes = Action.async { implicit req => for { - client <- getClient - nodes <- client.listNodes(EmptyMessage()) + nodes <- walletClient.fullRequest(_.listNodes(EmptyMessage())) } yield { Ok(Json.obj( "data" -> nodes.nodes.map(node => Json.obj( @@ -155,8 +149,7 @@ class GrpcFullApi @Inject() ( def listWitnesses = Action.async { implicit req => for { - client <- getClient - witnessList <- client.listWitnesses(EmptyMessage()) + witnessList <- walletClient.fullRequest(_.listWitnesses(EmptyMessage())) } yield { Ok(Json.obj( "data" -> witnessList.witnesses.map(_.asJson).asJson diff --git a/app/org/tronscan/grpc/GrpcBalancer.scala b/app/org/tronscan/grpc/GrpcBalancer.scala index 1880085..77be2c0 100644 --- a/app/org/tronscan/grpc/GrpcBalancer.scala +++ b/app/org/tronscan/grpc/GrpcBalancer.scala @@ -110,7 +110,7 @@ class GrpcBalancer @Inject() (configurationProvider: ConfigurationProvider) exte override def preStart(): Unit = { import context.dispatcher - pinger = Some(context.system.scheduler.schedule(6.second, 6.seconds, self, OptimizeNodes())) + pinger = Some(context.system.scheduler.schedule(4.second, 6.seconds, self, OptimizeNodes())) } override def postStop(): Unit = { diff --git a/app/org/tronscan/models/VoteWitnessContractModel.scala b/app/org/tronscan/models/VoteWitnessContractModel.scala index 124bcc3..408c4cf 100644 --- a/app/org/tronscan/models/VoteWitnessContractModel.scala +++ b/app/org/tronscan/models/VoteWitnessContractModel.scala @@ -15,7 +15,7 @@ case class VoteWitnessList( votes: List[VoteWitnessContractModel] = List.empty) case class VoteWitnessContractModel( - id: UUID = UUID.randomUUID(), + id: String, block: Long, transaction: String, timestamp: DateTime, @@ -24,7 +24,7 @@ case class VoteWitnessContractModel( votes: Long = 0L) class VoteWitnessContractModelTable(tag: Tag) extends Table[VoteWitnessContractModel](tag, "vote_witness_contract") { - def id = column[UUID]("id") + def id = column[String]("id") def block = column[Long]("block") def transaction = column[String]("transaction") def timestamp = column[DateTime]("date_created") diff --git a/app/org/tronscan/utils/ModelUtils.scala b/app/org/tronscan/utils/ModelUtils.scala index fd2cb29..af21280 100644 --- a/app/org/tronscan/utils/ModelUtils.scala +++ b/app/org/tronscan/utils/ModelUtils.scala @@ -62,6 +62,7 @@ object ModelUtils { case c: VoteWitnessContract => val inserts = for (vote <- c.votes) yield { VoteWitnessContractModel( + id = transactionHash, transaction = transactionHash, block = header.number, timestamp = transactionTime, diff --git a/build.sbt b/build.sbt index 60951ff..02c489e 100644 --- a/build.sbt +++ b/build.sbt @@ -5,14 +5,12 @@ organization := "org.tronscan" version := "latest" - scalaVersion := "2.12.4" dependencyOverrides ++= Seq( "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.2" ) - // Library Dependencies libraryDependencies ++= Seq( diff --git a/conf/routes b/conf/routes index cf77890..6e4a2af 100644 --- a/conf/routes +++ b/conf/routes @@ -47,7 +47,6 @@ GET /api/node org.tronscan.api.NodeApi.status GET /api/system/status org.tronscan.api.SystemApi.status GET /api/system/balancer org.tronscan.api.SystemApi.balancer -GET /api/system/sync-accounts org.tronscan.api.AccountApi.resync GET /api/system/sync-account/:address org.tronscan.api.AccountApi.sync(address: String) # Votes diff --git a/schema/schema.sql b/schema/schema.sql index 0385492..9adb685 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -45,9 +45,7 @@ create index if not exists transactions_block_hash_index create table if not exists vote_witness_contract ( - id uuid not null - constraint vote_witness_contract_id_pk - primary key, + id text not null constraint vote_witness_contract_id_pk primary key, transaction text, voter_address text, candidate_address text, From d92a7953f9cdcd238d87b5521fc461cd6da82816 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Mon, 3 Sep 2018 00:11:05 +0200 Subject: [PATCH 05/21] add vote round import --- .../api/models/TransactionSerializer.scala | 24 ++++ app/org/tronscan/grpc/GrpcBalancer.scala | 7 +- app/org/tronscan/grpc/WalletClient.scala | 13 ++- .../importer/BlockChainStreamBuilder.scala | 25 +++- app/org/tronscan/importer/BlockImporter.scala | 46 +++++++- ...eImporter.scala => ContractImporter.scala} | 4 +- .../tronscan/importer/FullNodeImporter.scala | 3 +- app/org/tronscan/importer/ImportManager.scala | 9 ++ .../importer/ImportStreamFactory.scala | 52 ++++++--- .../tronscan/importer/ImportersFactory.scala | 12 +- .../tronscan/importer/VoteRoundImporter.scala | 56 +++++++++ .../tronscan/models/MaintenanceRound.scala | 76 ++++++++++++ app/org/tronscan/models/RoundVote.scala | 46 ++++++++ .../models/VoteWitnessContractModel.scala | 15 ++- conf/application.conf | 2 + schema/schema.sql | 108 +++++++++++------- 16 files changed, 425 insertions(+), 73 deletions(-) rename app/org/tronscan/importer/{DatabaseImporter.scala => ContractImporter.scala} (96%) create mode 100644 app/org/tronscan/importer/VoteRoundImporter.scala create mode 100644 app/org/tronscan/models/MaintenanceRound.scala create mode 100644 app/org/tronscan/models/RoundVote.scala diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index 2ad19ec..0ce0558 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -134,6 +134,30 @@ object TransactionSerializer { ) } + implicit val decodeVoteWitnessContractVote = new Decoder[org.tron.protos.Contract.VoteWitnessContract.Vote] { + def apply(c: HCursor) = { + for { + voteAddress <- c.downField("voteAddress").as[String] + voteCount <- c.downField("voteCount").as[Long] + } yield org.tron.protos.Contract.VoteWitnessContract.Vote( + voteAddress = voteAddress.decodeAddress, + voteCount = voteCount + ) + } + } + + implicit val decodeVoteWitnessContract = new Decoder[org.tron.protos.Contract.VoteWitnessContract] { + def apply(c: HCursor) = { + for { + ownerAddress <- c.downField("ownerAddress").as[String] + votes <- c.downField("votes").as[List[org.tron.protos.Contract.VoteWitnessContract.Vote]] + } yield org.tron.protos.Contract.VoteWitnessContract( + ownerAddress = ownerAddress.decodeAddress, + votes = votes + ) + } + } + implicit val encodeAccountUpdateContract = new Encoder[org.tron.protos.Contract.AccountUpdateContract] { def apply(accountUpdateContract: org.tron.protos.Contract.AccountUpdateContract): Js = Js.obj( "ownerAddress" -> accountUpdateContract.ownerAddress.encodeAddress.asJson, diff --git a/app/org/tronscan/grpc/GrpcBalancer.scala b/app/org/tronscan/grpc/GrpcBalancer.scala index 77be2c0..0282130 100644 --- a/app/org/tronscan/grpc/GrpcBalancer.scala +++ b/app/org/tronscan/grpc/GrpcBalancer.scala @@ -1,5 +1,6 @@ package org.tronscan.grpc +import java.util.UUID import java.util.concurrent.TimeUnit import akka.actor.{Actor, ActorRef, Cancellable, Props, Terminated} @@ -16,9 +17,9 @@ import scala.collection.JavaConverters._ import scala.concurrent.Future import scala.concurrent.duration._ -case class GrpcRequest(request: WalletStub => Future[Any]) +case class GrpcRequest(request: WalletStub => Future[Any], id: UUID) case class GrpcRetry(request: GrpcRequest, sender: ActorRef) -case class GrpcResponse(response: Any) +case class GrpcResponse(response: Any, id: UUID, ip: NodeAddress) case class GrpcBlock(num: Long, hash: String) case class OptimizeNodes() case class GrpcBalancerStats( @@ -196,7 +197,7 @@ class GrpcClient(nodeAddress: NodeAddress) extends Actor { def handleRequest(request: GrpcRequest, s: ActorRef) = { import context.dispatcher request.request(walletStub.withDeadlineAfter(5, TimeUnit.SECONDS)).map { x => - s ! GrpcResponse(x) + s ! GrpcResponse(x, request.id, nodeAddress) requestsHandled += 1 }.recover { case _ => diff --git a/app/org/tronscan/grpc/WalletClient.scala b/app/org/tronscan/grpc/WalletClient.scala index 9eb7da1..a473621 100644 --- a/app/org/tronscan/grpc/WalletClient.scala +++ b/app/org/tronscan/grpc/WalletClient.scala @@ -1,5 +1,7 @@ package org.tronscan.grpc +import java.util.UUID + import akka.actor.ActorRef import akka.util import javax.inject.{Inject, Named, Singleton} @@ -9,6 +11,7 @@ import org.tronscan.grpc.GrpcPool.{Channel, RequestChannel} import akka.pattern.ask import akka.util.Timeout import org.tron.api.api.WalletGrpc.WalletStub +import play.api.Logger import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -29,9 +32,15 @@ class WalletClient @Inject() ( (grpcPool ? RequestChannel(ip, port)).mapTo[Channel].map(c => WalletGrpc.stub(c.channel)) } - def fullRequest[A](request: WalletStub => Future[A]) = { + def fullRequest[A](request: WalletStub => Future[A], id: UUID = UUID.randomUUID()) = { implicit val timeout = Timeout(3.seconds) - (grpcBalancer ? GrpcRequest(request)).mapTo[GrpcResponse].map(_.response.asInstanceOf[A]) + (grpcBalancer ? GrpcRequest(request, id)).mapTo[GrpcResponse].map { response => + if (id != response.id) { + throw new Exception(s"ID MISMATCH $id to ${response.id}") + } + Logger.info(s"RESPONSE FROM: ${response.ip}") + response.response.asInstanceOf[A] + } } def solidity = { diff --git a/app/org/tronscan/importer/BlockChainStreamBuilder.scala b/app/org/tronscan/importer/BlockChainStreamBuilder.scala index a61f855..0edea98 100644 --- a/app/org/tronscan/importer/BlockChainStreamBuilder.scala +++ b/app/org/tronscan/importer/BlockChainStreamBuilder.scala @@ -1,5 +1,7 @@ package org.tronscan.importer +import java.util.UUID + import akka.NotUsed import akka.event.EventStream import akka.stream.OverflowStrategy @@ -47,20 +49,33 @@ class BlockChainStreamBuilder { val nextBlock = fromBlock + batchSize val toBlock = if (nextBlock <= to) nextBlock else to - Some((toBlock, (fromBlock, toBlock))) + Logger.info(s"Publish $fromBlock -> $toBlock)") + Some((toBlock + 1, (fromBlock, toBlock))) } else { None } } - .mapAsync(30) { case (fromBlock, toBlock) => - client.fullRequest(_.getBlockByLimitNext(BlockLimit(fromBlock, toBlock + 1))).map { blocks => - val bs = blocks.block.filter(_.blockHeader.isDefined).sortBy(_.getBlockHeader.getRawData.number) -// Logger.info(s"DOWNLOADED $fromBlock to $toBlock. GOT ${bs.head.getBlockHeader.getRawData.number} => ${bs.last.getBlockHeader.getRawData.number}") + .mapAsync(1) { case (fromBlock, toBlock) => + val id = UUID.randomUUID() + val range = BlockLimit(fromBlock, toBlock + 1) + client.fullRequest(_.getBlockByLimitNext(range)).map { blocks => + val bs = blocks.block.sortBy(_.getBlockHeader.getRawData.number) +// if (range.startNum != bs.head.getBlockHeader.getRawData.number) { +// Logger.warn(s"WRONG START BLOCK: ${range.startNum} => ${bs.head.getBlockHeader.getRawData.number}") +// } +// if (range.endNum - 1 != bs.last.getBlockHeader.getRawData.number) { +// Logger.warn(s"WRONG END BLOCK: ${range.endNum} => ${bs.last.getBlockHeader.getRawData.number}") +// } +// Logger.info(s"DOWNLOADED $fromBlock to $toBlock. GOT ${bs.headOption.map(_.getBlockHeader.getRawData.number)} => ${bs.lastOption.map(_.getBlockHeader.getRawData.number)}") bs } } .mapConcat(x => x.toList) + .map { block => + Logger.info("Block: " + block.getBlockHeader.getRawData.number) + block + } } def filterContracts(contractTypes: List[Transaction.Contract.ContractType]) = { diff --git a/app/org/tronscan/importer/BlockImporter.scala b/app/org/tronscan/importer/BlockImporter.scala index 9720658..d267670 100644 --- a/app/org/tronscan/importer/BlockImporter.scala +++ b/app/org/tronscan/importer/BlockImporter.scala @@ -3,6 +3,7 @@ package org.tronscan.importer import akka.{Done, NotUsed} import akka.stream.scaladsl.{Flow, Keep, Sink, Source} import javax.inject.Inject +import org.joda.time.DateTime import org.tron.protos.Tron.Block import org.tronscan.models._ import org.tronscan.utils.ModelUtils @@ -17,11 +18,12 @@ import scala.concurrent.{ExecutionContext, Future} class BlockImporter @Inject() ( blockModelRepository: BlockModelRepository, + maintenanceRoundModelRepository: MaintenanceRoundModelRepository, transactionModelRepository: TransactionModelRepository, transferRepository: TransferModelRepository, assetIssueContractModelRepository: AssetIssueContractModelRepository, participateAssetIssueModelRepository: ParticipateAssetIssueModelRepository, - databaseImporter: DatabaseImporter) { + databaseImporter: ContractImporter) { /** * Build block importer that imports the full nodes into the database @@ -39,7 +41,9 @@ class BlockImporter @Inject() ( val header = block.getBlockHeader.getRawData val queries: ListBuffer[FixedSqlAction[_, NoStream, Effect.Write]] = ListBuffer() - Logger.info(s"FULL NODE BLOCK: ${header.number}, TX: ${block.transactions.size}, CONFIRM: $confirmBlocks") + if (header.number % 1000 == 0) { + Logger.info(s"FULL NODE BLOCK: ${header.number}, TX: ${block.transactions.size}, CONFIRM: $confirmBlocks") + } // Import Block queries.append(blockModelRepository.buildInsertOrUpdate(BlockModel.fromProto(block).copy(confirmed = confirmBlocks))) @@ -134,6 +138,8 @@ class BlockImporter @Inject() ( }) queries.toList + case _ => + List.empty } .flatMapConcat(queries => Source(queries)) .groupedWithin(500, 10.seconds) @@ -155,4 +161,40 @@ class BlockImporter @Inject() ( } } + /** + * Builds the importer of voting rounds + */ + def buildVotingRoundImporter(previousVotingRound: Option[MaintenanceRoundModel] = None) = { + val maintenanceRoundTime = 21600000L + + var currentRound = previousVotingRound + + Sink.foreach[Block] { block => +// Logger.info("Block Timestamp" + block.getBlockHeader.getRawData.timestamp) + + currentRound match { + case Some(round) => + if ((round.timestamp + maintenanceRoundTime) < block.getBlockHeader.getRawData.timestamp) { + val newRound = MaintenanceRoundModel( + block = block.getBlockHeader.getRawData.number, + number = round.number + 1, + timestamp = block.getBlockHeader.getRawData.timestamp, + dateStart = new DateTime(block.getBlockHeader.getRawData.timestamp), + ) + maintenanceRoundModelRepository.insertAsync(newRound) + Logger.info("Next Round: " + newRound.block + " => " + newRound.number) + currentRound = Some(newRound) + } + case _ => + currentRound = Some(MaintenanceRoundModel( + block = block.getBlockHeader.getRawData.number, + number = 1, + timestamp = block.getBlockHeader.getRawData.timestamp, + dateStart = new DateTime(block.getBlockHeader.getRawData.timestamp), + )) + maintenanceRoundModelRepository.insertAsync(currentRound.get) + } + } + } + } diff --git a/app/org/tronscan/importer/DatabaseImporter.scala b/app/org/tronscan/importer/ContractImporter.scala similarity index 96% rename from app/org/tronscan/importer/DatabaseImporter.scala rename to app/org/tronscan/importer/ContractImporter.scala index 46a30b3..9744aa1 100644 --- a/app/org/tronscan/importer/DatabaseImporter.scala +++ b/app/org/tronscan/importer/ContractImporter.scala @@ -12,7 +12,7 @@ import org.tronscan.Extensions._ /** * Builds queries from transaction contracts */ -class DatabaseImporter @Inject() ( +class ContractImporter @Inject()( blockModelRepository: BlockModelRepository, transactionModelRepository: TransactionModelRepository, transferRepository: TransferModelRepository, @@ -36,7 +36,7 @@ class DatabaseImporter @Inject() ( def importWitnessVote: ContractQueryBuilder = { case (VoteWitnessContract, _, votes: VoteWitnessList) => - voteWitnessContractModelRepository.buildUpdateVotes(votes.voterAddress, votes.votes) + voteWitnessContractModelRepository.buildInsertVotes(votes.votes) } def importAssetIssue: ContractQueryBuilder = { diff --git a/app/org/tronscan/importer/FullNodeImporter.scala b/app/org/tronscan/importer/FullNodeImporter.scala index 8923a25..fcff3a9 100644 --- a/app/org/tronscan/importer/FullNodeImporter.scala +++ b/app/org/tronscan/importer/FullNodeImporter.scala @@ -36,7 +36,7 @@ class FullNodeImporter @Inject()( // Logger.info("BuildStream::nodeState -> " + nodeState) val importAction = await(importStreamFactory.buildImportActionFromImportStatus(nodeState)) // Logger.info("BuildStream::importAction -> " + importAction) - val importers = importersFactory.buildFullNodeImporters(importAction) + val importers = await(importersFactory.buildFullNodeImporters(importAction)) // Logger.info("BuildStream::importers -> " + importers.debug) val synchronisationChecker = importStreamFactory.fullNodePreSynchronisationChecker val blockSource = importStreamFactory.buildBlockSource(walletClient) @@ -46,6 +46,7 @@ class FullNodeImporter @Inject()( .single(nodeState) .via(synchronisationChecker) .via(blockSource) + .via(importStreamFactory.buildBlockSequenceChecker) .toMat(blockSink)(Keep.right) } } diff --git a/app/org/tronscan/importer/ImportManager.scala b/app/org/tronscan/importer/ImportManager.scala index 0398afd..ececece 100644 --- a/app/org/tronscan/importer/ImportManager.scala +++ b/app/org/tronscan/importer/ImportManager.scala @@ -27,6 +27,7 @@ class ImportManager @Inject() ( fullNodeImporter: FullNodeImporter, solidityNodeImporter: SolidityNodeImporter, walletClient: WalletClient, + voteRoundImporter: VoteRoundImporter, accountImporter: AccountImporter) extends Actor { val config = configurationProvider.get @@ -65,6 +66,14 @@ class ImportManager @Inject() ( val syncSolidity = config.get[Boolean]("sync.solidity") val syncFull = config.get[Boolean]("sync.full") val syncAddresses = config.get[Boolean]("sync.addresses") + val syncVoteRounds = config.get[Boolean]("sync.votes") + + if (syncVoteRounds) { + startImporter("ROUNDS") { + Source.tick(3.seconds, 30.minutes, "") + .mapAsync(1)(_ => voteRoundImporter.importRounds().run()) + } + } if (syncFull) { startImporter("FULL") { diff --git a/app/org/tronscan/importer/ImportStreamFactory.scala b/app/org/tronscan/importer/ImportStreamFactory.scala index e6070a1..21084b9 100644 --- a/app/org/tronscan/importer/ImportStreamFactory.scala +++ b/app/org/tronscan/importer/ImportStreamFactory.scala @@ -1,7 +1,7 @@ package org.tronscan.importer import akka.stream.SinkShape -import akka.stream.scaladsl.{Broadcast, Flow, GraphDSL, Merge, Sink, Source} +import akka.stream.scaladsl.{Broadcast, Flow, GraphDSL, Merge, Sink} import akka.{Done, NotUsed} import javax.inject.Inject import org.tron.protos.Tron.{Block, Transaction} @@ -9,18 +9,12 @@ import org.tronscan.Extensions._ import org.tronscan.domain.Types.Address import org.tronscan.grpc.{FullNodeBlockChain, SolidityBlockChain, WalletClient} import org.tronscan.importer.StreamTypes._ -import org.tronscan.models._ +import org.tronscan.models.MaintenanceRoundModelRepository import org.tronscan.service.SynchronisationService -import org.tronscan.utils.{ModelUtils, StreamUtils} +import org.tronscan.utils.StreamUtils import play.api.Logger -import play.api.cache.NamedCache -import play.api.cache.redis.CacheAsyncApi -import slick.dbio.{Effect, NoStream} -import slick.sql.FixedSqlAction import scala.async.Async._ -import scala.collection.mutable.ListBuffer -import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} /** @@ -88,6 +82,11 @@ case class ImportAction( * If db should be reset */ resetDB: Boolean = false, + + /** + * If every block should be logged + */ + logAllBlocks: Boolean = true, ) class ImportStreamFactory @Inject()( @@ -103,6 +102,7 @@ class ImportStreamFactory @Inject()( var redisCleaner = true var asyncAddressImport = true var publishEvents = true + var logAllBlocks = true val fullNodeBlockHash = await(syncService.getFullNodeHashByNum(importStatus.solidityBlock)) val resetDB = !await(syncService.isSameChain()) @@ -122,6 +122,7 @@ class ImportStreamFactory @Inject()( // Don't publish events when there is lots to sync if (importStatus.dbLatestBlock < (importStatus.fullNodeBlock - 250)) { publishEvents = false + logAllBlocks = false } // No need to clean cache when starting a clean sync @@ -135,14 +136,15 @@ class ImportStreamFactory @Inject()( cleanRedisCache = redisCleaner, asyncAddressImport = asyncAddressImport, publishEvents = publishEvents, - resetDB = resetDB + resetDB = resetDB, + logAllBlocks = logAllBlocks, ) } /** * A sync that extracts all the blocks, transactions, contracts, addresses from the blocks and passes them to streams */ - def buildBlockSink(importers: BlockchainImporters): Sink[Block, Future[Done]] = { + def buildBlockSink(importers: BlockchainImporters): Sink[Block, Future[Done]] = { Sink.fromGraph(GraphDSL.create(Sink.ignore) { implicit b => sink => import GraphDSL.Implicits._ val blocks = b.add(Broadcast[Block](3)) @@ -185,6 +187,30 @@ class ImportStreamFactory @Inject()( }) } + /** + * Verifies that blocks are properly sequential + */ + def buildBlockSequenceChecker = { + Flow[Block] + .statefulMapConcat { () => + var number = -1L + block => { + val currentNumber = block.getBlockHeader.getRawData.number + if (number == -1) { + number = currentNumber + List(block) + } else if (number + 1 == currentNumber) { + number = block.getBlockHeader.getRawData.number + List(block) + } else if (number == currentNumber) { + List.empty + } else { + throw new Exception(s"Incorrect block number sequence, $number => $currentNumber") + } + } + } + } + /** * Build a stream of blocks from a solidity node */ @@ -193,8 +219,8 @@ class ImportStreamFactory @Inject()( .mapAsync(1) { status => walletClient.full.map { walletFull => val fullNodeBlockChain = new FullNodeBlockChain(walletFull) - val fromBlock = status.dbLatestBlock + 1 - val toBlock = status.fullNodeBlock - 2 + val fromBlock = if (status.dbLatestBlock <= 0) 0 else status.dbLatestBlock + 1 + val toBlock = status.fullNodeBlock - 1 // Switch between batch or single depending how far the sync is behind if (status.fullNodeBlocksToSync < 100) blockChainBuilder.readFullNodeBlocks(fromBlock, toBlock)(fullNodeBlockChain.client) diff --git a/app/org/tronscan/importer/ImportersFactory.scala b/app/org/tronscan/importer/ImportersFactory.scala index 0a23feb..16e77d3 100644 --- a/app/org/tronscan/importer/ImportersFactory.scala +++ b/app/org/tronscan/importer/ImportersFactory.scala @@ -13,7 +13,8 @@ import org.tronscan.service.SynchronisationService import play.api.cache.NamedCache import play.api.cache.redis.CacheAsyncApi import org.tronscan.Extensions._ - +import org.tronscan.models.MaintenanceRoundModelRepository +import async.Async._ import scala.concurrent.{ExecutionContext, Future} class ImportersFactory @Inject() ( @@ -22,16 +23,19 @@ class ImportersFactory @Inject() ( @NamedCache("redis") redisCache: CacheAsyncApi, accountImporter: AccountImporter, walletClient: WalletClient, + maintenanceRoundModelRepository: MaintenanceRoundModelRepository, blockImporter: BlockImporter) { /** * Build importers for Full Node * @return */ - def buildFullNodeImporters(importAction: ImportAction)(implicit actorSystem: ActorSystem, executionContext: ExecutionContext) = { + def buildFullNodeImporters(importAction: ImportAction)(implicit actorSystem: ActorSystem, executionContext: ExecutionContext) = async { val redisCleaner = if (importAction.cleanRedisCache) Flow[Address].alsoTo(redisCacheCleaner) else Flow[Address] + val maintenanceRound = blockImporter.buildVotingRoundImporter(await(maintenanceRoundModelRepository.findLatest)).toFlow + val accountUpdaterFlow: Flow[Address, Address, NotUsed] = { if (importAction.updateAccounts) { if (importAction.asyncAddressImport) { @@ -64,8 +68,12 @@ class ImportersFactory @Inject() ( .addAddress(redisCleaner) .addContract(eventsPublisher) .addBlock(blockFlow) + .addBlock(maintenanceRound) } + /** + * Build Solidity Blockchain importers + */ def buildSolidityImporters(importAction: ImportAction)(implicit actorSystem: ActorSystem, executionContext: ExecutionContext) = { val eventsPublisher = if (importAction.publishEvents) { diff --git a/app/org/tronscan/importer/VoteRoundImporter.scala b/app/org/tronscan/importer/VoteRoundImporter.scala new file mode 100644 index 0000000..b1fcaab --- /dev/null +++ b/app/org/tronscan/importer/VoteRoundImporter.scala @@ -0,0 +1,56 @@ +package org.tronscan.importer + +import akka.stream.scaladsl.{Keep, Sink, Source} +import io.circe.Json +import javax.inject.Inject +import org.tron.protos.Tron.Transaction.Contract.ContractType +import org.tronscan.Extensions._ +import org.tronscan.api.models.TransactionSerializer._ +import org.tronscan.models.{MaintenanceRoundModelRepository, RoundVoteModelRepository} +import play.api.Logger + +import scala.concurrent.ExecutionContext + +class VoteRoundImporter @Inject() ( + maintenanceRepository: MaintenanceRoundModelRepository, + roundVoteModelRepository: RoundVoteModelRepository) { + + /** + * Import all vote rounds + */ + def importRounds()(implicit executionContext: ExecutionContext) = { + + Source + .single(0) + // Load rounds + .mapAsync(1)(_ => maintenanceRepository.findAllRounds) + // Combine rounds + .mapConcat(_.sliding(2).toList) + // Iterate all votes + .foldAsync(Map[String, Map[String, Long]]()) { case (previousVotes, Seq(currentRound, nextRound)) => + Logger.info(s"Importing round ${currentRound.number}") + for { + votes <- maintenanceRepository.getVotesBetweenBlocks(currentRound.block, nextRound.block) + newMap = buildVotes(previousVotes, votes) + _ <- roundVoteModelRepository.insertVoteRounds(newMap, currentRound.number) + } yield newMap + } + .toMat(Sink.ignore)(Keep.right) + } + + def buildVotes(voteMap: Map[String, Map[String, Long]] = Map.empty, roundVotes: Vector[(String, Int, Json)]) = { + + roundVotes.foldLeft(voteMap) { + case (votes, (address, contractType, contractData)) => + if (contractType == ContractType.UnfreezeBalanceContract.value) { + votes - address + } else if (contractType == ContractType.VoteWitnessContract.value) { + val contractVotes = contractData.as[org.tron.protos.Contract.VoteWitnessContract].toOption.get + votes ++ Map(address -> contractVotes.votes.map(x => (x.voteAddress.encodeAddress, x.voteCount)).toMap) + } else { + votes + } + } + } + +} diff --git a/app/org/tronscan/models/MaintenanceRound.scala b/app/org/tronscan/models/MaintenanceRound.scala new file mode 100644 index 0000000..2838a5e --- /dev/null +++ b/app/org/tronscan/models/MaintenanceRound.scala @@ -0,0 +1,76 @@ +package org.tronscan.models + +import com.google.inject.{Inject, Singleton} +import io.circe.parser.parse +import org.joda.time.DateTime +import org.tronscan.db.PgProfile.api._ +import org.tronscan.db.TableRepository +import play.api.db.slick.DatabaseConfigProvider + +import scala.concurrent.ExecutionContext + +case class MaintenanceRoundModel( + block: Long, + number: Int, + timestamp: Long, + dateStart: DateTime = DateTime.now, + dateEnd: Option[DateTime] = None, +) + +class MaintenanceRoundModelTable(tag: Tag) extends Table[MaintenanceRoundModel](tag, "maintenance_round") { + def block = column[Long]("block", O.PrimaryKey) + def number = column[Int]("number") + def timestamp = column[Long]("timestamp") + def dateStart = column[DateTime]("date_start") + def dateEnd = column[DateTime]("date_end") + def * = (block, number, timestamp, dateStart, dateEnd.?) <> (MaintenanceRoundModel.tupled, MaintenanceRoundModel.unapply) +} + +@Singleton() +class MaintenanceRoundModelRepository @Inject() (val dbConfig: DatabaseConfigProvider) extends TableRepository[MaintenanceRoundModelTable, MaintenanceRoundModel] { + + lazy val table = TableQuery[MaintenanceRoundModelTable] + lazy val transactionTable = TableQuery[TransactionModelTable] + lazy val transferTable = TableQuery[TransferModelTable] + + def findAll = run { + table.result + } + + def findAllRounds = run { + table.sortBy(_.number.asc).result + } + + def findLatest = run { + table.sortBy(_.number.desc).result.headOption + } + + /** + * Retrieve the votes for the given blocks + */ + def getVotesBetweenBlocks(fromBlock: Long, toBlock: Long)(implicit executionContext: ExecutionContext) = run { + sql""" + SELECT + t.owner_address, + t.contract_type, + t.contract_data + FROM ( + SELECT + owner_address, + max(date_created) as ts + FROM transactions + WHERE (block > $fromBlock AND block <= $toBlock) + AND (contract_type = 12 OR contract_type = 4) + GROUP BY owner_address + ) r + INNER JOIN transactions t + ON (t.owner_address = r.owner_address AND t.date_created = r.ts) + """.as[(String, Int, String)] + .map { result => + result.map { case (owner, contractType, contract) => + (owner, contractType, parse(contract).toOption.get) + } + } + } + +} \ No newline at end of file diff --git a/app/org/tronscan/models/RoundVote.scala b/app/org/tronscan/models/RoundVote.scala new file mode 100644 index 0000000..3c87882 --- /dev/null +++ b/app/org/tronscan/models/RoundVote.scala @@ -0,0 +1,46 @@ +package org.tronscan.models + +import com.google.inject.{Inject, Singleton} +import io.circe.parser.parse +import org.joda.time.DateTime +import org.tronscan.db.PgProfile.api._ +import org.tronscan.db.TableRepository +import play.api.db.slick.DatabaseConfigProvider + +import scala.concurrent.ExecutionContext + +case class RoundVoteModel( + address: String, + round: Int, + candidate: String, + votes: Long, +) + +class RoundVoteModelTable(tag: Tag) extends Table[RoundVoteModel](tag, "round_votes") { + def address = column[String]("address", O.PrimaryKey) + def round = column[Int]("round", O.PrimaryKey) + def candidate = column[String]("candidate", O.PrimaryKey) + def votes = column[Long]("votes") + def * = (address, round, candidate, votes) <> (RoundVoteModel.tupled, RoundVoteModel.unapply) +} + +@Singleton() +class RoundVoteModelRepository @Inject() (val dbConfig: DatabaseConfigProvider) extends TableRepository[RoundVoteModelTable, RoundVoteModel] { + + lazy val table = TableQuery[RoundVoteModelTable] + lazy val transactionTable = TableQuery[TransactionModelTable] + lazy val transferTable = TableQuery[TransferModelTable] + + def findAll = run { + table.result + } + + def insertVoteRounds(votes: Map[String, Map[String, Long]], round: Int) = run { + val models = for { + (address, candidateVotes) <- votes + (candidate, voteCount) <- candidateVotes + } yield RoundVoteModel(address, round, candidate, voteCount) + DBIO.seq(Seq(table.filter(_.round === round).delete) ++ models.map(m => table += m): _*).transactionally.withPinnedSession + } + +} \ No newline at end of file diff --git a/app/org/tronscan/models/VoteWitnessContractModel.scala b/app/org/tronscan/models/VoteWitnessContractModel.scala index 408c4cf..bcb22f7 100644 --- a/app/org/tronscan/models/VoteWitnessContractModel.scala +++ b/app/org/tronscan/models/VoteWitnessContractModel.scala @@ -3,11 +3,16 @@ package org.tronscan.models import java.util.UUID import com.google.inject.{Inject, Singleton} +import io.circe.Json import org.joda.time.DateTime import org.tronscan.db.PgProfile.api._ import org.tronscan.db.TableRepository import play.api.db.slick.DatabaseConfigProvider +import slick.dbio.Effect +import slick.sql.FixedSqlAction +import io.circe.parser._ +import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext.Implicits.global case class VoteWitnessList( @@ -24,12 +29,12 @@ case class VoteWitnessContractModel( votes: Long = 0L) class VoteWitnessContractModelTable(tag: Tag) extends Table[VoteWitnessContractModel](tag, "vote_witness_contract") { - def id = column[String]("id") + def id = column[String]("id", O.PrimaryKey) + def candidateAddress = column[String]("candidate_address", O.PrimaryKey) def block = column[Long]("block") def transaction = column[String]("transaction") def timestamp = column[DateTime]("date_created") def voterAddress = column[String]("voter_address") - def candidateAddress = column[String]("candidate_address") def votes = column[Long]("votes") def * = (id, block, transaction, timestamp, voterAddress, candidateAddress, votes) <> (VoteWitnessContractModel.tupled, VoteWitnessContractModel.unapply) } @@ -57,10 +62,14 @@ class VoteWitnessContractModelRepository @Inject() (val dbConfig: DatabaseConfig DBIO.seq(Seq(table.filter(_.voterAddress === address).delete) ++ votes.map(x => table += x): _*).transactionally } - def buildUpdateVotes(address: String, votes: Seq[VoteWitnessContractModel]) = { + def buildUpdateVotes(address: String, votes: Seq[VoteWitnessContractModel]): Seq[FixedSqlAction[Int, NoStream, Effect.Write]] = { Seq(table.filter(_.voterAddress === address).delete) ++ votes.map(x => table += x) } + def buildInsertVotes(votes: Seq[VoteWitnessContractModel]) = { + votes.map(x => table.insertOrUpdate(x)) + } + def buildDeleteVotesForAddress(address: String) = { table.filter(_.voterAddress === address).delete } diff --git a/conf/application.conf b/conf/application.conf index 4eb1dde..785f57e 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -100,6 +100,8 @@ sync { solidity = ${?ENABLE_SYNC} addresses = true addresses = ${?ENABLE_SYNC} + votes = true + votes = ${?ENABLE_SYNC} } network { diff --git a/schema/schema.sql b/schema/schema.sql index 9adb685..9945865 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -1,3 +1,28 @@ +create database "tron-explorer" +; + +create sequence analytics.vote_snapshot_id_seq +; + +create table if not exists analytics.vote_snapshot +( + id bigserial not null + constraint vote_snapshot_pkey + primary key, + address text not null, + timestamp timestamp with time zone default now() not null, + votes bigint default 0 not null +) +; + +create table if not exists analytics.requests +( + id uuid not null, + timestamp timestamp with time zone default now() not null, + host text default ''::text, + uri text default ''::text +) +; create table if not exists blocks ( @@ -19,10 +44,7 @@ create table if not exists blocks create table if not exists transactions ( date_created timestamp with time zone, - block bigint - constraint transactions_blocks_id_fk - references blocks - on update cascade on delete cascade, + block bigint, hash text not null constraint transactions_hash_pk primary key, @@ -43,15 +65,25 @@ create index if not exists transactions_block_hash_index on transactions (block, hash) ; +create index if not exists transactions_date_created_owner_address_contract_type_block_ind + on transactions (owner_address, date_created, block, contract_type) +; + +create index if not exists transactions_owner_address_date_created_index + on transactions (owner_address, date_created) +; + create table if not exists vote_witness_contract ( - id text not null constraint vote_witness_contract_id_pk primary key, + id text not null, transaction text, voter_address text, - candidate_address text, + candidate_address text not null, votes bigint, date_created timestamp with time zone, - block bigint + block bigint, + constraint vote_witness_contract_id_candidate_address_pk + primary key (id, candidate_address) ) ; @@ -75,9 +107,9 @@ create table if not exists accounts address text not null constraint accounts_pkey primary key, - name text, - balance bigint, - token_balances jsonb, + name text default ''::text, + balance bigint default 0, + token_balances jsonb default '{}'::jsonb, date_created timestamp with time zone default now() not null, date_updated timestamp with time zone default now() not null, date_synced timestamp with time zone default now() not null, @@ -97,7 +129,6 @@ create table if not exists asset_issue_contract num integer, date_end timestamp with time zone, date_start timestamp with time zone, - decay_ratio integer, vote_score integer, description text, url text, @@ -147,19 +178,6 @@ create table if not exists sr_account ) ; -CREATE SCHEMA IF NOT EXISTS analytics; - -create table if not exists analytics.vote_snapshot -( - id bigserial not null - constraint vote_snapshot_pkey - primary key, - address text not null, - timestamp timestamp with time zone default now() not null, - votes bigint default 0 not null -) -; - create table if not exists transfers ( id uuid not null @@ -170,10 +188,7 @@ create table if not exists transfers transfer_to_address text, amount bigint default 0, token_name text default 'TRX'::text, - block bigint - constraint transfers_blocks_id_fk - references blocks - on update cascade on delete cascade, + block bigint, transaction_hash text not null, confirmed boolean default false not null ) @@ -207,17 +222,6 @@ create index if not exists transfers_transfer_to_address_index on transfers (transfer_to_address) ; -create table if not exists analytics.requests -( - id uuid not null, - timestamp timestamp with time zone default now() not null, - host text default ''::text, - uri text default ''::text, - referer text default ''::text not null, - ip text default ''::text not null -) -; - create table if not exists trx_request ( address text not null @@ -230,7 +234,31 @@ create table if not exists trx_request create table if not exists funds ( - id int, + id integer, address text ) ; + +create table if not exists maintenance_round +( + block bigint not null + constraint maintenance_rounds_pkey + primary key, + number integer default 1, + date_start timestamp with time zone default now(), + date_end timestamp with time zone, + timestamp bigint +) +; + +create table if not exists round_votes +( + address text not null, + round integer not null, + candidate text not null, + votes bigint, + constraint round_votes_address_round_candidate_pk + primary key (address, round, candidate) +) +; + From e277f8b6360c35d4426677d823402b0800781837 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Mon, 3 Sep 2018 11:46:29 +0200 Subject: [PATCH 06/21] add allowance --- app/org/tronscan/api/AccountApi.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/app/org/tronscan/api/AccountApi.scala b/app/org/tronscan/api/AccountApi.scala index b20c612..17ee9f3 100644 --- a/app/org/tronscan/api/AccountApi.scala +++ b/app/org/tronscan/api/AccountApi.scala @@ -170,6 +170,7 @@ class AccountApi @Inject()( ), "balances" -> Json.toJson(balances), "balance" -> account.balance, + "allowance" -> account.allowance, "tokenBalances" -> Json.toJson(balances), "frozen" -> Json.obj( "total" -> account.frozen.map(_.frozenBalance).sum, From 806b3e9e43880ebbcdd1aa5f0d2f82c9d46a5376 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Mon, 3 Sep 2018 13:09:21 +0200 Subject: [PATCH 07/21] add new tvm grpc functions --- app/org/tronscan/api/GrpcFullApi.scala | 40 +++++++++++++++++-- .../api/models/TransactionSerializer.scala | 31 ++++++++++++++ app/org/tronscan/grpc/WalletClient.scala | 8 +--- .../tronscan/importer/VoteRoundImporter.scala | 6 +++ conf/routes | 3 ++ 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/app/org/tronscan/api/GrpcFullApi.scala b/app/org/tronscan/api/GrpcFullApi.scala index 7ea25ce..ff924d4 100644 --- a/app/org/tronscan/api/GrpcFullApi.scala +++ b/app/org/tronscan/api/GrpcFullApi.scala @@ -7,12 +7,12 @@ import io.circe.syntax._ import io.swagger.annotations.Api import javax.inject.Inject import org.tron.api.api._ -import org.tron.common.utils.{Base58, ByteArray} -import org.tron.protos.Tron.Account +import org.tron.common.utils.ByteArray +import org.tronscan.Extensions._ +import org.tronscan.api.models.TransactionSerializer._ import org.tronscan.api.models.{TransactionSerializer, TronModelsSerializers} import org.tronscan.grpc.{GrpcService, WalletClient} import play.api.mvc.Request -import org.tronscan.Extensions._ import scala.concurrent.Future @@ -156,4 +156,38 @@ class GrpcFullApi @Inject() ( )) } } + + def listProposals = Action.async { implicit req => + + for { + proposalList <- walletClient.fullRequest(_.listProposals(EmptyMessage())) + } yield { + Ok(Json.obj( + "data" -> proposalList.proposals.map(_.asJson).asJson + )) + } + } + + def listExchanges = Action.async { implicit req => + + for { + exchangeList <- walletClient.fullRequest(_.listExchanges(EmptyMessage())) + } yield { + Ok(Json.obj( + "data" -> exchangeList.exchanges.map(_.asJson).asJson + )) + } + } + + def getChainParameters = Action.async { implicit req => + + for { + chainParameters <- walletClient.fullRequest(_.getChainParameters(EmptyMessage())) + } yield { + Ok(Json.obj( + "data" -> chainParameters.chainParameter.map(_.asJson).asJson + )) + } + } + } diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index 0ce0558..c3ec27f 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -245,6 +245,37 @@ object TransactionSerializer { } + implicit val encodeProposal = new Encoder[org.tron.protos.Tron.Proposal] { + def apply(contract: org.tron.protos.Tron.Proposal): Js = Js.obj( + "proposalId" -> contract.proposalId.asJson, + "proposerAddress" -> contract.proposerAddress.encodeAddress.asJson, + "parameters" -> contract.parameters.asJson, + "expirationTime" -> contract.expirationTime.asJson, + "createTime" -> contract.createTime.asJson, + "approvals" -> contract.approvals.map(_.encodeAddress).asJson, + "state" -> contract.state.name.asJson, + ) + } + + implicit val encodeExchange = new Encoder[org.tron.protos.Tron.Exchange] { + def apply(contract: org.tron.protos.Tron.Exchange): Js = Js.obj( + "exchangeId" -> contract.exchangeId.asJson, + "creatorAddress" -> contract.creatorAddress.encodeAddress.asJson, + "createTime" -> contract.createTime.asJson, + "firstTokenId" -> contract.firstTokenId.decodeString.asJson, + "firstTokenBalance" -> contract.firstTokenBalance.asJson, + "secondTokenId" -> contract.secondTokenId.decodeString.asJson, + "secondTokenBalance" -> contract.secondTokenBalance.asJson, + ) + } + + implicit val encodeChainParameter = new Encoder[org.tron.protos.Tron.ChainParameters.ChainParameter] { + def apply(contract: org.tron.protos.Tron.ChainParameters.ChainParameter): Js = Js.obj( + "key" -> contract.key.asJson, + "value" -> contract.value.asJson, + ) + } + implicit val encodeProposalCreateContract = new Encoder[org.tron.protos.Contract.ProposalCreateContract] { def apply(contract: org.tron.protos.Contract.ProposalCreateContract): Js = Js.obj( "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, diff --git a/app/org/tronscan/grpc/WalletClient.scala b/app/org/tronscan/grpc/WalletClient.scala index a473621..fb52308 100644 --- a/app/org/tronscan/grpc/WalletClient.scala +++ b/app/org/tronscan/grpc/WalletClient.scala @@ -34,13 +34,7 @@ class WalletClient @Inject() ( def fullRequest[A](request: WalletStub => Future[A], id: UUID = UUID.randomUUID()) = { implicit val timeout = Timeout(3.seconds) - (grpcBalancer ? GrpcRequest(request, id)).mapTo[GrpcResponse].map { response => - if (id != response.id) { - throw new Exception(s"ID MISMATCH $id to ${response.id}") - } - Logger.info(s"RESPONSE FROM: ${response.ip}") - response.response.asInstanceOf[A] - } + (grpcBalancer ? GrpcRequest(request, id)).mapTo[GrpcResponse].map(_.response.asInstanceOf[A]) } def solidity = { diff --git a/app/org/tronscan/importer/VoteRoundImporter.scala b/app/org/tronscan/importer/VoteRoundImporter.scala index b1fcaab..d0d51e1 100644 --- a/app/org/tronscan/importer/VoteRoundImporter.scala +++ b/app/org/tronscan/importer/VoteRoundImporter.scala @@ -38,16 +38,22 @@ class VoteRoundImporter @Inject() ( .toMat(Sink.ignore)(Keep.right) } + /** + * Build the vote map based on the round votes + */ def buildVotes(voteMap: Map[String, Map[String, Long]] = Map.empty, roundVotes: Vector[(String, Int, Json)]) = { roundVotes.foldLeft(voteMap) { case (votes, (address, contractType, contractData)) => if (contractType == ContractType.UnfreezeBalanceContract.value) { + // Reset votes when unfreezing balance votes - address } else if (contractType == ContractType.VoteWitnessContract.value) { + // Read votes from the contract data and add them to the total votes val contractVotes = contractData.as[org.tron.protos.Contract.VoteWitnessContract].toOption.get votes ++ Map(address -> contractVotes.votes.map(x => (x.voteAddress.encodeAddress, x.voteCount)).toMap) } else { + // Do nothing votes } } diff --git a/conf/routes b/conf/routes index 6e4a2af..83cad6e 100644 --- a/conf/routes +++ b/conf/routes @@ -103,6 +103,9 @@ GET /api/grpc/full/gettransactionbyid/:hash org.tronscan.api.GrpcFul GET /api/grpc/full/getaccountnet/:address org.tronscan.api.GrpcFullApi.getAccountNet(address: String) GET /api/grpc/full/listnodes org.tronscan.api.GrpcFullApi.listNodes GET /api/grpc/full/listwitnesses org.tronscan.api.GrpcFullApi.listWitnesses +GET /api/grpc/full/listproposals org.tronscan.api.GrpcFullApi.listProposals +GET /api/grpc/full/listexchanges org.tronscan.api.GrpcFullApi.listExchanges +GET /api/grpc/full/getchainparameters org.tronscan.api.GrpcFullApi.getChainParameters # GRPC Solidity API From d5bc8c925f3131dafdaf55b0bf2fea73bd66db14 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Mon, 3 Sep 2018 13:53:24 +0200 Subject: [PATCH 08/21] add documentation for transaction builder --- app/org/tronscan/actions/ActionRunner.scala | 14 ++-- .../tronscan/api/TransactionBuilderApi.scala | 71 +++++++++++++++---- .../api/models/TransactionBuilder.scala | 62 ++++++++++++++++ .../api/models/TransactionCreate.scala | 32 +++++++++ .../api/models/TransactionSerializer.scala | 16 +++++ conf/application.conf | 2 + conf/routes | 11 +-- 7 files changed, 184 insertions(+), 24 deletions(-) create mode 100644 app/org/tronscan/api/models/TransactionBuilder.scala create mode 100644 app/org/tronscan/api/models/TransactionCreate.scala diff --git a/app/org/tronscan/actions/ActionRunner.scala b/app/org/tronscan/actions/ActionRunner.scala index b4d2f7c..b1c5088 100644 --- a/app/org/tronscan/actions/ActionRunner.scala +++ b/app/org/tronscan/actions/ActionRunner.scala @@ -7,6 +7,7 @@ import javax.inject.Inject import play.api.Logger import play.api.cache.NamedCache import play.api.cache.redis.CacheAsyncApi +import play.api.inject.ConfigurationProvider import scala.concurrent.duration._ @@ -15,7 +16,8 @@ class ActionRunner @Inject()( representativeListReader: RepresentativeListReader, statsOverview: StatsOverview, voteList: VoteList, - voteScraper: VoteScraper) extends Actor { + voteScraper: VoteScraper, + configurationProvider: ConfigurationProvider) extends Actor { val decider: Supervision.Decider = { exc => Logger.error("CACHE WARMER ERROR", exc) @@ -57,10 +59,12 @@ class ActionRunner @Inject()( } override def preStart(): Unit = { - startWitnessReader() - startVoteListWarmer() - startVoteScraper() - startStatsOverview() + if (configurationProvider.get.get[Boolean]("cache.warmer")) { + startWitnessReader() + startVoteListWarmer() + startVoteScraper() + startStatsOverview() + } } def receive = { diff --git a/app/org/tronscan/api/TransactionBuilderApi.scala b/app/org/tronscan/api/TransactionBuilderApi.scala index d2021d0..bd4a7a3 100644 --- a/app/org/tronscan/api/TransactionBuilderApi.scala +++ b/app/org/tronscan/api/TransactionBuilderApi.scala @@ -7,7 +7,7 @@ import io.circe.{Decoder, Json} import io.swagger.annotations._ import javax.inject.Inject import org.tron.common.utils.ByteArray -import org.tron.protos.Contract.{AccountCreateContract, AccountUpdateContract, TransferAssetContract, TransferContract} +import org.tron.protos.Contract.{AccountCreateContract, AccountUpdateContract, TransferAssetContract, TransferContract, _} import org.tron.protos.Tron.Transaction import org.tron.protos.Tron.Transaction.Contract.ContractType import org.tronscan.Extensions._ @@ -17,20 +17,7 @@ import org.tronscan.service.TransactionBuilder import play.api.mvc.{AnyContent, Request, Result} import scala.concurrent.Future -import io.circe.syntax._ -import org.tron.protos.Contract._ -import org.tron.protos.Tron.Transaction.Contract.ContractType -import scalapb.Message - - -case class TransactionAction( - contract: Transaction.Contract, - broadcast: Boolean, - key: Option[String] = None, - data: Option[String] = None, -) -case class Signature(pk: String) @Api( value = "Transaction Builder", @@ -39,6 +26,13 @@ class TransactionBuilderApi @Inject()( transactionBuilder: TransactionBuilder, walletClient: WalletClient) extends BaseApi { + + case class TransactionAction( + contract: Transaction.Contract, + broadcast: Boolean, + key: Option[String] = None, + data: Option[String] = None) + import TransactionSerializer._ import scala.concurrent.ExecutionContext.Implicits.global @@ -76,6 +70,11 @@ class TransactionBuilderApi @Inject()( Transaction.Contract( `type` = ContractType.WithdrawBalanceContract, parameter = Some(Any.pack(c.asInstanceOf[WithdrawBalanceContract]))) + + case c: ProposalApproveContract => + Transaction.Contract( + `type` = ContractType.ProposalApproveContract, + parameter = Some(Any.pack(c.asInstanceOf[ProposalApproveContract]))) } TransactionAction(transactionContract, broadcast.getOrElse(false), key, json.hcursor.downField("data").as[String].toOption) @@ -126,31 +125,75 @@ class TransactionBuilderApi @Inject()( @ApiOperation( value = "Build TransferContract") + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.TransferTransaction", + paramType = "body"), + )) def transfer = Action.async { implicit req => handleTransaction[org.tron.protos.Contract.TransferContract]() } @ApiOperation( value = "Build TransferAssetContract" ) + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.TransferAssetTransaction", + paramType = "body"), + )) def transferAsset = Action.async { implicit req => handleTransaction[org.tron.protos.Contract.TransferAssetContract]() } @ApiOperation( value = "Build AccountCreateContract" ) + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.AccountCreateTransaction", + paramType = "body"), + )) def accountCreate = Action.async { implicit req => handleTransaction[org.tron.protos.Contract.AccountCreateContract]() } @ApiOperation( value = "Build AccountUpdateContract" ) + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.AccountUpdateTransaction", + paramType = "body"), + )) def accountUpdate = Action.async { implicit req => handleTransaction[org.tron.protos.Contract.AccountUpdateContract]() } @ApiOperation( value = "Build WithdrawBalancecontract" ) + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.WithdrawBalanceTransaction", + paramType = "body"), + )) def withdrawBalance = Action.async { implicit req => handleTransaction[org.tron.protos.Contract.WithdrawBalanceContract]() } + + @ApiOperation( + value = "Build ProposalApproveContract") + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.ProposalApproveTransaction", + paramType = "body"), + )) + def proposalApprove = Action.async { implicit req => + handleTransaction[org.tron.protos.Contract.ProposalApproveContract]() + } } + + diff --git a/app/org/tronscan/api/models/TransactionBuilder.scala b/app/org/tronscan/api/models/TransactionBuilder.scala new file mode 100644 index 0000000..753dee4 --- /dev/null +++ b/app/org/tronscan/api/models/TransactionBuilder.scala @@ -0,0 +1,62 @@ +package org.tronscan.api.models + +import io.swagger.annotations.ApiModelProperty + +// Approve Proposal +case class ProposalApproveTransaction( + contract: ProposalApprove) extends TransactionCreateBase + +case class ProposalApprove( + ownerAddress: String, + @ApiModelProperty(value = "ID of the proposal") + proposalId: Long, + @ApiModelProperty(value = "true to approve, false to reject") + approved: Boolean) + + +// Transfer +case class TransferTransaction( + contract: Transfer) extends TransactionCreateBase + +case class Transfer( + ownerAddress: String, + toAddress: String, + @ApiModelProperty(value = "Amount of TRX to send in Sun") + amount: Long) + +// Transfer Asset +case class TransferAssetTransaction( + contract: TransferAsset) extends TransactionCreateBase + +case class TransferAsset( + ownerAddress: String, + toAddress: String, + @ApiModelProperty(value = "Name of the token") + assetName: String, + @ApiModelProperty(value = "Amount of tokens to send") + amount: Long) + + +// Withdraw Balance +case class WithdrawBalanceTransaction( + contract: WithdrawBalance) extends TransactionCreateBase + +case class WithdrawBalance( + ownerAddress: String) + +// Account Update +case class AccountUpdateTransaction( + contract: AccountUpdate) extends TransactionCreateBase + +case class AccountUpdate( + ownerAddress: String, + accountName: String) + +// Account Create +case class AccountCreateTransaction( + contract: AccountCreate) extends TransactionCreateBase + +case class AccountCreate( + ownerAddress: String, + accountAddress: String) + diff --git a/app/org/tronscan/api/models/TransactionCreate.scala b/app/org/tronscan/api/models/TransactionCreate.scala new file mode 100644 index 0000000..3e743ff --- /dev/null +++ b/app/org/tronscan/api/models/TransactionCreate.scala @@ -0,0 +1,32 @@ +package org.tronscan.api.models + +import io.swagger.annotations.{ApiModel, ApiModelProperty} +import org.tron.protos.Tron.Transaction + +@ApiModel( + value="TransactionCreate") +case class TransactionCreate( + @ApiModelProperty(value = "Contract") + contract: Any, + + @ApiModelProperty(value = "If the transaction should be broadcast to the network") + broadcast: Boolean, + + @ApiModelProperty(value = "Private Key to sign the transaction") + key: Option[String] = None, + + @ApiModelProperty(value = "Optional extra data to attach to the transaction") + data: Option[String] = None +) + + +trait TransactionCreateBase { + @ApiModelProperty(value = "If the transaction should be broadcast to the network") + val broadcast: Boolean = false + + @ApiModelProperty(value = "Private Key to sign the transaction") + val key: Option[String] = None + + @ApiModelProperty(value = "Optional extra data to attach to the transaction") + val data: Option[String] = None +} \ No newline at end of file diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index c3ec27f..7ac4210 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -291,6 +291,22 @@ object TransactionSerializer { ) } + implicit val decodeProposalApproveContract = new Decoder[org.tron.protos.Contract.ProposalApproveContract] { + def apply(c: HCursor) = { + for { + ownerAddress <- c.downField("ownerAddress").as[String] + proposalId <- c.downField("proposalId").as[Long] + isAddApproval <- c.downField("approve").as[Boolean] + } yield { + org.tron.protos.Contract.ProposalApproveContract( + ownerAddress = ownerAddress.decodeAddress, + proposalId = proposalId, + isAddApproval = isAddApproval + ) + } + } + } + implicit val encodeProposalDeleteContract = new Encoder[org.tron.protos.Contract.ProposalDeleteContract] { def apply(contract: org.tron.protos.Contract.ProposalDeleteContract): Js = Js.obj( "ownerAddress" -> contract.ownerAddress.encodeAddress.asJson, diff --git a/conf/application.conf b/conf/application.conf index 785f57e..29530b7 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -84,6 +84,8 @@ cache { # /api/node cache nodes = true } + + warmer = true } grpc { diff --git a/conf/routes b/conf/routes index 83cad6e..3a6d9ff 100644 --- a/conf/routes +++ b/conf/routes @@ -116,11 +116,12 @@ GET /api/grpc/solidity/listwitnesses org.tronscan.api.GrpcSol # Transaction Builder -POST /api/transaction-builder/contract/transfer org.tronscan.api.TransactionBuilderApi.transfer -POST /api/transaction-builder/contract/transferasset org.tronscan.api.TransactionBuilderApi.transferAsset -POST /api/transaction-builder/contract/accountcreate org.tronscan.api.TransactionBuilderApi.accountCreate -POST /api/transaction-builder/contract/accountupdate org.tronscan.api.TransactionBuilderApi.accountUpdate -POST /api/transaction-builder/contract/withdrawbalance org.tronscan.api.TransactionBuilderApi.withdrawBalance +POST /api/transaction-builder/contract/transfer org.tronscan.api.TransactionBuilderApi.transfer +POST /api/transaction-builder/contract/transferasset org.tronscan.api.TransactionBuilderApi.transferAsset +POST /api/transaction-builder/contract/accountcreate org.tronscan.api.TransactionBuilderApi.accountCreate +POST /api/transaction-builder/contract/accountupdate org.tronscan.api.TransactionBuilderApi.accountUpdate +POST /api/transaction-builder/contract/withdrawbalance org.tronscan.api.TransactionBuilderApi.withdrawBalance +POST /api/transaction-builder/contract/proposalapprovecontract org.tronscan.api.TransactionBuilderApi.proposalApprove # Socket IO From de5568a39609f91e1f4f25e5f27cacf7295b07c9 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Mon, 3 Sep 2018 15:16:17 +0200 Subject: [PATCH 09/21] add round vote api --- app/org/tronscan/api/VoteApi.scala | 55 ++++++++++++++++--- app/org/tronscan/db/Repository.scala | 6 +- .../tronscan/models/MaintenanceRound.scala | 4 ++ app/org/tronscan/models/RoundVote.scala | 4 ++ conf/routes | 5 ++ 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/app/org/tronscan/api/VoteApi.scala b/app/org/tronscan/api/VoteApi.scala index fd02cdc..f865c1d 100644 --- a/app/org/tronscan/api/VoteApi.scala +++ b/app/org/tronscan/api/VoteApi.scala @@ -4,14 +4,10 @@ package tronscan.api import io.circe.Json import io.circe.generic.auto._ import io.circe.syntax._ -import io.circe.generic.auto._ -import io.circe.syntax._ import javax.inject.Inject import org.joda.time.DateTime import org.tron.api.api.EmptyMessage import org.tron.api.api.WalletSolidityGrpc.WalletSolidity -import org.tronscan.App._ -import org.tronscan.actions.VoteList import org.tronscan.actions.VoteList import org.tronscan.db.PgProfile.api._ import org.tronscan.domain.Constants @@ -20,8 +16,6 @@ import org.tronscan.models._ import play.api.cache.redis.CacheAsyncApi import play.api.cache.{Cached, NamedCache} import play.api.mvc.InjectedController -import play.api.cache.{Cached, NamedCache} -import play.api.mvc.InjectedController import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -36,6 +30,8 @@ class VoteApi @Inject()( voteSnapshotModelRepository: VoteSnapshotModelRepository, walletSolidity: WalletSolidity, voteList: VoteList, + roundVoteModelRepository: RoundVoteModelRepository, + maintenanceRoundModelRepository: MaintenanceRoundModelRepository, @NamedCache("redis") redisCache: CacheAsyncApi) extends InjectedController { def findAll() = Action.async { implicit request => @@ -134,4 +130,49 @@ class VoteApi @Inject()( )) } } -} \ No newline at end of file + + /** + * Retrieves a single voting round + */ + def round(id: Int) = Action.async { + maintenanceRoundModelRepository.findByNumber(id).map { + case Some(round) => + Ok(round.asJson) + case _ => + NotFound + } + } + + /** + * Retrieve the votes for the given round + */ + def roundVotes(id: Int) = Action.async { implicit req => + + import roundVoteModelRepository._ + + var q = sortWithRequest() { + case (t, "votes") => t.votes + } + + q = q andThen { _.filter(_.round === id) } + + q = q andThen filterRequest { + case (query, ("voter", value)) => + query.filter(_.address === value) + case (query, ("candidate", value)) => + query.filter(_.candidate === value) + case (query, _) => + query + } + + for { + total <- readTotals(q) + accounts <- readQuery(q andThen limitWithRequest()) + } yield { + Ok(Json.obj( + "total" -> total.asJson, + "data" -> accounts.asJson + )) + } + } +} diff --git a/app/org/tronscan/db/Repository.scala b/app/org/tronscan/db/Repository.scala index 195dd57..ee93d19 100644 --- a/app/org/tronscan/db/Repository.scala +++ b/app/org/tronscan/db/Repository.scala @@ -63,7 +63,6 @@ case class EntityModel(id: Option[Int]) extends Entity * @tparam T Entity type */ abstract class EntityTable[T <: Entity](tag: Tag, tableName: String) extends Table[T](tag, tableName) { - def id: Rep[Int] = column[Int]("id", O.PrimaryKey, O.AutoInc) } @@ -114,11 +113,11 @@ trait TableRepository[T <: Table[E], E <: Any] extends Repository { runQuery(params.foldLeft(query)(e)) } - def specialProcess(fun: QueryType => QueryType)(implicit request: Request[AnyContent]): QueryType => QueryType = { (query: QueryType) => + def specialProcess(fun: QueryType => QueryType)(implicit request: Request[AnyContent]): QueryType => QueryType = { query: QueryType => fun(query) } - def filterRequest(e: (QueryType, (String, String)) => QueryType)(implicit request: Request[AnyContent]): QueryType => QueryType = { (query: QueryType) => + def filterRequest(e: (QueryType, (String, String)) => QueryType)(implicit request: Request[AnyContent]): QueryType => QueryType = { query: QueryType => val params = request.queryString.map(x => (x._1, x._2.mkString)) params.foldLeft(query)(e) } @@ -131,7 +130,6 @@ trait TableRepository[T <: Table[E], E <: Any] extends Repository { func(table).length.result } - def sortWithRequest(sortParam: String = "sort")(sorter: PartialFunction[(T, String), Rep[_ <: Any]]) (implicit request: Request[AnyContent]): QueryType => QueryType = { query: QueryType => (for { diff --git a/app/org/tronscan/models/MaintenanceRound.scala b/app/org/tronscan/models/MaintenanceRound.scala index 2838a5e..bc8d9ac 100644 --- a/app/org/tronscan/models/MaintenanceRound.scala +++ b/app/org/tronscan/models/MaintenanceRound.scala @@ -73,4 +73,8 @@ class MaintenanceRoundModelRepository @Inject() (val dbConfig: DatabaseConfigPro } } + + def findByNumber(number: Int) = run { + table.filter(_.number === number).result.headOption + } } \ No newline at end of file diff --git a/app/org/tronscan/models/RoundVote.scala b/app/org/tronscan/models/RoundVote.scala index 3c87882..4f64c7f 100644 --- a/app/org/tronscan/models/RoundVote.scala +++ b/app/org/tronscan/models/RoundVote.scala @@ -35,6 +35,10 @@ class RoundVoteModelRepository @Inject() (val dbConfig: DatabaseConfigProvider) table.result } + def findByNumber(number: Int) = run { + table.filter(_.round === number).result.headOption + } + def insertVoteRounds(votes: Map[String, Map[String, Long]], round: Int) = run { val models = for { (address, candidateVotes) <- votes diff --git a/conf/routes b/conf/routes index 3a6d9ff..9381623 100644 --- a/conf/routes +++ b/conf/routes @@ -57,6 +57,11 @@ GET /api/vote/current-cycle org.tronscan.api.VoteApi.candidateTotals GET /api/vote/next-cycle org.tronscan.api.VoteApi.nextCycle GET /api/vote/stats org.tronscan.api.VoteApi.stats +# Vote Rounds + +GET /api/round/:number org.tronscan.api.VoteApi.round(number: Int) +GET /api/round/:number/votes org.tronscan.api.VoteApi.roundVotes(number: Int) + # Tokens GET /api/token org.tronscan.api.TokenApi.findAll From 66f937c6e84a8a0e6c25d2b70b362e0ea20c7aa8 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Mon, 3 Sep 2018 15:22:43 +0200 Subject: [PATCH 10/21] add api documentation --- app/org/tronscan/api/GrpcFullApi.scala | 42 +++++++++++++++++++++-- app/org/tronscan/api/TransactionApi.scala | 2 +- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app/org/tronscan/api/GrpcFullApi.scala b/app/org/tronscan/api/GrpcFullApi.scala index ff924d4..5e1de92 100644 --- a/app/org/tronscan/api/GrpcFullApi.scala +++ b/app/org/tronscan/api/GrpcFullApi.scala @@ -4,7 +4,7 @@ package tronscan.api import com.google.protobuf.ByteString import io.circe.Json import io.circe.syntax._ -import io.swagger.annotations.Api +import io.swagger.annotations.{Api, ApiImplicitParam, ApiImplicitParams, ApiOperation} import javax.inject.Inject import org.tron.api.api._ import org.tron.common.utils.ByteArray @@ -46,6 +46,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve the latest block on") def getNowBlock = Action.async { implicit req => for { @@ -57,7 +59,8 @@ class GrpcFullApi @Inject() ( } } - + @ApiOperation( + value = "Retrieve a block by number") def getBlockByNum(number: Long) = Action.async { implicit req => for { @@ -74,6 +77,22 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve a range of blocks") + @ApiImplicitParams(Array( + new ApiImplicitParam( + name = "from", + value = "From block", + required = true, + dataType = "long", + paramType = "query"), + new ApiImplicitParam( + name = "to", + value = "To block", + required = true, + dataType = "long", + paramType = "query"), + )) def getBlockByLimitNext = Action.async { implicit req => val from = req.getQueryString("from").get.toLong @@ -88,6 +107,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve a transaction by the transaction hash") def getTransactionById(hash: String) = Action.async { implicit req => for { @@ -99,6 +120,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve total number of transactions") def totalTransaction = Action.async { implicit req => for { @@ -110,6 +133,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve account by address") def getAccount(address: String) = Action.async { implicit req => for { @@ -121,6 +146,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve bandwidth information for the given account") def getAccountNet(address: String) = Action.async { implicit req => for { @@ -132,7 +159,8 @@ class GrpcFullApi @Inject() ( } } - + @ApiOperation( + value = "Retrieve nodes") def listNodes = Action.async { implicit req => for { @@ -146,6 +174,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve all witnesses") def listWitnesses = Action.async { implicit req => for { @@ -157,6 +187,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve all proposals") def listProposals = Action.async { implicit req => for { @@ -168,6 +200,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve exchanges") def listExchanges = Action.async { implicit req => for { @@ -179,6 +213,8 @@ class GrpcFullApi @Inject() ( } } + @ApiOperation( + value = "Retrieve blockchain parameters") def getChainParameters = Action.async { implicit req => for { diff --git a/app/org/tronscan/api/TransactionApi.scala b/app/org/tronscan/api/TransactionApi.scala index 75a378c..9335ad2 100644 --- a/app/org/tronscan/api/TransactionApi.scala +++ b/app/org/tronscan/api/TransactionApi.scala @@ -53,7 +53,7 @@ class TransactionApi @Inject()( paramType = "query"), )) @ApiOperation( - value = "List Transfers", + value = "Retrieve transactions", response = classOf[TransactionModel], responseContainer = "List") def findAll() = Action.async { implicit request => From 920ed0224a4a28290d24488475c4abdb49dd20e3 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Tue, 4 Sep 2018 11:46:04 +0200 Subject: [PATCH 11/21] cleanup upgrade dependencies --- app/org/tronscan/api/SystemApi.scala | 33 +++++++++++++- .../importer/BlockChainStreamBuilder.scala | 1 - build.sbt | 2 +- conf/routes | 15 ++++--- project/build.properties | 2 +- project/plugins.sbt | 2 +- project/scaffold.sbt | 5 --- project/scalapb.sbt | 2 +- test/org/tronscan/grpc/GrpcSpec.scala | 44 +++++++++++++++++++ 9 files changed, 88 insertions(+), 18 deletions(-) delete mode 100644 project/scaffold.sbt create mode 100644 test/org/tronscan/grpc/GrpcSpec.scala diff --git a/app/org/tronscan/api/SystemApi.scala b/app/org/tronscan/api/SystemApi.scala index 7bd31f2..09c13c1 100644 --- a/app/org/tronscan/api/SystemApi.scala +++ b/app/org/tronscan/api/SystemApi.scala @@ -2,7 +2,7 @@ package org.tronscan.api import akka.actor.ActorRef import javax.inject.{Inject, Named} -import org.tron.api.api.EmptyMessage +import org.tron.api.api.{BlockLimit, EmptyMessage, WalletGrpc} import org.tron.api.api.WalletSolidityGrpc.WalletSolidity import play.api.cache.Cached import play.api.inject.ConfigurationProvider @@ -16,6 +16,7 @@ import scala.concurrent.Future import scala.concurrent.duration._ import akka.pattern.ask import akka.util.Timeout +import io.grpc.ManagedChannelBuilder class SystemApi @Inject()( walletClient: WalletClient, @@ -83,4 +84,34 @@ class SystemApi @Inject()( } } } +// +// def test = Action.async { +// import scala.concurrent.ExecutionContext.Implicits.global +// +// val channel = ManagedChannelBuilder +// .forAddress("47.254.146.147", 50051) +// .usePlaintext(true) +// .build +// +// val wallet = WalletGrpc.stub(channel) +// +// val blocks = for (i <- 1 to 500 by 50) yield i +// +// val done = Future.sequence(blocks.sliding(2).toList.map { case Seq(now, next) => +// wallet.getBlockByLimitNext(BlockLimit(now, next)).map { result => +// val resultBlocks = result.block.sortBy(_.getBlockHeader.getRawData.number).map(_.getBlockHeader.getRawData.number) +// if (resultBlocks.head != now) { +// println(s"INVALID HEAD ${resultBlocks.head} => $now") +// } +// if (resultBlocks.last != next) { +// println(s"INVALID HEAD ${resultBlocks.last} => $next") +// } +// resultBlocks +// } +// }) +// +// done.map { _ => +// Ok("done") +// } +// } } diff --git a/app/org/tronscan/importer/BlockChainStreamBuilder.scala b/app/org/tronscan/importer/BlockChainStreamBuilder.scala index 0edea98..2b0eab2 100644 --- a/app/org/tronscan/importer/BlockChainStreamBuilder.scala +++ b/app/org/tronscan/importer/BlockChainStreamBuilder.scala @@ -49,7 +49,6 @@ class BlockChainStreamBuilder { val nextBlock = fromBlock + batchSize val toBlock = if (nextBlock <= to) nextBlock else to - Logger.info(s"Publish $fromBlock -> $toBlock)") Some((toBlock + 1, (fromBlock, toBlock))) } else { diff --git a/build.sbt b/build.sbt index 02c489e..3ea261c 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ organization := "org.tronscan" version := "latest" -scalaVersion := "2.12.4" +scalaVersion := "2.12.6" dependencyOverrides ++= Seq( "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.2" diff --git a/conf/routes b/conf/routes index 9381623..d43fb3e 100644 --- a/conf/routes +++ b/conf/routes @@ -47,15 +47,16 @@ GET /api/node org.tronscan.api.NodeApi.status GET /api/system/status org.tronscan.api.SystemApi.status GET /api/system/balancer org.tronscan.api.SystemApi.balancer +GET /api/system/test org.tronscan.api.SystemApi.test GET /api/system/sync-account/:address org.tronscan.api.AccountApi.sync(address: String) # Votes -GET /api/vote org.tronscan.api.VoteApi.findAll -GET /api/vote/live org.tronscan.api.VoteApi.currentCycle -GET /api/vote/current-cycle org.tronscan.api.VoteApi.candidateTotals -GET /api/vote/next-cycle org.tronscan.api.VoteApi.nextCycle -GET /api/vote/stats org.tronscan.api.VoteApi.stats +GET /api/vote org.tronscan.api.VoteApi.findAll +GET /api/vote/live org.tronscan.api.VoteApi.currentCycle +GET /api/vote/current-cycle org.tronscan.api.VoteApi.candidateTotals +GET /api/vote/next-cycle org.tronscan.api.VoteApi.nextCycle +GET /api/vote/stats org.tronscan.api.VoteApi.stats # Vote Rounds @@ -70,8 +71,8 @@ GET /api/token/:name/address org.tronscan.api.TokenApi.getAccounts(na # Witnesses -GET /api/witness org.tronscan.api.WitnessApi.findAll -GET /api/witness/maintenance-statistic org.tronscan.api.WitnessApi.maintenanceStatistic +GET /api/witness org.tronscan.api.WitnessApi.findAll +GET /api/witness/maintenance-statistic org.tronscan.api.WitnessApi.maintenanceStatistic # Nodes diff --git a/project/build.properties b/project/build.properties index 7c81737..5620cc5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.5 +sbt.version=1.2.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index c2ef3f2..414fdde 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") \ No newline at end of file diff --git a/project/scaffold.sbt b/project/scaffold.sbt deleted file mode 100644 index e1139bf..0000000 --- a/project/scaffold.sbt +++ /dev/null @@ -1,5 +0,0 @@ -// Defines scaffolding (found under .g8 folder) -// http://www.foundweekends.org/giter8/scaffolding.html -// sbt "g8Scaffold form" -// not working on sbt 1.0 -//addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.10.0") diff --git a/project/scalapb.sbt b/project/scalapb.sbt index 93742e9..2a20c38 100644 --- a/project/scalapb.sbt +++ b/project/scalapb.sbt @@ -1,3 +1,3 @@ addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.18") -libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.7.1" \ No newline at end of file +libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.7.4" \ No newline at end of file diff --git a/test/org/tronscan/grpc/GrpcSpec.scala b/test/org/tronscan/grpc/GrpcSpec.scala new file mode 100644 index 0000000..8be9a3e --- /dev/null +++ b/test/org/tronscan/grpc/GrpcSpec.scala @@ -0,0 +1,44 @@ +package org +package tronscan.grpc + +import io.grpc.ManagedChannelBuilder +import org.specs2.mutable._ +import org.tron.api.api.{BlockLimit, WalletGrpc} + +import scala.concurrent.Future + +class GrpcSpec extends Specification { + + "GRPC" should { + + "Read BlockLimit Next" in { + + import scala.concurrent.ExecutionContext.Implicits.global + + val channel = ManagedChannelBuilder + .forAddress("47.254.146.147", 50051) + .usePlaintext() + .build + + val wallet = WalletGrpc.stub(channel) + + val blocks = for (i <- 1 to 500 by 50) yield i + + val done = Future.sequence(blocks.sliding(2).toList.map { case Seq(now, next) => + wallet.getBlockByLimitNext(BlockLimit(now, next)).map { result => + val resultBlocks = result.block.sortBy(_.getBlockHeader.getRawData.number).map(_.getBlockHeader.getRawData.number) + if (resultBlocks.head != now) { + println(s"INVALID HEAD ${resultBlocks.head} => $now") + } + if (resultBlocks.last != next) { + println(s"INVALID HEAD ${resultBlocks.last} => $next") + } + resultBlocks + } + }) + + awaitSync(done) + ok + } + } +} From 1896e591dfadb7ee8d5c9c98c78421c2861eb1f1 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Tue, 4 Sep 2018 12:43:15 +0200 Subject: [PATCH 12/21] remove test url --- conf/routes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/routes b/conf/routes index d43fb3e..e2dd500 100644 --- a/conf/routes +++ b/conf/routes @@ -47,7 +47,7 @@ GET /api/node org.tronscan.api.NodeApi.status GET /api/system/status org.tronscan.api.SystemApi.status GET /api/system/balancer org.tronscan.api.SystemApi.balancer -GET /api/system/test org.tronscan.api.SystemApi.test +#GET /api/system/test org.tronscan.api.SystemApi.test GET /api/system/sync-account/:address org.tronscan.api.AccountApi.sync(address: String) # Votes From 657a8b8ef3e166483d3d2157d9a71cfea6f83348 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Tue, 4 Sep 2018 16:02:27 +0200 Subject: [PATCH 13/21] add http port scanner --- app/org/tronscan/api/SystemApi.scala | 2 +- app/org/tronscan/grpc/GrpcBalancer.scala | 2 +- app/org/tronscan/network/NetworkNode.scala | 3 ++ app/org/tronscan/network/NetworkScanner.scala | 15 +++++++- app/org/tronscan/network/NetworkStreams.scala | 36 +++++++++++++++++++ app/org/tronscan/utils/NetworkUtils.scala | 13 +++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/app/org/tronscan/api/SystemApi.scala b/app/org/tronscan/api/SystemApi.scala index 09c13c1..f053de7 100644 --- a/app/org/tronscan/api/SystemApi.scala +++ b/app/org/tronscan/api/SystemApi.scala @@ -90,7 +90,7 @@ class SystemApi @Inject()( // // val channel = ManagedChannelBuilder // .forAddress("47.254.146.147", 50051) -// .usePlaintext(true) +// .usePlainText() // .build // // val wallet = WalletGrpc.stub(channel) diff --git a/app/org/tronscan/grpc/GrpcBalancer.scala b/app/org/tronscan/grpc/GrpcBalancer.scala index 0282130..6b15490 100644 --- a/app/org/tronscan/grpc/GrpcBalancer.scala +++ b/app/org/tronscan/grpc/GrpcBalancer.scala @@ -146,7 +146,7 @@ class GrpcClient(nodeAddress: NodeAddress) extends Actor { lazy val channel = ManagedChannelBuilder .forAddress(nodeAddress.ip, nodeAddress.port) - .usePlaintext(true) + .usePlaintext() .build lazy val walletStub = { diff --git a/app/org/tronscan/network/NetworkNode.scala b/app/org/tronscan/network/NetworkNode.scala index 04ffbe2..0ca43b9 100644 --- a/app/org/tronscan/network/NetworkNode.scala +++ b/app/org/tronscan/network/NetworkNode.scala @@ -16,6 +16,9 @@ case class NetworkNode( grpcResponseTime: Long = 0, pingOnline: Boolean = false, pingResponseTime: Long = 0, + httpEnabled: Boolean = false, + httpResponseTime: Long = 0, + httpUrl: String = "", country: String = "", city: String = "", lat: Double = 0, diff --git a/app/org/tronscan/network/NetworkScanner.scala b/app/org/tronscan/network/NetworkScanner.scala index 8c6c32f..6bd3431 100644 --- a/app/org/tronscan/network/NetworkScanner.scala +++ b/app/org/tronscan/network/NetworkScanner.scala @@ -18,6 +18,7 @@ import org.tronscan.utils.StreamUtils import play.api.Logger import play.api.inject.ConfigurationProvider import play.api.libs.concurrent.Futures +import play.api.libs.ws.WSClient import scala.concurrent.Future import scala.concurrent.duration._ @@ -42,7 +43,8 @@ class NetworkScanner @Inject()( configurationProvider: ConfigurationProvider, @Named("grpc-pool") actorRef: ActorRef, geoIPService: GeoIPService, - implicit val futures: Futures) extends Actor { + implicit val futures: Futures, + implicit val WSClient: WSClient) extends Actor { val workContext = context.system.dispatchers.lookup("contexts.node-watcher") val debugEnabled = configurationProvider.get.get[Boolean]("network.scanner.debug") @@ -124,6 +126,17 @@ class NetworkScanner @Inject()( } node } + .via(NetworkStreams.httpPinger(12)) + .map { node => + if (debugEnabled) { + if (node.httpEnabled) { + Logger.debug("HTTP Online: " + node.ip) + } else { + Logger.debug("HTTP Offline: " + node.ip) + } + } + node + } } def seedNodes = { diff --git a/app/org/tronscan/network/NetworkStreams.scala b/app/org/tronscan/network/NetworkStreams.scala index 6c4bda3..c9816d9 100644 --- a/app/org/tronscan/network/NetworkStreams.scala +++ b/app/org/tronscan/network/NetworkStreams.scala @@ -13,6 +13,7 @@ import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} import org.tronscan.Extensions._ import org.tronscan.utils.NetworkUtils +import play.api.libs.ws.WSClient object NetworkStreams { @@ -108,4 +109,39 @@ object NetworkStreams { } } } + + /** + * Ping the given IPS and returns a node + * + * @param parallel parallel number of processes + */ + def httpPinger(parallel: Int = 4)(implicit executionContext: ExecutionContext, wsClient: WSClient): Flow[NetworkNode, NetworkNode, NotUsed] = { + Flow[NetworkNode] + .mapAsyncUnordered(parallel) { networkNode => + + val ia = InetAddress.getByName(networkNode.ip) + val startPing = System.currentTimeMillis() + + (for { + (url, online) <- NetworkUtils.pingHttp(s"http://${networkNode.ip}:8090/wallet/getnowblock").recoverWith { + case _ => + NetworkUtils.pingHttp(s"http://${networkNode.ip}:8091/walletsolidity/getnowblock") + } + response = System.currentTimeMillis() - startPing + } yield { + if (online) { + networkNode.copy( + httpEnabled = online, + httpResponseTime = response, + httpUrl = url, + ) + } else { + networkNode.copy(httpEnabled = false) + } + }).recover { + case _ => + networkNode.copy(httpEnabled = false) + } + } + } } diff --git a/app/org/tronscan/utils/NetworkUtils.scala b/app/org/tronscan/utils/NetworkUtils.scala index ef5ecdf..0de7e03 100644 --- a/app/org/tronscan/utils/NetworkUtils.scala +++ b/app/org/tronscan/utils/NetworkUtils.scala @@ -2,6 +2,8 @@ package org.tronscan.utils import java.net.{InetSocketAddress, Socket} +import play.api.libs.ws.WSClient + import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ @@ -21,4 +23,15 @@ object NetworkUtils { false } } + + /** + * Try to open a socket for the given ip and port + */ + def pingHttp(url: String, timeout: FiniteDuration = 5.seconds)(implicit executionContext: ExecutionContext, wsClient: WSClient) = { + wsClient + .url(url) + .withRequestTimeout(timeout) + .get() + .map(x => (url, (x.json \ "blockID").isDefined)) + } } From 6eff589accba5ebda0be71179f4b1edbc37437af Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Tue, 4 Sep 2018 16:17:14 +0200 Subject: [PATCH 14/21] check node type instead of handling exception --- app/org/tronscan/network/NetworkStreams.scala | 19 +++++++++++-------- app/org/tronscan/utils/NetworkUtils.scala | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/org/tronscan/network/NetworkStreams.scala b/app/org/tronscan/network/NetworkStreams.scala index c9816d9..84d776b 100644 --- a/app/org/tronscan/network/NetworkStreams.scala +++ b/app/org/tronscan/network/NetworkStreams.scala @@ -4,16 +4,16 @@ import java.net.InetAddress import java.util.concurrent.TimeUnit import akka.NotUsed -import akka.stream.scaladsl.{Flow, Source} +import akka.stream.scaladsl.Flow import org.tron.api.api.EmptyMessage +import org.tronscan.Extensions._ +import org.tronscan.utils.NetworkUtils import play.api.libs.concurrent.Futures import play.api.libs.concurrent.Futures._ +import play.api.libs.ws.WSClient import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} -import org.tronscan.Extensions._ -import org.tronscan.utils.NetworkUtils -import play.api.libs.ws.WSClient object NetworkStreams { @@ -123,17 +123,20 @@ object NetworkStreams { val startPing = System.currentTimeMillis() (for { - (url, online) <- NetworkUtils.pingHttp(s"http://${networkNode.ip}:8090/wallet/getnowblock").recoverWith { - case _ => - NetworkUtils.pingHttp(s"http://${networkNode.ip}:8091/walletsolidity/getnowblock") + online <- if (networkNode.nodeType == NetworkScanner.full) { + NetworkUtils.pingHttp(s"http://${networkNode.ip}:8090/wallet/getnowblock") + } else { + NetworkUtils.pingHttp(s"http://${networkNode.ip}:8091/walletsolidity/getnowblock") } response = System.currentTimeMillis() - startPing } yield { if (online) { + val httpPort = if (networkNode.nodeType == NetworkScanner.full) 8090 else 8091 + networkNode.copy( httpEnabled = online, httpResponseTime = response, - httpUrl = url, + httpUrl = s"http://${networkNode.ip}:$httpPort", ) } else { networkNode.copy(httpEnabled = false) diff --git a/app/org/tronscan/utils/NetworkUtils.scala b/app/org/tronscan/utils/NetworkUtils.scala index 0de7e03..60ba418 100644 --- a/app/org/tronscan/utils/NetworkUtils.scala +++ b/app/org/tronscan/utils/NetworkUtils.scala @@ -32,6 +32,6 @@ object NetworkUtils { .url(url) .withRequestTimeout(timeout) .get() - .map(x => (url, (x.json \ "blockID").isDefined)) + .map(x => (x.json \ "blockID").isDefined) } } From 4c2fe9c5757e7d36ab7d40091a01e1e909bdb51f Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Fri, 7 Sep 2018 18:34:24 +0200 Subject: [PATCH 15/21] add UpdateAsset contract to transaction builder --- .../tronscan/api/TransactionBuilderApi.scala | 17 +++++++++++++++++ .../api/models/TransactionBuilder.scala | 12 ++++++++++++ .../api/models/TransactionSerializer.scala | 18 ++++++++++++++++++ conf/routes | 3 ++- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/org/tronscan/api/TransactionBuilderApi.scala b/app/org/tronscan/api/TransactionBuilderApi.scala index bd4a7a3..d4446e7 100644 --- a/app/org/tronscan/api/TransactionBuilderApi.scala +++ b/app/org/tronscan/api/TransactionBuilderApi.scala @@ -75,6 +75,11 @@ class TransactionBuilderApi @Inject()( Transaction.Contract( `type` = ContractType.ProposalApproveContract, parameter = Some(Any.pack(c.asInstanceOf[ProposalApproveContract]))) + + case c: UpdateAssetContract => + Transaction.Contract( + `type` = ContractType.UpdateAssetContract, + parameter = Some(Any.pack(c.asInstanceOf[UpdateAssetContract]))) } TransactionAction(transactionContract, broadcast.getOrElse(false), key, json.hcursor.downField("data").as[String].toOption) @@ -194,6 +199,18 @@ class TransactionBuilderApi @Inject()( def proposalApprove = Action.async { implicit req => handleTransaction[org.tron.protos.Contract.ProposalApproveContract]() } + + @ApiOperation( + value = "Build UpdateAssetContract") + @ApiImplicitParams(Array( + new ApiImplicitParam( + required = true, + dataType = "org.tronscan.api.models.UpdateAssetTransaction", + paramType = "body"), + )) + def updateAsset = Action.async { implicit req => + handleTransaction[org.tron.protos.Contract.UpdateAssetContract]() + } } diff --git a/app/org/tronscan/api/models/TransactionBuilder.scala b/app/org/tronscan/api/models/TransactionBuilder.scala index 753dee4..9f038bb 100644 --- a/app/org/tronscan/api/models/TransactionBuilder.scala +++ b/app/org/tronscan/api/models/TransactionBuilder.scala @@ -60,3 +60,15 @@ case class AccountCreate( ownerAddress: String, accountAddress: String) + +// Update Asset +case class UpdateAssetTransaction( + contract: UpdateAsset) extends TransactionCreateBase + +case class UpdateAsset( + ownerAddress: String, + url: String, + description: String, + newLimit: Option[Long], + newPublicLimit: Option[Long]) + diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index 7ac4210..7d0e93a 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -47,6 +47,24 @@ object TransactionSerializer { ) } + implicit val decodeUpdateAssetContract = new Encoder[org.tron.protos.Contract.UpdateAssetContract] { + def apply(c: HCursor) = { + for { + from <- c.downField("ownerAddress").as[String] + description <- c.downField("description").as[String] + url <- c.downField("url").as[String] + } yield { + org.tron.protos.Contract.UpdateAssetContract( + ownerAddress = from.decodeAddress, + description = description.encodeString, + url = url.encodeString, + newLimit = c.downField("newLimit").as[Long].getOrElse(0L), + newPublicLimit = c.downField("newPublicLimit").as[Long].getOrElse(0L), + ) + } + } + } + implicit val encodeTransferContract = new Encoder[org.tron.protos.Contract.TransferContract] { def apply(transferContract: org.tron.protos.Contract.TransferContract): Js = Js.obj( "from" -> Base58.encode58Check(transferContract.ownerAddress.toByteArray).asJson, diff --git a/conf/routes b/conf/routes index e2dd500..b5908a7 100644 --- a/conf/routes +++ b/conf/routes @@ -127,7 +127,8 @@ POST /api/transaction-builder/contract/transferasset org.tron POST /api/transaction-builder/contract/accountcreate org.tronscan.api.TransactionBuilderApi.accountCreate POST /api/transaction-builder/contract/accountupdate org.tronscan.api.TransactionBuilderApi.accountUpdate POST /api/transaction-builder/contract/withdrawbalance org.tronscan.api.TransactionBuilderApi.withdrawBalance -POST /api/transaction-builder/contract/proposalapprovecontract org.tronscan.api.TransactionBuilderApi.proposalApprove +POST /api/transaction-builder/contract/proposalapprove org.tronscan.api.TransactionBuilderApi.proposalApprove +POST /api/transaction-builder/contract/updateasset org.tronscan.api.TransactionBuilderApi.updateAsset # Socket IO From 6b9644f35fd7f1e19f9b8c8d914a03b542295b85 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 13 Sep 2018 13:31:08 +0200 Subject: [PATCH 16/21] cats --- .../tronscan/models/AccountBandwidthCapsule.scala | 1 + app/org/tronscan/utils/CatsUtils.scala | 15 +++++++++++++++ build.sbt | 10 +++++++++- conf/framework.conf | 1 + project/Dependencies.scala | 11 +++++++---- 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 app/org/tronscan/utils/CatsUtils.scala diff --git a/app/org/tronscan/models/AccountBandwidthCapsule.scala b/app/org/tronscan/models/AccountBandwidthCapsule.scala index 408be74..8334a5f 100644 --- a/app/org/tronscan/models/AccountBandwidthCapsule.scala +++ b/app/org/tronscan/models/AccountBandwidthCapsule.scala @@ -1,5 +1,6 @@ package org.tronscan.models +import com.google.inject.Guice import org.tron.api.api.AccountNetMessage case class AccountBandwidthCapsule(accountNetMessage: AccountNetMessage) { diff --git a/app/org/tronscan/utils/CatsUtils.scala b/app/org/tronscan/utils/CatsUtils.scala new file mode 100644 index 0000000..5df1304 --- /dev/null +++ b/app/org/tronscan/utils/CatsUtils.scala @@ -0,0 +1,15 @@ +package org.tronscan.utils + +import cats.Monoid +import org.tron.protos.Tron.Block + +object CatsUtils { + + implicit val blockSemiGroup = new Monoid[Block] { + def empty = Block() + def combine(x: Block, y: Block): Block = { + x.withTransactions(x.transactions ++ y.transactions) + } + } + +} diff --git a/build.sbt b/build.sbt index 3ea261c..208a70c 100644 --- a/build.sbt +++ b/build.sbt @@ -80,7 +80,12 @@ libraryDependencies ++= Seq( "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test, "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.2" -) ++ grpcDeps ++ akkaDeps ++ circeDependencies ++ akkaStreamsContribDeps +) ++ + grpcDeps ++ + akkaDeps ++ + circeDependencies ++ + akkaStreamsContribDeps ++ + catsDeps // Disable API Documentation sources in (Compile, doc) := Seq.empty @@ -95,3 +100,6 @@ lazy val root = (project in file(".")) scalapb.gen() -> (sourceManaged in Compile).value ), ) + + +scalacOptions += "-Ypartial-unification" \ No newline at end of file diff --git a/conf/framework.conf b/conf/framework.conf index 6984628..8c2761a 100644 --- a/conf/framework.conf +++ b/conf/framework.conf @@ -2,6 +2,7 @@ play.filters.enabled = [ "play.filters.cors.CORSFilter", "play.filters.headers.SecurityHeadersFilter", + "org.tronscan.filter.LoggingFilter" ] play.filters.headers = { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index faf9851..ed6d34d 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,9 +6,9 @@ object Dependencies { val slickPgVersion = "0.16.1" val monixVersion = "2.3.0" val akkaVersion = "2.5.14" - val catsVersion = "0.9.0" val grpcVersion = "1.9.0" val scaleCubeVersion = "1.0.7" + val catsVersion = "1.3.1" val akkaStreamsContribDeps = Seq( "com.typesafe.akka" %% "akka-stream-contrib" % "0.9" @@ -31,9 +31,6 @@ object Dependencies { "com.typesafe.akka" %% "akka-stream-testkit" ).map(_ % akkaVersion) - val catsDeps = Seq( - "org.typelevel" %% "cats" % catsVersion) - val macroParadise = addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) val scalaAsync = Seq( @@ -52,4 +49,10 @@ object Dependencies { "io.scalecube" % "scalecube-cluster", "io.scalecube" % "scalecube-transport" ).map(_ % scaleCubeVersion) + + val catsDeps = Seq( + "org.typelevel" %% "cats-core" + ).map(_ % catsVersion) ++ Seq( + "org.typelevel" %% "cats-effect" % "1.0.0" + ) } From af80a22a716d43a2a7a7af8dd89154cc75e4c21a Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 20 Sep 2018 16:35:39 +0200 Subject: [PATCH 17/21] fix decoder --- .../tronscan/api/models/TransactionSerializer.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/org/tronscan/api/models/TransactionSerializer.scala b/app/org/tronscan/api/models/TransactionSerializer.scala index 7d0e93a..50f240d 100644 --- a/app/org/tronscan/api/models/TransactionSerializer.scala +++ b/app/org/tronscan/api/models/TransactionSerializer.scala @@ -1,12 +1,10 @@ package org package tronscan.api.models -import com.google.protobuf.ByteString import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor, Json => Js} +import io.circe.{Decoder, DecodingFailure, Encoder, HCursor, Json => Js} import org.joda.time.DateTime -import org.tron.common.crypto.ECKey -import org.tron.common.utils.{Base58, ByteArray, Crypto, Sha256Hash} +import org.tron.common.utils.{Base58, ByteArray, Crypto} import org.tron.protos.Tron.Transaction.Contract.ContractType.{AccountCreateContract, AccountUpdateContract, AssetIssueContract, CreateSmartContract, ExchangeCreateContract, ExchangeInjectContract, ExchangeTransactionContract, ExchangeWithdrawContract, FreezeBalanceContract, ParticipateAssetIssueContract, ProposalApproveContract, ProposalCreateContract, ProposalDeleteContract, TransferAssetContract, TransferContract, TriggerSmartContract, UnfreezeAssetContract, UnfreezeBalanceContract, UpdateAssetContract, VoteAssetContract, VoteWitnessContract, WithdrawBalanceContract, WitnessCreateContract, WitnessUpdateContract} import org.tron.protos.Tron.{AccountType, Transaction} import org.tronscan.Extensions._ @@ -47,7 +45,7 @@ object TransactionSerializer { ) } - implicit val decodeUpdateAssetContract = new Encoder[org.tron.protos.Contract.UpdateAssetContract] { + implicit val decodeUpdateAssetContract = new Decoder[org.tron.protos.Contract.UpdateAssetContract] { def apply(c: HCursor) = { for { from <- c.downField("ownerAddress").as[String] @@ -59,7 +57,7 @@ object TransactionSerializer { description = description.encodeString, url = url.encodeString, newLimit = c.downField("newLimit").as[Long].getOrElse(0L), - newPublicLimit = c.downField("newPublicLimit").as[Long].getOrElse(0L), + newPublicLimit = c.downField("newPublicLimit").as[Long].getOrElse(0L) ) } } From ed221e254783622603c3ef0793f3c20be57a358b Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 20 Sep 2018 21:06:21 +0200 Subject: [PATCH 18/21] add token filter add comments cleanup --- app/org/tronscan/api/AccountApi.scala | 18 +++++++++++++++++- app/org/tronscan/api/TransactionApi.scala | 4 ++++ .../api/models/TransactionBuilder.scala | 5 +++++ .../importer/BlockChainStreamBuilder.scala | 1 - conf/framework.conf | 1 - 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/org/tronscan/api/AccountApi.scala b/app/org/tronscan/api/AccountApi.scala index 17ee9f3..f23a6f4 100644 --- a/app/org/tronscan/api/AccountApi.scala +++ b/app/org/tronscan/api/AccountApi.scala @@ -56,6 +56,10 @@ class AccountApi @Inject()( val key = configurationProvider.get.get[String]("play.http.secret.key") + /** + * Query accounts + * @return + */ @ApiResponses(Array( new ApiResponse( code = 200, @@ -259,6 +263,11 @@ class AccountApi @Inject()( } } + /** + * Retrieve the Super Representative github link + * @param address address of the SR + * @return + */ @ApiOperation( value = "", hidden = true @@ -278,6 +287,9 @@ class AccountApi @Inject()( } } + /** + * Update the super representative pages + */ @ApiOperation( value = "", hidden = true) @@ -319,6 +331,9 @@ class AccountApi @Inject()( } } + /** + * Synchronises the given account to the database + */ @ApiOperation( value = "", hidden = true) @@ -431,7 +446,8 @@ class AccountApi @Inject()( } /** - * Generates a new private key + * Generates a new account with private key + * * @return */ def create = Action { diff --git a/app/org/tronscan/api/TransactionApi.scala b/app/org/tronscan/api/TransactionApi.scala index 9335ad2..e6e30f8 100644 --- a/app/org/tronscan/api/TransactionApi.scala +++ b/app/org/tronscan/api/TransactionApi.scala @@ -102,6 +102,10 @@ class TransactionApi @Inject()( query.filter(x => x.timestamp <= dateStart) case (query, ("contract_type", value)) => query.filter(x => x.contractType === value.toInt) + case (query, ("token", value)) if value.toUpperCase == "TRX" => + query.filter(x => x.contractType === ContractType.TransferContract.value) + case (query, ("token", value)) => + query.filter(x => x.contractType === ContractType.TransferAssetContract.value && x.contractData.+>>("token") === value) case (query, _) => query } diff --git a/app/org/tronscan/api/models/TransactionBuilder.scala b/app/org/tronscan/api/models/TransactionBuilder.scala index 9f038bb..43d9c78 100644 --- a/app/org/tronscan/api/models/TransactionBuilder.scala +++ b/app/org/tronscan/api/models/TransactionBuilder.scala @@ -1,7 +1,12 @@ +/* + * Models which are shown in the Swagger API for the TransactionBuilder will be listed here + */ + package org.tronscan.api.models import io.swagger.annotations.ApiModelProperty + // Approve Proposal case class ProposalApproveTransaction( contract: ProposalApprove) extends TransactionCreateBase diff --git a/app/org/tronscan/importer/BlockChainStreamBuilder.scala b/app/org/tronscan/importer/BlockChainStreamBuilder.scala index 2b0eab2..e0e61ac 100644 --- a/app/org/tronscan/importer/BlockChainStreamBuilder.scala +++ b/app/org/tronscan/importer/BlockChainStreamBuilder.scala @@ -56,7 +56,6 @@ class BlockChainStreamBuilder { } } .mapAsync(1) { case (fromBlock, toBlock) => - val id = UUID.randomUUID() val range = BlockLimit(fromBlock, toBlock + 1) client.fullRequest(_.getBlockByLimitNext(range)).map { blocks => val bs = blocks.block.sortBy(_.getBlockHeader.getRawData.number) diff --git a/conf/framework.conf b/conf/framework.conf index 8c2761a..6984628 100644 --- a/conf/framework.conf +++ b/conf/framework.conf @@ -2,7 +2,6 @@ play.filters.enabled = [ "play.filters.cors.CORSFilter", "play.filters.headers.SecurityHeadersFilter", - "org.tronscan.filter.LoggingFilter" ] play.filters.headers = { From ed0f5d4a22dcd4395cd5f67f5c4bd2bbe352799c Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 20 Sep 2018 21:07:14 +0200 Subject: [PATCH 19/21] fix import --- app/org/tronscan/api/TransactionApi.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/app/org/tronscan/api/TransactionApi.scala b/app/org/tronscan/api/TransactionApi.scala index e6e30f8..ac7b1a0 100644 --- a/app/org/tronscan/api/TransactionApi.scala +++ b/app/org/tronscan/api/TransactionApi.scala @@ -8,6 +8,7 @@ import javax.inject.Inject import org.joda.time.DateTime import org.tron.common.utils.ByteArray import org.tron.protos.Tron.Transaction +import org.tron.protos.Tron.Transaction.Contract.ContractType import org.tronscan.api.models.TransactionSerializer import org.tronscan.db.PgProfile.api._ import org.tronscan.grpc.WalletClient From 57e7b7be5efe7d598faae22f9c9984c303a0605d Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 20 Sep 2018 23:12:51 +0200 Subject: [PATCH 20/21] update schema don't use pid file --- docker/api/Dockerfile | 1 + schema/schema.sql | 62 +++++++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile index 7fc6123..28e55bd 100644 --- a/docker/api/Dockerfile +++ b/docker/api/Dockerfile @@ -5,4 +5,5 @@ ADD ./target/universal/stage /opt/tronscan-api CMD /opt/tronscan-api/bin/tronscan \ -J-Xms128M -J-Xmx2048m \ + -Dpidfile.path=/dev/null \ -Dconfig.resource=docker.conf diff --git a/schema/schema.sql b/schema/schema.sql index 9945865..a64c8b4 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -1,10 +1,13 @@ create database "tron-explorer" ; +create schema if not exists analytics +; + create sequence analytics.vote_snapshot_id_seq ; -create table if not exists analytics.vote_snapshot +create table analytics.vote_snapshot ( id bigserial not null constraint vote_snapshot_pkey @@ -15,7 +18,7 @@ create table if not exists analytics.vote_snapshot ) ; -create table if not exists analytics.requests +create table analytics.requests ( id uuid not null, timestamp timestamp with time zone default now() not null, @@ -24,7 +27,7 @@ create table if not exists analytics.requests ) ; -create table if not exists blocks +create table blocks ( id bigint not null constraint blocks_pkey @@ -41,7 +44,7 @@ create table if not exists blocks ) ; -create table if not exists transactions +create table transactions ( date_created timestamp with time zone, block bigint, @@ -53,27 +56,28 @@ create table if not exists transactions contract_type integer default '-1'::integer not null, owner_address text default ''::text not null, to_address text default ''::text not null, - data text default ''::text not null + data text default ''::text not null, + fee bigint ) ; -create index if not exists transactions_date_created_index +create index transactions_date_created_index on transactions (date_created desc) ; -create index if not exists transactions_block_hash_index +create index transactions_block_hash_index on transactions (block, hash) ; -create index if not exists transactions_date_created_owner_address_contract_type_block_ind +create index transactions_date_created_owner_address_contract_type_block_ind on transactions (owner_address, date_created, block, contract_type) ; -create index if not exists transactions_owner_address_date_created_index +create index transactions_owner_address_date_created_index on transactions (owner_address, date_created) ; -create table if not exists vote_witness_contract +create table vote_witness_contract ( id text not null, transaction text, @@ -87,7 +91,7 @@ create table if not exists vote_witness_contract ) ; -create table if not exists participate_asset_issue +create table participate_asset_issue ( id uuid not null constraint participate_asset_issue_id_pk @@ -102,7 +106,7 @@ create table if not exists participate_asset_issue ) ; -create table if not exists accounts +create table accounts ( address text not null constraint accounts_pkey @@ -117,7 +121,7 @@ create table if not exists accounts ) ; -create table if not exists asset_issue_contract +create table asset_issue_contract ( id uuid constraint asset_issue_contract_id_pk @@ -140,7 +144,7 @@ create table if not exists asset_issue_contract ) ; -create table if not exists address_balance +create table address_balance ( address text, token text, @@ -150,7 +154,7 @@ create table if not exists address_balance ) ; -create table if not exists witness_create_contract +create table witness_create_contract ( address text not null constraint witness_create_contract_pkey @@ -159,7 +163,7 @@ create table if not exists witness_create_contract ) ; -create table if not exists ip_geo +create table ip_geo ( ip text, city text, @@ -169,7 +173,7 @@ create table if not exists ip_geo ) ; -create table if not exists sr_account +create table sr_account ( address text not null constraint sr_account_pkey @@ -178,7 +182,7 @@ create table if not exists sr_account ) ; -create table if not exists transfers +create table transfers ( id uuid not null constraint transfers_pkey @@ -194,35 +198,35 @@ create table if not exists transfers ) ; -create index if not exists transfers_transfer_from_address_transfer_to_address_date_cre +create index transfers_transfer_from_address_transfer_to_address_date_cre on transfers (transfer_from_address asc, transfer_to_address asc, date_created desc) ; -create index if not exists transfers_block_hash_index +create index transfers_block_hash_index on transfers (block, transaction_hash) ; -create index if not exists transfers_date_created_index +create index transfers_date_created_index on transfers (date_created desc) ; -create index if not exists transfers_transfer_from_address_date_created_index +create index transfers_transfer_from_address_date_created_index on transfers (transfer_from_address asc, date_created desc) ; -create index if not exists transfers_transfer_to_address_date_created_index +create index transfers_transfer_to_address_date_created_index on transfers (transfer_to_address asc, date_created desc) ; -create index if not exists transfers_transfer_from_address_index +create index transfers_transfer_from_address_index on transfers (transfer_from_address) ; -create index if not exists transfers_transfer_to_address_index +create index transfers_transfer_to_address_index on transfers (transfer_to_address) ; -create table if not exists trx_request +create table trx_request ( address text not null constraint trx_request_pkey @@ -232,14 +236,14 @@ create table if not exists trx_request ) ; -create table if not exists funds +create table funds ( id integer, address text ) ; -create table if not exists maintenance_round +create table maintenance_round ( block bigint not null constraint maintenance_rounds_pkey @@ -251,7 +255,7 @@ create table if not exists maintenance_round ) ; -create table if not exists round_votes +create table round_votes ( address text not null, round integer not null, From 5c5c77b2bae05d289f9665bfd4767c1d33a347f1 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Thu, 27 Sep 2018 17:33:22 +0200 Subject: [PATCH 21/21] update protobuf add comments --- app/org/tronscan/importer/BlockImporter.scala | 3 ++- app/protobuf/core/Contract.proto | 1 + app/protobuf/core/Tron.proto | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/org/tronscan/importer/BlockImporter.scala b/app/org/tronscan/importer/BlockImporter.scala index d267670..16ee529 100644 --- a/app/org/tronscan/importer/BlockImporter.scala +++ b/app/org/tronscan/importer/BlockImporter.scala @@ -170,10 +170,10 @@ class BlockImporter @Inject() ( var currentRound = previousVotingRound Sink.foreach[Block] { block => -// Logger.info("Block Timestamp" + block.getBlockHeader.getRawData.timestamp) currentRound match { case Some(round) => + // The current block timestamp has exceeded the (previous maintenance timestamp + maintenanceRoundTime) then insert a new voting rond if ((round.timestamp + maintenanceRoundTime) < block.getBlockHeader.getRawData.timestamp) { val newRound = MaintenanceRoundModel( block = block.getBlockHeader.getRawData.number, @@ -186,6 +186,7 @@ class BlockImporter @Inject() ( currentRound = Some(newRound) } case _ => + // If there isn't a previous maintenance round then just use the current block as the start of a round currentRound = Some(MaintenanceRoundModel( block = block.getBlockHeader.getRawData.number, number = 1, diff --git a/app/protobuf/core/Contract.proto b/app/protobuf/core/Contract.proto index 5d5c142..18eafde 100644 --- a/app/protobuf/core/Contract.proto +++ b/app/protobuf/core/Contract.proto @@ -225,4 +225,5 @@ message ExchangeTransactionContract { int64 exchange_id = 2; bytes token_id = 3; int64 quant = 4; + int64 expected = 5; } \ No newline at end of file diff --git a/app/protobuf/core/Tron.proto b/app/protobuf/core/Tron.proto index ed9526c..38c4f3a 100644 --- a/app/protobuf/core/Tron.proto +++ b/app/protobuf/core/Tron.proto @@ -73,6 +73,7 @@ message Account { int64 frozen_balance = 1; // the frozen trx balance int64 expire_time = 2; // the expire time } + // account nick name bytes account_name = 1; AccountType type = 2; // the create address @@ -83,7 +84,6 @@ message Account { repeated Vote votes = 5; // the other asset owned by this account map asset = 6; - // latest asset operation time // the frozen balance repeated Frozen frozen = 7; @@ -113,6 +113,7 @@ message Account { int64 latest_consume_time = 21; int64 latest_consume_free_time = 22; + // the identity of this account, case insensitive bytes account_id = 23; message AccountResource {