TransactionAdapter.java
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLContextType;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nonnull;
import graphql.schema.DataFetchingEnvironment;
import org.apache.tuweni.bytes.Bytes;
@SuppressWarnings("unused") // reflected by GraphQL
public class TransactionAdapter extends AdapterBase {
private final TransactionWithMetadata transactionWithMetadata;
private Optional<TransactionReceiptWithMetadata> transactionReceiptWithMetadata;
public TransactionAdapter(final @Nonnull TransactionWithMetadata transactionWithMetadata) {
this.transactionWithMetadata = transactionWithMetadata;
}
private Optional<TransactionReceiptWithMetadata> getReceipt(
final DataFetchingEnvironment environment) {
if (transactionReceiptWithMetadata == null) {
final BlockchainQueries query = getBlockchainQueries(environment);
final ProtocolSchedule protocolSchedule =
environment.getGraphQlContext().get(GraphQLContextType.PROTOCOL_SCHEDULE);
final Transaction transaction = transactionWithMetadata.getTransaction();
if (transaction == null) {
transactionReceiptWithMetadata = Optional.empty();
} else {
transactionReceiptWithMetadata =
query.transactionReceiptByTransactionHash(transaction.getHash(), protocolSchedule);
}
}
return transactionReceiptWithMetadata;
}
public Hash getHash() {
return transactionWithMetadata.getTransaction().getHash();
}
public Optional<Integer> getType() {
return Optional.of(transactionWithMetadata.getTransaction().getType().ordinal());
}
public Long getNonce() {
return transactionWithMetadata.getTransaction().getNonce();
}
public Optional<Integer> getIndex() {
return transactionWithMetadata.getTransactionIndex();
}
public AccountAdapter getFrom(final DataFetchingEnvironment environment) {
final BlockchainQueries query = getBlockchainQueries(environment);
final Long blockNumber =
Optional.<Long>ofNullable(environment.getArgument("block"))
.or(transactionWithMetadata::getBlockNumber)
.orElseGet(query::headBlockNumber);
final Address addr = transactionWithMetadata.getTransaction().getSender();
return query
.getAndMapWorldState(
blockNumber,
mutableWorldState -> Optional.of(new AccountAdapter(mutableWorldState.get(addr))))
.orElse(new EmptyAccountAdapter(addr));
}
public Optional<AccountAdapter> getTo(final DataFetchingEnvironment environment) {
final BlockchainQueries query = getBlockchainQueries(environment);
final Long blockNumber =
Optional.<Long>ofNullable(environment.getArgument("block"))
.or(transactionWithMetadata::getBlockNumber)
.orElseGet(query::headBlockNumber);
return transactionWithMetadata
.getTransaction()
.getTo()
.flatMap(
address ->
query
.getAndMapWorldState(
blockNumber,
ws -> Optional.of(new AccountAdapter(address, ws.get(address))))
.or(() -> Optional.of(new EmptyAccountAdapter(address))));
}
public Wei getValue() {
return transactionWithMetadata.getTransaction().getValue();
}
public Wei getGasPrice() {
return transactionWithMetadata.getTransaction().getGasPrice().orElse(Wei.ZERO);
}
public Optional<Wei> getMaxFeePerGas() {
return transactionWithMetadata.getTransaction().getMaxFeePerGas();
}
public Optional<Wei> getMaxPriorityFeePerGas() {
return transactionWithMetadata.getTransaction().getMaxPriorityFeePerGas();
}
public Optional<Wei> getMaxFeePerBlobGas() {
return transactionWithMetadata.getTransaction().getMaxFeePerBlobGas();
}
public Optional<Wei> getEffectiveTip(final DataFetchingEnvironment environment) {
return getReceipt(environment)
.map(rwm -> rwm.getTransaction().getEffectivePriorityFeePerGas(rwm.getBaseFee()));
}
public Long getGas() {
return transactionWithMetadata.getTransaction().getGasLimit();
}
public Bytes getInputData() {
return transactionWithMetadata.getTransaction().getPayload();
}
public Optional<NormalBlockAdapter> getBlock(final DataFetchingEnvironment environment) {
return transactionWithMetadata
.getBlockHash()
.flatMap(blockHash -> getBlockchainQueries(environment).blockByHash(blockHash))
.map(NormalBlockAdapter::new);
}
public Optional<Long> getStatus(final DataFetchingEnvironment environment) {
return getReceipt(environment)
.map(TransactionReceiptWithMetadata::getReceipt)
.flatMap(
receipt ->
receipt.getStatus() == -1
? Optional.empty()
: Optional.of((long) receipt.getStatus()));
}
public Optional<Long> getGasUsed(final DataFetchingEnvironment environment) {
return getReceipt(environment).map(TransactionReceiptWithMetadata::getGasUsed);
}
public Optional<Long> getCumulativeGasUsed(final DataFetchingEnvironment environment) {
return getReceipt(environment).map(rpt -> rpt.getReceipt().getCumulativeGasUsed());
}
public Optional<Wei> getEffectiveGasPrice(final DataFetchingEnvironment environment) {
return getReceipt(environment)
.map(rwm -> rwm.getTransaction().getEffectiveGasPrice(rwm.getBaseFee()));
}
public Optional<Long> getBlobGasUsed(final DataFetchingEnvironment environment) {
return getReceipt(environment).flatMap(TransactionReceiptWithMetadata::getBlobGasUsed);
}
public Optional<Wei> getBlobGasPrice(final DataFetchingEnvironment environment) {
return getReceipt(environment).flatMap(TransactionReceiptWithMetadata::getBlobGasPrice);
}
public Optional<AccountAdapter> getCreatedContract(final DataFetchingEnvironment environment) {
final boolean contractCreated = transactionWithMetadata.getTransaction().isContractCreation();
if (contractCreated) {
final Optional<Address> addr = transactionWithMetadata.getTransaction().contractAddress();
if (addr.isPresent()) {
final BlockchainQueries query = getBlockchainQueries(environment);
final Optional<Long> txBlockNumber = transactionWithMetadata.getBlockNumber();
final Optional<Long> bn = Optional.ofNullable(environment.getArgument("block"));
if (txBlockNumber.isEmpty() && bn.isEmpty()) {
return Optional.empty();
}
final long blockNumber = bn.orElseGet(txBlockNumber::get);
return query
.getAndMapWorldState(
blockNumber, ws -> Optional.of(new AccountAdapter(ws.get(addr.get()))))
.or(() -> Optional.of(new EmptyAccountAdapter(addr.get())));
}
}
return Optional.empty();
}
public List<LogAdapter> getLogs(final DataFetchingEnvironment environment) {
final BlockchainQueries query = getBlockchainQueries(environment);
final ProtocolSchedule protocolSchedule =
environment.getGraphQlContext().get(GraphQLContextType.PROTOCOL_SCHEDULE);
final Hash hash = transactionWithMetadata.getTransaction().getHash();
final Optional<BlockHeader> maybeBlockHeader =
transactionWithMetadata.getBlockNumber().flatMap(query::getBlockHeaderByNumber);
if (maybeBlockHeader.isEmpty()) {
throw new RuntimeException(
"Cannot get block ("
+ transactionWithMetadata.getBlockNumber()
+ ") for transaction "
+ transactionWithMetadata.getTransaction().getHash());
}
final Optional<TransactionReceiptWithMetadata> maybeTransactionReceiptWithMetadata =
query.transactionReceiptByTransactionHash(hash, protocolSchedule);
final List<LogAdapter> results = new ArrayList<>();
if (maybeTransactionReceiptWithMetadata.isPresent()) {
final List<LogWithMetadata> logs =
query.matchingLogs(
maybeBlockHeader.get().getBlockHash(), transactionWithMetadata, () -> true);
for (final LogWithMetadata log : logs) {
results.add(new LogAdapter(log));
}
}
return results;
}
public BigInteger getR() {
return transactionWithMetadata.getTransaction().getR();
}
public BigInteger getS() {
return transactionWithMetadata.getTransaction().getS();
}
public Optional<BigInteger> getV() {
return Optional.ofNullable(transactionWithMetadata.getTransaction().getV());
}
public Optional<BigInteger> getYParity() {
return Optional.ofNullable(transactionWithMetadata.getTransaction().getYParity());
}
public List<AccessListEntryAdapter> getAccessList() {
return transactionWithMetadata
.getTransaction()
.getAccessList()
.map(l -> l.stream().map(AccessListEntryAdapter::new).toList())
.orElse(List.of());
}
public Optional<Bytes> getRaw() {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
transactionWithMetadata.getTransaction().writeTo(rlpOutput);
return Optional.of(rlpOutput.encoded());
}
public Optional<Bytes> getRawReceipt(final DataFetchingEnvironment environment) {
return getReceipt(environment)
.map(
receipt -> {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
receipt.getReceipt().writeToForNetwork(rlpOutput);
return rlpOutput.encoded();
});
}
public List<VersionedHash> getBlobVersionedHashes() {
return transactionWithMetadata.getTransaction().getVersionedHashes().orElse(List.of());
}
}