diff --git a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java index 0fc7adba674..8235daa9cdb 100644 --- a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java +++ b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java @@ -575,6 +575,17 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore, } break; } + case MEMO_FEE: { + if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_6)) { + throw new ContractValidateException( + "Bad chain parameter id [MEMO_FEE]"); + } + if (value < 0 || value > 1_000_000_000) { + throw new ContractValidateException( + "This value[MEMO_FEE] is only allowed to be in the range 0-1000_000_000"); + } + break; + } default: break; } @@ -639,7 +650,8 @@ public enum ProposalType { // current value, value range ALLOW_TVM_LONDON(63), // 0, 1 ALLOW_HIGHER_LIMIT_FOR_MAX_CPU_TIME_OF_ONE_TX(65), // 0, 1 ALLOW_ASSET_OPTIMIZATION(66), // 0, 1 - ALLOW_NEW_REWARD_ALGO(67); // 0, 1 + ALLOW_NEW_REWARD_ALGO(67), // 0, 1 + MEMO_FEE(68); // 0, [1, 1000_000_000] private long code; diff --git a/chainbase/src/main/java/org/tron/core/capsule/ReceiptCapsule.java b/chainbase/src/main/java/org/tron/core/capsule/ReceiptCapsule.java index 3124494c67e..30fd1f7ecd3 100644 --- a/chainbase/src/main/java/org/tron/core/capsule/ReceiptCapsule.java +++ b/chainbase/src/main/java/org/tron/core/capsule/ReceiptCapsule.java @@ -24,6 +24,9 @@ public class ReceiptCapsule { @Setter private long multiSignFee; + @Getter + @Setter + private long memoFee; /** * Available energy of contract deployer before executing transaction */ diff --git a/chainbase/src/main/java/org/tron/core/capsule/utils/TransactionUtil.java b/chainbase/src/main/java/org/tron/core/capsule/utils/TransactionUtil.java index a2c3facd565..fc164bb9e36 100644 --- a/chainbase/src/main/java/org/tron/core/capsule/utils/TransactionUtil.java +++ b/chainbase/src/main/java/org/tron/core/capsule/utils/TransactionUtil.java @@ -72,9 +72,9 @@ public static TransactionInfoCapsule buildTransactionInfoInstance(TransactionCap } builder.setId(ByteString.copyFrom(trxCap.getTransactionId().getBytes())); ProgramResult programResult = trace.getRuntimeResult(); - long fee = - programResult.getRet().getFee() + traceReceipt.getEnergyFee() - + traceReceipt.getNetFee() + traceReceipt.getMultiSignFee(); + long fee = programResult.getRet().getFee() + traceReceipt.getEnergyFee() + + traceReceipt.getNetFee() + traceReceipt.getMultiSignFee() + + traceReceipt.getMemoFee(); boolean supportTransactionFeePool = trace.getTransactionContext().getStoreFactory() .getChainBaseManager().getDynamicPropertiesStore().supportTransactionFeePool(); diff --git a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java index df37059f2ad..9f4c9444cbe 100644 --- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java @@ -187,6 +187,9 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking private static final byte[] ALLOW_HIGHER_LIMIT_FOR_MAX_CPU_TIME_OF_ONE_TX = "ALLOW_HIGHER_LIMIT_FOR_MAX_CPU_TIME_OF_ONE_TX".getBytes(); + private static final byte[] MEMO_FEE = "MEMO_FEE".getBytes(); + private static final byte[] MEMO_FEE_HISTORY = "MEMO_FEE_HISTORY".getBytes(); + @Autowired private DynamicPropertiesStore(@Value("properties") String dbName) { super(dbName); @@ -857,6 +860,14 @@ private DynamicPropertiesStore(@Value("properties") String dbName) { new BytesCapsule(ByteArray.fromLong(Long.MAX_VALUE))); } } + + try { + this.getMemoFee(); + } catch (IllegalArgumentException e) { + long memoFee = CommonParameter.getInstance().getMemoFee(); + this.saveMemoFee(memoFee); + this.saveMemoFeeHistory("0:" + memoFee); + } } public String intArrayToString(int[] a) { @@ -2528,6 +2539,28 @@ public long getAllowHigherLimitForMaxCpuTimeOfOneTx() { () -> new IllegalArgumentException(msg)); } + public long getMemoFee() { + return Optional.ofNullable(getUnchecked(MEMO_FEE)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElseThrow(() -> new IllegalArgumentException("not found MEMO_FEE")); + } + + public void saveMemoFee(long value) { + this.put(MEMO_FEE, new BytesCapsule(ByteArray.fromLong(value))); + } + + public String getMemoFeeHistory() { + return Optional.ofNullable(getUnchecked(MEMO_FEE_HISTORY)) + .map(BytesCapsule::getData) + .map(ByteArray::toStr) + .orElseThrow(() -> new IllegalArgumentException("not found MEMO_FEE_HISTORY")); + } + + public void saveMemoFeeHistory(String value) { + this.put(MEMO_FEE_HISTORY, new BytesCapsule(ByteArray.fromString(value))); + } + private static class DynamicResourceProperties { private static final byte[] ONE_DAY_NET_LIMIT = "ONE_DAY_NET_LIMIT".getBytes(); diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index 26aa7d44834..0331628d53c 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -561,6 +561,10 @@ public class CommonParameter { @Setter public long allowNewRewardAlgorithm; + @Getter + @Setter + public long memoFee = 0L; + private static double calcMaxTimeRatio() { //return max(2.0, min(5.0, 5 * 4.0 / max(Runtime.getRuntime().availableProcessors(), 1))); return 5.0; diff --git a/common/src/main/java/org/tron/core/Constant.java b/common/src/main/java/org/tron/core/Constant.java index af96fbea859..aa287b1d885 100644 --- a/common/src/main/java/org/tron/core/Constant.java +++ b/common/src/main/java/org/tron/core/Constant.java @@ -307,6 +307,7 @@ public class Constant { public static final String ALLOW_ACCOUNT_ASSET_OPTIMIZATION = "committee.allowAccountAssetOptimization"; public static final String ALLOW_ASSET_OPTIMIZATION = "committee.allowAssetOptimization"; + public static final String MEMO_FEE = "committee.memoFee"; public static final String LOCAL_HOST = "127.0.0.1"; diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 33bd411c28f..5f77a651ea0 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -1103,6 +1103,11 @@ public Protocol.ChainParameters getChainParameters() { .setValue(dbManager.getDynamicPropertiesStore().useNewRewardAlgorithm() ? 1 : 0) .build()); + builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder() + .setKey("getMemoFee") + .setValue(dbManager.getDynamicPropertiesStore().getMemoFee()) + .build()); + return builder.build(); } @@ -4080,5 +4085,13 @@ public Block getBlock(GrpcAPI.BlockReq request) { return block.toBuilder().clearTransactions().build(); } + public String getMemoFeePrices() { + try { + return chainBaseManager.getDynamicPropertiesStore().getMemoFeeHistory(); + } catch (Exception e) { + logger.error("getMemoFeePrices failed, error is {}", e.getMessage()); + } + return null; + } } diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index b75664ed3c1..26a3250a369 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -1034,6 +1034,16 @@ public static void setParam(final String[] args, final String confFileName) { PARAMETER.blockCacheTimeout = config.getLong(Constant.BLOCK_CACHE_TIMEOUT); } + if (config.hasPath(Constant.MEMO_FEE)) { + PARAMETER.memoFee = config.getLong(Constant.MEMO_FEE); + if (PARAMETER.memoFee > 1_000_000_000) { + PARAMETER.memoFee = 1_000_000_000; + } + if (PARAMETER.memoFee < 0) { + PARAMETER.memoFee = 0; + } + } + logConfig(); } diff --git a/framework/src/main/java/org/tron/core/consensus/ProposalService.java b/framework/src/main/java/org/tron/core/consensus/ProposalService.java index 92472335d20..0d2ed945f42 100644 --- a/framework/src/main/java/org/tron/core/consensus/ProposalService.java +++ b/framework/src/main/java/org/tron/core/consensus/ProposalService.java @@ -288,6 +288,14 @@ public static boolean process(Manager manager, ProposalCapsule proposalCapsule) manager.getDynamicPropertiesStore().saveNewRewardAlgorithmEffectiveCycle(); break; } + case MEMO_FEE: { + manager.getDynamicPropertiesStore().saveMemoFee(entry.getValue()); + // update memo fee history + manager.getDynamicPropertiesStore().saveMemoFeeHistory( + manager.getDynamicPropertiesStore().getMemoFeeHistory() + + "," + proposalCapsule.getExpirationTime() + ":" + entry.getValue()); + break; + } default: find = false; break; diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index 80be86403ba..fa75deafb3f 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -855,6 +855,42 @@ public void consumeMultiSignFee(TransactionCapsule trx, TransactionTrace trace) } } + public void consumeMemoFee(TransactionCapsule trx, TransactionTrace trace) + throws AccountResourceInsufficientException { + if (trx.getInstance().getRawData().getData().isEmpty()) { + // no memo + return; + } + + long fee = getDynamicPropertiesStore().getMemoFee(); + if (fee == 0) { + return; + } + + List contracts = trx.getInstance().getRawData().getContractList(); + for (Contract contract : contracts) { + byte[] address = TransactionCapsule.getOwner(contract); + AccountCapsule accountCapsule = getAccountStore().get(address); + try { + if (accountCapsule != null) { + adjustBalance(getAccountStore(), accountCapsule, -fee); + + if (getDynamicPropertiesStore().supportBlackHoleOptimization()) { + getDynamicPropertiesStore().burnTrx(fee); + } else { + adjustBalance(getAccountStore(), this.getAccountStore().getBlackhole(), +fee); + } + } + } catch (BalanceInsufficientException e) { + throw new AccountResourceInsufficientException( + String.format("account %s insufficient balance[%d] to memo fee", + StringUtil.encode58Check(address), fee)); + } + } + + trace.getReceipt().setMemoFee(fee); + } + public void consumeBandwidth(TransactionCapsule trx, TransactionTrace trace) throws ContractValidateException, AccountResourceInsufficientException, TooBigTransactionResultException { @@ -1338,6 +1374,7 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block consumeBandwidth(trxCap, trace); consumeMultiSignFee(trxCap, trace); + consumeMemoFee(trxCap, trace); trace.init(blockCap, eventPluginLoaded); trace.checkIsConstant(); diff --git a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java index 031bd38e50c..30c0b380551 100644 --- a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java +++ b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java @@ -293,6 +293,8 @@ public class FullNodeHttpApiService implements Service { private GetBandwidthPricesServlet getBandwidthPricesServlet; @Autowired private GetBlockServlet getBlockServlet; + @Autowired + private GetMemoFeePricesServlet getMemoFeePricesServlet; private static String getParamsFile(String fileName) { InputStream in = Thread.currentThread().getContextClassLoader() @@ -542,6 +544,7 @@ public void start() { context.addServlet(new ServletHolder(getBandwidthPricesServlet), "/wallet/getbandwidthprices"); context.addServlet(new ServletHolder(getBlockServlet), "/wallet/getblock"); + context.addServlet(new ServletHolder(getMemoFeePricesServlet), "/wallet/getmemofee"); int maxHttpConnectNumber = Args.getInstance().getMaxHttpConnectNumber(); if (maxHttpConnectNumber > 0) { diff --git a/framework/src/main/java/org/tron/core/services/http/GetMemoFeePricesServlet.java b/framework/src/main/java/org/tron/core/services/http/GetMemoFeePricesServlet.java new file mode 100644 index 00000000000..8d5f46d8236 --- /dev/null +++ b/framework/src/main/java/org/tron/core/services/http/GetMemoFeePricesServlet.java @@ -0,0 +1,37 @@ +package org.tron.core.services.http; + +import com.alibaba.fastjson.JSONObject; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.tron.core.Wallet; + + +@Component +@Slf4j(topic = "API") +public class GetMemoFeePricesServlet extends RateLimiterServlet { + + @Autowired + private Wallet wallet; + + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + String reply = wallet.getMemoFeePrices(); + if (reply != null) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("prices", reply); + response.getWriter().println(jsonObject); + } else { + response.getWriter().println("{}"); + } + } catch (Exception e) { + Util.processError(e, response); + } + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + doGet(request, response); + } +}