Skip to content
38 changes: 30 additions & 8 deletions chainbase/src/main/java/org/tron/core/ChainBaseManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.storage.metric.DbStatService;
import org.tron.common.utils.ForkController;
import org.tron.common.utils.Sha256Hash;
Expand Down Expand Up @@ -244,10 +246,38 @@ public class ChainBaseManager {
@Setter
private long lowestBlockNum = -1; // except num = 0.

// first block of transactionRetStore; head + 1 when the store is empty,
// Long.MAX_VALUE when receipts are not persisted
@Getter
private long lowestBlockNumOfReceiptStore = -1;

@Getter
@Setter
private long latestSaveBlockTime;

@PostConstruct
private void init() {
this.lowestBlockNum = this.blockIndexStore.getLimitNumber(1, 1).stream()
.map(BlockId::getNum).findFirst().orElse(0L);
this.nodeType = getLowestBlockNum() > 1 ? NodeType.LITE : NodeType.FULL;
this.latestSaveBlockTime = System.currentTimeMillis();
}

/**
* Reads the lowest receipt block from the store itself, not from snapshot metadata; an empty
* store means receipts begin with the next executed block. With receipt persistence off
* the store never grows, so no lower bound exists. Must run after checkpoint recovery (so the
* last session's tail is visible) and before any session is built ({@code getNext} does
* not merge in-flight layers).
*/
public void initLowestBlockNumOfReceiptStore() {
boolean persistReceipts = BooleanUtils.toBoolean(CommonParameter.getInstance()
.getStorage().getTransactionHistorySwitch());
this.lowestBlockNumOfReceiptStore = persistReceipts
? this.transactionRetStore.getLowestBlockNum().orElseGet(() -> getHeadBlockNum() + 1)
: Long.MAX_VALUE;
}

// for test only
public List<ByteString> getWitnesses() {
return witnessScheduleStore.getActiveWitnesses();
Expand Down Expand Up @@ -389,14 +419,6 @@ public long getNextBlockSlotTime() {
+ slotCount * BLOCK_PRODUCED_INTERVAL;
}

@PostConstruct
private void init() {
this.lowestBlockNum = this.blockIndexStore.getLimitNumber(1, 1).stream()
.map(BlockId::getNum).findFirst().orElse(0L);
this.nodeType = getLowestBlockNum() > 1 ? NodeType.LITE : NodeType.FULL;
this.latestSaveBlockTime = System.currentTimeMillis();
}

public void shutdown() {
dbStatService.shutdown();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ public void setErrorCode(Result.code code) {
this.transactionResult = this.transactionResult.toBuilder().setRet(code).build();
}

public void setResultCode(contractResult code) {
this.transactionResult = this.transactionResult.toBuilder().setContractRet(code).build();
}

public long getShieldedTransactionFee() {
return transactionResult.getShieldedTransactionFee();
}
Expand Down Expand Up @@ -184,4 +188,4 @@ public byte[] getData() {
public Result getInstance() {
return this.transactionResult;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.tron.core.store;

import com.google.common.primitives.Longs;
import com.google.protobuf.ByteString;
import java.util.Objects;
import java.util.OptionalLong;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -37,6 +39,18 @@ public void put(byte[] key, TransactionRetCapsule item) {
}
}

/**
* Lowest block number that has receipts, or empty when the store has none. On a LiteNode
* this is generally above the lowest block: a snapshot ships block bodies but no receipts.
*
* <p>Startup use only — must run before any session is built. With in-flight snapshot
* layers, {@code getNext} does not merge deletions correctly.
*/
public OptionalLong getLowestBlockNum() {
return revokingDB.getNext(ByteArray.fromLong(0), 1).keySet().stream()
.mapToLong(Longs::fromByteArray).findFirst();
}

public TransactionInfoCapsule getTransactionInfo(byte[] key) throws BadItemException {
long blockNumber = transactionStore.getBlockNumber(key);
if (blockNumber == -1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.tron.core.exception.jsonrpc;

public class JsonRpcExecutionRevertedException extends JsonRpcException {

public JsonRpcExecutionRevertedException(String message) {
super(message);
}

public JsonRpcExecutionRevertedException(String message, Object data) {
super(message, data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.tron.core.exception.jsonrpc;

/**
* Thrown when a request targets data the node does not have: history pruned on a LiteNode,
* or receipts and logs on a node that does not persist them. Maps to JSON-RPC error code 4444
* "Pruned history unavailable", as standardized by the Ethereum Execution API (EIP-4444).
*/
public class JsonRpcPrunedHistoryException extends JsonRpcException {

public JsonRpcPrunedHistoryException(String message) {
super(message);
}

public JsonRpcPrunedHistoryException(String message, Object data) {
super(message, data);
}
}
14 changes: 14 additions & 0 deletions framework/src/main/java/org/tron/core/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@
import org.tron.protos.Protocol.Transaction.Contract;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.Protocol.Transaction.Result.code;
import org.tron.protos.Protocol.Transaction.Result.contractResult;
import org.tron.protos.Protocol.TransactionInfo;
import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract;
import org.tron.protos.contract.BalanceContract;
Expand Down Expand Up @@ -737,6 +738,18 @@ public long getHeadBlockNum() {
return chainBaseManager.getHeadBlockNum();
}

public boolean isLiteNode() {
return chainBaseManager.isLiteNode();
}

public long getLowestBlockNum() {
return chainBaseManager.getLowestBlockNum();
}

public long getLowestBlockNumOfReceiptStore() {
return chainBaseManager.getLowestBlockNumOfReceiptStore();
}

public BlockCapsule getBlockCapsuleByNum(long blockNum) {
try {
return chainBaseManager.getBlockByNum(blockNum);
Expand Down Expand Up @@ -3192,6 +3205,7 @@ public Transaction callConstantContract(TransactionCapsule trxCap,
}
if (result.isRevert()) {
ret.setStatus(0, code.FAILED);
ret.setResultCode(contractResult.REVERT);
retBuilder.setMessage(ByteString.copyFromUtf8("REVERT opcode executed"))
.build();
}
Expand Down
1 change: 1 addition & 0 deletions framework/src/main/java/org/tron/core/db/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ public void init() {
trieService.setChainBaseManager(chainBaseManager);
revokingStore.disable();
revokingStore.check();
chainBaseManager.initLowestBlockNumOfReceiptStore();
transactionCache.initCache();
rewardViCalService.init();
this.setProposalController(ProposalController.createInstance(this));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.tron.common.utils.StringUtil;
import org.tron.core.Wallet;
import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.exception.jsonrpc.JsonRpcPrunedHistoryException;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.Transaction;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
Expand Down Expand Up @@ -62,6 +63,7 @@ public class JsonRpcApiUtil {
public static final String TAG_PENDING_SUPPORT_ERROR = "TAG pending not supported";
public static final String TAG_SAFE_SUPPORT_ERROR = "TAG safe not supported";
public static final String BLOCK_NUM_ERROR = "invalid block number";
public static final String PRUNED_HISTORY_ERROR = "Pruned history unavailable";
public static final String TX_INDEX_ERROR = "invalid index value";

private static final SecureRandom random = new SecureRandom();
Expand Down Expand Up @@ -636,7 +638,7 @@ public static long parseBlockTag(String tag, Wallet wallet)
return wallet.getHeadBlockNum();
}
if (EARLIEST_STR.equalsIgnoreCase(tag)) {
return 0;
return resolveEarliestBlockNum(wallet);
}
if (FINALIZED_STR.equalsIgnoreCase(tag)) {
return wallet.getSolidBlockNum();
Expand Down Expand Up @@ -700,6 +702,47 @@ public static long parseBlockNumber(String blockNumOrTag, Wallet wallet)
return parseBlockNumber(blockNumOrTag);
}

/**
* The lowest block for which everything the node persists is available; what "earliest"
* resolves to. On a LiteNode this is the
* lowest receipt block when receipts are persisted, otherwise the lowest block (receipt endpoints
* answer 4444 on such a node regardless of this value). On a FullNode it is genesis.
*/
public static long resolveEarliestBlockNum(Wallet wallet) {
if (!wallet.isLiteNode()) {
return 0;
}
long lowestReceiptBlockNum = wallet.getLowestBlockNumOfReceiptStore();
return lowestReceiptBlockNum == Long.MAX_VALUE
? wallet.getLowestBlockNum() : lowestReceiptBlockNum;
}

/**
* Rejects a query for a block below the LiteNode pruning cutoff with error code 4444.
* Raw primitive — no genesis exemption; callers own that semantics.
*/
public static void checkPrunedBlockHistory(long blockNum, Wallet wallet)
throws JsonRpcPrunedHistoryException {
if (wallet.isLiteNode() && blockNum < wallet.getLowestBlockNum()) {
throw new JsonRpcPrunedHistoryException(PRUNED_HISTORY_ERROR);
}
}

/**
* Receipt form of {@link #checkPrunedBlockHistory(long, Wallet)} for endpoints that read
* receipts or logs; their lower bound is the lowest receipt block. Same raw-primitive
* contract. Receipt persistence is a per-node switch independent of node type, so a node
* that never persists receipts is rejected before the LiteNode gate.
*/
public static void checkPrunedReceiptHistory(long blockNum, Wallet wallet)
throws JsonRpcPrunedHistoryException {
long lowestReceiptBlockNum = wallet.getLowestBlockNumOfReceiptStore();
if (lowestReceiptBlockNum == Long.MAX_VALUE
|| (wallet.isLiteNode() && blockNum < lowestReceiptBlockNum)) {
throw new JsonRpcPrunedHistoryException(PRUNED_HISTORY_ERROR);
}
}

/**
* Max hex digits of a 32-bit int (0x7FFFFFFF). A transaction index fits a signed int, so the
* longest valid input is "0x" + 8 hex digits; the +2 in the guard covers the prefix.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import org.tron.core.exception.BadItemException;
import org.tron.core.exception.ItemNotFoundException;
import org.tron.core.exception.jsonrpc.JsonRpcExceedLimitException;
import org.tron.core.exception.jsonrpc.JsonRpcExecutionRevertedException;
import org.tron.core.exception.jsonrpc.JsonRpcInternalException;
import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
import org.tron.core.exception.jsonrpc.JsonRpcMethodNotFoundException;
import org.tron.core.exception.jsonrpc.JsonRpcPrunedHistoryException;
import org.tron.core.exception.jsonrpc.JsonRpcTooManyResultException;
import org.tron.core.services.jsonrpc.types.BlockResult;
import org.tron.core.services.jsonrpc.types.BuildArguments;
Expand Down Expand Up @@ -55,8 +57,10 @@ public interface TronJsonRpc {
@JsonRpcMethod("eth_getBlockTransactionCountByNumber")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcPrunedHistoryException.class, code = 4444, data = "{}"),
})
String ethGetBlockTransactionCountByNumber(String bnOrId) throws JsonRpcInvalidParamsException;
String ethGetBlockTransactionCountByNumber(String bnOrId)
throws JsonRpcInvalidParamsException, JsonRpcPrunedHistoryException;

@JsonRpcMethod("eth_getBlockByHash")
@JsonRpcErrors({
Expand All @@ -68,9 +72,10 @@ BlockResult ethGetBlockByHash(String blockHash, Boolean fullTransactionObjects)
@JsonRpcMethod("eth_getBlockByNumber")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcPrunedHistoryException.class, code = 4444, data = "{}"),
})
BlockResult ethGetBlockByNumber(String bnOrId, Boolean fullTransactionObjects)
throws JsonRpcInvalidParamsException;
throws JsonRpcInvalidParamsException, JsonRpcPrunedHistoryException;

@JsonRpcMethod("net_version")
String getNetVersion() throws JsonRpcInternalException;
Expand Down Expand Up @@ -120,10 +125,12 @@ String getABIOfSmartContract(String contractAddress, String bnOrId)
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidRequestException.class, code = -32600, data = "{}"),
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcExecutionRevertedException.class, code = 3, data = "{}"),
@JsonRpcError(exception = JsonRpcInternalException.class, code = -32000, data = "{}"),
})
String estimateGas(CallArguments args) throws JsonRpcInvalidRequestException,
JsonRpcInvalidParamsException, JsonRpcInternalException;
JsonRpcInvalidParamsException, JsonRpcInternalException,
JsonRpcExecutionRevertedException;

@JsonRpcMethod("eth_getTransactionByHash")
@JsonRpcErrors({
Expand All @@ -141,9 +148,10 @@ TransactionResult getTransactionByBlockHashAndIndex(String blockHash, String ind
@JsonRpcMethod("eth_getTransactionByBlockNumberAndIndex")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcPrunedHistoryException.class, code = 4444, data = "{}"),
})
TransactionResult getTransactionByBlockNumberAndIndex(String blockNumOrTag, String index)
throws JsonRpcInvalidParamsException;
throws JsonRpcInvalidParamsException, JsonRpcPrunedHistoryException;

@JsonRpcMethod("eth_getTransactionReceipt")
@JsonRpcErrors({
Expand All @@ -154,20 +162,23 @@ TransactionResult getTransactionByBlockNumberAndIndex(String blockNumOrTag, Stri
@JsonRpcMethod("eth_getBlockReceipts")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcPrunedHistoryException.class, code = 4444, data = "{}"),
@JsonRpcError(exception = JsonRpcInternalException.class, code = -32000, data = "{}")
})
List<TransactionReceipt> getBlockReceipts(String blockNumOrHashOrTag)
throws JsonRpcInvalidParamsException, JsonRpcInternalException;
throws JsonRpcInvalidParamsException, JsonRpcInternalException,
JsonRpcPrunedHistoryException;

@JsonRpcMethod("eth_call")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidRequestException.class, code = -32600, data = "{}"),
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcExecutionRevertedException.class, code = 3, data = "{}"),
@JsonRpcError(exception = JsonRpcInternalException.class, code = -32000, data = "{}"),
})
String getCall(CallArguments transactionCall, Object blockNumOrTag)
throws JsonRpcInvalidParamsException, JsonRpcInvalidRequestException,
JsonRpcInternalException;
JsonRpcInternalException, JsonRpcExecutionRevertedException;

@JsonRpcMethod("net_peerCount")
String getPeerCount();
Expand Down Expand Up @@ -292,9 +303,10 @@ CompilationResult ethSubmitHashrate(String hashrate, String id)
@JsonRpcError(exception = JsonRpcMethodNotFoundException.class, code = -32601, data = "{}"),
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcExceedLimitException.class, code = -32005, data = "{}"),
@JsonRpcError(exception = JsonRpcPrunedHistoryException.class, code = 4444, data = "{}"),
})
String newFilter(FilterRequest fr) throws JsonRpcInvalidParamsException,
JsonRpcMethodNotFoundException, JsonRpcExceedLimitException;
JsonRpcMethodNotFoundException, JsonRpcExceedLimitException, JsonRpcPrunedHistoryException;

@JsonRpcMethod("eth_newBlockFilter")
@JsonRpcErrors({
Expand Down Expand Up @@ -327,14 +339,16 @@ Object[] getFilterChanges(String filterId)
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
@JsonRpcError(exception = JsonRpcMethodNotFoundException.class, code = -32601, data = "{}"),
@JsonRpcError(exception = JsonRpcTooManyResultException.class, code = -32005, data = "{}"),
@JsonRpcError(exception = JsonRpcPrunedHistoryException.class, code = 4444, data = "{}"),
@JsonRpcError(exception = BadItemException.class, code = -32000, data = "{}"),
@JsonRpcError(exception = ExecutionException.class, code = -32000, data = "{}"),
@JsonRpcError(exception = InterruptedException.class, code = -32000, data = "{}"),
@JsonRpcError(exception = ItemNotFoundException.class, code = -32000, data = "{}"),
})
LogFilterElement[] getLogs(FilterRequest fr) throws JsonRpcInvalidParamsException,
ExecutionException, InterruptedException, BadItemException, ItemNotFoundException,
JsonRpcMethodNotFoundException, JsonRpcTooManyResultException;
JsonRpcMethodNotFoundException, JsonRpcTooManyResultException,
JsonRpcPrunedHistoryException;

@JsonRpcMethod("eth_getFilterLogs")
@JsonRpcErrors({
Expand Down
Loading
Loading