Skip to content

Commit

Permalink
Dedup allowances in crypto approve allowance transaction handler (#3663)
Browse files Browse the repository at this point in the history
Signed-off-by: Xin Li <xin.li@hedera.com>
  • Loading branch information
xin-hedera committed Apr 27, 2022
1 parent 952a277 commit 0b5a739
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import static com.hedera.mirror.importer.parser.PartialDataAction.ERROR;

import com.hederahashgraph.api.proto.java.AccountID;
import java.util.HashMap;
import java.util.List;
import javax.inject.Named;
import lombok.RequiredArgsConstructor;

Expand All @@ -32,6 +34,7 @@
import com.hedera.mirror.common.domain.entity.NftAllowance;
import com.hedera.mirror.common.domain.entity.TokenAllowance;
import com.hedera.mirror.common.domain.token.Nft;
import com.hedera.mirror.common.domain.token.NftId;
import com.hedera.mirror.common.domain.transaction.RecordItem;
import com.hedera.mirror.common.domain.transaction.Transaction;
import com.hedera.mirror.common.domain.transaction.TransactionType;
Expand Down Expand Up @@ -66,12 +69,22 @@ public void updateTransaction(Transaction transaction, RecordItem recordItem) {
return;
}

long consensusTimestamp = transaction.getConsensusTimestamp();
var payerAccountId = recordItem.getPayerAccountId();

var transactionBody = recordItem.getTransactionBody().getCryptoApproveAllowance();
parseCryptoAllowances(transactionBody.getCryptoAllowancesList(), recordItem);
parseNftAllowances(transactionBody.getNftAllowancesList(), recordItem);
parseTokenAllowances(transactionBody.getTokenAllowancesList(), recordItem);
}

private void parseCryptoAllowances(List<com.hederahashgraph.api.proto.java.CryptoAllowance> cryptoAllowances,
RecordItem recordItem) {
var consensusTimestamp = recordItem.getConsensusTimestamp();
var cryptoAllowanceState = new HashMap<CryptoAllowance.Id, CryptoAllowance>();
var payerAccountId = recordItem.getPayerAccountId();

for (var cryptoApproval : transactionBody.getCryptoAllowancesList()) {
// iterate the crypto allowance list in reverse order and honor the last allowance for the same owner and spender
var iterator = cryptoAllowances.listIterator(cryptoAllowances.size());
while (iterator.hasPrevious()) {
var cryptoApproval = iterator.previous();
EntityId ownerAccountId = getOwnerAccountId(cryptoApproval.getOwner(), payerAccountId);
if (ownerAccountId == EntityId.EMPTY) {
// ownerAccountId will be EMPTY only when getOwnerAccountId fails to resolve the owner in the alias form
Expand All @@ -85,10 +98,26 @@ public void updateTransaction(Transaction transaction, RecordItem recordItem) {
cryptoAllowance.setPayerAccountId(payerAccountId);
cryptoAllowance.setSpender(EntityId.of(cryptoApproval.getSpender()).getId());
cryptoAllowance.setTimestampLower(consensusTimestamp);
entityListener.onCryptoAllowance(cryptoAllowance);

if (cryptoAllowanceState.putIfAbsent(cryptoAllowance.getId(), cryptoAllowance) == null) {
entityListener.onCryptoAllowance(cryptoAllowance);
}
}
}

for (var nftApproval : transactionBody.getNftAllowancesList()) {
private void parseNftAllowances(List<com.hederahashgraph.api.proto.java.NftAllowance> nftAllowances,
RecordItem recordItem) {
var consensusTimestamp = recordItem.getConsensusTimestamp();
var payerAccountId = recordItem.getPayerAccountId();
var nftAllowanceState = new HashMap<NftAllowance.Id, NftAllowance>();
var nftSerialAllowanceState = new HashMap<NftId, Nft>();

// iterate the nft allowance list in reverse order and honor the last allowance for either
// the same owner, spender, and token for approved for all allowances, or the last serial allowance for
// the same owner, spender, token, and serial
var iterator = nftAllowances.listIterator(nftAllowances.size());
while (iterator.hasPrevious()) {
var nftApproval = iterator.previous();
EntityId ownerAccountId = getOwnerAccountId(nftApproval.getOwner(), payerAccountId);
if (ownerAccountId == EntityId.EMPTY) {
// ownerAccountId will be EMPTY only when getOwnerAccountId fails to resolve the owner in the alias form
Expand All @@ -108,24 +137,40 @@ public void updateTransaction(Transaction transaction, RecordItem recordItem) {
nftAllowance.setSpender(spender.getId());
nftAllowance.setTokenId(tokenId.getId());
nftAllowance.setTimestampLower(consensusTimestamp);
entityListener.onNftAllowance(nftAllowance);

if (nftAllowanceState.putIfAbsent(nftAllowance.getId(), nftAllowance) == null) {
entityListener.onNftAllowance(nftAllowance);
}
}

EntityId delegatingSpender = EntityId.of(nftApproval.getDelegatingSpender());
for (var serialNumber : nftApproval.getSerialNumbersList()) {
// nft instance allowance update doesn't set nft modifiedTimestamp
// services allows the same serial number of a nft token appears in multiple nft allowances to
// different spenders. The last spender will be granted such allowance.
Nft nft = new Nft(serialNumber, tokenId);
nft.setAccountId(ownerAccountId);
nft.setDelegatingSpender(delegatingSpender);
nft.setModifiedTimestamp(consensusTimestamp);
nft.setSpender(spender);
entityListener.onNft(nft);

if (nftSerialAllowanceState.putIfAbsent(nft.getId(), nft) == null) {
entityListener.onNft(nft);
}
}
}
}

for (var tokenApproval : transactionBody.getTokenAllowancesList()) {
private void parseTokenAllowances(List<com.hederahashgraph.api.proto.java.TokenAllowance> tokenAllowances,
RecordItem recordItem) {
var consensusTimestamp = recordItem.getConsensusTimestamp();
var payerAccountId = recordItem.getPayerAccountId();
var tokenAllowanceState = new HashMap<TokenAllowance.Id, TokenAllowance>();

// iterate the token allowance list in reverse order and honor the last allowance for the same owner, spender,
// and token
var iterator = tokenAllowances.listIterator(tokenAllowances.size());
while (iterator.hasPrevious()) {
var tokenApproval = iterator.previous();
EntityId ownerAccountId = getOwnerAccountId(tokenApproval.getOwner(), payerAccountId);
if (ownerAccountId == EntityId.EMPTY) {
// ownerAccountId will be EMPTY only when getOwnerAccountId fails to resolve the owner in the alias form
Expand All @@ -140,7 +185,10 @@ public void updateTransaction(Transaction transaction, RecordItem recordItem) {
tokenAllowance.setSpender(EntityId.of(tokenApproval.getSpender()).getId());
tokenAllowance.setTokenId(EntityId.of(tokenApproval.getTokenId()).getId());
tokenAllowance.setTimestampLower(consensusTimestamp);
entityListener.onTokenAllowance(tokenAllowance);

if (tokenAllowanceState.putIfAbsent(tokenAllowance.getId(), tokenAllowance) == null) {
entityListener.onTokenAllowance(tokenAllowance);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ public Builder<CryptoApproveAllowanceTransactionBody.Builder> cryptoApproveAllow
.setOwner(accountId())
.setSpender(accountId())
.setTokenId(tokenId()));
// duplicate allowances
builder.addCryptoAllowances(builder.getCryptoAllowances(0))
.addTokenAllowances(builder.getTokenAllowances(0))
.addNftAllowances(builder.getNftAllowances(0))
.addNftAllowances(builder.getNftAllowances(2).toBuilder().setApprovedForAll(BoolValue.of(false)));
return new Builder<>(TransactionType.CRYPTOAPPROVEALLOWANCE, builder);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,10 @@ private List<NftAllowance> customizeNftAllowances(Timestamp consensusTimestamp,
.setSpender(spender2)
.setTokenId(tokenId)
.build());

// duplicate nft allowance
nftAllowances.add(nftAllowances.get(nftAllowances.size() - 1));

// serial number 2's allowance is granted twice, the allowance should be granted to spender2 since it appears
// after the nft allowance to spender1
expectedNfts.add(nft2.toBuilder().modifiedTimestamp(timestamp).spender(EntityId.of(spender2)).build());
Expand Down

0 comments on commit 0b5a739

Please sign in to comment.