diff --git a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java index 326e2472757..a6157f9e6e2 100644 --- a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java +++ b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java @@ -26,7 +26,6 @@ import org.tron.common.utils.StorageUtils; import org.tron.common.utils.StringUtil; import org.tron.common.utils.WalletUtil; -import org.tron.core.ChainBaseManager; import org.tron.core.capsule.AccountCapsule; import org.tron.core.capsule.BlockCapsule; import org.tron.core.capsule.ContractCapsule; @@ -35,6 +34,7 @@ import org.tron.core.db.TransactionContext; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.exception.HeaderNotFound; import org.tron.core.utils.TransactionUtil; import org.tron.core.vm.EnergyCost; import org.tron.core.vm.LogInfoTriggerParser; @@ -53,6 +53,7 @@ import org.tron.core.vm.program.invoke.ProgramInvokeFactory; import org.tron.core.vm.repository.Repository; import org.tron.core.vm.repository.RepositoryImpl; +import org.tron.core.vm.repository.RepositoryStateImpl; import org.tron.core.vm.utils.MUtil; import org.tron.protos.Protocol; import org.tron.protos.Protocol.Block; @@ -94,6 +95,9 @@ public class VMActuator implements Actuator2 { private LogInfoTriggerParser logInfoTriggerParser; + private static final boolean isAllowStateRoot = CommonParameter.getInstance().getStorage() + .isAllowStateRoot(); + public VMActuator(boolean isConstantCall) { this.isConstantCall = isConstantCall; this.maxEnergyLimit = CommonParameter.getInstance().maxEnergyLimitForConstant; @@ -110,21 +114,38 @@ private static long getEnergyFee(long callerEnergyUsage, long callerEnergyFrozen @Override public void validate(Object object) throws ContractValidateException { - TransactionContext context = (TransactionContext) object; if (Objects.isNull(context)) { throw new RuntimeException("TransactionContext is null"); } - // Load Config - ConfigLoader.load(context.getStoreFactory()); // Warm up registry class OperationRegistry.init(); trx = context.getTrxCap().getInstance(); + + // check whether is state query + boolean stateQuery = isAllowStateRoot && isConstantCall; + stateQuery = stateQuery && context.getBlockCap() != null; + try { + stateQuery = stateQuery && context.getBlockCap().getNum() < context.getStoreFactory() + .getChainBaseManager().getHead().getNum(); + } catch (HeaderNotFound e) { + throw new ContractValidateException(e.getMessage()); + } + + //Prepare Repository + if (stateQuery) { + rootRepository = RepositoryStateImpl.createRoot(context.getBlockCap().getArchiveRoot()); + } else { + rootRepository = RepositoryImpl.createRoot(context.getStoreFactory()); + } + // Load Config + ConfigLoader.load(rootRepository); + // If tx`s fee limit is set, use it to calc max energy limit for constant call if (isConstantCall && trx.getRawData().getFeeLimit() > 0) { maxEnergyLimit = Math.min(maxEnergyLimit, trx.getRawData().getFeeLimit() - / context.getStoreFactory().getChainBaseManager() + / rootRepository .getDynamicPropertiesStore().getEnergyFee()); } blockCap = context.getBlockCap(); @@ -134,9 +155,6 @@ public void validate(Object object) throws ContractValidateException { } //Route Type ContractType contractType = this.trx.getRawData().getContract(0).getType(); - //Prepare Repository - rootRepository = RepositoryImpl.createRoot(context.getStoreFactory()); - enableEventListener = context.isEventPluginLoaded(); //set executorType type @@ -297,7 +315,6 @@ public void execute(Object object) throws ContractExeException { String txHash = Hex.toHexString(rootInternalTx.getHash()); VMUtils.saveProgramTraceFile(txHash, traceContent); } - } private void create() @@ -563,8 +580,7 @@ public long getAccountEnergyLimitWithFixRatio(AccountCapsule account, long feeLi long now = rootRepository.getHeadSlot(); EnergyProcessor energyProcessor = new EnergyProcessor( - rootRepository.getDynamicPropertiesStore(), - ChainBaseManager.getInstance().getAccountStore()); + rootRepository.getDynamicPropertiesStore(), rootRepository.getAccountStore()); energyProcessor.updateUsage(account); account.setLatestConsumeTimeForEnergy(now); receipt.setCallerEnergyUsage(account.getEnergyUsage()); @@ -726,8 +742,7 @@ public long getTotalEnergyLimitWithFixRatio(AccountCapsule creator, AccountCapsu long now = rootRepository.getHeadSlot(); EnergyProcessor energyProcessor = new EnergyProcessor( - rootRepository.getDynamicPropertiesStore(), - ChainBaseManager.getInstance().getAccountStore()); + rootRepository.getDynamicPropertiesStore(), rootRepository.getAccountStore()); energyProcessor.updateUsage(creator); creator.setLatestConsumeTimeForEnergy(now); receipt.setOriginEnergyUsage(creator.getEnergyUsage()); diff --git a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java index 463d8c8995a..2c2acd9edf9 100644 --- a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java +++ b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java @@ -5,7 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.tron.common.parameter.CommonParameter; import org.tron.core.store.DynamicPropertiesStore; -import org.tron.core.store.StoreFactory; +import org.tron.core.vm.repository.Repository; @Slf4j(topic = "VMConfigLoader") public class ConfigLoader { @@ -13,9 +13,9 @@ public class ConfigLoader { //only for unit test public static boolean disable = false; - public static void load(StoreFactory storeFactory) { + public static void load(Repository repository) { if (!disable) { - DynamicPropertiesStore ds = storeFactory.getChainBaseManager().getDynamicPropertiesStore(); + DynamicPropertiesStore ds = repository.getDynamicPropertiesStore(); VMConfig.setVmTrace(CommonParameter.getInstance().isVmTrace()); if (ds != null) { VMConfig.initVmHardFork(checkForEnergyLimit(ds)); diff --git a/actuator/src/main/java/org/tron/core/vm/nativecontract/DelegateResourceProcessor.java b/actuator/src/main/java/org/tron/core/vm/nativecontract/DelegateResourceProcessor.java index bb0d4e8297c..bff17ec0ec5 100644 --- a/actuator/src/main/java/org/tron/core/vm/nativecontract/DelegateResourceProcessor.java +++ b/actuator/src/main/java/org/tron/core/vm/nativecontract/DelegateResourceProcessor.java @@ -13,7 +13,6 @@ import org.apache.commons.lang3.ArrayUtils; import org.tron.common.utils.DecodeUtil; import org.tron.common.utils.StringUtil; -import org.tron.core.ChainBaseManager; import org.tron.core.actuator.ActuatorConstant; import org.tron.core.capsule.AccountCapsule; import org.tron.core.capsule.DelegatedResourceAccountIndexCapsule; @@ -56,7 +55,9 @@ public void validate(DelegateResourceParam param, Repository repo) throws Contra switch (param.getResourceType()) { case BANDWIDTH: { - BandwidthProcessor processor = new BandwidthProcessor(ChainBaseManager.getInstance()); + BandwidthProcessor processor = new BandwidthProcessor(dynamicStore, + repo.getAccountStore(), repo.getAssetIssueStore(), + repo.getAssetIssueV2Store()); processor.updateUsageForDelegated(ownerCapsule); long netUsage = (long) (ownerCapsule.getNetUsage() * TRX_PRECISION * ((double) @@ -71,8 +72,7 @@ public void validate(DelegateResourceParam param, Repository repo) throws Contra } break; case ENERGY: { - EnergyProcessor processor = - new EnergyProcessor(dynamicStore, ChainBaseManager.getInstance().getAccountStore()); + EnergyProcessor processor = new EnergyProcessor(dynamicStore, repo.getAccountStore()); processor.updateUsage(ownerCapsule); long energyUsage = (long) (ownerCapsule.getEnergyUsage() * TRX_PRECISION * ((double) diff --git a/actuator/src/main/java/org/tron/core/vm/nativecontract/UnDelegateResourceProcessor.java b/actuator/src/main/java/org/tron/core/vm/nativecontract/UnDelegateResourceProcessor.java index d521e596e3e..1d40d5cd3d9 100644 --- a/actuator/src/main/java/org/tron/core/vm/nativecontract/UnDelegateResourceProcessor.java +++ b/actuator/src/main/java/org/tron/core/vm/nativecontract/UnDelegateResourceProcessor.java @@ -12,7 +12,6 @@ import lombok.extern.slf4j.Slf4j; import org.tron.common.utils.DecodeUtil; import org.tron.common.utils.StringUtil; -import org.tron.core.ChainBaseManager; import org.tron.core.capsule.AccountCapsule; import org.tron.core.capsule.DelegatedResourceAccountIndexCapsule; import org.tron.core.capsule.DelegatedResourceCapsule; @@ -100,7 +99,9 @@ public void execute(UnDelegateResourceParam param, Repository repo) { if (receiverCapsule != null) { switch (param.getResourceType()) { case BANDWIDTH: - BandwidthProcessor bandwidthProcessor = new BandwidthProcessor(ChainBaseManager.getInstance()); + BandwidthProcessor bandwidthProcessor = new BandwidthProcessor(dynamicStore, + repo.getAccountStore(), repo.getAssetIssueStore(), + repo.getAssetIssueV2Store()); bandwidthProcessor.updateUsageForDelegated(receiverCapsule); /* For example, in a scenario where a regular account can be upgraded to a contract account through an interface, the account information will be cleared after the @@ -125,8 +126,8 @@ public void execute(UnDelegateResourceParam param, Repository repo) { receiverCapsule.setLatestConsumeTime(now); break; case ENERGY: - EnergyProcessor energyProcessor = - new EnergyProcessor(dynamicStore, ChainBaseManager.getInstance().getAccountStore()); + EnergyProcessor energyProcessor = new EnergyProcessor(dynamicStore, + repo.getAccountStore()); energyProcessor.updateUsage(receiverCapsule); if (receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy() @@ -165,7 +166,9 @@ public void execute(UnDelegateResourceParam param, Repository repo) { ownerCapsule.addDelegatedFrozenV2BalanceForBandwidth(-unDelegateBalance); ownerCapsule.addFrozenBalanceForBandwidthV2(unDelegateBalance); - BandwidthProcessor processor = new BandwidthProcessor(ChainBaseManager.getInstance()); + BandwidthProcessor processor = new BandwidthProcessor(dynamicStore, + repo.getAccountStore(), repo.getAssetIssueStore(), + repo.getAssetIssueV2Store()); if (Objects.nonNull(receiverCapsule) && transferUsage > 0) { processor.unDelegateIncrease(ownerCapsule, receiverCapsule, transferUsage, BANDWIDTH, now); @@ -179,7 +182,7 @@ public void execute(UnDelegateResourceParam param, Repository repo) { ownerCapsule.addFrozenBalanceForEnergyV2(unDelegateBalance); EnergyProcessor processor = - new EnergyProcessor(dynamicStore, ChainBaseManager.getInstance().getAccountStore()); + new EnergyProcessor(dynamicStore, repo.getAccountStore()); if (Objects.nonNull(receiverCapsule) && transferUsage > 0) { processor.unDelegateIncrease(ownerCapsule, receiverCapsule, transferUsage, ENERGY, now); } diff --git a/actuator/src/main/java/org/tron/core/vm/program/ContractState.java b/actuator/src/main/java/org/tron/core/vm/program/ContractState.java index 657558b3725..62f6b5c7f2b 100644 --- a/actuator/src/main/java/org/tron/core/vm/program/ContractState.java +++ b/actuator/src/main/java/org/tron/core/vm/program/ContractState.java @@ -12,6 +12,7 @@ import org.tron.core.capsule.DelegatedResourceCapsule; import org.tron.core.capsule.VotesCapsule; import org.tron.core.capsule.WitnessCapsule; +import org.tron.core.store.AccountStore; import org.tron.core.store.AssetIssueStore; import org.tron.core.store.AssetIssueV2Store; import org.tron.core.store.DelegationStore; @@ -46,6 +47,11 @@ public AssetIssueCapsule getAssetIssue(byte[] tokenId) { return repository.getAssetIssue(tokenId); } + @Override + public AccountStore getAccountStore() { + return repository.getAccountStore(); + } + @Override public AssetIssueV2Store getAssetIssueV2Store() { return repository.getAssetIssueV2Store(); diff --git a/actuator/src/main/java/org/tron/core/vm/program/Program.java b/actuator/src/main/java/org/tron/core/vm/program/Program.java index e02ba225c6b..3e86c3042b0 100644 --- a/actuator/src/main/java/org/tron/core/vm/program/Program.java +++ b/actuator/src/main/java/org/tron/core/vm/program/Program.java @@ -39,7 +39,6 @@ import org.tron.common.utils.FastByteComparisons; import org.tron.common.utils.Utils; import org.tron.common.utils.WalletUtil; -import org.tron.core.ChainBaseManager; import org.tron.core.Constant; import org.tron.core.capsule.AccountCapsule; import org.tron.core.capsule.BlockCapsule; @@ -556,7 +555,8 @@ private long transferFrozenV2BalanceToInheritor(byte[] ownerAddr, byte[] inherit }); // merge usage - BandwidthProcessor bandwidthProcessor = new BandwidthProcessor(ChainBaseManager.getInstance()); + BandwidthProcessor bandwidthProcessor = new BandwidthProcessor(repo.getDynamicPropertiesStore() + , repo.getAccountStore(), repo.getAssetIssueStore(), repo.getAssetIssueV2Store()); bandwidthProcessor.updateUsageForDelegated(ownerCapsule); ownerCapsule.setLatestConsumeTime(now); if (ownerCapsule.getNetUsage() > 0) { @@ -566,7 +566,7 @@ private long transferFrozenV2BalanceToInheritor(byte[] ownerAddr, byte[] inherit EnergyProcessor energyProcessor = new EnergyProcessor( - repo.getDynamicPropertiesStore(), ChainBaseManager.getInstance().getAccountStore()); + repo.getDynamicPropertiesStore(), repo.getAccountStore()); energyProcessor.updateUsage(ownerCapsule); ownerCapsule.setLatestConsumeTimeForEnergy(now); if (ownerCapsule.getEnergyUsage() > 0) { diff --git a/actuator/src/main/java/org/tron/core/vm/repository/Repository.java b/actuator/src/main/java/org/tron/core/vm/repository/Repository.java index b4324bb8d18..801fd6a1f92 100644 --- a/actuator/src/main/java/org/tron/core/vm/repository/Repository.java +++ b/actuator/src/main/java/org/tron/core/vm/repository/Repository.java @@ -9,6 +9,8 @@ public interface Repository { + AccountStore getAccountStore(); + AssetIssueCapsule getAssetIssue(byte[] tokenId); AssetIssueV2Store getAssetIssueV2Store(); diff --git a/actuator/src/main/java/org/tron/core/vm/repository/RepositoryStateImpl.java b/actuator/src/main/java/org/tron/core/vm/repository/RepositoryStateImpl.java new file mode 100644 index 00000000000..cb537877e98 --- /dev/null +++ b/actuator/src/main/java/org/tron/core/vm/repository/RepositoryStateImpl.java @@ -0,0 +1,1054 @@ +package org.tron.core.vm.repository; + +import static java.lang.Long.max; +import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCED_INTERVAL; +import static org.tron.core.config.Parameter.ChainConstant.TRX_PRECISION; + +import com.google.protobuf.ByteString; +import java.util.HashMap; +import java.util.Optional; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.util.Strings; +import org.bouncycastle.util.encoders.Hex; +import org.tron.common.crypto.Hash; +import org.tron.common.parameter.CommonParameter; +import org.tron.common.runtime.vm.DataWord; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.StorageUtils; +import org.tron.common.utils.StringUtil; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.AssetIssueCapsule; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.capsule.BytesCapsule; +import org.tron.core.capsule.CodeCapsule; +import org.tron.core.capsule.ContractCapsule; +import org.tron.core.capsule.ContractStateCapsule; +import org.tron.core.capsule.DelegatedResourceAccountIndexCapsule; +import org.tron.core.capsule.DelegatedResourceCapsule; +import org.tron.core.capsule.VotesCapsule; +import org.tron.core.capsule.WitnessCapsule; +import org.tron.core.config.Parameter; +import org.tron.core.db.TransactionTrace; +import org.tron.core.exception.BadItemException; +import org.tron.core.exception.ItemNotFoundException; +import org.tron.core.exception.StoreException; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.AccountStateStore; +import org.tron.core.state.store.AssetIssueV2StateStore; +import org.tron.core.state.store.DelegationStateStore; +import org.tron.core.state.store.DynamicPropertiesStateStore; +import org.tron.core.state.store.StorageRowStateStore; +import org.tron.core.store.AccountStore; +import org.tron.core.store.AssetIssueStore; +import org.tron.core.store.AssetIssueV2Store; +import org.tron.core.store.DelegationStore; +import org.tron.core.store.DynamicPropertiesStore; +import org.tron.core.store.StorageRowStore; +import org.tron.core.vm.config.VMConfig; +import org.tron.core.vm.program.Program; +import org.tron.core.vm.program.Storage; +import org.tron.protos.Protocol; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.AccountType; +import org.tron.protos.Protocol.DelegatedResource; +import org.tron.protos.Protocol.Votes; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.Common; +import org.tron.protos.contract.SmartContractOuterClass; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; + +@Slf4j(topic = "Repository") +public class RepositoryStateImpl implements Repository { + + private final long precision = Parameter.ChainConstant.PRECISION; + + private static final byte[] TOTAL_NET_WEIGHT = "TOTAL_NET_WEIGHT".getBytes(); + private static final byte[] TOTAL_ENERGY_WEIGHT = "TOTAL_ENERGY_WEIGHT".getBytes(); + private static final byte[] TOTAL_TRON_POWER_WEIGHT = "TOTAL_TRON_POWER_WEIGHT".getBytes(); + + private final Bytes32 rootHash; + + @Getter + private final WorldStateQueryInstance worldStateQueryInstance; + + private final AccountStateStore accountStateStore; + private final AssetIssueV2StateStore assetIssueV2StateStore; + private final DelegationStateStore delegationStateStore; + private final DynamicPropertiesStateStore dynamicPropertiesStateStore; + private final StorageRowStateStore storageRowStateStore; + + private Repository parent = null; + + private final HashMap> accountCache = new HashMap<>(); + private final HashMap> codeCache = new HashMap<>(); + private final HashMap> contractCache = new HashMap<>(); + private final HashMap> contractStateCache + = new HashMap<>(); + private final HashMap storageCache = new HashMap<>(); + + private final HashMap> assetIssueCache = new HashMap<>(); + private final HashMap> dynamicPropertiesCache = new HashMap<>(); + private final HashMap> delegatedResourceCache = new HashMap<>(); + private final HashMap> votesCache = new HashMap<>(); + private final HashMap> delegationCache = new HashMap<>(); + private final HashMap> + delegatedResourceAccountIndexCache = new HashMap<>(); + + public RepositoryStateImpl(Bytes32 rootHash, RepositoryStateImpl repository) { + this.rootHash = rootHash; + this.parent = repository; + this.worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + this.accountStateStore = new AccountStateStore(worldStateQueryInstance); + this.assetIssueV2StateStore = new AssetIssueV2StateStore(worldStateQueryInstance); + this.delegationStateStore = new DelegationStateStore(worldStateQueryInstance); + this.dynamicPropertiesStateStore = new DynamicPropertiesStateStore(worldStateQueryInstance); + this.storageRowStateStore = new StorageRowStateStore(worldStateQueryInstance); + } + + public static RepositoryStateImpl createRoot(Bytes32 rootHash) { + return new RepositoryStateImpl(rootHash, null); + } + + @Override + public Repository newRepositoryChild() { + return new RepositoryStateImpl(this.rootHash, this); + } + + @Override + public long getAccountLeftEnergyFromFreeze(AccountCapsule accountCapsule) { + long now = getHeadSlot(); + + long energyUsage = accountCapsule.getEnergyUsage(); + long latestConsumeTime = accountCapsule.getAccountResource().getLatestConsumeTimeForEnergy(); + long energyLimit = calculateGlobalEnergyLimit(accountCapsule); + + long windowSize = accountCapsule.getWindowSize(Common.ResourceCode.ENERGY); + long newEnergyUsage = recover(energyUsage, latestConsumeTime, now, windowSize); + + return max(energyLimit - newEnergyUsage, 0); // us + } + + @Override + public long getAccountEnergyUsage(AccountCapsule accountCapsule) { + long now = getHeadSlot(); + long energyUsage = accountCapsule.getEnergyUsage(); + long latestConsumeTime = accountCapsule.getAccountResource().getLatestConsumeTimeForEnergy(); + + long accountWindowSize = accountCapsule.getWindowSize(Common.ResourceCode.ENERGY); + + return recover(energyUsage, latestConsumeTime, now, accountWindowSize); + } + + @Override + public Pair getAccountEnergyUsageBalanceAndRestoreSeconds(AccountCapsule accountCapsule) { + long now = getHeadSlot(); + + long energyUsage = accountCapsule.getEnergyUsage(); + long latestConsumeTime = accountCapsule.getAccountResource().getLatestConsumeTimeForEnergy(); + long accountWindowSize = accountCapsule.getWindowSize(Common.ResourceCode.ENERGY); + + if (now >= latestConsumeTime + accountWindowSize) { + return Pair.of(0L, 0L); + } + + long restoreSlots = latestConsumeTime + accountWindowSize - now; + + long newEnergyUsage = recover(energyUsage, latestConsumeTime, now, accountWindowSize); + + long totalEnergyLimit = getDynamicPropertiesStore().getTotalEnergyCurrentLimit(); + long totalEnergyWeight = getTotalEnergyWeight(); + + long balance = (long) ((double) newEnergyUsage * totalEnergyWeight / totalEnergyLimit * TRX_PRECISION); + + return Pair.of(balance, restoreSlots * BLOCK_PRODUCED_INTERVAL / 1_000); + } + + @Override + public Pair getAccountNetUsageBalanceAndRestoreSeconds(AccountCapsule accountCapsule) { + long now = getHeadSlot(); + + long netUsage = accountCapsule.getNetUsage(); + long latestConsumeTime = accountCapsule.getLatestConsumeTime(); + long accountWindowSize = accountCapsule.getWindowSize(Common.ResourceCode.BANDWIDTH); + + if (now >= latestConsumeTime + accountWindowSize) { + return Pair.of(0L, 0L); + } + + long restoreSlots = latestConsumeTime + accountWindowSize - now; + + long newNetUsage = recover(netUsage, latestConsumeTime, now, accountWindowSize); + + long totalNetLimit = getDynamicPropertiesStore().getTotalNetLimit(); + long totalNetWeight = getTotalNetWeight(); + + long balance = (long) ((double) newNetUsage * totalNetWeight / totalNetLimit * TRX_PRECISION); + + return Pair.of(balance, restoreSlots * BLOCK_PRODUCED_INTERVAL / 1_000); + } + + @Override + public AssetIssueCapsule getAssetIssue(byte[] tokenId) { + byte[] tokenIdWithoutLeadingZero = ByteUtil.stripLeadingZeroes(tokenId); + Key key = Key.create(tokenIdWithoutLeadingZero); + if (assetIssueCache.containsKey(key)) { + return new AssetIssueCapsule(assetIssueCache.get(key).getValue()); + } + + AssetIssueCapsule assetIssueCapsule; + if (this.parent != null) { + assetIssueCapsule = parent.getAssetIssue(tokenIdWithoutLeadingZero); + } else { + assetIssueCapsule = worldStateQueryInstance.getAssetIssue(tokenIdWithoutLeadingZero); + } + if (assetIssueCapsule != null) { + assetIssueCache.put(key, Value.create(assetIssueCapsule)); + } + return assetIssueCapsule; + } + + @Override + public AccountStore getAccountStore() { + return accountStateStore; + } + + @Override + public AssetIssueV2Store getAssetIssueV2Store() { + return assetIssueV2StateStore; + } + + @Override + public AssetIssueStore getAssetIssueStore() { + return assetIssueV2StateStore; + } + + @Override + public DynamicPropertiesStore getDynamicPropertiesStore() { + return dynamicPropertiesStateStore; + } + + @Override + public DelegationStore getDelegationStore() { + return delegationStateStore; + } + + public StorageRowStore getStorageRowStore() { + return storageRowStateStore; + } + + @Override + public AccountCapsule createAccount(byte[] address, Protocol.AccountType type) { + Key key = new Key(address); + AccountCapsule account = new AccountCapsule(ByteString.copyFrom(address), type); + accountCache.put(key, Value.create(account, Type.CREATE)); + return account; + } + + @Override + public AccountCapsule createAccount(byte[] address, String accountName, + Protocol.AccountType type) { + Key key = new Key(address); + AccountCapsule account = new AccountCapsule(ByteString.copyFrom(address), + ByteString.copyFromUtf8(accountName), + type); + accountCache.put(key, Value.create(account, Type.CREATE)); + return account; + } + + @Override + public AccountCapsule getAccount(byte[] address) { + Key key = new Key(address); + if (accountCache.containsKey(key)) { + return new AccountCapsule(accountCache.get(key).getValue()); + } + + AccountCapsule accountCapsule; + if (parent != null) { + accountCapsule = parent.getAccount(address); + } else { + accountCapsule = worldStateQueryInstance.getAccount(address); + } + + if (accountCapsule != null) { + accountCache.put(key, Value.create(accountCapsule)); + } + return accountCapsule; + } + + @Override + public BytesCapsule getDynamicProperty(byte[] word) { + Key key = Key.create(word); + if (dynamicPropertiesCache.containsKey(key)) { + return new BytesCapsule(dynamicPropertiesCache.get(key).getValue()); + } + + BytesCapsule bytesCapsule; + if (parent != null) { + bytesCapsule = parent.getDynamicProperty(word); + } else { + try { + bytesCapsule = getDynamicPropertiesStore().get(word); + } catch (BadItemException | ItemNotFoundException e) { + logger.warn("Not found dynamic property:" + Strings.fromUTF8ByteArray(word)); + bytesCapsule = null; + } + } + + if (bytesCapsule != null) { + dynamicPropertiesCache.put(key, Value.create(bytesCapsule.getData())); + } + return bytesCapsule; + } + + @Override + public DelegatedResourceCapsule getDelegatedResource(byte[] key) { + Key cacheKey = new Key(key); + if (delegatedResourceCache.containsKey(cacheKey)) { + return new DelegatedResourceCapsule(delegatedResourceCache.get(cacheKey).getValue()); + } + + DelegatedResourceCapsule delegatedResourceCapsule; + if (parent != null) { + delegatedResourceCapsule = parent.getDelegatedResource(key); + } else { + delegatedResourceCapsule = worldStateQueryInstance.getDelegatedResource(key); + } + + if (delegatedResourceCapsule != null) { + delegatedResourceCache.put(cacheKey, Value.create(delegatedResourceCapsule)); + } + return delegatedResourceCapsule; + } + + @Override + public VotesCapsule getVotes(byte[] address) { + Key cacheKey = new Key(address); + if (votesCache.containsKey(cacheKey)) { + return new VotesCapsule(votesCache.get(cacheKey).getValue()); + } + + VotesCapsule votesCapsule; + if (parent != null) { + votesCapsule = parent.getVotes(address); + } else { + votesCapsule = worldStateQueryInstance.getVotes(address); + } + + if (votesCapsule != null) { + votesCache.put(cacheKey, Value.create(votesCapsule)); + } + return votesCapsule; + } + + @Override + public WitnessCapsule getWitness(byte[] address) { + return worldStateQueryInstance.getWitness(address); + } + + @Override + public long getBeginCycle(byte[] address) { + Key cacheKey = new Key(address); + BytesCapsule bytesCapsule = getDelegation(cacheKey); + return bytesCapsule == null ? 0 : ByteArray.toLong(bytesCapsule.getData()); + } + + @Override + public long getEndCycle(byte[] address) { + byte[] key = ("end-" + Hex.toHexString(address)).getBytes(); + Key cacheKey = new Key(key); + BytesCapsule bytesCapsule = getDelegation(cacheKey); + return bytesCapsule == null ? DelegationStore.REMARK : ByteArray.toLong(bytesCapsule.getData()); + } + + @Override + public AccountCapsule getAccountVote(long cycle, byte[] address) { + byte[] key = (cycle + "-" + Hex.toHexString(address) + "-account-vote").getBytes(); + Key cacheKey = new Key(key); + BytesCapsule bytesCapsule = getDelegation(cacheKey); + if (bytesCapsule == null) { + return null; + } else { + return new AccountCapsule(bytesCapsule.getData()); + } + } + + @Override + public BytesCapsule getDelegation(Key key) { + if (delegationCache.containsKey(key)) { + return new BytesCapsule(delegationCache.get(key).getValue()); + } + BytesCapsule bytesCapsule; + if (parent != null) { + bytesCapsule = parent.getDelegation(key); + } else { + bytesCapsule = worldStateQueryInstance.getDelegation(key.getData()); + } + if (bytesCapsule != null) { + delegationCache.put(key, Value.create(bytesCapsule.getData())); + } + return bytesCapsule; + } + + @Override + public DelegatedResourceAccountIndexCapsule getDelegatedResourceAccountIndex(byte[] key) { + Key cacheKey = new Key(key); + if (delegatedResourceAccountIndexCache.containsKey(cacheKey)) { + return new DelegatedResourceAccountIndexCapsule( + delegatedResourceAccountIndexCache.get(cacheKey).getValue()); + } + + DelegatedResourceAccountIndexCapsule delegatedResourceAccountIndexCapsule; + if (parent != null) { + delegatedResourceAccountIndexCapsule = parent.getDelegatedResourceAccountIndex(key); + } else { + delegatedResourceAccountIndexCapsule = worldStateQueryInstance + .getDelegatedResourceAccountIndex(key); + } + + if (delegatedResourceAccountIndexCapsule != null) { + delegatedResourceAccountIndexCache.put( + cacheKey, Value.create(delegatedResourceAccountIndexCapsule)); + } + return delegatedResourceAccountIndexCapsule; + } + + + @Override + public void deleteContract(byte[] address) { + throw new UnsupportedOperationException(); + + } + + @Override + public void createContract(byte[] address, ContractCapsule contractCapsule) { + contractCache.put(Key.create(address), + Value.create(contractCapsule, Type.CREATE)); + } + + @Override + public ContractCapsule getContract(byte[] address) { + Key key = Key.create(address); + if (contractCache.containsKey(key)) { + return new ContractCapsule(contractCache.get(key).getValue()); + } + + ContractCapsule contractCapsule; + if (parent != null) { + contractCapsule = parent.getContract(address); + } else { + contractCapsule = worldStateQueryInstance.getContract(address); + } + + if (contractCapsule != null) { + contractCache.put(key, Value.create(contractCapsule)); + } + return contractCapsule; + } + + @Override + public ContractStateCapsule getContractState(byte[] address) { + Key key = Key.create(address); + if (contractStateCache.containsKey(key)) { + return new ContractStateCapsule(contractStateCache.get(key).getValue()); + } + + ContractStateCapsule contractStateCapsule; + if (parent != null) { + contractStateCapsule = parent.getContractState(address); + } else { + contractStateCapsule = worldStateQueryInstance.getContractState(address); + } + + if (contractStateCapsule != null) { + contractStateCache.put(key, Value.create(contractStateCapsule)); + } + return contractStateCapsule; + } + + @Override + public void updateContract(byte[] address, ContractCapsule contractCapsule) { + contractCache.put(Key.create(address), + Value.create(contractCapsule, Type.DIRTY)); + } + + @Override + public void updateContractState(byte[] address, ContractStateCapsule contractStateCapsule) { + contractStateCache.put(Key.create(address), + Value.create(contractStateCapsule, Type.DIRTY)); + } + + @Override + public void updateAccount(byte[] address, AccountCapsule accountCapsule) { + accountCache.put(Key.create(address), + Value.create(accountCapsule, Type.DIRTY)); + } + + @Override + public void updateDynamicProperty(byte[] word, BytesCapsule bytesCapsule) { + dynamicPropertiesCache.put(Key.create(word), + Value.create(bytesCapsule.getData(), Type.DIRTY)); + } + + @Override + public void updateDelegatedResource(byte[] word, + DelegatedResourceCapsule delegatedResourceCapsule) { + delegatedResourceCache.put(Key.create(word), + Value.create(delegatedResourceCapsule, Type.DIRTY)); + } + + @Override + public void updateVotes(byte[] word, VotesCapsule votesCapsule) { + votesCache.put(Key.create(word), + Value.create(votesCapsule, Type.DIRTY)); + } + + @Override + public void updateBeginCycle(byte[] word, long cycle) { + updateDelegation(word, new BytesCapsule(ByteArray.fromLong(cycle))); + } + + @Override + public void updateEndCycle(byte[] word, long cycle) { + BytesCapsule bytesCapsule = new BytesCapsule(ByteArray.fromLong(cycle)); + byte[] key = ("end-" + Hex.toHexString(word)).getBytes(); + updateDelegation(key, bytesCapsule); + } + + @Override + public void updateAccountVote(byte[] word, long cycle, AccountCapsule accountCapsule) { + BytesCapsule bytesCapsule = new BytesCapsule(accountCapsule.getData()); + byte[] key = (cycle + "-" + Hex.toHexString(word) + "-account-vote").getBytes(); + updateDelegation(key, bytesCapsule); + } + + @Override + public void updateDelegation(byte[] word, BytesCapsule bytesCapsule) { + delegationCache.put(Key.create(word), + Value.create(bytesCapsule.getData(), Type.DIRTY)); + } + + @Override + public void updateDelegatedResourceAccountIndex(byte[] word, + DelegatedResourceAccountIndexCapsule delegatedResourceAccountIndexCapsule) { + delegatedResourceAccountIndexCache.put( + Key.create(word), Value.create(delegatedResourceAccountIndexCapsule, Type.DIRTY)); + } + + @Override + public void saveCode(byte[] address, byte[] code) { + codeCache.put(Key.create(address), Value.create(code, Type.CREATE)); + + if (VMConfig.allowTvmConstantinople()) { + ContractCapsule contract = getContract(address); + byte[] codeHash = Hash.sha3(code); + contract.setCodeHash(codeHash); + updateContract(address, contract); + } + } + + @Override + public byte[] getCode(byte[] address) { + Key key = Key.create(address); + if (codeCache.containsKey(key)) { + return codeCache.get(key).getValue(); + } + + byte[] code; + if (parent != null) { + code = parent.getCode(address); + } else { + CodeCapsule c = worldStateQueryInstance.getCode(address); + code = null == c ? null : c.getData(); + } + if (code != null) { + codeCache.put(key, Value.create(code)); + } + return code; + } + + @Override + public void putStorageValue(byte[] address, DataWord key, DataWord value) { + Storage storage = getStorageInternal(address); + if (storage != null) { + storage.put(key, value); + } + } + + @Override + public DataWord getStorageValue(byte[] address, DataWord key) { + Storage storage = getStorageInternal(address); + return storage == null ? null : storage.getValue(key); + } + + private Storage getStorageInternal(byte[] address) { + address = TransactionTrace.convertToTronAddress(address); + if (getAccount(address) == null) { + return null; + } + Key addressKey = Key.create(address); + Storage storage; + if (storageCache.containsKey(addressKey)) { + storage = storageCache.get(addressKey); + } else { + storage = getStorage(address); + storageCache.put(addressKey, storage); + } + return storage; + } + + @Override + public Storage getStorage(byte[] address) { + Key key = Key.create(address); + if (storageCache.containsKey(key)) { + return storageCache.get(key); + } + Storage storage; + if (this.parent != null) { + Storage parentStorage = parent.getStorage(address); + if (StorageUtils.getEnergyLimitHardFork()) { + // deep copy + storage = new Storage(parentStorage); + } else { + storage = parentStorage; + } + } else { + storage = new Storage(address, getStorageRowStore()); + } + ContractCapsule contract = getContract(address); + if (contract != null) { + storage.setContractVersion(contract.getContractVersion()); + if (!ByteUtil.isNullOrZeroArray(contract.getTrxHash())) { + storage.generateAddrHash(contract.getTrxHash()); + } + } + return storage; + } + + @Override + public long getBalance(byte[] address) { + AccountCapsule accountCapsule = getAccount(address); + return accountCapsule == null ? 0L : accountCapsule.getBalance(); + } + + @Override + public long addBalance(byte[] address, long value) { + AccountCapsule accountCapsule = getAccount(address); + if (accountCapsule == null) { + accountCapsule = createAccount(address, Protocol.AccountType.Normal); + } + + long balance = accountCapsule.getBalance(); + if (value == 0) { + return balance; + } + + if (value < 0 && balance < -value) { + throw new RuntimeException( + StringUtil.createReadableString(accountCapsule.createDbKey()) + + " insufficient balance"); + } + accountCapsule.setBalance(Math.addExact(balance, value)); + Key key = Key.create(address); + accountCache.put(key, Value.create(accountCapsule, + accountCache.get(key).getType().addType(Type.DIRTY))); + return accountCapsule.getBalance(); + } + + @Override + public void setParent(Repository repository) { + parent = repository; + } + + @Override + public void commit() { + Repository repository = null; + if (parent != null) { + repository = parent; + } + commitAccountCache(repository); + commitCodeCache(repository); + commitContractCache(repository); + commitContractStateCache(repository); + commitStorageCache(repository); + commitDynamicCache(repository); + commitDelegatedResourceCache(repository); + commitVotesCache(repository); + commitDelegationCache(repository); + commitDelegatedResourceAccountIndexCache(repository); + } + + @Override + public void putAccount(Key key, Value value) { + accountCache.put(key, value); + } + + @Override + public void putCode(Key key, Value value) { + codeCache.put(key, value); + } + + @Override + public void putContract(Key key, Value value) { + contractCache.put(key, value); + } + + @Override + public void putContractState(Key key, Value value) { + contractStateCache.put(key, value); + } + + @Override + public void putStorage(Key key, Storage cache) { + storageCache.put(key, cache); + } + + @Override + public void putAccountValue(byte[] address, AccountCapsule accountCapsule) { + accountCache.put(new Key(address), + Value.create(accountCapsule, Type.CREATE)); + } + + @Override + public void putDynamicProperty(Key key, Value value) { + dynamicPropertiesCache.put(key, value); + } + + @Override + public void putDelegatedResource(Key key, Value value) { + delegatedResourceCache.put(key, value); + } + + @Override + public void putVotes(Key key, Value value) { + votesCache.put(key, value); + } + + @Override + public void putDelegation(Key key, Value value) { + delegationCache.put(key, value); + } + + @Override + public void putDelegatedResourceAccountIndex(Key key, Value value) { + delegatedResourceAccountIndexCache.put(key, value); + } + + @Override + public long addTokenBalance(byte[] address, byte[] tokenId, long value) { + byte[] tokenIdWithoutLeadingZero = ByteUtil.stripLeadingZeroes(tokenId); + AccountCapsule accountCapsule = getAccount(address); + if (accountCapsule == null) { + accountCapsule = createAccount(address, Protocol.AccountType.Normal); + } + long balance = accountCapsule.getAssetV2(new String(tokenIdWithoutLeadingZero)); + if (value == 0) { + return balance; + } + + if (value < 0 && balance < -value) { + throw new RuntimeException( + StringUtil.createReadableString(accountCapsule.createDbKey()) + + " insufficient balance"); + } + if (value >= 0) { + accountCapsule.addAssetAmountV2(tokenIdWithoutLeadingZero, value, getDynamicPropertiesStore(), + getAssetIssueStore()); + } else { + accountCapsule + .reduceAssetAmountV2(tokenIdWithoutLeadingZero, -value, getDynamicPropertiesStore(), + getAssetIssueStore()); + } + Key key = Key.create(address); + accountCache.put(key, Value.create(accountCapsule, + accountCache.get(key).getType().addType(Type.DIRTY))); + return accountCapsule.getAssetV2(new String(tokenIdWithoutLeadingZero)); + } + + @Override + public long getTokenBalance(byte[] address, byte[] tokenId) { + AccountCapsule accountCapsule = getAccount(address); + if (accountCapsule == null) { + return 0; + } + String tokenStr = new String(ByteUtil.stripLeadingZeroes(tokenId)); + return accountCapsule.getAssetV2(tokenStr); + } + + @Override + public byte[] getBlackHoleAddress() { + return AccountStore.getBlackholeAddress(); + } + + @Override + public BlockCapsule getBlockByNum(long num) { + try { + return worldStateQueryInstance.getBlockByNum(num); + } catch (StoreException e) { + throw new Program.IllegalOperationException("cannot find block num"); + } + } + + // new recover method, use personal window size. + private long recover(long lastUsage, long lastTime, long now, long personalWindowSize) { + return increase(lastUsage, 0, lastTime, now, personalWindowSize); + } + + private long increase(long lastUsage, long usage, long lastTime, long now, long windowSize) { + long averageLastUsage = divideCeil(lastUsage * precision, windowSize); + long averageUsage = divideCeil(usage * precision, windowSize); + + if (lastTime != now) { + assert now > lastTime; + if (lastTime + windowSize > now) { + long delta = now - lastTime; + double decay = (windowSize - delta) / (double) windowSize; + averageLastUsage = Math.round(averageLastUsage * decay); + } else { + averageLastUsage = 0; + } + } + averageLastUsage += averageUsage; + return getUsage(averageLastUsage, windowSize); + } + + private long divideCeil(long numerator, long denominator) { + return (numerator / denominator) + ((numerator % denominator) > 0 ? 1 : 0); + } + + private long getUsage(long usage, long windowSize) { + return usage * windowSize / precision; + } + + @Override + public long calculateGlobalEnergyLimit(AccountCapsule accountCapsule) { + long frozeBalance = accountCapsule.getAllFrozenBalanceForEnergy(); + if (frozeBalance < 1_000_000L) { + return 0; + } + long energyWeight = frozeBalance / 1_000_000L; + long totalEnergyLimit = getDynamicPropertiesStore().getTotalEnergyCurrentLimit(); + long totalEnergyWeight = getDynamicPropertiesStore().getTotalEnergyWeight(); + + assert totalEnergyWeight > 0; + + return (long) (energyWeight * ((double) totalEnergyLimit / totalEnergyWeight)); + } + + public long getHeadSlot() { + return getSlotByTimestampMs(getDynamicPropertiesStore().getLatestBlockHeaderTimestamp()); + } + + @Override + public long getSlotByTimestampMs(long timestamp) { + return (timestamp - Long.parseLong(CommonParameter.getInstance() + .getGenesisBlock().getTimestamp())) + / BLOCK_PRODUCED_INTERVAL; + } + + private void commitAccountCache(Repository deposit) { + accountCache.forEach((key, value) -> { + if (value.getType().isCreate() || value.getType().isDirty()) { + if (deposit != null) { + deposit.putAccount(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + }); + } + + private void commitCodeCache(Repository deposit) { + codeCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putCode(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + private void commitContractCache(Repository deposit) { + contractCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putContract(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + private void commitContractStateCache(Repository deposit) { + contractStateCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putContractState(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + private void commitStorageCache(Repository deposit) { + storageCache.forEach((Key address, Storage storage) -> { + if (deposit != null) { + // write to parent cache + deposit.putStorage(address, storage); + } else { + throw new UnsupportedOperationException(); + } + }); + + } + + private void commitDynamicCache(Repository deposit) { + dynamicPropertiesCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putDynamicProperty(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + private void commitDelegatedResourceCache(Repository deposit) { + delegatedResourceCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putDelegatedResource(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + private void commitVotesCache(Repository deposit) { + votesCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putVotes(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + private void commitDelegationCache(Repository deposit) { + delegationCache.forEach((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putDelegation(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + }); + } + + private void commitDelegatedResourceAccountIndexCache(Repository deposit) { + delegatedResourceAccountIndexCache.forEach(((key, value) -> { + if (value.getType().isDirty() || value.getType().isCreate()) { + if (deposit != null) { + deposit.putDelegatedResourceAccountIndex(key, value); + } else { + throw new UnsupportedOperationException(); + } + } + })); + } + + @Override + public AccountCapsule createNormalAccount(byte[] address) { + boolean withDefaultPermission = + getDynamicPropertiesStore().getAllowMultiSign() == 1; + Key key = new Key(address); + AccountCapsule account = new AccountCapsule(ByteString.copyFrom(address), AccountType.Normal, + getDynamicPropertiesStore().getLatestBlockHeaderTimestamp(), withDefaultPermission, + getDynamicPropertiesStore()); + + accountCache.put(key, Value.create(account, Type.CREATE)); + return account; + } + + //The unit is trx + @Override + public void addTotalNetWeight(long amount) { + long totalNetWeight = getTotalNetWeight(); + totalNetWeight += amount; + saveTotalNetWeight(totalNetWeight); + } + + //The unit is trx + @Override + public void addTotalEnergyWeight(long amount) { + long totalEnergyWeight = getTotalEnergyWeight(); + totalEnergyWeight += amount; + saveTotalEnergyWeight(totalEnergyWeight); + } + + @Override + public void addTotalTronPowerWeight(long amount) { + long totalTronPowerWeight = getTotalTronPowerWeight(); + totalTronPowerWeight += amount; + saveTotalTronPowerWeight(totalTronPowerWeight); + } + + @Override + public void saveTotalNetWeight(long totalNetWeight) { + updateDynamicProperty(TOTAL_NET_WEIGHT, + new BytesCapsule(ByteArray.fromLong(totalNetWeight))); + } + + @Override + public void saveTotalEnergyWeight(long totalEnergyWeight) { + updateDynamicProperty(TOTAL_ENERGY_WEIGHT, + new BytesCapsule(ByteArray.fromLong(totalEnergyWeight))); + } + + @Override + public void saveTotalTronPowerWeight(long totalTronPowerWeight) { + updateDynamicProperty(TOTAL_TRON_POWER_WEIGHT, + new BytesCapsule(ByteArray.fromLong(totalTronPowerWeight))); + } + + @Override + public long getTotalNetWeight() { + return Optional.ofNullable(getDynamicProperty(TOTAL_NET_WEIGHT)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElseThrow( + () -> new IllegalArgumentException("not found TOTAL_NET_WEIGHT")); + } + + @Override + public long getTotalEnergyWeight() { + return Optional.ofNullable(getDynamicProperty(TOTAL_ENERGY_WEIGHT)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElseThrow( + () -> new IllegalArgumentException("not found TOTAL_ENERGY_WEIGHT")); + } + + @Override + public long getTotalTronPowerWeight() { + return Optional.ofNullable(getDynamicProperty(TOTAL_TRON_POWER_WEIGHT)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElseThrow( + () -> new IllegalArgumentException("not found TOTAL_TRON_POWER_WEIGHT")); + } + +} diff --git a/build.gradle b/build.gradle index a56be97afa1..aba239d6785 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,9 @@ subprojects { compile "com.google.code.findbugs:jsr305:3.0.0" compile group: 'org.springframework', name: 'spring-context', version: '5.3.18' compile group: 'org.springframework', name: 'spring-tx', version: '5.3.18' - compile "org.apache.commons:commons-lang3:3.4" + testImplementation group: 'org.springframework', name: 'spring-test', version: '5.3.18' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.9.1' + compile "org.apache.commons:commons-lang3:3.12.0" compile group: 'org.apache.commons', name: 'commons-math', version: '2.2' compile "org.apache.commons:commons-collections4:4.1" compile group: 'joda-time', name: 'joda-time', version: '2.3' diff --git a/chainbase/build.gradle b/chainbase/build.gradle index 408fe56ba42..51cbd580be7 100644 --- a/chainbase/build.gradle +++ b/chainbase/build.gradle @@ -10,6 +10,7 @@ dependencies { compile project(":protocol") compile project(":common") compile project(":crypto") + compile project(":state-trie-jdk8") compile "org.fusesource.jansi:jansi:$jansiVersion" compile 'io.github.tronprotocol:zksnark-java-sdk:1.0.0' compile 'org.reflections:reflections:0.9.11' diff --git a/chainbase/src/main/java/org/tron/common/storage/metric/DbStatService.java b/chainbase/src/main/java/org/tron/common/storage/metric/DbStatService.java index b6fa25d5901..a46f676a8de 100644 --- a/chainbase/src/main/java/org/tron/common/storage/metric/DbStatService.java +++ b/chainbase/src/main/java/org/tron/common/storage/metric/DbStatService.java @@ -6,8 +6,6 @@ import org.springframework.stereotype.Component; import org.tron.common.es.ExecutorServiceManager; import org.tron.common.prometheus.Metrics; -import org.tron.core.db.common.DbSourceInter; -import org.tron.core.db2.common.DB; @Slf4j(topic = "metrics") @Component @@ -16,15 +14,9 @@ public class DbStatService { private final ScheduledExecutorService statExecutor = ExecutorServiceManager.newSingleThreadScheduledExecutor(esName); - public void register(DB db) { + public void register(Stat stat) { if (Metrics.enabled()) { - statExecutor.scheduleWithFixedDelay(db::stat, 0, 6, TimeUnit.HOURS); - } - } - - public void register(DbSourceInter db) { - if (Metrics.enabled()) { - statExecutor.scheduleWithFixedDelay(db::stat, 0, 6, TimeUnit.HOURS); + statExecutor.scheduleWithFixedDelay(stat::stat, 0, 6, TimeUnit.HOURS); } } diff --git a/chainbase/src/main/java/org/tron/common/storage/metric/Stat.java b/chainbase/src/main/java/org/tron/common/storage/metric/Stat.java new file mode 100644 index 00000000000..94366498d41 --- /dev/null +++ b/chainbase/src/main/java/org/tron/common/storage/metric/Stat.java @@ -0,0 +1,6 @@ +package org.tron.common.storage.metric; + +public interface Stat { + + void stat(); +} diff --git a/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java b/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java index 6c5d8018487..a749a9a5a64 100644 --- a/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java +++ b/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java @@ -23,7 +23,7 @@ import org.rocksdb.BlockBasedTableConfig; import org.rocksdb.BloomFilter; import org.rocksdb.Checkpoint; -import org.rocksdb.DirectComparator; +import org.rocksdb.AbstractComparator; import org.rocksdb.InfoLogLevel; import org.rocksdb.Logger; import org.rocksdb.Options; @@ -60,11 +60,11 @@ public class RocksDbDataSourceImpl extends DbStat implements DbSourceInter() { + @Override + public WorldStateQueryInstance load(@NotNull Bytes32 key) throws Exception { + return new WorldStateQueryInstance(key, chainBaseManager); + } + }); + AssetUtil.setAccountAssetStore(manager.getAccountAssetStore()); AssetUtil.setDynamicPropertiesStore(manager.getDynamicPropertiesStore()); } @@ -402,5 +429,18 @@ public enum NodeType { this.type = type; } } + + private static TronCache cache; + + public static WorldStateQueryInstance fetch(Bytes32 root) { + try { + if (root == null || root.isEmpty()) { + throw new IllegalStateException("root can not be empty"); + } + return cache.get(root); + } catch (ExecutionException e) { + throw new MerkleTrieException("fetch worldStateQueryInstance", e); + } + } } diff --git a/chainbase/src/main/java/org/tron/core/capsule/AccountCapsule.java b/chainbase/src/main/java/org/tron/core/capsule/AccountCapsule.java index b60fed63cda..8e77bcd7c39 100644 --- a/chainbase/src/main/java/org/tron/core/capsule/AccountCapsule.java +++ b/chainbase/src/main/java/org/tron/core/capsule/AccountCapsule.java @@ -18,10 +18,14 @@ import com.google.common.collect.Maps; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.tuweni.bytes.Bytes32; import org.tron.common.utils.ByteArray; import org.tron.core.capsule.utils.AssetUtil; +import org.tron.core.state.utils.AssetStateUtil; import org.tron.core.store.AssetIssueStore; import org.tron.core.store.DynamicPropertiesStore; import org.tron.protos.Protocol.Account; @@ -57,6 +61,10 @@ public class AccountCapsule implements ProtoCapsule, Comparable assetMap = accountCapsule.getAssetMap(); assetMap.forEach((assetName, balance) -> { long oldFreeAssetNetUsage = accountCapsule.getFreeAssetNetUsage(assetName); @@ -79,7 +93,7 @@ public void updateUsage(AccountCapsule accountCapsule) { // update usage for asset issue public void updateUsage(AssetIssueCapsule assetIssueCapsule) { - long now = chainBaseManager.getHeadSlot(); + long now = getHeadSlot(); updateUsage(assetIssueCapsule, now); } @@ -103,7 +117,7 @@ public void consume(TransactionCapsule trx, TransactionTrace trace) long bytesSize; - if (chainBaseManager.getDynamicPropertiesStore().supportVM()) { + if (dynamicPropertiesStore.supportVM()) { bytesSize = trx.getInstance().toBuilder().clearRet().build().getSerializedSize(); } else { bytesSize = trx.getSerializedSize(); @@ -113,19 +127,19 @@ public void consume(TransactionCapsule trx, TransactionTrace trace) if (contract.getType() == ShieldedTransferContract) { continue; } - if (chainBaseManager.getDynamicPropertiesStore().supportVM()) { + if (dynamicPropertiesStore.supportVM()) { bytesSize += Constant.MAX_RESULT_SIZE_IN_TX; } logger.debug("TxId {}, bandwidth cost: {}.", trx.getTransactionId(), bytesSize); trace.setNetBill(bytesSize, 0); byte[] address = TransactionCapsule.getOwner(contract); - AccountCapsule accountCapsule = chainBaseManager.getAccountStore().get(address); + AccountCapsule accountCapsule = accountStore.get(address); if (accountCapsule == null) { throw new ContractValidateException(String.format("account [%s] does not exist", StringUtil.encode58Check(address))); } - long now = chainBaseManager.getHeadSlot(); + long now = getHeadSlot(); if (contractCreateNewAccount(contract)) { consumeForCreateNewAccount(accountCapsule, bytesSize, now, trace); continue; @@ -148,7 +162,7 @@ public void consume(TransactionCapsule trx, TransactionTrace trace) continue; } - long fee = chainBaseManager.getDynamicPropertiesStore().getTransactionFee() * bytesSize; + long fee = dynamicPropertiesStore.getTransactionFee() * bytesSize; throw new AccountResourceInsufficientException( String.format( "account [%s] has insufficient bandwidth[%d] and balance[%d] to create new account", @@ -158,10 +172,10 @@ public void consume(TransactionCapsule trx, TransactionTrace trace) private boolean useTransactionFee(AccountCapsule accountCapsule, long bytes, TransactionTrace trace) { - long fee = chainBaseManager.getDynamicPropertiesStore().getTransactionFee() * bytes; + long fee = dynamicPropertiesStore.getTransactionFee() * bytes; if (consumeFeeForBandwidth(accountCapsule, fee)) { trace.setNetBill(0, fee); - chainBaseManager.getDynamicPropertiesStore().addTotalTransactionCost(fee); + dynamicPropertiesStore.addTotalTransactionCost(fee); return true; } else { return false; @@ -179,7 +193,7 @@ private void consumeForCreateNewAccount(AccountCapsule accountCapsule, long byte throw new AccountResourceInsufficientException(String.format( "account [%s] has insufficient bandwidth[%d] and balance[%d] to create new account", StringUtil.encode58Check(accountCapsule.createDbKey()), bytes, - chainBaseManager.getDynamicPropertiesStore().getCreateAccountFee())); + dynamicPropertiesStore.getCreateAccountFee())); } } } @@ -187,7 +201,7 @@ private void consumeForCreateNewAccount(AccountCapsule accountCapsule, long byte public boolean consumeBandwidthForCreateNewAccount(AccountCapsule accountCapsule, long bytes, long now, TransactionTrace trace) { - long createNewAccountBandwidthRatio = chainBaseManager.getDynamicPropertiesStore() + long createNewAccountBandwidthRatio = dynamicPropertiesStore .getCreateNewAccountBandwidthRate(); long netUsage = accountCapsule.getNetUsage(); @@ -203,7 +217,7 @@ public boolean consumeBandwidthForCreateNewAccount(AccountCapsule accountCapsule long netCost = bytes * createNewAccountBandwidthRatio; if (netCost <= (netLimit - newNetUsage)) { - long latestOperationTime = chainBaseManager.getHeadBlockTimeStamp(); + long latestOperationTime = dynamicPropertiesStore.getLatestBlockHeaderTimestamp(); if (!dynamicPropertiesStore.supportUnfreezeDelay()) { newNetUsage = increase(newNetUsage, netCost, now, now); } else { @@ -216,7 +230,7 @@ public boolean consumeBandwidthForCreateNewAccount(AccountCapsule accountCapsule accountCapsule.setNetUsage(newNetUsage); trace.setNetBillForCreateNewAccount(netCost, 0); - chainBaseManager.getAccountStore().put(accountCapsule.createDbKey(), accountCapsule); + accountStore.put(accountCapsule.createDbKey(), accountCapsule); return true; } @@ -225,10 +239,10 @@ public boolean consumeBandwidthForCreateNewAccount(AccountCapsule accountCapsule public boolean consumeFeeForCreateNewAccount(AccountCapsule accountCapsule, TransactionTrace trace) { - long fee = chainBaseManager.getDynamicPropertiesStore().getCreateAccountFee(); + long fee = dynamicPropertiesStore.getCreateAccountFee(); if (consumeFeeForNewAccount(accountCapsule, fee)) { trace.setNetBillForCreateNewAccount(0, fee); - chainBaseManager.getDynamicPropertiesStore().addTotalCreateAccountCost(fee); + dynamicPropertiesStore.addTotalCreateAccountCost(fee); return true; } else { return false; @@ -247,8 +261,7 @@ public boolean contractCreateNewAccount(Contract contract) { } catch (Exception ex) { throw new RuntimeException(ex.getMessage()); } - toAccount = - chainBaseManager.getAccountStore().get(transferContract.getToAddress().toByteArray()); + toAccount = accountStore.get(transferContract.getToAddress().toByteArray()); return toAccount == null; case TransferAssetContract: TransferAssetContract transferAssetContract; @@ -257,8 +270,7 @@ public boolean contractCreateNewAccount(Contract contract) { } catch (Exception ex) { throw new RuntimeException(ex.getMessage()); } - toAccount = chainBaseManager.getAccountStore() - .get(transferAssetContract.getToAddress().toByteArray()); + toAccount = accountStore.get(transferAssetContract.getToAddress().toByteArray()); return toAccount == null; default: return false; @@ -280,8 +292,7 @@ private boolean useAssetAccountNet(Contract contract, AccountCapsule accountCaps AssetIssueCapsule assetIssueCapsule; AssetIssueCapsule assetIssueCapsuleV2; assetIssueCapsule = Commons.getAssetIssueStoreFinal( - chainBaseManager.getDynamicPropertiesStore(), - chainBaseManager.getAssetIssueStore(), chainBaseManager.getAssetIssueV2Store()) + dynamicPropertiesStore, assetIssueStore, assetIssueV2Store) .get(assetName.toByteArray()); if (assetIssueCapsule == null) { throw new ContractValidateException(String.format("asset [%s] does not exist", assetName)); @@ -311,7 +322,7 @@ private boolean useAssetAccountNet(Contract contract, AccountCapsule accountCaps long freeAssetNetUsage; long latestAssetOperationTime; - if (chainBaseManager.getDynamicPropertiesStore().getAllowSameTokenName() == 0) { + if (dynamicPropertiesStore.getAllowSameTokenName() == 0) { freeAssetNetUsage = accountCapsule .getFreeAssetNetUsage(tokenName); latestAssetOperationTime = accountCapsule @@ -331,7 +342,7 @@ private boolean useAssetAccountNet(Contract contract, AccountCapsule accountCaps return false; } - AccountCapsule issuerAccountCapsule = chainBaseManager.getAccountStore() + AccountCapsule issuerAccountCapsule = accountStore .get(assetIssueCapsule.getOwnerAddress().toByteArray()); long issuerNetUsage = issuerAccountCapsule.getNetUsage(); @@ -355,7 +366,7 @@ private boolean useAssetAccountNet(Contract contract, AccountCapsule accountCaps latestAssetOperationTime = now; publicLatestFreeNetTime = now; - long latestOperationTime = chainBaseManager.getHeadBlockTimeStamp(); + long latestOperationTime = dynamicPropertiesStore.getLatestBlockHeaderTimestamp(); if (!dynamicPropertiesStore.supportUnfreezeDelay()) { newIssuerNetUsage = increase(newIssuerNetUsage, bytes, now, now); } else { @@ -376,7 +387,7 @@ private boolean useAssetAccountNet(Contract contract, AccountCapsule accountCaps assetIssueCapsule.setPublicLatestFreeNetTime(publicLatestFreeNetTime); accountCapsule.setLatestOperationTime(latestOperationTime); - if (chainBaseManager.getDynamicPropertiesStore().getAllowSameTokenName() == 0) { + if (dynamicPropertiesStore.getAllowSameTokenName() == 0) { accountCapsule.putLatestAssetOperationTimeMap(tokenName, latestAssetOperationTime); accountCapsule.putFreeAssetNetUsage(tokenName, newFreeAssetNetUsage); @@ -384,24 +395,23 @@ private boolean useAssetAccountNet(Contract contract, AccountCapsule accountCaps latestAssetOperationTime); accountCapsule.putFreeAssetNetUsageV2(tokenID, newFreeAssetNetUsage); - chainBaseManager.getAssetIssueStore().put(assetIssueCapsule.createDbKey(), assetIssueCapsule); + assetIssueStore.put(assetIssueCapsule.createDbKey(), assetIssueCapsule); - assetIssueCapsuleV2 = - chainBaseManager.getAssetIssueV2Store().get(assetIssueCapsule.createDbV2Key()); + assetIssueCapsuleV2 = assetIssueV2Store.get(assetIssueCapsule.createDbV2Key()); assetIssueCapsuleV2.setPublicFreeAssetNetUsage(newPublicFreeAssetNetUsage); assetIssueCapsuleV2.setPublicLatestFreeNetTime(publicLatestFreeNetTime); - chainBaseManager.getAssetIssueV2Store() + assetIssueV2Store .put(assetIssueCapsuleV2.createDbV2Key(), assetIssueCapsuleV2); } else { accountCapsule.putLatestAssetOperationTimeMapV2(tokenID, latestAssetOperationTime); accountCapsule.putFreeAssetNetUsageV2(tokenID, newFreeAssetNetUsage); - chainBaseManager.getAssetIssueV2Store() + assetIssueV2Store .put(assetIssueCapsule.createDbV2Key(), assetIssueCapsule); } - chainBaseManager.getAccountStore().put(accountCapsule.createDbKey(), accountCapsule); - chainBaseManager.getAccountStore().put(issuerAccountCapsule.createDbKey(), + accountStore.put(accountCapsule.createDbKey(), accountCapsule); + accountStore.put(issuerAccountCapsule.createDbKey(), issuerAccountCapsule); return true; @@ -417,8 +427,8 @@ public long calculateGlobalNetLimit(AccountCapsule accountCapsule) { return 0; } long netWeight = frozeBalance / TRX_PRECISION; - long totalNetLimit = chainBaseManager.getDynamicPropertiesStore().getTotalNetLimit(); - long totalNetWeight = chainBaseManager.getDynamicPropertiesStore().getTotalNetWeight(); + long totalNetLimit = dynamicPropertiesStore.getTotalNetLimit(); + long totalNetWeight = dynamicPropertiesStore.getTotalNetWeight(); if (dynamicPropertiesStore.allowNewReward() && totalNetWeight <= 0) { return 0; } @@ -460,7 +470,7 @@ private boolean useAccountNet(AccountCapsule accountCapsule, long bytes, long no return false; } - long latestOperationTime = chainBaseManager.getHeadBlockTimeStamp(); + long latestOperationTime = dynamicPropertiesStore.getLatestBlockHeaderTimestamp(); if (!dynamicPropertiesStore.supportUnfreezeDelay()) { newNetUsage = increase(newNetUsage, bytes, now, now); } else { @@ -472,13 +482,13 @@ private boolean useAccountNet(AccountCapsule accountCapsule, long bytes, long no accountCapsule.setLatestOperationTime(latestOperationTime); accountCapsule.setLatestConsumeTime(now); - chainBaseManager.getAccountStore().put(accountCapsule.createDbKey(), accountCapsule); + accountStore.put(accountCapsule.createDbKey(), accountCapsule); return true; } private boolean useFreeNet(AccountCapsule accountCapsule, long bytes, long now) { - long freeNetLimit = chainBaseManager.getDynamicPropertiesStore().getFreeNetLimit(); + long freeNetLimit = dynamicPropertiesStore.getFreeNetLimit(); long freeNetUsage = accountCapsule.getFreeNetUsage(); long latestConsumeFreeTime = accountCapsule.getLatestConsumeFreeTime(); long newFreeNetUsage = increase(freeNetUsage, 0, latestConsumeFreeTime, now); @@ -490,9 +500,9 @@ private boolean useFreeNet(AccountCapsule accountCapsule, long bytes, long now) return false; } - long publicNetLimit = chainBaseManager.getDynamicPropertiesStore().getPublicNetLimit(); - long publicNetUsage = chainBaseManager.getDynamicPropertiesStore().getPublicNetUsage(); - long publicNetTime = chainBaseManager.getDynamicPropertiesStore().getPublicNetTime(); + long publicNetLimit = dynamicPropertiesStore.getPublicNetLimit(); + long publicNetUsage = dynamicPropertiesStore.getPublicNetUsage(); + long publicNetTime = dynamicPropertiesStore.getPublicNetTime(); long newPublicNetUsage = increase(publicNetUsage, 0, publicNetTime, now); @@ -504,7 +514,7 @@ private boolean useFreeNet(AccountCapsule accountCapsule, long bytes, long now) } latestConsumeFreeTime = now; - long latestOperationTime = chainBaseManager.getHeadBlockTimeStamp(); + long latestOperationTime = dynamicPropertiesStore.getLatestBlockHeaderTimestamp(); publicNetTime = now; newFreeNetUsage = increase(newFreeNetUsage, bytes, latestConsumeFreeTime, now); newPublicNetUsage = increase(newPublicNetUsage, bytes, publicNetTime, now); @@ -512,9 +522,9 @@ private boolean useFreeNet(AccountCapsule accountCapsule, long bytes, long now) accountCapsule.setLatestConsumeFreeTime(latestConsumeFreeTime); accountCapsule.setLatestOperationTime(latestOperationTime); - chainBaseManager.getDynamicPropertiesStore().savePublicNetUsage(newPublicNetUsage); - chainBaseManager.getDynamicPropertiesStore().savePublicNetTime(publicNetTime); - chainBaseManager.getAccountStore().put(accountCapsule.createDbKey(), accountCapsule); + dynamicPropertiesStore.savePublicNetUsage(newPublicNetUsage); + dynamicPropertiesStore.savePublicNetTime(publicNetTime); + accountStore.put(accountCapsule.createDbKey(), accountCapsule); return true; } diff --git a/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java b/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java index 5cd0f796374..865e0d7a949 100644 --- a/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java +++ b/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java @@ -1,11 +1,9 @@ package org.tron.core.db; import static java.lang.Long.max; -import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCED_INTERVAL; import static org.tron.core.config.Parameter.ChainConstant.TRX_PRECISION; import lombok.extern.slf4j.Slf4j; -import org.tron.common.parameter.CommonParameter; import org.tron.core.capsule.AccountCapsule; import org.tron.core.capsule.TransactionCapsule; import org.tron.core.config.Parameter.AdaptiveResourceLimitConstants; @@ -24,13 +22,6 @@ public EnergyProcessor(DynamicPropertiesStore dynamicPropertiesStore, AccountSto super(dynamicPropertiesStore, accountStore); } - public static long getHeadSlot(DynamicPropertiesStore dynamicPropertiesStore) { - return (dynamicPropertiesStore.getLatestBlockHeaderTimestamp() - - Long.parseLong(CommonParameter.getInstance() - .getGenesisBlock().getTimestamp())) - / BLOCK_PRODUCED_INTERVAL; - } - public void updateUsage(AccountCapsule accountCapsule) { long now = getHeadSlot(); updateUsage(accountCapsule, now); @@ -181,11 +172,6 @@ public long getAccountLeftEnergyFromFreeze(AccountCapsule accountCapsule) { return max(energyLimit - newEnergyUsage, 0); // us } - private long getHeadSlot() { - return getHeadSlot(dynamicPropertiesStore); - } - - } diff --git a/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java b/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java index a7a22390958..387fd09e4b1 100644 --- a/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java +++ b/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java @@ -3,6 +3,7 @@ import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCED_INTERVAL; import static org.tron.core.config.Parameter.ChainConstant.WINDOW_SIZE_PRECISION; +import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.Commons; import org.tron.core.capsule.AccountCapsule; import org.tron.core.capsule.TransactionCapsule; @@ -34,6 +35,20 @@ protected ResourceProcessor(DynamicPropertiesStore dynamicPropertiesStore, AdaptiveResourceLimitConstants.PERIODS_MS / BLOCK_PRODUCED_INTERVAL; } + public long getHeadSlot() { + return (dynamicPropertiesStore.getLatestBlockHeaderTimestamp() + - Long.parseLong(CommonParameter.getInstance() + .getGenesisBlock().getTimestamp())) + / BLOCK_PRODUCED_INTERVAL; + } + + public static long getHeadSlot(DynamicPropertiesStore dynamicPropertiesStore) { + return (dynamicPropertiesStore.getLatestBlockHeaderTimestamp() + - Long.parseLong(CommonParameter.getInstance() + .getGenesisBlock().getTimestamp())) + / BLOCK_PRODUCED_INTERVAL; + } + abstract void consume(TransactionCapsule trx, TransactionTrace trace) throws ContractValidateException, AccountResourceInsufficientException, TooBigTransactionResultException; diff --git a/chainbase/src/main/java/org/tron/core/db/TronDatabase.java b/chainbase/src/main/java/org/tron/core/db/TronDatabase.java index d791e189eb4..f6d82bd3cd9 100644 --- a/chainbase/src/main/java/org/tron/core/db/TronDatabase.java +++ b/chainbase/src/main/java/org/tron/core/db/TronDatabase.java @@ -9,7 +9,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.iq80.leveldb.WriteOptions; -import org.rocksdb.DirectComparator; +import org.rocksdb.AbstractComparator; import org.springframework.beans.factory.annotation.Autowired; import org.tron.common.parameter.CommonParameter; import org.tron.common.storage.WriteOptionsWrapper; @@ -70,7 +70,7 @@ protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { return StorageUtils.getOptionsByDbName(dbName); } - protected DirectComparator getDirectComparator() { + protected AbstractComparator getDirectComparator() { return null; } diff --git a/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java b/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java index 4b75ddee3a4..449df035e13 100644 --- a/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java +++ b/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java @@ -16,7 +16,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.iq80.leveldb.WriteOptions; -import org.rocksdb.DirectComparator; +import org.rocksdb.AbstractComparator; import org.springframework.beans.factory.annotation.Autowired; import org.tron.common.parameter.CommonParameter; import org.tron.common.storage.leveldb.LevelDbDataSourceImpl; @@ -29,12 +29,15 @@ import org.tron.core.db2.common.IRevokingDB; import org.tron.core.db2.common.LevelDB; import org.tron.core.db2.common.RocksDB; +import org.tron.core.db2.common.Value; import org.tron.core.db2.common.WrappedByteArray; import org.tron.core.db2.core.Chainbase; import org.tron.core.db2.core.ITronChainBase; import org.tron.core.db2.core.SnapshotRoot; import org.tron.core.exception.BadItemException; import org.tron.core.exception.ItemNotFoundException; +import org.tron.core.state.StateType; +import org.tron.core.state.WorldStateCallBack; @Slf4j(topic = "DB") @@ -54,6 +57,11 @@ public abstract class TronStoreWithRevoking implements I @Getter private final DB db; + private StateType type; + + @Autowired + protected WorldStateCallBack worldStateCallBack; + protected TronStoreWithRevoking(String dbName) { String dbEngine = CommonParameter.getInstance().getStorage().getDbEngine(); if ("LEVELDB".equals(dbEngine.toUpperCase())) { @@ -75,24 +83,30 @@ protected TronStoreWithRevoking(String dbName) { throw new RuntimeException(String.format("db engine %s is error", dbEngine)); } this.revokingDB = new Chainbase(new SnapshotRoot(this.db)); + type = StateType.get(getDbName()); + } + + protected TronStoreWithRevoking() { + this.db = null; } protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { return StorageUtils.getOptionsByDbName(dbName); } - protected DirectComparator getDirectComparator() { + protected AbstractComparator getDirectComparator() { return null; } protected TronStoreWithRevoking(DB db) { this.db = db; this.revokingDB = new Chainbase(new SnapshotRoot(db)); + type = StateType.get(getDbName()); } @Override public String getDbName() { - return null; + return db.getDbName(); } @PostConstruct @@ -106,12 +120,16 @@ public void put(byte[] key, T item) { if (Objects.isNull(key) || Objects.isNull(item)) { return; } - - revokingDB.put(key, item.getData()); + byte[] value = item.getData(); + revokingDB.put(key, value); + worldStateCallBack.callBack(type, key, value, Value.Operator.PUT); } @Override public void delete(byte[] key) { + worldStateCallBack.callBack(type, key, + StateType.Account == type ? revokingDB.getUnchecked(key) : null, + Value.Operator.DELETE); revokingDB.delete(key); } diff --git a/chainbase/src/main/java/org/tron/core/db/common/DbSourceInter.java b/chainbase/src/main/java/org/tron/core/db/common/DbSourceInter.java index 0823b7a7cf4..a265f3c4b15 100755 --- a/chainbase/src/main/java/org/tron/core/db/common/DbSourceInter.java +++ b/chainbase/src/main/java/org/tron/core/db/common/DbSourceInter.java @@ -17,6 +17,7 @@ */ package org.tron.core.db.common; +import org.tron.common.storage.metric.Stat; import org.tron.core.db2.common.WrappedByteArray; import java.util.Map; @@ -24,7 +25,7 @@ public interface DbSourceInter extends BatchSourceInter, - Iterable> { + Iterable>, Stat { String getDBName(); @@ -44,8 +45,6 @@ public interface DbSourceInter extends BatchSourceInter, long getTotal() throws RuntimeException; - void stat(); - Map prefixQuery(byte[] key); } diff --git a/chainbase/src/main/java/org/tron/core/db2/common/DB.java b/chainbase/src/main/java/org/tron/core/db2/common/DB.java index eae529c5ca9..2858ac06f4d 100644 --- a/chainbase/src/main/java/org/tron/core/db2/common/DB.java +++ b/chainbase/src/main/java/org/tron/core/db2/common/DB.java @@ -1,9 +1,11 @@ package org.tron.core.db2.common; +import org.tron.common.storage.metric.Stat; + import java.util.Iterator; import java.util.Map; -public interface DB extends Iterable>, Instance> { +public interface DB extends Iterable>, Instance>, Stat { V get(K k); @@ -20,6 +22,4 @@ public interface DB extends Iterable>, Instance> void close(); String getDbName(); - - void stat(); } diff --git a/chainbase/src/main/java/org/tron/core/state/StateType.java b/chainbase/src/main/java/org/tron/core/state/StateType.java new file mode 100644 index 00000000000..ab5e73de983 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/StateType.java @@ -0,0 +1,74 @@ +package org.tron.core.state; + +import com.google.common.primitives.Bytes; +import java.util.Arrays; +import lombok.Getter; + +public enum StateType { + + UNDEFINED((byte) 0x00, "undefined"), + + Account((byte) 0x01, "account"), + AccountAsset((byte) 0x02, "account-asset"), + AccountIndex((byte) 0x03, "account-index"), + AccountIdIndex((byte) 0x04, "accountid-index"), + AssetIssue((byte) 0x05, "asset-issue-v2"), + Code((byte) 0x07, "code"), + Contract((byte) 0x08, "contract"), + Delegation((byte) 0x09, "delegation"), + DelegatedResource((byte) 0x0a, "DelegatedResource"), + DelegatedResourceAccountIndex((byte) 0x0b, "DelegatedResourceAccountIndex"), + Exchange((byte) 0x0c, "exchange"), + ExchangeV2((byte) 0x0d, "exchange-v2"), + IncrementalMerkleTree((byte) 0x0e, "IncrementalMerkleTree"), + MarketAccount((byte) 0x0f, "market_account"), + MarketOrder((byte) 0x10, "market_order"), + MarketPairPriceToOrder((byte) 0x11, "market_pair_price_to_order"), + MarketPairToPrice((byte) 0x12, "market_pair_to_price"), + Nullifier((byte) 0x13, "nullifier"), + Properties((byte) 0x14, "properties"), + Proposal((byte) 0x15, "proposal"), + StorageRow((byte) 0x16, "storage-row"), + Votes((byte) 0x17, "votes"), + Witness((byte) 0x18, "witness"), + WitnessSchedule((byte) 0x19, "witness_schedule"), + ContractState((byte) 0x20, "contract-state"); + + + private final byte value; + @Getter + private final String name; + + StateType(byte value, String name) { + this.value = value; + this.name = name; + } + + public byte value() { + return this.value; + } + + public static StateType get(String name) { + return Arrays.stream(StateType.values()).filter(type -> type.name.equals(name)) + .findFirst().orElse(UNDEFINED); + } + + public static StateType get(byte value) { + return Arrays.stream(StateType.values()).filter(type -> type.value == value) + .findFirst().orElse(UNDEFINED); + } + + public static byte[] encodeKey(StateType type, byte[] key) { + byte[] p = new byte[]{type.value}; + return Bytes.concat(p, key); + } + + public static byte[] decodeKey(byte[] key) { + return Arrays.copyOfRange(key, 1, key.length); + } + + public static StateType decodeType(org.apache.tuweni.bytes.Bytes key) { + return StateType.get(key.get(0)); + } + +} diff --git a/chainbase/src/main/java/org/tron/core/state/WorldStateCallBack.java b/chainbase/src/main/java/org/tron/core/state/WorldStateCallBack.java new file mode 100644 index 00000000000..1b860715ddc --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/WorldStateCallBack.java @@ -0,0 +1,192 @@ +package org.tron.core.state; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Longs; +import io.prometheus.client.Histogram; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.springframework.stereotype.Component; +import org.tron.common.parameter.CommonParameter; +import org.tron.common.prometheus.MetricKeys; +import org.tron.common.prometheus.Metrics; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.db2.common.Value; +import org.tron.core.state.trie.TrieImpl2; + +@Slf4j(topic = "State") +@Component +public class WorldStateCallBack { + + @Setter + protected volatile boolean execute; + protected volatile boolean allowGenerateRoot; + protected Map trieEntryList = new HashMap<>(); + @Setter + protected ChainBaseManager chainBaseManager; + + private BlockCapsule blockCapsule; + + @Getter + @VisibleForTesting + private volatile TrieImpl2 trie; + + public WorldStateCallBack() { + // set false when p2p is disabled + this.execute = !CommonParameter.getInstance().isP2pDisable(); + this.allowGenerateRoot = CommonParameter.getInstance().getStorage().isAllowStateRoot(); + } + + public void callBack(StateType type, byte[] key, byte[] value, Value.Operator op) { + if (!exe() || type == StateType.UNDEFINED) { + return; + } + if (op == Value.Operator.DELETE || ArrayUtils.isEmpty(value)) { + if (type == StateType.Account && chainBaseManager.getDynamicPropertiesStore() + .getAllowAccountAssetOptimizationFromRoot() == 1) { + // @see org.tron.core.db2.core.SnapshotRoot#remove(byte[] key) + // @see org.tron.core.db2.core.SnapshotRoot#put(byte[] key, byte[] value) + AccountCapsule accountCapsule = new AccountCapsule(value); + accountCapsule.getAssetMapV2().keySet().forEach(tokenId -> addFix32( + StateType.AccountAsset, com.google.common.primitives.Bytes.concat(key, + Longs.toByteArray(Long.parseLong(tokenId))), + WorldStateQueryInstance.DELETE)); + } + add(type, key, WorldStateQueryInstance.DELETE); + return; + } + if (type == StateType.Account && chainBaseManager.getDynamicPropertiesStore() + .getAllowAccountAssetOptimizationFromRoot() == 1) { + // @see org.tron.core.db2.core.SnapshotRoot#put(byte[] key, byte[] value) + AccountCapsule accountCapsule = new AccountCapsule(value); + if (accountCapsule.getAssetOptimized()) { + accountCapsule.getInstance().getAssetV2Map().forEach((tokenId, amount) -> addFix32( + StateType.AccountAsset, com.google.common.primitives.Bytes.concat(key, + Longs.toByteArray(Long.parseLong(tokenId))), + Longs.toByteArray(amount))); + } else { + accountCapsule.getAssetMapV2().forEach((tokenId, amount) -> addFix32( + StateType.AccountAsset, com.google.common.primitives.Bytes.concat(key, + Longs.toByteArray(Long.parseLong(tokenId))), + Longs.toByteArray(amount))); + accountCapsule.setAssetOptimized(true); + } + value = accountCapsule.getInstance().toBuilder() + .clearAsset() + .clearAssetV2() + .build().toByteArray(); + + } + add(type, key, value); + } + + private void add(StateType type, byte[] key, byte[] value) { + trieEntryList.put(Bytes.of(StateType.encodeKey(type, key)), Bytes.of(value)); + } + + private void addFix32(StateType type, byte[] key, byte[] value) { + trieEntryList.put(fix32(StateType.encodeKey(type, key)), Bytes.of(value)); + } + + public static Bytes32 fix32(byte[] key) { + return Bytes32.rightPad(Bytes.wrap(key)); + } + + public static Bytes32 fix32(Bytes key) { + return Bytes32.rightPad(key); + } + + + protected boolean exe() { + if (!allowGenerateRoot || !execute) { + //Agreement same block high to generate archive root + execute = false; + return false; + } + return true; + } + + @VisibleForTesting + public void clear() { + if (!exe()) { + return; + } + Histogram.Timer timer = trieEntryList.isEmpty() ? null : Metrics.histogramStartTimer( + MetricKeys.Histogram.TRON_STATE_PUT_PER_TRANS_LATENCY); + trieEntryList.forEach((key, value) -> { + Histogram.Timer t = Metrics.histogramStartTimer( + MetricKeys.Histogram.TRON_STATE_PUT_LATENCY, StateType.decodeType(key).getName()); + trie.put(key, value); + Metrics.histogramObserve(t); + }); + trieEntryList.clear(); + Metrics.histogramObserve(timer); + } + + public void preExeTrans() { + clear(); + } + + public void exeTransFinish() { + clear(); + } + + public void preExecute(BlockCapsule blockCapsule) { + this.blockCapsule = blockCapsule; + this.execute = true; + if (!exe()) { + return; + } + try { + BlockCapsule parentBlockCapsule = + chainBaseManager.getBlockById(blockCapsule.getParentBlockId()); + Bytes32 rootHash = parentBlockCapsule.getArchiveRoot(); + trie = new TrieImpl2(chainBaseManager.getMerkleStorage(), rootHash); + } catch (Exception e) { + throw new MerkleTrieException(e.getMessage()); + } + } + + public void executePushFinish() { + if (!exe()) { + return; + } + logger.trace("block num: {}, before root: {}", blockCapsule.getNum(), trie.getRootHashByte32()); + trieEntryList.forEach((key, value) -> + logger.trace("StateType: {}, key: {}, value: {}", + StateType.decodeType(key).getName(), key, value)); + clear(); + trie.commit(); + trie.flush(); + Bytes32 newRoot = trie.getRootHashByte32(); + blockCapsule.setArchiveRoot(newRoot.toArray()); + logger.trace("block num: {}, after root: {}", blockCapsule.getNum(), trie.getRootHashByte32()); + execute = false; + } + + public void initGenesis(BlockCapsule blockCapsule) { + if (!exe()) { + return; + } + trie = new TrieImpl2(chainBaseManager.getMerkleStorage()); + clear(); + trie.commit(); + trie.flush(); + Bytes32 newRoot = trie.getRootHashByte32(); + blockCapsule.setArchiveRoot(newRoot.toArray()); + execute = false; + } + + public void exceptionFinish() { + execute = false; + } + +} diff --git a/chainbase/src/main/java/org/tron/core/state/WorldStateGenesis.java b/chainbase/src/main/java/org/tron/core/state/WorldStateGenesis.java new file mode 100644 index 00000000000..6cbf964cda7 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/WorldStateGenesis.java @@ -0,0 +1,424 @@ +package org.tron.core.state; + +import static org.fusesource.leveldbjni.JniDBFactory.factory; + +import com.google.common.collect.Maps; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.iq80.leveldb.DBException; +import org.iq80.leveldb.DBIterator; +import org.rocksdb.BlockBasedTableConfig; +import org.rocksdb.BloomFilter; +import org.rocksdb.ComparatorOptions; +import org.rocksdb.LRUCache; +import org.rocksdb.Options; +import org.rocksdb.ReadOptions; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; +import org.springframework.stereotype.Component; +import org.tron.common.parameter.CommonParameter; +import org.tron.common.utils.DbOptionalsUtils; +import org.tron.common.utils.FileUtil; +import org.tron.common.utils.MarketOrderPriceComparatorForLevelDB; +import org.tron.common.utils.MarketOrderPriceComparatorForRockDB; +import org.tron.common.utils.PropUtil; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.db.BlockStore; +import org.tron.core.exception.HeaderNotFound; + +@Component("worldStateGenesis") +@Slf4j(topic = "DB") +public class WorldStateGenesis { + + private ChainBaseManager chainBaseManager; + + private final boolean allowStateRoot = CommonParameter.getInstance().getStorage() + .isAllowStateRoot(); + + private Path stateGenesisPath = Paths.get(CommonParameter.getInstance().getStorage() + .getStateGenesisDirectory()); + + @Getter + private long stateGenesisHeight; + + private long genesisHeight; + + private static final String STATE_GENESIS_PROPERTIES = "genesis.properties"; + + private static final String STATE_GENESIS_HEIGHT = "height"; + + private final Map genesisDBs = Maps.newConcurrentMap(); + + private volatile boolean inited = false; + + private static final String KEY_ENGINE = "ENGINE"; + private static final String ENGINE_FILE = "engine.properties"; + private static final String ROCKSDB = "ROCKSDB"; + + public synchronized void init(ChainBaseManager chainBaseManager) { + if (!allowStateRoot) { + return; + } + this.chainBaseManager = chainBaseManager; + genesisHeight = chainBaseManager.getGenesisBlockId().getNum(); + tryInitGenesis(); + initGenesisDBs(); + inited = true; + } + + @PostConstruct + private void open() { + if (!stateGenesisPath.isAbsolute()) { + stateGenesisPath = Paths.get(CommonParameter.getInstance().getOutputDirectory(), + CommonParameter.getInstance().getStorage().getStateGenesisDirectory()); + } + } + + @PreDestroy + private void close() { + if (!inited) { + return; + } + genesisDBs.values().forEach(db -> { + try { + db.close(); + } catch (IOException e) { + logger.warn(db.name(), e.getMessage()); + } + }); + genesisDBs.clear(); + } + + public byte[] get(StateType type, byte[] key) { + if (!allowStateRoot) { + throw new IllegalStateException("StateRoot is not allowed."); + } + if (!inited) { + throw new IllegalStateException("StateRoot is not inited."); + } + + if (stateGenesisHeight == 0) { + return null; + } + + if (stateGenesisHeight > genesisHeight) { + try { + return genesisDBs.get(type).get(key); + } catch (RocksDBException | DBException e) { + throw new RuntimeException(type.getName(), e); + } + } else { + throw new IllegalStateException("stateGenesis is not available."); + } + } + + public Map prefixQuery(StateType type, byte[] key) { + if (!allowStateRoot) { + throw new IllegalStateException("StateRoot is not allowed."); + } + if (!inited) { + throw new IllegalStateException("StateRoot is not inited."); + } + + if (stateGenesisHeight == 0) { + return Collections.emptyMap(); + } + + if (stateGenesisHeight > genesisHeight) { + return genesisDBs.get(type).prefixQuery(key); + } else { + throw new IllegalStateException("stateGenesis is not available."); + } + } + + private void tryInitGenesis() { + if ((this.stateGenesisHeight = tryFindStateGenesisHeight()) > -1) { + return; + } + // copy state db + initGenesis(); + // reset root + resetArchiveRoot(); + // init genesis properties + initGenesisProperties(); + } + + public void resetArchiveRoot() { + BlockStore blockStore = chainBaseManager.getBlockStore(); + BlockCapsule blockCapsule = null; + try { + blockCapsule = chainBaseManager.getHead(); + if (blockCapsule.getNum() == genesisHeight) { + logger.debug("skip reset archive root in case of the head is 0"); + return; + } + } catch (HeaderNotFound e) { + logger.error("reset archive root failed, err: {}", e.getMessage()); + System.exit(1); + } + logger.info("reset archive root, number: {}, prev root: {}", + blockCapsule.getNum(), + blockCapsule.getArchiveRoot()); + blockCapsule.setArchiveRoot(Bytes32.ZERO.toArray()); + blockStore.put(blockCapsule.getBlockId().getBytes(), blockCapsule); + } + + private void initGenesisDBs() { + if (this.stateGenesisHeight == genesisHeight) { + return; + } + Arrays.stream(StateType.values()).filter(type -> type != StateType.UNDEFINED) + .parallel().forEach(type -> { + try { + genesisDBs.put(type, getDb(stateGenesisPath, type.getName())); + } catch (RocksDBException | IOException e) { + throw new RuntimeException(e); + } + }); + } + + private DB getDb(Path sourceDir, String dbName) throws RocksDBException, IOException { + File engineFile = Paths.get(sourceDir.toString(), dbName, ENGINE_FILE).toFile(); + if (!engineFile.exists()) { + return new LevelDB(sourceDir, dbName); + } + String engine = PropUtil.readProperty(engineFile.toString(), KEY_ENGINE); + if (engine.equalsIgnoreCase(ROCKSDB)) { + return new RocksDB(sourceDir, dbName); + } else { + return new LevelDB(sourceDir, dbName); + } + } + + private void initGenesis() { + logger.info("State genesis init start"); + FileUtil.createDirIfNotExists(stateGenesisPath.toString()); + long height = chainBaseManager.getHeadBlockNum(); + if (height == genesisHeight) { + logger.info("Skip state genesis init since head is {}", height); + return; + } + List dbs = Arrays.stream(StateType.values()) + .filter(type -> type != StateType.UNDEFINED) + .map(StateType::getName).collect(Collectors.toList()); + Path source = Paths.get(CommonParameter.getInstance().getOutputDirectory(), + CommonParameter.getInstance().getStorage().getDbDirectory()); + // check dbs if exist + List miss = dbs.stream().map(db -> Paths.get(source.toString(), db) + .toFile()).filter(db -> !db.exists()).map(File::getName).collect(Collectors.toList()); + if (!miss.isEmpty()) { + logger.error("Corrupted source path, miss : {}", miss); + throw new IllegalArgumentException(String.format("Corrupted source path: %s", miss)); + } + // delete if exit + dbs.stream().map(db -> Paths.get(stateGenesisPath.toString(), db).toFile()) + .filter(File::exists).forEach(dir -> { + logger.info("Delete corrupted state genesis path : {}", dir); + FileUtil.deleteDir(dir); + }); + FileUtil.copyDatabases(source, stateGenesisPath, dbs); + logger.info("State genesis init end, {}, {}", stateGenesisPath, dbs); + } + + private void initGenesisProperties() { + logger.info("State genesis properties init start"); + long height = chainBaseManager.getHeadBlockNum(); + Map properties = new HashMap<>(); + properties.put(STATE_GENESIS_HEIGHT, String.valueOf(height)); + String genesisFile = new File(stateGenesisPath.toString(), STATE_GENESIS_PROPERTIES).toString(); + PropUtil.writeProperties(genesisFile, properties); + this.stateGenesisHeight = height; + logger.info("State genesis properties init end, detail: {}", properties); + + } + + private long tryFindStateGenesisHeight() { + // Read "genesis.properties" file, which contains a pointer to the current header + File genesisFile = new File(stateGenesisPath.toString(), STATE_GENESIS_PROPERTIES); + if (!genesisFile.exists()) { + return -1; + } + + String height = PropUtil.readProperty(genesisFile.toString(), STATE_GENESIS_HEIGHT); + if (StringUtils.isEmpty(height)) { + return -1; + } + long header = Long.parseLong(height); + logger.info("State genesis header :{}", header); + return header; + } + + + interface DB extends Closeable { + byte[] get(byte[] key) throws RocksDBException; + + Map prefixQuery(byte[] key); + + String name(); + } + + static class RocksDB implements DB { + + private final org.rocksdb.RocksDB rocksDB; + private final String name; + + private static final String MARKET_PAIR_PRICE_TO_ORDER = "market_pair_price_to_order"; + + private static final LRUCache CACHE = new LRUCache(128 * 1024 * 1024L); + + public RocksDB(Path path, String name) throws RocksDBException { + this.name = name; + this.rocksDB = newRocksDbReadOnly(Paths.get(path.toString(), name)); + } + + @Override + public byte[] get(byte[] key) throws RocksDBException { + return this.rocksDB.get(key); + } + + @Override + public Map prefixQuery(byte[] key) { + try (ReadOptions readOptions = new ReadOptions().setFillCache(false); + RocksIterator iterator = this.rocksDB.newIterator(readOptions)) { + Map result = new HashMap<>(); + for (iterator.seek(key); iterator.isValid(); iterator.next()) { + if (com.google.common.primitives.Bytes.indexOf(iterator.key(), key) == 0) { + result.put(Bytes.wrap(iterator.key()), Bytes.wrap(iterator.value())); + } else { + return result; + } + } + return result; + } + } + + @Override + public String name() { + return this.name; + } + + private org.rocksdb.RocksDB newRocksDbReadOnly(Path db) throws RocksDBException { + try (Options options = newDefaultRocksDbOptions()) { + if (MARKET_PAIR_PRICE_TO_ORDER.equalsIgnoreCase(db.getFileName().toString())) { + options.setComparator(new MarketOrderPriceComparatorForRockDB(new ComparatorOptions())); + } + return org.rocksdb.RocksDB.openReadOnly(options, db.toString()); + } + } + + private Options newDefaultRocksDbOptions() { + Options options = new Options(); + options.setCreateIfMissing(false); + options.setIncreaseParallelism(1); + options.setNumLevels(7); + options.setMaxOpenFiles(100); + options.setTargetFileSizeBase(64 * 1024 * 1024); + options.setTargetFileSizeMultiplier(1); + options.setMaxBytesForLevelBase(512 * 1024 * 1024); + options.setMaxBackgroundCompactions(Math.max(1, Runtime.getRuntime().availableProcessors())); + options.setLevel0FileNumCompactionTrigger(4); + options.setLevelCompactionDynamicLevelBytes(true); + final BlockBasedTableConfig tableCfg; + options.setTableFormatConfig(tableCfg = new BlockBasedTableConfig()); + tableCfg.setBlockSize(64 * 1024); + tableCfg.setBlockCache(CACHE); + tableCfg.setCacheIndexAndFilterBlocks(true); + tableCfg.setPinL0FilterAndIndexBlocksInCache(true); + tableCfg.setFilter(new BloomFilter(10, false)); + return options; + } + + @Override + public void close() throws IOException { + this.rocksDB.close(); + } + } + + static class LevelDB implements DB { + + private final org.iq80.leveldb.DB levelDB; + private final String name; + + private static final String MARKET_PAIR_PRICE_TO_ORDER = "market_pair_price_to_order"; + + public LevelDB(Path path, String name) throws IOException { + this.name = name; + this.levelDB = newLevelDb(Paths.get(path.toString(), name)); + } + + @Override + public byte[] get(byte[] key) throws DBException { + return this.levelDB.get(key); + } + + @Override + public Map prefixQuery(byte[] key) { + try (DBIterator iterator = this.levelDB.iterator( + new org.iq80.leveldb.ReadOptions().fillCache(false))) { + Map result = new HashMap<>(); + for (iterator.seek(key); iterator.hasNext(); iterator.next()) { + Map.Entry entry = iterator.peekNext(); + if (com.google.common.primitives.Bytes.indexOf(entry.getKey(), key) == 0) { + result.put(Bytes.wrap(entry.getKey()), Bytes.wrap(entry.getValue())); + } else { + return result; + } + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String name() { + return this.name; + } + + private org.iq80.leveldb.DB newLevelDb(Path db) throws IOException { + org.iq80.leveldb.Options options = createDefaultDbOptions(); + if (MARKET_PAIR_PRICE_TO_ORDER.equalsIgnoreCase(db.getFileName().toString())) { + options.comparator(new MarketOrderPriceComparatorForLevelDB()); + } + return factory.open(db.toFile(), options); + } + + private org.iq80.leveldb.Options createDefaultDbOptions() { + org.iq80.leveldb.Options options = new org.iq80.leveldb.Options(); + options.createIfMissing(false); + options.paranoidChecks(true); + options.verifyChecksums(true); + + options.compressionType(DbOptionalsUtils.DEFAULT_COMPRESSION_TYPE); + options.blockSize(DbOptionalsUtils.DEFAULT_BLOCK_SIZE); + options.writeBufferSize(DbOptionalsUtils.DEFAULT_WRITE_BUFFER_SIZE); + options.cacheSize(DbOptionalsUtils.DEFAULT_CACHE_SIZE); + options.maxOpenFiles(DbOptionalsUtils.DEFAULT_MAX_OPEN_FILES); + return options; + } + + @Override + public void close() throws IOException { + this.levelDB.close(); + } + } + + +} diff --git a/chainbase/src/main/java/org/tron/core/state/WorldStateQueryInstance.java b/chainbase/src/main/java/org/tron/core/state/WorldStateQueryInstance.java new file mode 100644 index 00000000000..0e5a86c9fd7 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/WorldStateQueryInstance.java @@ -0,0 +1,214 @@ +package org.tron.core.state; + +import static org.tron.core.state.WorldStateCallBack.fix32; + +import com.google.common.primitives.Longs; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import lombok.Getter; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.DecodeUtil; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.AssetIssueCapsule; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.capsule.BytesCapsule; +import org.tron.core.capsule.CodeCapsule; +import org.tron.core.capsule.ContractCapsule; +import org.tron.core.capsule.ContractStateCapsule; +import org.tron.core.capsule.DelegatedResourceAccountIndexCapsule; +import org.tron.core.capsule.DelegatedResourceCapsule; +import org.tron.core.capsule.StorageRowCapsule; +import org.tron.core.capsule.VotesCapsule; +import org.tron.core.capsule.WitnessCapsule; +import org.tron.core.exception.ItemNotFoundException; +import org.tron.core.exception.StoreException; +import org.tron.core.state.trie.TrieImpl2; +import org.tron.protos.Protocol; + +public class WorldStateQueryInstance { + + private final TrieImpl2 trieImpl; + + @Getter + private final Bytes32 rootHash; + + public static final byte[] DELETE = UInt256.ZERO.toArray(); + public static final int ADDRESS_SIZE = DecodeUtil.ADDRESS_SIZE >> 1; + public static final Bytes MAX_ASSET_ID = Bytes.ofUnsignedLong(Long.MAX_VALUE); + public static final Bytes MIN_ASSET_ID = Bytes.ofUnsignedLong(0); + + private final WorldStateGenesis worldStateGenesis; + private final ChainBaseManager chainBaseManager; + + public WorldStateQueryInstance(Bytes32 rootHash, ChainBaseManager chainBaseManager) { + this.rootHash = rootHash; + this.trieImpl = new TrieImpl2(chainBaseManager.getMerkleStorage(), rootHash); + this.worldStateGenesis = chainBaseManager.getWorldStateGenesis(); + this.chainBaseManager = chainBaseManager; + } + + private byte[] get(StateType type, byte[] key) { + byte[] encodeKey = StateType.encodeKey(type, key); + Bytes value = trieImpl.get(encodeKey); + + if (Objects.nonNull(value)) { + if (Objects.equals(value, UInt256.ZERO)) { + return null; + } + return value.toArrayUnsafe(); + } + return worldStateGenesis.get(type, key); + } + + public AccountCapsule getAccount(byte[] address) { + byte[] value = get(StateType.Account, address); + AccountCapsule accountCapsule = null; + if (Objects.nonNull(value)) { + accountCapsule = new AccountCapsule(value); + accountCapsule.setRoot(rootHash); + } + return accountCapsule; + } + + public Long getAccountAsset(byte[] address, long tokenId) { + byte[] key = com.google.common.primitives.Bytes.concat(address, + Bytes.ofUnsignedLong(tokenId).toArray()); + byte[] encodeKey = StateType.encodeKey(StateType.AccountAsset, key); + Bytes value = trieImpl.get(fix32(encodeKey)); + if (Objects.nonNull(value)) { + if (Objects.equals(value, UInt256.ZERO)) { + return null; + } + return value.toLong(); + } + byte[] v = worldStateGenesis.get(StateType.AccountAsset, + com.google.common.primitives.Bytes.concat(address, + Long.toString(tokenId).getBytes())); + return Objects.nonNull(v) ? Longs.fromByteArray(v) : null; + } + + public long getAccountAsset(Protocol.Account account, long tokenId) { + Long amount = getAccountAsset(account.getAddress().toByteArray(), tokenId); + return amount == null ? 0 : amount; + } + + public boolean hasAssetV2(Protocol.Account account, long tokenId) { + return getAccountAsset(account.getAddress().toByteArray(), tokenId) != null; + } + + public Map importAllAsset(Protocol.Account account) { + Map assets = new TreeMap<>(); + Map genesis = worldStateGenesis.prefixQuery(StateType.AccountAsset, + account.getAddress().toByteArray()); + genesis.forEach((k, v) -> assets.put( + ByteArray.toStr(k.slice(ADDRESS_SIZE).toArray()), + v.toLong()) + ); + Bytes address = Bytes.of(account.getAddress().toByteArray()); + Bytes32 min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), address, + MIN_ASSET_ID)); + + Bytes32 max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), address, + MAX_ASSET_ID)); + TreeMap state = trieImpl.entriesFrom(min, max); + // remove asset is deleted + state.entrySet().stream() + .filter(e -> !Objects.equals(e.getValue(), UInt256.ZERO)) + .forEach(e -> assets.put( + String.valueOf( + e.getKey().slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong()), + e.getValue().toLong())); + // remove asset = 0 + assets.entrySet().removeIf(e -> e.getValue() <= 0); + return assets; + } + + // contract + public ContractCapsule getContract(byte[] address) { + byte[] value = get(StateType.Contract, address); + return Objects.nonNull(value) ? new ContractCapsule(value) : null; + } + + public ContractStateCapsule getContractState(byte[] address) { + byte[] value = get(StateType.ContractState, address); + return Objects.nonNull(value) ? new ContractStateCapsule(value) : null; + } + + public CodeCapsule getCode(byte[] address) { + byte[] value = get(StateType.Code, address); + return Objects.nonNull(value) ? new CodeCapsule(value) : null; + } + + + // never return null + public StorageRowCapsule getStorageRow(byte[] key) { + byte[] value = get(StateType.StorageRow, key); + StorageRowCapsule storageRowCapsule = new StorageRowCapsule(value); + storageRowCapsule.setRowKey(key); + return storageRowCapsule; + } + + // asset + public AssetIssueCapsule getAssetIssue(byte[] tokenId) { + byte[] value = get(StateType.AssetIssue, tokenId); + return Objects.nonNull(value) ? new AssetIssueCapsule(value) : null; + } + + // witness + public WitnessCapsule getWitness(byte[] address) { + byte[] value = get(StateType.Witness, address); + return Objects.nonNull(value) ? new WitnessCapsule(value) : null; + } + + // delegate + public DelegatedResourceCapsule getDelegatedResource(byte[] key) { + byte[] value = get(StateType.DelegatedResource, key); + return Objects.nonNull(value) ? new DelegatedResourceCapsule(value) : null; + } + + public BytesCapsule getDelegation(byte[] key) { + byte[] value = get(StateType.Delegation, key); + return Objects.nonNull(value) ? new BytesCapsule(value) : null; + } + + // TODO prefix query + public DelegatedResourceAccountIndexCapsule getDelegatedResourceAccountIndex(byte[] key) { + byte[] value = get(StateType.DelegatedResourceAccountIndex, key); + return Objects.nonNull(value) ? new DelegatedResourceAccountIndexCapsule(value) : null; + } + + // vote + public VotesCapsule getVotes(byte[] address) { + byte[] value = get(StateType.Votes, address); + return Objects.nonNull(value) ? new VotesCapsule(value) : null; + } + + // properties + public BytesCapsule getDynamicProperty(byte[] key) throws ItemNotFoundException { + BytesCapsule value = getUncheckedDynamicProperty(key); + if (Objects.nonNull(value)) { + return value; + } else { + throw new ItemNotFoundException(); + } + } + + public BytesCapsule getUncheckedDynamicProperty(byte[] key) { + byte[] value = get(StateType.Properties, key); + if (Objects.nonNull(value)) { + return new BytesCapsule(value); + } + return null; + } + + // getBlockByNum + public BlockCapsule getBlockByNum(long num) throws StoreException { + return chainBaseManager.getBlockByNum(num); + } + +} diff --git a/chainbase/src/main/java/org/tron/core/state/annotation/NeedWorldStateTrieStoreCondition.java b/chainbase/src/main/java/org/tron/core/state/annotation/NeedWorldStateTrieStoreCondition.java new file mode 100644 index 00000000000..acdd7ff762a --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/annotation/NeedWorldStateTrieStoreCondition.java @@ -0,0 +1,13 @@ +package org.tron.core.state.annotation; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.tron.common.parameter.CommonParameter; + +public class NeedWorldStateTrieStoreCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return CommonParameter.getInstance().getStorage().isAllowStateRoot(); + } +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/AccountStateStore.java b/chainbase/src/main/java/org/tron/core/state/store/AccountStateStore.java new file mode 100644 index 00000000000..2612099d363 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/AccountStateStore.java @@ -0,0 +1,117 @@ +package org.tron.core.state.store; + +import org.rocksdb.AbstractComparator; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.db2.common.WrappedByteArray; +import org.tron.core.db2.core.Chainbase; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.store.AccountStore; + +import java.util.Iterator; +import java.util.Map; + +public class AccountStateStore extends AccountStore implements StateStore { + + private WorldStateQueryInstance worldStateQueryInstance; + + public AccountStateStore(WorldStateQueryInstance worldStateQueryInstance) { + this.worldStateQueryInstance = worldStateQueryInstance; + } + + //**** Override Operation For StateDB + + @Override + public String getDbName() { + return worldStateQueryInstance.getRootHash().toHexString(); + } + + @Override + public AccountCapsule get(byte[] key) { + return getFromRoot(key); + } + + @Override + public AccountCapsule getFromRoot(byte[] key) { + return getUnchecked(key); + + } + + @Override + public AccountCapsule getUnchecked(byte[] key) { + return worldStateQueryInstance.getAccount(key); + } + + @Override + public boolean has(byte[] key) { + return getUnchecked(key) != null; + } + + @Override + public void close() { + this.worldStateQueryInstance = null; + } + + @Override + public void reset() { + } + + //**** Override Operation For StateDB + + //**** Unsupported Operation For StateDB + + public static void setAccount(com.typesafe.config.Config config) { + throw new UnsupportedOperationException(); + } + + protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { + throw new UnsupportedOperationException(); + } + + protected AbstractComparator getDirectComparator() { + throw new UnsupportedOperationException(); + } + + @Override + public void put(byte[] key, AccountCapsule item) { + throwIfError(); + } + + @Override + public void delete(byte[] key) { + throwIfError(); + } + + @Override + public AccountCapsule of(byte[] value) { + throwIfError(); + return null; + } + + @Override + public boolean isNotEmpty() { + throwIfError(); + return false; + } + + @Override + public Iterator> iterator() { + throwIfError(); + return null; + } + + public long size() { + throwIfError(); + return 0; + } + + public void setCursor(Chainbase.Cursor cursor) { + throwIfError(); + } + + public Map prefixQuery(byte[] key) { + throwIfError(); + return null; + } + + //**** Unsupported Operation For StateDB +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/AssetIssueV2StateStore.java b/chainbase/src/main/java/org/tron/core/state/store/AssetIssueV2StateStore.java new file mode 100644 index 00000000000..736444ecaf9 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/AssetIssueV2StateStore.java @@ -0,0 +1,118 @@ +package org.tron.core.state.store; + +import lombok.extern.slf4j.Slf4j; +import org.rocksdb.AbstractComparator; +import org.tron.core.capsule.AssetIssueCapsule; +import org.tron.core.db2.common.WrappedByteArray; +import org.tron.core.db2.core.Chainbase; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.store.AssetIssueV2Store; + +import java.util.Iterator; +import java.util.Map; + +@Slf4j(topic = "DB") + +public class AssetIssueV2StateStore extends AssetIssueV2Store implements StateStore { + + private WorldStateQueryInstance worldStateQueryInstance; + + + public AssetIssueV2StateStore(WorldStateQueryInstance worldStateQueryInstance) { + this.worldStateQueryInstance = worldStateQueryInstance; + } + + //**** Override Operation For StateDB + + @Override + public String getDbName() { + return worldStateQueryInstance.getRootHash().toHexString(); + } + + @Override + public AssetIssueCapsule get(byte[] key) { + return getFromRoot(key); + } + + @Override + public AssetIssueCapsule getFromRoot(byte[] key) { + return getUnchecked(key); + + } + + @Override + public AssetIssueCapsule getUnchecked(byte[] key) { + return worldStateQueryInstance.getAssetIssue(key); + } + + @Override + public boolean has(byte[] key) { + return getUnchecked(key) != null; + } + + @Override + public void close() { + this.worldStateQueryInstance = null; + } + + @Override + public void reset() { + } + + //**** Override Operation For StateDB + + //**** Unsupported Operation For StateDB + + protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { + throw new UnsupportedOperationException(); + } + + protected AbstractComparator getDirectComparator() { + throw new UnsupportedOperationException(); + } + + @Override + public void put(byte[] key, AssetIssueCapsule item) { + throwIfError(); + } + + @Override + public void delete(byte[] key) { + throwIfError(); + } + + @Override + public AssetIssueCapsule of(byte[] value) { + throwIfError(); + return null; + } + + @Override + public boolean isNotEmpty() { + throwIfError(); + return false; + } + + @Override + public Iterator> iterator() { + throwIfError(); + return null; + } + + public long size() { + throwIfError(); + return 0; + } + + public void setCursor(Chainbase.Cursor cursor) { + throwIfError(); + } + + public Map prefixQuery(byte[] key) { + throwIfError(); + return null; + } + + //**** Unsupported Operation For StateDB + +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/DelegationStateStore.java b/chainbase/src/main/java/org/tron/core/state/store/DelegationStateStore.java new file mode 100644 index 00000000000..1da81908ac7 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/DelegationStateStore.java @@ -0,0 +1,113 @@ +package org.tron.core.state.store; + +import org.rocksdb.AbstractComparator; +import org.tron.core.capsule.BytesCapsule; +import org.tron.core.db2.common.WrappedByteArray; +import org.tron.core.db2.core.Chainbase; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.store.DelegationStore; + +import java.util.Iterator; +import java.util.Map; + +public class DelegationStateStore extends DelegationStore implements StateStore { + + private WorldStateQueryInstance worldStateQueryInstance; + + public DelegationStateStore(WorldStateQueryInstance worldStateQueryInstance) { + this.worldStateQueryInstance = worldStateQueryInstance; + } + + //**** Override Operation For StateDB + + @Override + public String getDbName() { + return worldStateQueryInstance.getRootHash().toHexString(); + } + + @Override + public BytesCapsule get(byte[] key) { + return getFromRoot(key); + } + + @Override + public BytesCapsule getFromRoot(byte[] key) { + return getUnchecked(key); + + } + + @Override + public BytesCapsule getUnchecked(byte[] key) { + return worldStateQueryInstance.getDelegation(key); + } + + @Override + public boolean has(byte[] key) { + return getUnchecked(key) != null; + } + + @Override + public void close() { + this.worldStateQueryInstance = null; + } + + @Override + public void reset() { + } + + //**** Override Operation For StateDB + + //**** Unsupported Operation For StateDB + + protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { + throw new UnsupportedOperationException(); + } + + protected AbstractComparator getDirectComparator() { + throw new UnsupportedOperationException(); + } + + @Override + public void put(byte[] key, BytesCapsule item) { + throwIfError(); + } + + @Override + public void delete(byte[] key) { + throwIfError(); + } + + @Override + public BytesCapsule of(byte[] value) { + throwIfError(); + return null; + } + + @Override + public boolean isNotEmpty() { + throwIfError(); + return false; + } + + @Override + public Iterator> iterator() { + throwIfError(); + return null; + } + + public long size() { + throwIfError(); + return 0; + } + + public void setCursor(Chainbase.Cursor cursor) { + throwIfError(); + } + + public Map prefixQuery(byte[] key) { + throwIfError(); + return null; + } + + //**** Unsupported Operation For StateDB +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/DynamicPropertiesStateStore.java b/chainbase/src/main/java/org/tron/core/state/store/DynamicPropertiesStateStore.java new file mode 100644 index 00000000000..116ecae1361 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/DynamicPropertiesStateStore.java @@ -0,0 +1,114 @@ +package org.tron.core.state.store; + +import org.rocksdb.AbstractComparator; +import org.tron.core.capsule.BytesCapsule; +import org.tron.core.db2.common.WrappedByteArray; +import org.tron.core.db2.core.Chainbase; +import org.tron.core.exception.ItemNotFoundException; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.store.DynamicPropertiesStore; + +import java.util.Iterator; +import java.util.Map; + +public class DynamicPropertiesStateStore extends DynamicPropertiesStore implements StateStore { + + private WorldStateQueryInstance worldStateQueryInstance; + + public DynamicPropertiesStateStore(WorldStateQueryInstance worldStateQueryInstance) { + this.worldStateQueryInstance = worldStateQueryInstance; + } + + //**** Override Operation For StateDB + + @Override + public String getDbName() { + return worldStateQueryInstance.getRootHash().toHexString(); + } + + @Override + public BytesCapsule get(byte[] key) throws ItemNotFoundException { + return getFromRoot(key); + } + + @Override + public BytesCapsule getFromRoot(byte[] key) throws ItemNotFoundException { + return worldStateQueryInstance.getDynamicProperty(key); + + } + + @Override + public BytesCapsule getUnchecked(byte[] key) { + return worldStateQueryInstance.getUncheckedDynamicProperty(key); + } + + @Override + public boolean has(byte[] key) { + return getUnchecked(key) != null; + } + + @Override + public void close() { + this.worldStateQueryInstance = null; + } + + @Override + public void reset() { + } + + //**** Override Operation For StateDB + + //**** Unsupported Operation For StateDB + + protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { + throw new UnsupportedOperationException(); + } + + protected AbstractComparator getDirectComparator() { + throw new UnsupportedOperationException(); + } + + @Override + public void put(byte[] key, BytesCapsule item) { + throwIfError(); + } + + @Override + public void delete(byte[] key) { + throwIfError(); + } + + @Override + public BytesCapsule of(byte[] value) { + throwIfError(); + return null; + } + + @Override + public boolean isNotEmpty() { + throwIfError(); + return false; + } + + @Override + public Iterator> iterator() { + throwIfError(); + return null; + } + + public long size() { + throwIfError(); + return 0; + } + + public void setCursor(Chainbase.Cursor cursor) { + throwIfError(); + } + + public Map prefixQuery(byte[] key) { + throwIfError(); + return null; + } + + //**** Unsupported Operation For StateDB +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/KeyValueMerkleCacheStorage.java b/chainbase/src/main/java/org/tron/core/state/store/KeyValueMerkleCacheStorage.java new file mode 100644 index 00000000000..1251d09693a --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/KeyValueMerkleCacheStorage.java @@ -0,0 +1,99 @@ +package org.tron.core.state.store; + +import com.google.common.cache.CacheLoader; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.trie.KeyValueMerkleStorage; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; +import org.tron.common.cache.CacheManager; +import org.tron.common.cache.CacheType; +import org.tron.common.cache.TronCache; +import org.tron.core.state.StateType; +import org.tron.core.state.annotation.NeedWorldStateTrieStoreCondition; + +@Component +@Conditional(NeedWorldStateTrieStoreCondition.class) +public class KeyValueMerkleCacheStorage extends KeyValueMerkleStorage { + + private final Map>> cache; + + private final List cacheTypes = Arrays.asList( + StateType.DelegatedResource, StateType.DelegatedResourceAccountIndex, + StateType.StorageRow, StateType.Account, + StateType.Votes, + StateType.Code, StateType.Contract); + + @Autowired + public KeyValueMerkleCacheStorage(@Autowired KeyValueStorage keyValueStorage) { + super(keyValueStorage); + cache = Collections.synchronizedMap(new HashMap<>()); + for (StateType stateType : cacheTypes) { + cache.put(stateType, CacheManager.allocate(CacheType.findByType( + CacheType.worldStateTrie.type + '.' + stateType.getName()), + new CacheLoader>() { + @Override + public Optional load(@NotNull Bytes32 key) { + return get(key); + } + }, (key, value) -> Bytes32.SIZE + value.orElse(Bytes.EMPTY).size())); + } + } + + @Override + public Optional get(final Bytes location, final Bytes32 hash) { + try { + StateType stateType = parse(location); + if (stateType != StateType.UNDEFINED) { + return cache.get(stateType).get(hash); + } + return get(hash); + } catch (ExecutionException e) { + throw new MerkleTrieException(e.getMessage(), hash, location); + } + } + + + private Optional get(final Bytes32 hash) { + return super.get(null, hash); + } + + @Override + public void put(final Bytes location, final Bytes32 hash, final Bytes value) { + super.put(location, hash, value); + StateType stateType = parse(location); + if (stateType != StateType.UNDEFINED) { + cache.get(stateType).put(hash, Optional.of(value)); + } + } + + private StateType parse(final Bytes location) { + byte stateTypeLen = Byte.BYTES << 1; + if (location.size() < stateTypeLen) { + return StateType.UNDEFINED; + } + byte high = location.get(0); + byte low = location.get(1); + if ((high & 0xf0) != 0 || (low & 0xf0) != 0) { + throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble"); + } + + byte type = (byte) (high << 4 | low); + StateType s = StateType.get(type); + if (cacheTypes.contains(s)) { + return s; + } + return StateType.UNDEFINED; + } +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/StateStore.java b/chainbase/src/main/java/org/tron/core/state/store/StateStore.java new file mode 100644 index 00000000000..cb5612ca89d --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/StateStore.java @@ -0,0 +1,9 @@ +package org.tron.core.state.store; + + +public interface StateStore { + + default void throwIfError() { + throw new UnsupportedOperationException(); + } +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/StorageRowStateStore.java b/chainbase/src/main/java/org/tron/core/state/store/StorageRowStateStore.java new file mode 100644 index 00000000000..847eeba10e6 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/StorageRowStateStore.java @@ -0,0 +1,113 @@ +package org.tron.core.state.store; + +import org.rocksdb.AbstractComparator; +import org.tron.core.capsule.StorageRowCapsule; +import org.tron.core.db2.common.WrappedByteArray; +import org.tron.core.db2.core.Chainbase; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.store.StorageRowStore; + +import java.util.Iterator; +import java.util.Map; + +public class StorageRowStateStore extends StorageRowStore implements StateStore { + + private WorldStateQueryInstance worldStateQueryInstance; + + public StorageRowStateStore(WorldStateQueryInstance worldStateQueryInstance) { + this.worldStateQueryInstance = worldStateQueryInstance; + } + + //**** Override Operation For StateDB + + @Override + public String getDbName() { + return worldStateQueryInstance.getRootHash().toHexString(); + } + + @Override + public StorageRowCapsule get(byte[] key) { + return getFromRoot(key); + } + + @Override + public StorageRowCapsule getFromRoot(byte[] key) { + return getUnchecked(key); + + } + + @Override + public StorageRowCapsule getUnchecked(byte[] key) { + return worldStateQueryInstance.getStorageRow(key); + } + + @Override + public boolean has(byte[] key) { + return getUnchecked(key).getData() != null; + } + + @Override + public void close() { + this.worldStateQueryInstance = null; + } + + @Override + public void reset() { + } + + //**** Override Operation For StateDB + + //**** Unsupported Operation For StateDB + + protected org.iq80.leveldb.Options getOptionsByDbNameForLevelDB(String dbName) { + throw new UnsupportedOperationException(); + } + + protected AbstractComparator getDirectComparator() { + throw new UnsupportedOperationException(); + } + + @Override + public void put(byte[] key, StorageRowCapsule item) { + throwIfError(); + } + + @Override + public void delete(byte[] key) { + throwIfError(); + } + + @Override + public StorageRowCapsule of(byte[] value) { + throwIfError(); + return null; + } + + @Override + public boolean isNotEmpty() { + throwIfError(); + return false; + } + + @Override + public Iterator> iterator() { + throwIfError(); + return null; + } + + public long size() { + throwIfError(); + return 0; + } + + public void setCursor(Chainbase.Cursor cursor) { + throwIfError(); + } + + public Map prefixQuery(byte[] key) { + throwIfError(); + return null; + } + + //**** Unsupported Operation For StateDB +} diff --git a/chainbase/src/main/java/org/tron/core/state/store/WorldStateTrieStore.java b/chainbase/src/main/java/org/tron/core/state/store/WorldStateTrieStore.java new file mode 100644 index 00000000000..e2946f050f4 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/store/WorldStateTrieStore.java @@ -0,0 +1,88 @@ +package org.tron.core.state.store; + +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.besu.storage.RocksDBConfiguration; +import org.hyperledger.besu.storage.RocksDBKeyValueStorage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; +import org.tron.common.parameter.CommonParameter; +import org.tron.common.prometheus.MetricKeys; +import org.tron.common.prometheus.Metrics; +import org.tron.common.storage.metric.DbStatService; +import org.tron.common.storage.metric.Stat; +import org.tron.common.utils.FileUtil; +import org.tron.core.state.annotation.NeedWorldStateTrieStoreCondition; + + +@Slf4j(topic = "State") +@Component +@Conditional(NeedWorldStateTrieStoreCondition.class) +public class WorldStateTrieStore extends RocksDBKeyValueStorage implements Stat { + + private static final String NAME = "world-state-trie"; + private static final String ROCKSDB = "ROCKSDB"; + + @Autowired + private DbStatService dbStatService; + + @Autowired + private WorldStateTrieStore(@Value(NAME) String dbName) { + super(buildConf(dbName)); + } + + @PostConstruct + private void init() { + dbStatService.register(this); + } + + @PreDestroy + private void clearUp() { + try { + this.close(); + } catch (Exception e) { + logger.warn("WorldStateTrieStore close error", e); + } + } + + + private static RocksDBConfiguration buildConf(String dbName) { + String stateGenesis = CommonParameter.getInstance().getStorage() + .getStateGenesisDirectory(); + if (!Paths.get(stateGenesis).isAbsolute()) { + stateGenesis = Paths.get(CommonParameter.getInstance().getOutputDirectory(), + stateGenesis).toString(); + } + FileUtil.createDirIfNotExists(stateGenesis); + return CommonParameter.getInstance().getStorage().getStateDbConf() + .databaseDir(Paths.get(stateGenesis, dbName)).build(); + } + + @Override + public void stat() { + if (closed.get()) { + return; + } + try { + String[] stats = db.getProperty("rocksdb.levelstats").split("\n"); + Arrays.stream(stats).skip(2).collect(Collectors.toList()).forEach(stat -> { + String[] tmp = stat.trim().replaceAll(" +", ",").split(","); + String level = tmp[0]; + double files = Double.parseDouble(tmp[1]); + double size = Double.parseDouble(tmp[2]) * 1048576.0; + Metrics.gaugeSet(MetricKeys.Gauge.DB_SST_LEVEL, files, ROCKSDB, NAME, level); + Metrics.gaugeSet(MetricKeys.Gauge.DB_SIZE_BYTES, size, ROCKSDB, NAME, level); + logger.info("DB {}, level:{},files:{},size:{} M", + NAME, level, files, size / 1048576.0); + }); + } catch (Exception e) { + logger.warn("DB {} stats error", NAME, e); + } + } +} diff --git a/chainbase/src/main/java/org/tron/core/state/trie/Trie.java b/chainbase/src/main/java/org/tron/core/state/trie/Trie.java new file mode 100644 index 00000000000..c80910829de --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/trie/Trie.java @@ -0,0 +1,30 @@ +package org.tron.core.state.trie; + +/** + * + */ +public interface Trie { + + byte[] getRootHash(); + + void setRoot(byte[] root); + + /** + * Recursively delete all nodes from root. + */ + void clear(); + + void put(byte[] key, V val); + + V get(byte[] key); + + void delete(byte[] key); + + /** + * Commits any pending changes to the underlying storage. + */ + default void commit() {} + + /** Persist accumulated changes to underlying storage. */ + boolean flush(); +} diff --git a/chainbase/src/main/java/org/tron/core/state/trie/TrieImpl2.java b/chainbase/src/main/java/org/tron/core/state/trie/TrieImpl2.java new file mode 100644 index 00000000000..de407274008 --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/trie/TrieImpl2.java @@ -0,0 +1,208 @@ +package org.tron.core.state.trie; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import io.prometheus.client.Histogram; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.trie.KeyValueMerkleStorage; +import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; +import org.hyperledger.besu.ethereum.trie.MerkleStorage; +import org.hyperledger.besu.ethereum.trie.Node; +import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; +import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.storage.InMemoryKeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.RocksDBConfiguration; +import org.hyperledger.besu.storage.RocksDBConfigurationBuilder; +import org.hyperledger.besu.storage.RocksDBKeyValueStorage; +import org.tron.common.prometheus.MetricKeys; +import org.tron.common.prometheus.Metrics; + +/** + * + */ +@Slf4j(topic = "db") +public class TrieImpl2 implements Trie { + + private final MerklePatriciaTrie trie; + @Getter + private final MerkleStorage merkleStorage; + + public TrieImpl2() { + this(Bytes32.ZERO); + } + + public TrieImpl2(Bytes32 root) { + this(new KeyValueMerkleStorage(new InMemoryKeyValueStorage()), root); + } + + public TrieImpl2(String keyValueStore) { + this(keyValueStore, Bytes32.ZERO); + } + + public TrieImpl2(String keyValueStore, Bytes32 root) { + this(createStore(keyValueStore), root); + } + + public TrieImpl2(KeyValueStorage keyValueStore) { + this(keyValueStore, Bytes32.ZERO); + } + + public TrieImpl2(KeyValueStorage keyValueStore, Bytes32 root) { + this(new KeyValueMerkleStorage(keyValueStore), root); + } + + public TrieImpl2(MerkleStorage merkleStorage) { + this(merkleStorage, Bytes32.ZERO); + } + + public TrieImpl2(MerkleStorage merkleStorage, Bytes32 root) { + this.merkleStorage = merkleStorage; + + if (root.isZero()) { + trie = new StoredMerklePatriciaTrie<>(merkleStorage::get, + Function.identity(), + Function.identity()); + } else { + trie = new StoredMerklePatriciaTrie<>(merkleStorage::get, root, + Function.identity(), + Function.identity()); + } + } + + private static KeyValueStorage createStore(String store) { + try { + return new RocksDBKeyValueStorage(config(Paths.get(store))); + } catch (IOException e) { + logger.error("{}", e); + return null; + } + } + + private static RocksDBConfiguration config(Path store) throws IOException { + return new RocksDBConfigurationBuilder().databaseDir(store).build(); + } + + public void visitAll(Consumer> nodeConsumer) { + trie.visitAll(nodeConsumer); + } + + @Override + public Bytes get(byte[] key) { + return trie.get(Bytes.wrap(key)).orElse(null); + } + + public Bytes get(Bytes key) { + return trie.get(key).orElse(null); + } + + @Override + public void put(byte[] key, Bytes value) { + trie.put(Bytes.wrap(key), value); + } + + public void put(Bytes key, Bytes value) { + trie.put(key, value); + } + + @Override + public void delete(byte[] key) { + trie.remove(Bytes.wrap(key)); + } + + @Override + public void commit() { + Histogram.Timer timer = Metrics.histogramStartTimer( + MetricKeys.Histogram.STATE_PUSH_BLOCK_FINISH_LATENCY, "commit"); + trie.commit(merkleStorage::put); + Metrics.histogramObserve(timer); + } + + @Override + public byte[] getRootHash() { + return trie.getRootHash().toArrayUnsafe(); + } + + public Bytes32 getRootHashByte32() { + return trie.getRootHash(); + } + + @Override + public void setRoot(byte[] root) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new RuntimeException("Not implemented yet"); + } + + /** + * NOTE: This is an exact range query. + * + * @see RangeStorageEntriesCollector#onLeaf(Bytes32, Node) + * @code {eth/protocols/snap/handler.go:283} + * @link https://github.com/hyperledger/besu/issues/5222 + * @param startKeyHash start,include + * @param endKeyHash end,include + * @return exact range query. + */ + public TreeMap entriesFrom(Bytes32 startKeyHash, Bytes32 endKeyHash) { + final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector( + startKeyHash, endKeyHash, Integer.MAX_VALUE, Integer.MAX_VALUE); + final TrieIterator visitor = RangeStorageEntriesCollector.createVisitor(collector); + final TreeMap origin = (TreeMap) + this.entriesFrom(root -> RangeStorageEntriesCollector.collectEntries(collector, visitor, + root, startKeyHash)); + return new TreeMap<>(origin.subMap(startKeyHash, true, endKeyHash, true)); + } + + public Map entriesFrom(final Function, Map> handler) { + return trie.entriesFrom(handler); + } + + @Override + public boolean flush() { + Histogram.Timer timer = Metrics.histogramStartTimer( + MetricKeys.Histogram.STATE_PUSH_BLOCK_FINISH_LATENCY, "flush"); + merkleStorage.commit(); + Metrics.histogramObserve(timer); + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TrieImpl2 trieImpl1 = (TrieImpl2) o; + + return Objects.equals(getRootHashByte32(), trieImpl1.getRootHashByte32()); + + } + + @Override + public int hashCode() { + return Arrays.hashCode(getRootHash()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + trie.getRootHash() + "]"; + } +} diff --git a/chainbase/src/main/java/org/tron/core/state/utils/AssetStateUtil.java b/chainbase/src/main/java/org/tron/core/state/utils/AssetStateUtil.java new file mode 100644 index 00000000000..f346ef2527f --- /dev/null +++ b/chainbase/src/main/java/org/tron/core/state/utils/AssetStateUtil.java @@ -0,0 +1,67 @@ +package org.tron.core.state.utils; + +import org.apache.tuweni.bytes.Bytes32; +import org.tron.common.utils.ByteArray; +import org.tron.core.ChainBaseManager; +import org.tron.core.state.store.DynamicPropertiesStateStore; +import org.tron.protos.Protocol.Account; + +import java.util.HashMap; +import java.util.Map; + +public class AssetStateUtil { + + + public static boolean hasAssetV2(Account account, byte[] key, Bytes32 root) { + if (account.getAssetV2Map().containsKey(ByteArray.toStr(key))) { + return true; + } + if (!isAllowAssetOptimization(root)) { + return false; + } + if (!account.getAssetOptimized()) { + return false; + } + return ChainBaseManager.fetch(root).hasAssetV2(account, Long.valueOf(ByteArray.toStr(key))); + } + + public static Account importAssetV2(Account account, byte[] key, Bytes32 root) { + String tokenId = ByteArray.toStr(key); + if (account.getAssetV2Map().containsKey(tokenId)) { + return account; + } + if (!isAllowAssetOptimization(root)) { + return account; + } + if (!account.getAssetOptimized()) { + return account; + } + + long balance = ChainBaseManager.fetch(root).getAccountAsset(account, + Long.valueOf(ByteArray.toStr(key))); + + Map map = new HashMap<>(account.getAssetV2Map()); + map.put(tokenId, balance); + return account.toBuilder().clearAssetV2().putAllAssetV2(map).build(); + } + + public static Account importAllAsset(Account account, Bytes32 root) { + if (!isAllowAssetOptimization(root)) { + return account; + } + + if (!account.getAssetOptimized()) { + return account; + } + Map map = ChainBaseManager.fetch(root).importAllAsset(account); + return account.toBuilder().clearAssetV2().putAllAssetV2(map).build(); + } + + public static boolean isAllowAssetOptimization(Bytes32 root) { + try (DynamicPropertiesStateStore store = + new DynamicPropertiesStateStore(ChainBaseManager.fetch(root))) { + return store.supportAllowAssetOptimization(); + } + } + +} diff --git a/chainbase/src/main/java/org/tron/core/store/AccountStore.java b/chainbase/src/main/java/org/tron/core/store/AccountStore.java index 4d39049ee79..6414e2959c9 100644 --- a/chainbase/src/main/java/org/tron/core/store/AccountStore.java +++ b/chainbase/src/main/java/org/tron/core/store/AccountStore.java @@ -42,6 +42,10 @@ private AccountStore(@Value("account") String dbName) { super(dbName); } + protected AccountStore() { + super(); + } + public static void setAccount(com.typesafe.config.Config config) { List list = config.getObjectList("genesis.block.assets"); for (int i = 0; i < list.size(); i++) { @@ -113,7 +117,7 @@ public AccountCapsule getBlackhole() { } - public byte[] getBlackholeAddress() { + public static byte[] getBlackholeAddress() { return assertsAddress.get("Blackhole"); } diff --git a/chainbase/src/main/java/org/tron/core/store/AssetIssueStore.java b/chainbase/src/main/java/org/tron/core/store/AssetIssueStore.java index d38a5f0677e..14c6ce94110 100644 --- a/chainbase/src/main/java/org/tron/core/store/AssetIssueStore.java +++ b/chainbase/src/main/java/org/tron/core/store/AssetIssueStore.java @@ -22,6 +22,9 @@ protected AssetIssueStore(@Value("asset-issue") String dbName) { super(dbName); } + protected AssetIssueStore() { + super(); + } @Override public AssetIssueCapsule get(byte[] key) { diff --git a/chainbase/src/main/java/org/tron/core/store/AssetIssueV2Store.java b/chainbase/src/main/java/org/tron/core/store/AssetIssueV2Store.java index a2109767e9f..1c0a6a2659c 100644 --- a/chainbase/src/main/java/org/tron/core/store/AssetIssueV2Store.java +++ b/chainbase/src/main/java/org/tron/core/store/AssetIssueV2Store.java @@ -10,8 +10,11 @@ public class AssetIssueV2Store extends AssetIssueStore { @Autowired - private AssetIssueV2Store(@Value("asset-issue-v2") String dbName) { + protected AssetIssueV2Store(@Value("asset-issue-v2") String dbName) { super(dbName); } + protected AssetIssueV2Store() { + super(); + } } diff --git a/chainbase/src/main/java/org/tron/core/store/ContractStateStore.java b/chainbase/src/main/java/org/tron/core/store/ContractStateStore.java index 19dfb11cdcd..c3052f5d14c 100644 --- a/chainbase/src/main/java/org/tron/core/store/ContractStateStore.java +++ b/chainbase/src/main/java/org/tron/core/store/ContractStateStore.java @@ -1,7 +1,5 @@ package org.tron.core.store; -import java.util.Objects; - import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -23,13 +21,4 @@ public ContractStateCapsule get(byte[] key) { return getUnchecked(key); } - @Override - public void put(byte[] key, ContractStateCapsule item) { - if (Objects.isNull(key) || Objects.isNull(item)) { - return; - } - - revokingDB.put(key, item.getData()); - } - } diff --git a/chainbase/src/main/java/org/tron/core/store/ContractStore.java b/chainbase/src/main/java/org/tron/core/store/ContractStore.java index 169d2b9c67e..763186920ff 100644 --- a/chainbase/src/main/java/org/tron/core/store/ContractStore.java +++ b/chainbase/src/main/java/org/tron/core/store/ContractStore.java @@ -2,14 +2,11 @@ import com.google.common.collect.Streams; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.tron.core.capsule.AbiCapsule; import org.tron.core.capsule.ContractCapsule; import org.tron.core.db.TronStoreWithRevoking; -import org.tron.protos.contract.SmartContractOuterClass.SmartContract; import java.util.Objects; @@ -36,7 +33,7 @@ public void put(byte[] key, ContractCapsule item) { if (item.getInstance().hasAbi()) { item = new ContractCapsule(item.getInstance().toBuilder().clearAbi().build()); } - revokingDB.put(key, item.getData()); + super.put(key, item); } /** diff --git a/chainbase/src/main/java/org/tron/core/store/DelegationStore.java b/chainbase/src/main/java/org/tron/core/store/DelegationStore.java index 4e95e480cfd..a347b8e9119 100644 --- a/chainbase/src/main/java/org/tron/core/store/DelegationStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DelegationStore.java @@ -26,6 +26,10 @@ public DelegationStore(@Value("delegation") String dbName) { super(dbName); } + protected DelegationStore() { + super(); + } + @Override public BytesCapsule get(byte[] key) { byte[] value = revokingDB.getUnchecked(key); 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 bf788232640..230eab8de64 100644 --- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java @@ -21,6 +21,7 @@ import org.tron.core.db.TronStoreWithRevoking; import org.tron.core.exception.BadItemException; import org.tron.core.exception.ItemNotFoundException; +import org.tron.core.state.WorldStateCallBack; @Slf4j(topic = "DB") @Component @@ -220,8 +221,10 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking private static final byte[] ALLOW_OLD_REWARD_OPT = "ALLOW_OLD_REWARD_OPT".getBytes(); @Autowired - private DynamicPropertiesStore(@Value("properties") String dbName) { + private DynamicPropertiesStore(@Value("properties") String dbName, + @Autowired WorldStateCallBack worldStateCallBack) { super(dbName); + this.worldStateCallBack = worldStateCallBack; try { this.getTotalSignNum(); @@ -956,6 +959,10 @@ private DynamicPropertiesStore(@Value("properties") String dbName) { } } + protected DynamicPropertiesStore () { + super(); + } + public String intArrayToString(int[] a) { StringBuilder sb = new StringBuilder(); for (int i : a) { diff --git a/chainbase/src/main/java/org/tron/core/store/MarketPairPriceToOrderStore.java b/chainbase/src/main/java/org/tron/core/store/MarketPairPriceToOrderStore.java index 605952328ed..35931f10bba 100644 --- a/chainbase/src/main/java/org/tron/core/store/MarketPairPriceToOrderStore.java +++ b/chainbase/src/main/java/org/tron/core/store/MarketPairPriceToOrderStore.java @@ -5,7 +5,7 @@ import java.util.List; import org.iq80.leveldb.Options; import org.rocksdb.ComparatorOptions; -import org.rocksdb.DirectComparator; +import org.rocksdb.AbstractComparator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -35,7 +35,7 @@ protected Options getOptionsByDbNameForLevelDB(String dbName) { //todo: to test later @Override - protected DirectComparator getDirectComparator() { + protected AbstractComparator getDirectComparator() { ComparatorOptions comparatorOptions = new ComparatorOptions(); return new MarketOrderPriceComparatorForRockDB(comparatorOptions); } diff --git a/chainbase/src/main/java/org/tron/core/store/StorageRowStore.java b/chainbase/src/main/java/org/tron/core/store/StorageRowStore.java index d5c6b6d73ac..a2c1e064a45 100644 --- a/chainbase/src/main/java/org/tron/core/store/StorageRowStore.java +++ b/chainbase/src/main/java/org/tron/core/store/StorageRowStore.java @@ -16,6 +16,10 @@ private StorageRowStore(@Value("storage-row") String dbName) { super(dbName); } + protected StorageRowStore() { + super(); + } + @Override public StorageRowCapsule get(byte[] key) { StorageRowCapsule row = getUnchecked(key); diff --git a/common/build.gradle b/common/build.gradle index 6c1545e5d13..6b1848ed18c 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -37,7 +37,7 @@ dependencies { compile group: 'com.beust', name: 'jcommander', version: '1.72' compile group: 'com.typesafe', name: 'config', version: '1.3.2' compile group: leveldbGroup, name: leveldbName, version: leveldbVersion - compile group: 'org.rocksdb', name: 'rocksdbjni', version: '5.15.10' + compile group: 'org.rocksdb', name: 'rocksdbjni', version: '7.7.3' // https://mvnrepository.com/artifact/org.quartz-scheduler/quartz compile group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2' compile group: 'io.prometheus', name: 'simpleclient', version: '0.15.0' @@ -53,6 +53,7 @@ dependencies { exclude group: 'com.google.protobuf', module: 'protobuf-java' exclude group: 'com.google.protobuf', module: 'protobuf-java-util' } + compile project(":state-trie-jdk8") compile project(":protocol") } diff --git a/common/src/main/java/org/tron/common/cache/CacheManager.java b/common/src/main/java/org/tron/common/cache/CacheManager.java index fa1fbff193b..2b647019cd8 100644 --- a/common/src/main/java/org/tron/common/cache/CacheManager.java +++ b/common/src/main/java/org/tron/common/cache/CacheManager.java @@ -2,6 +2,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheStats; +import com.google.common.cache.Weigher; import com.google.common.collect.Maps; import java.util.Map; import java.util.stream.Collectors; @@ -18,14 +19,30 @@ public static TronCache allocate(CacheType name) { return cache; } - public static TronCache allocate(CacheType name, String strategy) { + public static TronCache allocate(CacheType name, String strategy) { TronCache cache = new TronCache<>(name, strategy); CACHES.put(name, cache); return cache; } - public static TronCache allocate(CacheType name, String strategy, - CacheLoader loader) { + public static TronCache allocate(CacheType name, + CacheLoader loader) { + TronCache cache = new TronCache<>(name, CommonParameter.getInstance() + .getStorage().getCacheStrategy(name), loader); + CACHES.put(name, cache); + return cache; + } + + public static TronCache allocate(CacheType name, CacheLoader loader, + Weigher weigher) { + TronCache cache = new TronCache<>(name, CommonParameter.getInstance() + .getStorage().getCacheStrategy(name), loader, weigher); + CACHES.put(name, cache); + return cache; + } + + public static TronCache allocate(CacheType name, String strategy, + CacheLoader loader) { TronCache cache = new TronCache<>(name, strategy, loader); CACHES.put(name, cache); return cache; diff --git a/common/src/main/java/org/tron/common/cache/CacheStrategies.java b/common/src/main/java/org/tron/common/cache/CacheStrategies.java index b282ca1a687..97f63514366 100644 --- a/common/src/main/java/org/tron/common/cache/CacheStrategies.java +++ b/common/src/main/java/org/tron/common/cache/CacheStrategies.java @@ -14,6 +14,7 @@ import static org.tron.common.cache.CacheType.votes; import static org.tron.common.cache.CacheType.witness; import static org.tron.common.cache.CacheType.witnessSchedule; +import static org.tron.common.cache.CacheType.worldStateQueryInstance; import java.util.Arrays; import java.util.Collection; @@ -35,7 +36,7 @@ public class CacheStrategies { String.format(PATTERNS, 100, 100, "30s", CPUS); private static final List CACHE_SMALL_DBS = Arrays.asList(recentBlock, witness, witnessSchedule, delegatedResource, delegatedResourceAccountIndex, - votes, abi); + votes, abi, worldStateQueryInstance); private static final String CACHE_STRATEGY_NORMAL_DEFAULT = String.format(PATTERNS, 500, 500, "30s", CPUS); private static final List CACHE_NORMAL_DBS = Arrays.asList(code, contract, @@ -44,6 +45,11 @@ public class CacheStrategies { String.format(PATTERNS, 10000, 10000, "30s", CPUS); private static final String CACHE_STRATEGY_HUGE_DEFAULT = String.format(PATTERNS, 20000, 20000, "30s", CPUS); + + // for world state trie cache 512M + private static final String CACHE_STRATEGY_WORLD_STATE_TRIE_DEFAULT = + "maximumWeight=536870912,expireAfterAccess=7d,recordStats"; + private static final List CACHE_HUGE_DBS = Arrays.asList(storageRow, account); public static final List CACHE_DBS = Stream.of(CACHE_SMALL_DBS, CACHE_NORMAL_DBS, @@ -65,6 +71,9 @@ public static String getCacheStrategy(CacheType dbName) { if (CACHE_HUGE_DBS.contains(dbName)) { defaultStrategy = CACHE_STRATEGY_HUGE_DEFAULT; } + if (dbName.type.contains(CacheType.worldStateTrie.type)) { + defaultStrategy = CACHE_STRATEGY_WORLD_STATE_TRIE_DEFAULT; + } return defaultStrategy; } } diff --git a/common/src/main/java/org/tron/common/cache/CacheType.java b/common/src/main/java/org/tron/common/cache/CacheType.java index b9b28d0bd7c..d20acbe3f98 100644 --- a/common/src/main/java/org/tron/common/cache/CacheType.java +++ b/common/src/main/java/org/tron/common/cache/CacheType.java @@ -19,9 +19,20 @@ public enum CacheType { properties("properties"), delegation("delegation"), storageRow("storage-row"), - account("account"); + account("account"), // for leveldb or rocksdb cache + // archive node + worldStateQueryInstance("worldStateQueryInstance"), + worldStateTrie("world-state-trie"), + worldStateTrieDelegatedResource("world-state-trie.DelegatedResource"), + worldStateTrieDelegatedResourceAccountIndex("world-state-trie.DelegatedResourceAccountIndex"), + worldStateTrieVotes("world-state-trie.votes"), + worldStateTrieStorageRow("world-state-trie.storage-row"), + worldStateTrieAccount("world-state-trie.account"), + worldStateTrieCode("world-state-trie.code"), + worldStateTrieContract("world-state-trie.contract"); + public final String type; CacheType(String type) { diff --git a/common/src/main/java/org/tron/common/cache/TronCache.java b/common/src/main/java/org/tron/common/cache/TronCache.java index 4faf73f864a..3d5bde12821 100644 --- a/common/src/main/java/org/tron/common/cache/TronCache.java +++ b/common/src/main/java/org/tron/common/cache/TronCache.java @@ -5,6 +5,8 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheStats; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.Weigher; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import lombok.Getter; @@ -25,6 +27,11 @@ public class TronCache { this.cache = CacheBuilder.from(strategy).build(loader); } + TronCache(CacheType name, String strategy, CacheLoader loader, Weigher weigher) { + this.name = name; + this.cache = CacheBuilder.from(strategy).weigher(weigher).build(loader); + } + public void put(K k, V v) { this.cache.put(k, v); } @@ -37,6 +44,13 @@ public V get(K k, Callable loader) throws ExecutionException { return this.cache.get(k, loader); } + public V get(K k) throws ExecutionException { + if (this.cache instanceof LoadingCache) { + return ((LoadingCache) this.cache).get(k); + } + return this.cache.getIfPresent(k); + } + public CacheStats stats() { return this.cache.stats(); } 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 95a1eb2d0ae..a1d5aa6f666 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -455,12 +455,24 @@ public class CommonParameter { public String cryptoEngine = Constant.ECKey_ENGINE; @Getter @Setter + public boolean fullNodeRpcEnable = true; + @Getter + @Setter + public boolean solidityNodeRpcEnable = true; + @Getter + @Setter + public boolean PBFTNodeRpcEnable = true; + @Getter + @Setter public boolean fullNodeHttpEnable = true; @Getter @Setter public boolean solidityNodeHttpEnable = true; @Getter @Setter + public boolean PBFTNodeHttpEnable = true; + @Getter + @Setter public boolean jsonRpcHttpFullNodeEnable = false; @Getter @Setter diff --git a/common/src/main/java/org/tron/common/prometheus/MetricKeys.java b/common/src/main/java/org/tron/common/prometheus/MetricKeys.java index 87ab6fae0a3..0fb3197c877 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricKeys.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricKeys.java @@ -32,6 +32,7 @@ public static class Gauge { public static final String DB_SIZE_BYTES = "tron:db_size_bytes"; public static final String DB_SST_LEVEL = "tron:db_sst_level"; public static final String MANAGER_QUEUE = "tron:manager_queue_size"; + public static final String BLOCK_TRANS_SIZE = "tron:block_trans_size"; public static final String TX_CACHE = "tron:tx_cache"; private Gauge() { @@ -62,6 +63,12 @@ public static class Histogram { public static final String MESSAGE_PROCESS_LATENCY = "tron:message_process_latency_seconds"; public static final String BLOCK_FETCH_LATENCY = "tron:block_fetch_latency_seconds"; public static final String BLOCK_RECEIVE_DELAY = "tron:block_receive_delay_seconds"; + public static final String TRON_STATE_PUT_PER_TRANS_LATENCY = + "tron:state_put_per_trans_latency_seconds"; + public static final String TRON_STATE_PUT_LATENCY = + "tron:state_put_latency_seconds"; + public static final String STATE_PUSH_BLOCK_FINISH_LATENCY = + "tron:state_push_block_finish_latency_seconds"; private Histogram() { throw new IllegalStateException("Histogram"); diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsGauge.java b/common/src/main/java/org/tron/common/prometheus/MetricsGauge.java index dc1e5447540..253d56ada63 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsGauge.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsGauge.java @@ -19,6 +19,7 @@ class MetricsGauge { init(MetricKeys.Gauge.DB_SIZE_BYTES, "tron db size .", "type", "db", "level"); init(MetricKeys.Gauge.DB_SST_LEVEL, "tron db files .", "type", "db", "level"); init(MetricKeys.Gauge.TX_CACHE, "tron tx cache info.", "type"); + init(MetricKeys.Gauge.BLOCK_TRANS_SIZE, "trans size per block ."); } private MetricsGauge() { diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java index 556db10feb5..13f84a34e9e 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java @@ -48,6 +48,12 @@ public class MetricsHistogram { init(MetricKeys.Histogram.BLOCK_FETCH_LATENCY, "fetch block latency."); init(MetricKeys.Histogram.BLOCK_RECEIVE_DELAY, "receive block delay time, receiveTime - blockTime."); + init(MetricKeys.Histogram.TRON_STATE_PUT_PER_TRANS_LATENCY, + "state put per trans latency."); + init(MetricKeys.Histogram.TRON_STATE_PUT_LATENCY, + "state put latency.", "db"); + init(MetricKeys.Histogram.STATE_PUSH_BLOCK_FINISH_LATENCY, + "state commit or flush per block latency.", "type"); } private MetricsHistogram() { diff --git a/common/src/main/java/org/tron/common/utils/FileUtil.java b/common/src/main/java/org/tron/common/utils/FileUtil.java index 8031c764019..19cd48054c5 100644 --- a/common/src/main/java/org/tron/common/utils/FileUtil.java +++ b/common/src/main/java/org/tron/common/utils/FileUtil.java @@ -21,11 +21,14 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.FileSystemException; +import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; @@ -149,4 +152,50 @@ public static boolean isExists(String path) { File file = new File(path); return file.exists(); } + + + /** + * Copy src to dest, if dest is a directory and already exists, throw Exception. + * + *

Note: This method is not rigorous, because all the dirs that its FileName + * is contained in List(subDirs) will be filtered, this may result in unpredictable result. + * just used in LiteFullNodeTool. + * + * @param src Path or File + * @param dest Path or File + * @param subDirs only the subDirs in {@code src} will be copied + * @throws IOException IOException + */ + public static void copyDatabases(Path src, Path dest, List subDirs) { + // create subdirs, as using parallel() to run, so should create dirs first. + subDirs.parallelStream().forEach(dir -> { + if (FileUtil.isExists(Paths.get(src.toString(), dir).toString())) { + try { + Files.walk(Paths.get(src.toString(), dir), FileVisitOption.FOLLOW_LINKS) + .forEach(source -> copy(source, dest.resolve(src.relativize(source)))); + } catch (IOException e) { + logger.error("copy database failed, src: {}, dest: {}, error: {}", + Paths.get(src.toString(), dir), Paths.get(dest.toString(), dir), e.getMessage()); + throw new RuntimeException(e); + } + } + }); + } + + private static void copy(Path source, Path dest) { + try { + // create hard link when file is .sst + if (source.toString().endsWith(".sst")) { + try { + Files.createLink(dest, source); + } catch (FileSystemException e) { + Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING); + } + } else { + Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING); + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } } diff --git a/common/src/main/java/org/tron/common/utils/PropUtil.java b/common/src/main/java/org/tron/common/utils/PropUtil.java index 93f47380e35..8149bb58b2d 100644 --- a/common/src/main/java/org/tron/common/utils/PropUtil.java +++ b/common/src/main/java/org/tron/common/utils/PropUtil.java @@ -10,6 +10,8 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.Map; import java.util.Properties; import lombok.extern.slf4j.Slf4j; @@ -106,4 +108,21 @@ public static boolean writeProperty(String file, String key, String value) { return true; } + public static boolean writeProperties(String file, Map kv) { + try (OutputStream o = new FileOutputStream(file); + FileInputStream f = new FileInputStream(file); + BufferedWriter w = new BufferedWriter(new OutputStreamWriter(o, StandardCharsets.UTF_8)); + BufferedReader r = new BufferedReader(new InputStreamReader(f, StandardCharsets.UTF_8)) + ) { + Properties properties = new Properties(); + properties.load(r); + kv.forEach(properties::setProperty); + properties.store(w, "Generated by the application. PLEASE DO NOT EDIT! "); + } catch (Exception e) { + logger.warn("writeProperty", e); + return false; + } + return true; + } + } \ No newline at end of file diff --git a/common/src/main/java/org/tron/core/Constant.java b/common/src/main/java/org/tron/core/Constant.java index 2cd9ea95f15..fce6dd36d68 100644 --- a/common/src/main/java/org/tron/core/Constant.java +++ b/common/src/main/java/org/tron/core/Constant.java @@ -118,6 +118,9 @@ public class Constant { public static final String NODE_DNS_AWS_REGION = "node.dns.awsRegion"; public static final String NODE_DNS_AWS_HOST_ZONE_ID = "node.dns.awsHostZoneId"; + public static final String NODE_RPC_FULLNODE_ENABLE = "node.rpc.fullNodeEnable"; + public static final String NODE_RPC_SOLIDITY_ENABLE = "node.rpc.solidityEnable"; + public static final String NODE_RPC_PBFT_ENABLE = "node.rpc.PBFTEnable"; public static final String NODE_RPC_PORT = "node.rpc.port"; public static final String NODE_RPC_SOLIDITY_PORT = "node.rpc.solidityPort"; public static final String NODE_RPC_PBFT_PORT = "node.rpc.PBFTPort"; @@ -125,6 +128,7 @@ public class Constant { public static final String NODE_HTTP_SOLIDITY_PORT = "node.http.solidityPort"; public static final String NODE_HTTP_FULLNODE_ENABLE = "node.http.fullNodeEnable"; public static final String NODE_HTTP_SOLIDITY_ENABLE = "node.http.solidityEnable"; + public static final String NODE_HTTP_PBFT_ENABLE = "node.http.PBFTEnable"; public static final String NODE_HTTP_PBFT_PORT = "node.http.PBFTPort"; public static final String NODE_JSONRPC_HTTP_FULLNODE_ENABLE = "node.jsonrpc.httpFullNodeEnable"; @@ -376,4 +380,6 @@ public class Constant { public static final String MAX_UNSOLIDIFIED_BLOCKS = "node.maxUnsolidifiedBlocks"; public static final String COMMITTEE_ALLOW_OLD_REWARD_OPT = "committee.allowOldRewardOpt"; + + public static final long TOKEN_NUM_START = 1000000L; } diff --git a/common/src/main/java/org/tron/core/config/args/Storage.java b/common/src/main/java/org/tron/core/config/args/Storage.java index 9cf6eb6bab1..b9f5962bcf3 100644 --- a/common/src/main/java/org/tron/core/config/args/Storage.java +++ b/common/src/main/java/org/tron/core/config/args/Storage.java @@ -26,6 +26,7 @@ import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; +import org.hyperledger.besu.storage.RocksDBConfigurationBuilder; import org.iq80.leveldb.CompressionType; import org.iq80.leveldb.Options; import org.tron.common.cache.CacheStrategies; @@ -82,6 +83,14 @@ public class Storage { public static final String TX_CACHE_INIT_OPTIMIZATION = "storage.txCache.initOptimization"; private static final String MERKLE_ROOT = "storage.merkleRoot"; + private static final String STATE_ROOT_SWITCH_KEY = "storage.stateRoot.switch"; + private static final String STATE_DB_MAX_OPEN_FILES_KEY = "storage.stateRoot.db.maxOpenFiles"; + private static final String STATE_DB_WRITE_BUFFER_SIZE_KEY = + "storage.stateRoot.db.writeBufferSize"; + private static final String STATE_DB_CACHE_CAPACITY_KEY = "storage.stateRoot.db.cacheCapacity"; + private static final String STATE_DB_CACHE_INDEX_AND_FILTER_KEY = + "storage.stateRoot.db.cacheIndexAndFilter"; + private static final String STATE_GENESIS_DIRECTORY_KEY = "storage.stateGenesis.directory"; /** * Default values of directory @@ -96,6 +105,7 @@ public class Storage { private static final boolean DEFAULT_CHECKPOINT_SYNC = true; private static final int DEFAULT_ESTIMATED_TRANSACTIONS = 1000; private static final int DEFAULT_SNAPSHOT_MAX_FLUSH_COUNT = 1; + private static final String DEFAULT_STATE_GENESIS_DIRECTORY = "state-genesis"; private Config storage; /** @@ -161,6 +171,16 @@ public class Storage { private final List cacheDbs = CacheStrategies.CACHE_DBS; // second cache + @Getter + private boolean allowStateRoot; + + @Getter + private String stateGenesisDirectory = DEFAULT_STATE_GENESIS_DIRECTORY; + + @Getter + private final RocksDBConfigurationBuilder stateDbConf = new RocksDBConfigurationBuilder(); + + /** * Key: dbName, Value: Property object of that database */ @@ -277,6 +297,28 @@ public void setDbRoots(Config config) { } } + public void setStateConfig(final Config config) { + if (config.hasPath(STATE_ROOT_SWITCH_KEY)) { + this.allowStateRoot = config.getBoolean(STATE_ROOT_SWITCH_KEY); + } + if (config.hasPath(STATE_GENESIS_DIRECTORY_KEY)) { + this.stateGenesisDirectory = config.getString(STATE_GENESIS_DIRECTORY_KEY); + } + if (config.hasPath(STATE_DB_MAX_OPEN_FILES_KEY)) { + this.stateDbConf.maxOpenFiles(config.getInt(STATE_DB_MAX_OPEN_FILES_KEY)); + } + if (config.hasPath(STATE_DB_CACHE_CAPACITY_KEY)) { + this.stateDbConf.cacheCapacity(config.getLong(STATE_DB_CACHE_CAPACITY_KEY)); + } + if (config.hasPath(STATE_DB_WRITE_BUFFER_SIZE_KEY)) { + this.stateDbConf.writeBufferSize(config.getLong(STATE_DB_WRITE_BUFFER_SIZE_KEY)); + } + if (config.hasPath(STATE_DB_CACHE_INDEX_AND_FILTER_KEY)) { + this.stateDbConf.isCacheIndexAndFilter( + config.getBoolean(STATE_DB_CACHE_INDEX_AND_FILTER_KEY)); + } + } + private Property createProperty(final ConfigObject conf) { Property property = new Property(); diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 96af6fc7476..404e146dd95 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -57,6 +57,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes32; import org.bouncycastle.util.encoders.DecoderException; import org.bouncycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; @@ -109,6 +110,7 @@ import org.tron.common.crypto.SignUtils; import org.tron.common.parameter.CommonParameter; import org.tron.common.runtime.ProgramResult; +import org.tron.common.runtime.vm.DataWord; import org.tron.common.runtime.vm.LogInfo; import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; @@ -184,6 +186,8 @@ import org.tron.core.net.TronNetDelegate; import org.tron.core.net.TronNetService; import org.tron.core.net.message.adv.TransactionMessage; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.StorageRowStateStore; import org.tron.core.store.AccountIdIndexStore; import org.tron.core.store.AccountStore; import org.tron.core.store.AccountTraceStore; @@ -196,6 +200,7 @@ import org.tron.core.store.StoreFactory; import org.tron.core.utils.TransactionUtil; import org.tron.core.vm.program.Program; +import org.tron.core.vm.program.Storage; import org.tron.core.zen.ShieldedTRC20ParametersBuilder; import org.tron.core.zen.ShieldedTRC20ParametersBuilder.ShieldedTRC20ParametersType; import org.tron.core.zen.ZenTransactionBuilder; @@ -345,6 +350,18 @@ public Account getAccount(Account account) { return accountCapsule.getInstance(); } + + public Account getAccount(byte[] address, long blockNumber) { + Bytes32 rootHash = getRootHashByNumber(blockNumber); + WorldStateQueryInstance worldStateQueryInstance = initWorldStateQueryInstance(rootHash); + AccountCapsule accountCapsule = worldStateQueryInstance.getAccount(address); + if (accountCapsule == null) { + return null; + } + return accountCapsule.getInstance(); + } + + private void sortFrozenV2List(AccountCapsule accountCapsule) { List oldFreezeV2List = accountCapsule.getFrozenV2List(); accountCapsule.clearFrozenV2(); @@ -365,6 +382,21 @@ private void sortFrozenV2List(AccountCapsule accountCapsule) { } } + public Account getAccountToken10(byte[] address, long tokenId, long blockNumber) { + Bytes32 rootHash = getRootHashByNumber(blockNumber); + WorldStateQueryInstance worldStateQueryInstance = initWorldStateQueryInstance(rootHash); + AccountCapsule accountCapsule = worldStateQueryInstance.getAccount(address); + if (accountCapsule == null) { + return null; + } + if (tokenId == -1) { + accountCapsule.importAllAsset(); + } else { + accountCapsule.importAsset(String.valueOf(tokenId).getBytes()); + } + return accountCapsule.getInstance(); + } + public Account getAccountById(Account account) { AccountStore accountStore = chainBaseManager.getAccountStore(); AccountIdIndexStore accountIdIndexStore = chainBaseManager.getAccountIdIndexStore(); @@ -2859,7 +2891,7 @@ public Transaction triggerContract(TriggerSmartContract triggerSmartContract.getData().toByteArray()); if (isConstant(abi, selector)) { - return callConstantContract(trxCap, builder, retBuilder, false); + return callConstantContract(trxCap, builder, retBuilder, false, -1); } else { return trxCap.getInstance(); } @@ -2977,18 +3009,19 @@ private Transaction cleanContextAndTriggerConstantContract( txExtBuilder.clear(); txRetBuilder.clear(); transaction = triggerConstantContract( - triggerSmartContract, txCap, txExtBuilder, txRetBuilder, true); + triggerSmartContract, txCap, txExtBuilder, txRetBuilder, true, -1); return transaction; } public Transaction triggerConstantContract(TriggerSmartContract triggerSmartContract, TransactionCapsule trxCap, Builder builder, Return.Builder retBuilder) throws ContractValidateException, ContractExeException, HeaderNotFound, VMIllegalException { - return triggerConstantContract(triggerSmartContract, trxCap, builder, retBuilder, false); + return triggerConstantContract(triggerSmartContract, trxCap, builder, retBuilder, false, -1); } public Transaction triggerConstantContract(TriggerSmartContract triggerSmartContract, - TransactionCapsule trxCap, Builder builder, Return.Builder retBuilder, boolean isEstimating) + TransactionCapsule trxCap, Builder builder, Return.Builder retBuilder, boolean isEstimating, + long blockNum) throws ContractValidateException, ContractExeException, HeaderNotFound, VMIllegalException { if (triggerSmartContract.getContractAddress().isEmpty()) { // deploy contract @@ -3008,17 +3041,25 @@ public Transaction triggerConstantContract(TriggerSmartContract triggerSmartCont trxCap = createTransactionCapsule(deployBuilder.build(), ContractType.CreateSmartContract); trxCap.setFeeLimit(feeLimit); } else { // call contract - ContractStore contractStore = chainBaseManager.getContractStore(); + ContractCapsule contractCapsule; byte[] contractAddress = triggerSmartContract.getContractAddress().toByteArray(); - if (contractStore.get(contractAddress) == null) { + if (blockNum == -1) { + ContractStore contractStore = chainBaseManager.getContractStore(); + contractCapsule = contractStore.get(contractAddress); + } else { + Bytes32 rootHash = getRootHashByNumber(blockNum); + WorldStateQueryInstance worldStateQueryInstance = initWorldStateQueryInstance(rootHash); + contractCapsule = worldStateQueryInstance.getContract(contractAddress); + } + if (contractCapsule == null) { throw new ContractValidateException("Smart contract is not exist."); } } - return callConstantContract(trxCap, builder, retBuilder, isEstimating); + return callConstantContract(trxCap, builder, retBuilder, isEstimating, blockNum); } public Transaction callConstantContract(TransactionCapsule trxCap, - Builder builder, Return.Builder retBuilder, boolean isEstimating) + Builder builder, Return.Builder retBuilder, boolean isEstimating, long blockNum) throws ContractValidateException, ContractExeException, HeaderNotFound, VMIllegalException { if (!Args.getInstance().isSupportConstant()) { @@ -3026,12 +3067,21 @@ public Transaction callConstantContract(TransactionCapsule trxCap, } Block headBlock; - List blockCapsuleList = chainBaseManager.getBlockStore() - .getBlockByLatestNum(1); - if (CollectionUtils.isEmpty(blockCapsuleList)) { - throw new HeaderNotFound("latest block not found"); + if (blockNum == -1) { + List blockCapsuleList = chainBaseManager.getBlockStore() + .getBlockByLatestNum(1); + if (CollectionUtils.isEmpty(blockCapsuleList)) { + throw new HeaderNotFound("latest block not found"); + } else { + headBlock = blockCapsuleList.get(0).getInstance(); + } } else { - headBlock = blockCapsuleList.get(0).getInstance(); + try { + headBlock = chainBaseManager.getBlockByNum(blockNum).getInstance(); + } catch (ItemNotFoundException | BadItemException e) { + logger.error("Block not found, number: {}, err: {}", blockNum, e.getMessage()); + throw new HeaderNotFound(String.format("Block not found, number: %d", blockNum)); + } } BlockCapsule headBlockCapsule = new BlockCapsule(headBlock); @@ -4454,5 +4504,56 @@ public PricesResponseMessage getMemoFeePrices() { } return null; } + + public byte[] getCode(byte[] address, long blockNumber) { + Bytes32 rootHash = getRootHashByNumber(blockNumber); + WorldStateQueryInstance worldStateQueryInstance = initWorldStateQueryInstance(rootHash); + CodeCapsule codeCapsule = worldStateQueryInstance.getCode(address); + if (codeCapsule == null) { + return null; + } + return codeCapsule.getInstance(); + } + + public byte[] getStorageAt(byte[] address, String storageIdx, long blockNumber) { + Bytes32 rootHash = getRootHashByNumber(blockNumber); + WorldStateQueryInstance worldStateQueryInstance = initWorldStateQueryInstance(rootHash); + ContractCapsule contractCapsule = worldStateQueryInstance.getContract(address); + if (contractCapsule == null) { + return null; + } + + try (StorageRowStateStore store = new StorageRowStateStore(worldStateQueryInstance)) { + Storage storage = new Storage(address, store); + storage.setContractVersion(contractCapsule.getInstance().getVersion()); + storage.generateAddrHash(contractCapsule.getTrxHash()); + DataWord value = storage.getValue(new DataWord(ByteArray.fromHexString(storageIdx))); + return value == null ? new byte[32] : value.getData(); + } + } + + private Bytes32 getRootHashByNumber(long blockNumber) { + if (!CommonParameter.getInstance().getStorage().isAllowStateRoot()) { + throw new IllegalArgumentException("Unsupported query, this is not a archive node"); + } + long stateStartHeight = chainBaseManager.getWorldStateGenesis().getStateGenesisHeight(); + if (blockNumber < stateStartHeight) { + throw new IllegalArgumentException( + "block number is lower than state genesis height, genesis height: " + stateStartHeight); + } + if (blockNumber > chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()) { + throw new IllegalArgumentException("block number is larger than current header"); + } + + try { + return chainBaseManager.getBlockByNum(blockNumber).getArchiveRoot(); + } catch (ItemNotFoundException | BadItemException e) { + throw new IllegalArgumentException("block not found, block number: " + blockNumber); + } + } + + private WorldStateQueryInstance initWorldStateQueryInstance(Bytes32 rootHash) { + return ChainBaseManager.fetch(rootHash); + } } 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 a8547b73948..6c755847263 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 @@ -189,8 +189,12 @@ public static void clearParam() { PARAMETER.validContractProtoThreadNum = 1; PARAMETER.shieldedTransInPendingMaxCounts = 10; PARAMETER.changedDelegation = 0; + PARAMETER.fullNodeRpcEnable = true; + PARAMETER.solidityNodeRpcEnable = true; + PARAMETER.PBFTNodeRpcEnable = true; PARAMETER.fullNodeHttpEnable = true; PARAMETER.solidityNodeHttpEnable = true; + PARAMETER.PBFTNodeHttpEnable = true; PARAMETER.jsonRpcHttpFullNodeEnable = false; PARAMETER.jsonRpcHttpSolidityNodeEnable = false; PARAMETER.jsonRpcHttpPBFTNodeEnable = false; @@ -447,6 +451,18 @@ public static void setParam(final String[] args, final String confFileName) { PARAMETER.lruCacheSize = config.getInt(Constant.VM_LRU_CACHE_SIZE); } + if (config.hasPath(Constant.NODE_RPC_FULLNODE_ENABLE)) { + PARAMETER.fullNodeRpcEnable = config.getBoolean(Constant.NODE_RPC_FULLNODE_ENABLE); + } + + if (config.hasPath(Constant.NODE_RPC_SOLIDITY_ENABLE)) { + PARAMETER.solidityNodeRpcEnable = config.getBoolean(Constant.NODE_RPC_SOLIDITY_ENABLE); + } + + if (config.hasPath(Constant.NODE_RPC_PBFT_ENABLE)) { + PARAMETER.PBFTNodeRpcEnable = config.getBoolean(Constant.NODE_RPC_PBFT_ENABLE); + } + if (config.hasPath(Constant.NODE_HTTP_FULLNODE_ENABLE)) { PARAMETER.fullNodeHttpEnable = config.getBoolean(Constant.NODE_HTTP_FULLNODE_ENABLE); } @@ -455,6 +471,10 @@ public static void setParam(final String[] args, final String confFileName) { PARAMETER.solidityNodeHttpEnable = config.getBoolean(Constant.NODE_HTTP_SOLIDITY_ENABLE); } + if (config.hasPath(Constant.NODE_HTTP_PBFT_ENABLE)) { + PARAMETER.PBFTNodeHttpEnable = config.getBoolean(Constant.NODE_HTTP_PBFT_ENABLE); + } + if (config.hasPath(Constant.NODE_JSONRPC_HTTP_FULLNODE_ENABLE)) { PARAMETER.jsonRpcHttpFullNodeEnable = config.getBoolean(Constant.NODE_JSONRPC_HTTP_FULLNODE_ENABLE); @@ -532,6 +552,8 @@ public static void setParam(final String[] args, final String confFileName) { PARAMETER.storage.setCacheStrategies(config); PARAMETER.storage.setDbRoots(config); + PARAMETER.storage.setStateConfig(config); + PARAMETER.seedNode = new SeedNode(); PARAMETER.seedNode.setAddressList(loadSeeds(config)); 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 63bbef9ff7f..8eefe2d17de 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -131,6 +131,8 @@ import org.tron.core.metrics.MetricsUtil; import org.tron.core.service.MortgageService; import org.tron.core.service.RewardViCalService; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateGenesis; import org.tron.core.store.AccountAssetStore; import org.tron.core.store.AccountIdIndexStore; import org.tron.core.store.AccountIndexStore; @@ -217,6 +219,8 @@ public class Manager { @Autowired private AccountStateCallBack accountStateCallBack; @Autowired + private WorldStateCallBack worldStateCallBack; + @Autowired private TrieService trieService; private Set ownerAddressSet = new HashSet<>(); @Getter @@ -227,6 +231,8 @@ public class Manager { @Autowired @Getter private ChainBaseManager chainBaseManager; + @Autowired + private WorldStateGenesis worldStateGenesis; // transactions cache private BlockingQueue pendingTransactions; @Getter @@ -465,6 +471,7 @@ public void init() { .initStore(chainBaseManager.getWitnessStore(), chainBaseManager.getDelegationStore(), chainBaseManager.getDynamicPropertiesStore(), chainBaseManager.getAccountStore()); accountStateCallBack.setChainBaseManager(chainBaseManager); + worldStateCallBack.setChainBaseManager(chainBaseManager); trieService.setChainBaseManager(chainBaseManager); revokingStore.disable(); revokingStore.check(); @@ -486,6 +493,9 @@ public void init() { chainBaseManager.setMerkleContainer(getMerkleContainer()); chainBaseManager.setMortgageService(mortgageService); this.initGenesis(); + worldStateCallBack.setExecute(false); + // worldState init, before khaosDb init + worldStateGenesis.init(chainBaseManager); try { this.khaosDb.start(chainBaseManager.getBlockById( getDynamicPropertiesStore().getLatestBlockHeaderHash())); @@ -592,11 +602,6 @@ public void initGenesis() { } else { logger.info("Create genesis block."); Args.getInstance().setChainId(genesisBlock.getBlockId().toString()); - - chainBaseManager.getBlockStore().put(genesisBlock.getBlockId().getBytes(), genesisBlock); - chainBaseManager.getBlockIndexStore().put(genesisBlock.getBlockId()); - - logger.info(SAVE_BLOCK, genesisBlock); // init Dynamic Properties Store chainBaseManager.getDynamicPropertiesStore().saveLatestBlockHeaderNumber(0); chainBaseManager.getDynamicPropertiesStore().saveLatestBlockHeaderHash( @@ -608,6 +613,13 @@ public void initGenesis() { this.khaosDb.start(genesisBlock); this.updateRecentBlock(genesisBlock); initAccountHistoryBalance(); + // init genesis state + worldStateCallBack.initGenesis(genesisBlock); + + chainBaseManager.getBlockStore().put(genesisBlock.getBlockId().getBytes(), genesisBlock); + chainBaseManager.getBlockIndexStore().put(genesisBlock.getBlockId()); + + logger.info(SAVE_BLOCK, genesisBlock); } } } @@ -1026,15 +1038,20 @@ private void applyBlock(BlockCapsule block, List txs) TooBigTransactionException, DupTransactionException, TaposException, ValidateScheduleException, ReceiptCheckErrException, VMIllegalException, TooBigTransactionResultException, ZksnarkException, BadBlockException, EventBloomException { - processBlock(block, txs); - chainBaseManager.getBlockStore().put(block.getBlockId().getBytes(), block); - chainBaseManager.getBlockIndexStore().put(block.getBlockId()); - if (block.getTransactions().size() != 0) { - chainBaseManager.getTransactionRetStore() - .put(ByteArray.fromLong(block.getNum()), block.getResult()); + try { + worldStateCallBack.preExecute(block); + processBlock(block, txs); + updateFork(block); + worldStateCallBack.executePushFinish(); + chainBaseManager.getBlockStore().put(block.getBlockId().getBytes(), block); + chainBaseManager.getBlockIndexStore().put(block.getBlockId()); + if (block.getTransactions().size() != 0) { + chainBaseManager.getTransactionRetStore() + .put(ByteArray.fromLong(block.getNum()), block.getResult()); + } + } finally { + worldStateCallBack.exceptionFinish(); } - - updateFork(block); if (System.currentTimeMillis() - block.getTimeStamp() >= 60_000) { revokingStore.setMaxFlushCount(maxFlushCount); if (Args.getInstance().getShutdownBlockTime() != null @@ -1372,6 +1389,7 @@ public void updateDynamicProperties(BlockCapsule block) { + 1)); Metrics.gaugeSet(MetricKeys.Gauge.HEADER_HEIGHT, block.getNum()); Metrics.gaugeSet(MetricKeys.Gauge.HEADER_TIME, block.getTimeStamp()); + Metrics.gaugeSet(MetricKeys.Gauge.BLOCK_TRANS_SIZE, block.getTransactions().size()); } /** @@ -1424,8 +1442,11 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block chainBaseManager.getBalanceTraceStore().initCurrentTransactionBalanceTrace(trxCap); } - validateTapos(trxCap); - validateCommon(trxCap); + if (Objects.isNull(blockCap) || !blockCap.generatedByMyself) { + validateTapos(trxCap); + validateCommon(trxCap); + validateDup(trxCap); + } if (trxCap.getInstance().getRawData().getContractList().size() != 1) { throw new ContractSizeNotEqualToOneException( @@ -1434,8 +1455,6 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block txId, trxCap.getInstance().getRawData().getContractList().size())); } - validateDup(trxCap); - if (!trxCap.validateSignature(chainBaseManager.getAccountStore(), chainBaseManager.getDynamicPropertiesStore())) { throw new ValidateSignatureException( @@ -1741,9 +1760,11 @@ private void processBlock(BlockCapsule block, List txs) if (block.generatedByMyself) { transactionCapsule.setVerified(true); } + worldStateCallBack.preExeTrans(); accountStateCallBack.preExeTrans(); TransactionInfo result = processTransaction(transactionCapsule, block); accountStateCallBack.exeTransFinish(); + worldStateCallBack.exeTransFinish(); if (Objects.nonNull(result)) { results.add(result); } diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java index 52a3a2380d1..7e8a1679b51 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -88,6 +89,21 @@ BlockResult ethGetBlockByNumber(String bnOrId, Boolean fullTransactionObjects) }) String getTrxBalance(String address, String blockNumOrTag) throws JsonRpcInvalidParamsException; + + @JsonRpcMethod("tron_getAssets") + @JsonRpcErrors({ + @JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"), + }) + List getToken10(String address, String blockNumOrTag) + throws JsonRpcInvalidParamsException; + + @JsonRpcMethod("tron_getAssetById") + @JsonRpcErrors({ + @JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"), + }) + Token10Result getToken10ById(String address, String tokenId, String blockNumOrTag) + throws JsonRpcInvalidParamsException; + @JsonRpcMethod("eth_getStorageAt") @JsonRpcErrors({ @JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"), @@ -465,4 +481,19 @@ public LogFilterElement(String blockHash, Long blockNum, String txId, Integer tx this.removed = removed; } } + + @AllArgsConstructor + @JsonPropertyOrder + @EqualsAndHashCode + class Token10Result { + @Getter + private final String key; + @Getter + private final String value; + + @Override + public String toString() { + return JSONObject.toJSONString(this); + } + } } diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java index 0ca57a3b98c..1461963282d 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java @@ -15,7 +15,11 @@ import com.google.protobuf.GeneratedMessageV3; import java.io.Closeable; import java.io.IOException; + +import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -47,6 +51,7 @@ import org.tron.common.runtime.vm.DataWord; import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; +import org.tron.core.Constant; import org.tron.core.Wallet; import org.tron.core.capsule.BlockCapsule; import org.tron.core.capsule.TransactionCapsule; @@ -155,9 +160,12 @@ public enum RequestSource { private final Manager manager; private final String esName = "query-section"; + private final boolean allowStateRoot = CommonParameter.getInstance().getStorage() + .isAllowStateRoot(); + @Autowired - public TronJsonRpcImpl(@Autowired NodeInfoService nodeInfoService, @Autowired Wallet wallet, - @Autowired Manager manager) { + public TronJsonRpcImpl(@Autowired NodeInfoService nodeInfoService, + @Autowired Wallet wallet, @Autowired Manager manager) { this.nodeInfoService = nodeInfoService; this.wallet = wallet; this.manager = manager; @@ -350,34 +358,119 @@ public String getLatestBlockNum() { @Override public String getTrxBalance(String address, String blockNumOrTag) throws JsonRpcInvalidParamsException { + byte[] addressData = addressCompatibleToByteArray(address); + Account reply; if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) || PENDING_STR.equalsIgnoreCase(blockNumOrTag)) { throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressData = addressCompatibleToByteArray(address); + Account account = Account.newBuilder().setAddress(ByteString.copyFrom(addressData)).build(); + reply = wallet.getAccount(account); + } else { + BigInteger blockNumber; + try { + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag); + } catch (Exception e) { + throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); + } + if (allowStateRoot) { + reply = wallet.getAccount(addressData, blockNumber.longValue()); + } else { + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } + } + long balance = 0; + if (reply != null) { + balance = reply.getBalance(); + } + return ByteArray.toJsonHex(balance); + } + + @Override + public List getToken10(String address, String blockNumOrTag) + throws JsonRpcInvalidParamsException { + byte[] addressData = addressCompatibleToByteArray(address); + Account reply; + if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) + || PENDING_STR.equalsIgnoreCase(blockNumOrTag)) { + throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); + } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { Account account = Account.newBuilder().setAddress(ByteString.copyFrom(addressData)).build(); - Account reply = wallet.getAccount(account); - long balance = 0; + reply = wallet.getAccount(account); + } else { + BigInteger blockNumber; + try { + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag); + } catch (Exception e) { + throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); + } + if (allowStateRoot) { + reply = wallet.getAccountToken10(addressData, -1, blockNumber.longValue()); + } else { + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } + } + + List token10s = new ArrayList<>(); + if (reply != null) { + reply.getAssetV2Map().entrySet().stream().filter(e -> e.getValue() > 0).map(e -> + new Token10Result(ByteArray.toJsonHex(Long.parseUnsignedLong(e.getKey())), + ByteArray.toJsonHex(e.getValue()))).forEach(token10s::add); + } + token10s.sort(Comparator.comparing(Token10Result::getKey)); + return token10s; + } - if (reply != null) { - balance = reply.getBalance(); + @Override + public Token10Result getToken10ById(String address, String tokenId, String blockNumOrTag) + throws JsonRpcInvalidParamsException { + + long tokenNum; + String tokenStr; + try { + tokenNum = ByteArray.hexToBigInteger(tokenId).longValue(); + if (tokenNum < Constant.TOKEN_NUM_START + || tokenNum > manager.getDynamicPropertiesStore().getTokenIdNum()) { + throw new JsonRpcInvalidParamsException("invalid token id"); } - return ByteArray.toJsonHex(balance); + tokenStr = Long.toString(tokenNum); + } catch (Exception e) { + throw new JsonRpcInvalidParamsException("invalid token id"); + } + + byte[] addressData = addressCompatibleToByteArray(address); + Account reply; + if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) + || PENDING_STR.equalsIgnoreCase(blockNumOrTag)) { + throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); + } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { + Account account = Account.newBuilder().setAddress(ByteString.copyFrom(addressData)).build(); + reply = wallet.getAccount(account); } else { + BigInteger blockNumber; try { - ByteArray.hexToBigInteger(blockNumOrTag); + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag); } catch (Exception e) { throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); } + if (allowStateRoot) { + reply = wallet.getAccountToken10(addressData, tokenNum, blockNumber.longValue()); + } else { + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } + } - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + long amt = 0; + if (reply != null) { + amt = reply.getAssetV2Map().getOrDefault(tokenStr, 0L); } + return new Token10Result(tokenId, ByteArray.toJsonHex(amt)); } private void callTriggerConstantContract(byte[] ownerAddressByte, byte[] contractAddressByte, long value, byte[] data, TransactionExtention.Builder trxExtBuilder, - Return.Builder retBuilder) + Return.Builder retBuilder, long blockNumber) throws ContractValidateException, ContractExeException, HeaderNotFound, VMIllegalException { TriggerSmartContract triggerContract = triggerCallContract( @@ -391,8 +484,8 @@ private void callTriggerConstantContract(byte[] ownerAddressByte, byte[] contrac TransactionCapsule trxCap = wallet.createTransactionCapsule(triggerContract, ContractType.TriggerSmartContract); - Transaction trx = - wallet.triggerConstantContract(triggerContract, trxCap, trxExtBuilder, retBuilder); + Transaction trx = wallet.triggerConstantContract( + triggerContract, trxCap, trxExtBuilder, retBuilder, false, blockNumber); trxExtBuilder.setTransaction(trx); trxExtBuilder.setTxid(trxCap.getTransactionId().getByteString()); @@ -430,7 +523,8 @@ private void estimateEnergy(byte[] ownerAddressByte, byte[] contractAddressByte, * getMethodSign(methodName(uint256,uint256)) || data1 || data2 */ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long value, - byte[] data) throws JsonRpcInvalidRequestException, JsonRpcInternalException { + byte[] data, long blockNumber) + throws JsonRpcInvalidRequestException, JsonRpcInternalException { TransactionExtention.Builder trxExtBuilder = TransactionExtention.newBuilder(); Return.Builder retBuilder = Return.newBuilder(); @@ -438,7 +532,7 @@ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long va try { callTriggerConstantContract(ownerAddressByte, contractAddressByte, value, data, - trxExtBuilder, retBuilder); + trxExtBuilder, retBuilder, blockNumber); } catch (ContractValidateException | VMIllegalException e) { String errString = CONTRACT_VALIDATE_ERROR; @@ -487,12 +581,11 @@ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long va @Override public String getStorageAt(String address, String storageIdx, String blockNumOrTag) throws JsonRpcInvalidParamsException { + byte[] addressByte = addressCompatibleToByteArray(address); if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) || PENDING_STR.equalsIgnoreCase(blockNumOrTag)) { throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressByte = addressCompatibleToByteArray(address); - // get contract from contractStore BytesMessage.Builder build = BytesMessage.newBuilder(); BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressByte)).build(); @@ -509,25 +602,29 @@ public String getStorageAt(String address, String storageIdx, String blockNumOrT DataWord value = storage.getValue(new DataWord(ByteArray.fromHexString(storageIdx))); return ByteArray.toJsonHex(value == null ? new byte[32] : value.getData()); } else { + BigInteger blockNumber; try { - ByteArray.hexToBigInteger(blockNumOrTag); + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag); } catch (Exception e) { throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); } - - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + if (allowStateRoot) { + byte[] value = wallet.getStorageAt(addressByte, storageIdx, blockNumber.longValue()); + return ByteArray.toJsonHex(value == null ? new byte[32] : value); + } else { + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } } } @Override public String getABIOfSmartContract(String contractAddress, String blockNumOrTag) throws JsonRpcInvalidParamsException { + byte[] addressData = addressCompatibleToByteArray(contractAddress); if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) || PENDING_STR.equalsIgnoreCase(blockNumOrTag)) { throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressData = addressCompatibleToByteArray(contractAddress); - BytesMessage.Builder build = BytesMessage.newBuilder(); BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressData)).build(); SmartContractDataWrapper contractDataWrapper = wallet.getContractInfo(bytesMessage); @@ -539,13 +636,18 @@ public String getABIOfSmartContract(String contractAddress, String blockNumOrTag } } else { + BigInteger blockNumber; try { - ByteArray.hexToBigInteger(blockNumOrTag); + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag); } catch (Exception e) { throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); } - - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + if (allowStateRoot) { + byte[] code = wallet.getCode(addressData, blockNumber.longValue()); + return code != null ? ByteArray.toJsonHex(code) : "0x"; + } else { + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } } } @@ -607,7 +709,8 @@ public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestExcept args.parseValue(), ByteArray.fromHexString(args.getData()), trxExtBuilder, - retBuilder); + retBuilder, + -1); } } catch (ContractValidateException e) { @@ -807,15 +910,17 @@ public String getCall(CallArguments transactionCall, Object blockParamObj) } catch (ClassCastException e) { throw new JsonRpcInvalidParamsException(JSON_ERROR); } - - if (getBlockByJsonHash(blockNumOrTag) == null) { + Block block = getBlockByJsonHash(blockNumOrTag); + if (block == null) { throw new JsonRpcInternalException(NO_BLOCK_HEADER_BY_HASH); } + blockNumOrTag = ByteArray.toJsonHex(block.getBlockHeader().getRawData().getNumber()); } else { throw new JsonRpcInvalidRequestException(JSON_ERROR); } - - blockNumOrTag = LATEST_STR; + if (!allowStateRoot) { + blockNumOrTag = LATEST_STR; + } } else if (blockParamObj instanceof String) { blockNumOrTag = (String) blockParamObj; } else { @@ -830,15 +935,23 @@ public String getCall(CallArguments transactionCall, Object blockParamObj) byte[] contractAddressData = addressCompatibleToByteArray(transactionCall.getTo()); return call(addressData, contractAddressData, transactionCall.parseValue(), - ByteArray.fromHexString(transactionCall.getData())); + ByteArray.fromHexString(transactionCall.getData()), -1); } else { + long blockNumber; try { - ByteArray.hexToBigInteger(blockNumOrTag); + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag).longValue(); } catch (Exception e) { throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); } + if (allowStateRoot) { + byte[] addressData = addressCompatibleToByteArray(transactionCall.getFrom()); + byte[] contractAddressData = addressCompatibleToByteArray(transactionCall.getTo()); - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + return call(addressData, contractAddressData, transactionCall.parseValue(), + ByteArray.fromHexString(transactionCall.getData()), blockNumber); + } else { + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } } } diff --git a/framework/src/main/resources/config-localtest.conf b/framework/src/main/resources/config-localtest.conf index f1ac104c9ed..3a906a276ba 100644 --- a/framework/src/main/resources/config-localtest.conf +++ b/framework/src/main/resources/config-localtest.conf @@ -52,6 +52,15 @@ storage { ] checkpoint.version = 2 checkpoint.sync = true + + stateRoot.switch = true + stateGenesis.directory = "state-genesis" + stateRoot.db = { + maxOpenFiles = 5000 + writeBufferSize = 10485760 // 10 MB = 10 * 1024 * 1024 B + cacheCapacity = 10485760 // 10 MB = 10 * 1024 * 1024 B + cacheIndexAndFilter = true + } } node.discovery = { diff --git a/framework/src/main/resources/config.conf b/framework/src/main/resources/config.conf index 78427c30f87..174e077b0cd 100644 --- a/framework/src/main/resources/config.conf +++ b/framework/src/main/resources/config.conf @@ -101,9 +101,18 @@ storage { # data root setting, for check data, currently, only reward-vi is used. # merkleRoot = { - # reward-vi = 9debcb9924055500aaae98cdee10501c5c39d4daa75800a996f4bdda73dbccd8 // main-net, Sha256Hash, hexString + # reward-vi = 9debcb9924055500aaae98cdee10501c5c39d4daa75800a996f4bdda73dbccd8 // main-net, Sha256Hash, hexString # } + # world state + # stateRoot.switch = false + # stateGenesis.directory = "state-genesis" + # stateRoot.db = { + # maxOpenFiles = 5000 + # writeBufferSize = 10485760 // 10 MB = 10 * 1024 * 1024 B + # cacheCapacity = 10485760 // 10 MB = 10 * 1024 * 1024 B + # cacheIndexAndFilter = false + # } } node.discovery = { @@ -209,6 +218,8 @@ node { fullNodePort = 8090 solidityEnable = true solidityPort = 8091 + PBFTEnable = true + PBFTPort = 8092 } # use your ipv6 address for node discovery and tcp connection, default false @@ -227,6 +238,11 @@ node { rpc { port = 50051 #solidityPort = 50061 + #PBFTPort = 50071 + fullNodeEnable = true + solidityEnable = true + PBFTEnable = true + # Number of gRPC thread, default availableProcessors / 2 # thread = 16 diff --git a/framework/src/test/java/org/tron/common/runtime/VmStateTestUtil.java b/framework/src/test/java/org/tron/common/runtime/VmStateTestUtil.java new file mode 100644 index 00000000000..66d7c54c805 --- /dev/null +++ b/framework/src/test/java/org/tron/common/runtime/VmStateTestUtil.java @@ -0,0 +1,69 @@ +package org.tron.common.runtime; + +import com.google.protobuf.ByteString; +import org.tron.common.utils.ByteArray; +import org.tron.core.ChainBaseManager; +import org.tron.core.Wallet; +import org.tron.core.actuator.VMActuator; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.capsule.TransactionCapsule; +import org.tron.core.db.TransactionContext; +import org.tron.core.exception.ContractExeException; +import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.trie.TrieImpl2; +import org.tron.core.store.StoreFactory; +import org.tron.protos.Protocol; + +public class VmStateTestUtil { + + private static String OWNER_ADDRESS = Wallet.getAddressPreFixString() + + "abd4b9367799eaa3197fecb144eb71de1e049abc"; + + public static ProgramResult runConstantCall( + ChainBaseManager chainBaseManager, + WorldStateCallBack worldStateCallBack, Protocol.Transaction tx) + throws ContractExeException, ContractValidateException { + BlockCapsule block = mockBlock(chainBaseManager, worldStateCallBack); + TransactionCapsule trxCap = new TransactionCapsule(tx); + TransactionContext context = new TransactionContext(block, trxCap, + StoreFactory.getInstance(), true, false); + VMActuator vmActuator = new VMActuator(true); + + vmActuator.validate(context); + vmActuator.execute(context); + return context.getProgramResult(); + } + + public static BlockCapsule mockBlock(ChainBaseManager chainBaseManager, + WorldStateCallBack worldStateCallBack) { + long mockNumber = 1000; + chainBaseManager.getDynamicPropertiesStore().saveLatestBlockHeaderNumber(mockNumber); + TrieImpl2 trie = flushTrie(worldStateCallBack); + BlockCapsule blockCap = new BlockCapsule(Protocol.Block.newBuilder().build()); + Protocol.BlockHeader.raw blockHeaderRaw = + blockCap.getInstance().getBlockHeader().getRawData().toBuilder() + .setNumber(mockNumber) + .setTimestamp(System.currentTimeMillis()) + .setWitnessAddress(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS))) + .build(); + Protocol.BlockHeader blockHeader = blockCap.getInstance().getBlockHeader().toBuilder() + .setRawData(blockHeaderRaw) + .setArchiveRoot(ByteString.copyFrom(trie.getRootHash())) + .build(); + blockCap = new BlockCapsule( + blockCap.getInstance().toBuilder().setBlockHeader(blockHeader).build()); + blockCap.generatedByMyself = true; + chainBaseManager.getBlockStore().put(blockCap.getBlockId().getBytes(), blockCap); + chainBaseManager.getBlockIndexStore().put(blockCap.getBlockId()); + return blockCap; + } + + public static TrieImpl2 flushTrie(WorldStateCallBack worldStateCallBack) { + worldStateCallBack.clear(); + TrieImpl2 trie = worldStateCallBack.getTrie(); + trie.commit(); + trie.flush(); + return trie; + } +} diff --git a/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java b/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java index 79ed1c7e1f8..01dc69be280 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java @@ -5,17 +5,20 @@ import java.util.Arrays; import java.util.Collections; + import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; import org.junit.Assert; import org.junit.Test; import org.tron.common.runtime.TVMTestResult; import org.tron.common.runtime.TvmTestUtils; +import org.tron.common.runtime.VmStateTestUtil; import org.tron.common.utils.ByteArray; import org.tron.common.utils.WalletUtil; import org.tron.common.utils.client.utils.AbiUtil; import org.tron.common.utils.client.utils.DataWord; import org.tron.core.Wallet; +import org.tron.core.capsule.BlockCapsule; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; import org.tron.core.exception.JsonRpcInvalidParamsException; @@ -23,6 +26,7 @@ import org.tron.core.exception.VMIllegalException; import org.tron.core.services.NodeInfoService; import org.tron.core.services.jsonrpc.TronJsonRpcImpl; +import org.tron.core.state.WorldStateCallBack; import org.tron.protos.Protocol.Transaction; @@ -103,10 +107,15 @@ triggercontract Txxxxxxxxxxx deploy(bytes,uint256) bytes,uint256 false 100000000 */ + private WorldStateCallBack worldStateCallBack; + @Test public void testCreate2() throws ContractExeException, ReceiptCheckErrException, VMIllegalException, ContractValidateException { + worldStateCallBack = context.getBean(WorldStateCallBack.class); + worldStateCallBack.setExecute(true); + manager.getDynamicPropertiesStore().saveAllowTvmTransferTrc10(1); manager.getDynamicPropertiesStore().saveAllowTvmConstantinople(1); manager.getDynamicPropertiesStore().saveAllowTvmIstanbul(0); @@ -203,6 +212,18 @@ private void testJsonRpc(byte[] actualContract, long loop) { } catch (JsonRpcInvalidParamsException e) { Assert.fail(); } + + // test for archive node + BlockCapsule blockCapsule = + VmStateTestUtil.mockBlock(manager.getChainBaseManager(), worldStateCallBack); + try { + String res = + tronJsonRpc.getStorageAt(ByteArray.toHexString(actualContract), "0", + "0x" + Long.toHexString(blockCapsule.getNum())); + Assert.assertEquals(loop, ByteArray.jsonHexToLong(res)); + } catch (JsonRpcInvalidParamsException e) { + Assert.fail(); + } } /* diff --git a/framework/src/test/java/org/tron/common/runtime/vm/FreezeV2Test.java b/framework/src/test/java/org/tron/common/runtime/vm/FreezeV2Test.java index 9558c701109..25856c9ae51 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/FreezeV2Test.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/FreezeV2Test.java @@ -26,6 +26,7 @@ import org.tron.common.runtime.RuntimeImpl; import org.tron.common.runtime.TVMTestResult; import org.tron.common.runtime.TvmTestUtils; +import org.tron.common.runtime.VmStateTestUtil; import org.tron.common.utils.Commons; import org.tron.common.utils.FastByteComparisons; import org.tron.common.utils.StringUtil; @@ -45,6 +46,7 @@ import org.tron.core.db.EnergyProcessor; import org.tron.core.db.Manager; import org.tron.core.db.TransactionTrace; +import org.tron.core.state.WorldStateCallBack; import org.tron.core.store.AccountStore; import org.tron.core.store.DelegatedResourceStore; import org.tron.core.store.DynamicPropertiesStore; @@ -162,6 +164,8 @@ public class FreezeV2Test { private static Manager manager; private static byte[] owner; private static Repository rootRepository; + private static ChainBaseManager chainBaseManager; + private static WorldStateCallBack worldStateCallBack; @Before public void init() throws Exception { @@ -169,6 +173,9 @@ public void init() throws Exception { temporaryFolder.newFolder().toString(), "--debug"}, Constant.TEST_CONF); context = new TronApplicationContext(DefaultConfig.class); manager = context.getBean(Manager.class); + chainBaseManager = context.getBean(ChainBaseManager.class); + worldStateCallBack = context.getBean(WorldStateCallBack.class); + worldStateCallBack.setExecute(true); owner = Hex.decode(Wallet.getAddressPreFixString() + "abd4b9367799eaa3197fecb144eb71de1e049abc"); rootRepository = RepositoryImpl.createRoot(StoreFactory.getInstance()); @@ -229,6 +236,8 @@ private TVMTestResult triggerContract(byte[] callerAddr, TransactionCapsule trxCap = new TransactionCapsule( TvmTestUtils.generateTriggerSmartContractAndGetTransaction( callerAddr, contractAddr, Hex.decode(hexInput), 0, feeLimit)); + VmStateTestUtil.runConstantCall( + chainBaseManager, worldStateCallBack, trxCap.getInstance()); TransactionTrace trace = new TransactionTrace(trxCap, StoreFactory.getInstance(), new RuntimeImpl()); trxCap.setTrxTrace(trace); @@ -565,7 +574,7 @@ public void testSuicideToOtherAccount() throws Exception { cancelAllUnfreezeV2(owner, contract, 0); AccountCapsule contractCapsule = manager.getAccountStore().get(contract); - contractCapsule.setLatestConsumeTimeForEnergy(ChainBaseManager.getInstance().getHeadSlot()); + contractCapsule.setLatestConsumeTimeForEnergy(chainBaseManager.getHeadSlot()); contractCapsule.setNewWindowSize(ENERGY, WINDOW_SIZE_MS / BLOCK_PRODUCED_INTERVAL); contractCapsule.setEnergyUsage(frozenBalance); manager.getAccountStore().put(contract, contractCapsule); @@ -904,15 +913,13 @@ private TVMTestResult unDelegateResource( Assert.assertEquals( oldReceiver.getNetUsage() - transferUsage, newReceiver.getNetUsage()); - Assert.assertEquals( - ChainBaseManager.getInstance().getHeadSlot(), + Assert.assertEquals(chainBaseManager.getHeadSlot(), newReceiver.getLastConsumeTime(BANDWIDTH)); } else { Assert.assertEquals( oldReceiver.getEnergyUsage() + transferUsage, newReceiver.getEnergyUsage()); - Assert.assertEquals( - ChainBaseManager.getInstance().getHeadSlot(), + Assert.assertEquals(chainBaseManager.getHeadSlot(), newReceiver.getLastConsumeTime(ENERGY)); } } else { @@ -966,12 +973,12 @@ private TVMTestResult suicide(byte[] callerAddr, byte[] contractAddr, byte[] inh oldInheritorBandwidthUsage = oldInheritor.getUsage(BANDWIDTH); oldInheritorEnergyUsage = oldInheritor.getUsage(ENERGY); } - BandwidthProcessor bandwidthProcessor = new BandwidthProcessor(ChainBaseManager.getInstance()); + BandwidthProcessor bandwidthProcessor = new BandwidthProcessor(chainBaseManager); bandwidthProcessor.updateUsage(oldContract); oldContract.setLatestConsumeTime(now); EnergyProcessor energyProcessor = new EnergyProcessor( - manager.getDynamicPropertiesStore(), ChainBaseManager.getInstance().getAccountStore()); + manager.getDynamicPropertiesStore(), chainBaseManager.getAccountStore()); energyProcessor.updateUsage(oldContract); oldContract.setLatestConsumeTimeForEnergy(now); diff --git a/framework/src/test/java/org/tron/common/runtime/vm/RewardBalanceTest.java b/framework/src/test/java/org/tron/common/runtime/vm/RewardBalanceTest.java index af95952ebf7..bf76310fafa 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/RewardBalanceTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/RewardBalanceTest.java @@ -2,6 +2,7 @@ import java.util.Collections; import lombok.extern.slf4j.Slf4j; +import org.apache.tuweni.bytes.Bytes32; import org.bouncycastle.util.encoders.Hex; import org.junit.Assert; import org.junit.Test; @@ -16,6 +17,8 @@ import org.tron.core.exception.ContractValidateException; import org.tron.core.exception.ReceiptCheckErrException; import org.tron.core.exception.VMIllegalException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.trie.TrieImpl2; import org.tron.core.store.StoreFactory; import org.tron.core.vm.config.ConfigLoader; import org.tron.core.vm.config.VMConfig; @@ -24,6 +27,7 @@ import org.tron.core.vm.program.invoke.ProgramInvokeFactory; import org.tron.core.vm.repository.Repository; import org.tron.core.vm.repository.RepositoryImpl; +import org.tron.core.vm.repository.RepositoryStateImpl; import org.tron.protos.Protocol; import org.tron.protos.Protocol.Transaction; @@ -75,10 +79,16 @@ function payableAddrTest(address payable addr) view public returns (uint256) { } */ + private WorldStateCallBack worldStateCallBack; + @Test public void testRewardBalance() throws ContractExeException, ReceiptCheckErrException, VMIllegalException, ContractValidateException { + // state query test init + worldStateCallBack = context.getBean(WorldStateCallBack.class); + TrieImpl2 trie = worldStateCallBack.getTrie(); + ConfigLoader.disable = true; VMConfig.initAllowTvmTransferTrc10(1); VMConfig.initAllowTvmConstantinople(1); @@ -176,6 +186,10 @@ public void testRewardBalance() "0000000000000000000000000000000000000000000000000000000000000000"); repository.commit(); + // test state query + testStateQuery(trie, blockCap, + new DataWord(Base58.decode(nonexistentAccount)), rootInternalTransaction, trx); + // trigger deployed contract hexInput = AbiUtil.parseMethod(methodByAddr, Collections.singletonList(factoryAddressStr)); trx = TvmTestUtils.generateTriggerSmartContractAndGetTransaction(Hex.decode(OWNER_ADDRESS), @@ -194,6 +208,10 @@ public void testRewardBalance() "0000000000000000000000000000000000000000000000000000000000000000"); repository.commit(); + // test state query + testStateQuery(trie, blockCap, + new DataWord(Base58.decode(factoryAddressStr)), rootInternalTransaction, trx); + // trigger deployed contract String witnessAccount = "27Ssb1WE8FArwJVRRb8Dwy3ssVGuLY8L3S1"; hexInput = AbiUtil.parseMethod(methodByAddr, Collections.singletonList(witnessAccount)); @@ -213,6 +231,10 @@ public void testRewardBalance() "0000000000000000000000000000000000000000000000000000000000000000"); repository.commit(); + // test state query + testStateQuery(trie, blockCap, + new DataWord(Base58.decode(witnessAccount)), rootInternalTransaction, trx); + // Trigger contract method: nullAddressTest(address) methodByAddr = "nullAddressTest()"; hexInput = AbiUtil.parseMethod(methodByAddr, Collections.singletonList("")); @@ -232,6 +254,9 @@ public void testRewardBalance() "0000000000000000000000000000000000000000000000000000000000000000"); repository.commit(); + // test state query + testStateQuery(trie, blockCap, DataWord.ZERO(), rootInternalTransaction, trx); + // Trigger contract method: localContractAddrTest() methodByAddr = "localContractAddrTest()"; hexInput = AbiUtil.parseMethod(methodByAddr, Collections.singletonList("")); @@ -251,8 +276,34 @@ public void testRewardBalance() "0000000000000000000000000000000000000000000000000000000000000000"); repository.commit(); + // test state query + testStateQuery(trie, blockCap, + new DataWord(Base58.decode(factoryAddressStr)), rootInternalTransaction, trx); + + ConfigLoader.disable = false; } + + private void testStateQuery(TrieImpl2 trie, BlockCapsule blockCap, DataWord address, + InternalTransaction rootInternalTransaction, + Transaction trx) throws ContractValidateException { + + worldStateCallBack.clear(); + trie.commit(); + trie.flush(); + byte[] root = trie.getRootHash(); + Repository repositoryState = RepositoryStateImpl.createRoot(Bytes32.wrap(root)); + ProgramInvoke programInvoke = ProgramInvokeFactory + .createProgramInvoke(InternalTransaction.TrxType.TRX_CONTRACT_CALL_TYPE, + InternalTransaction.ExecutorType.ET_PRE_TYPE, trx, + 0, 0, blockCap.getInstance(), repositoryState, System.nanoTime() / 1000, + System.nanoTime() / 1000 + 50000, 3_000_000L); + Program program = new Program(null, null, programInvoke, rootInternalTransaction); + byte[] result = program.getRewardBalance(address) + .getData(); + Assert.assertEquals(Hex.toHexString(result), + "0000000000000000000000000000000000000000000000000000000000000000"); + } } diff --git a/framework/src/test/java/org/tron/common/runtime/vm/TransferToAccountTest.java b/framework/src/test/java/org/tron/common/runtime/vm/TransferToAccountTest.java index ede47555f3f..a6659b0b4f6 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/TransferToAccountTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/TransferToAccountTest.java @@ -1,6 +1,7 @@ package org.tron.common.runtime.vm; import com.google.protobuf.ByteString; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; import org.junit.Assert; @@ -11,11 +12,11 @@ import org.tron.common.runtime.ProgramResult; import org.tron.common.runtime.Runtime; import org.tron.common.runtime.TvmTestUtils; +import org.tron.common.runtime.VmStateTestUtil; import org.tron.common.utils.ByteArray; import org.tron.common.utils.StringUtil; import org.tron.common.utils.Utils; import org.tron.common.utils.client.utils.AbiUtil; -import org.tron.core.ChainBaseManager; import org.tron.core.Constant; import org.tron.core.Wallet; import org.tron.core.actuator.VMActuator; @@ -29,6 +30,10 @@ import org.tron.core.exception.ContractValidateException; import org.tron.core.exception.ReceiptCheckErrException; import org.tron.core.exception.VMIllegalException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.AccountStateStore; +import org.tron.core.state.trie.TrieImpl2; import org.tron.core.store.StoreFactory; import org.tron.core.vm.EnergyCost; import org.tron.core.vm.repository.RepositoryImpl; @@ -52,6 +57,8 @@ public class TransferToAccountTest extends BaseTest { private static Runtime runtime; private static RepositoryImpl repository; private static AccountCapsule ownerCapsule; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath(), "--debug"}, Constant.TEST_CONF); @@ -116,15 +123,30 @@ private long createAsset(String tokenName) { public void TransferTokenTest() throws ContractExeException, ReceiptCheckErrException, VMIllegalException, ContractValidateException { + worldStateCallBack.setExecute(true); + // open account asset optimize + chainBaseManager.getDynamicPropertiesStore().setAllowAssetOptimization(1); + // 1. Test deploy with tokenValue and tokenId */ long id = createAsset("testToken1"); byte[] contractAddress = deployTransferContract(id); repository.commit(); + + WorldStateQueryInstance worldStateQueryInstance = flushTrieAndGetQueryInstance(); Assert.assertEquals(100, chainBaseManager.getAccountStore() .get(contractAddress).getAssetV2MapForTest().get(String.valueOf(id)).longValue()); + Assert.assertEquals(100, + worldStateQueryInstance.getAccount(contractAddress) + .getAssetV2MapForTest().get(String.valueOf(id)).longValue()); + try (AccountStateStore store = new AccountStateStore(worldStateQueryInstance)) { + Assert.assertEquals(100, + store.get(contractAddress).getAssetV2MapForTest().get(String.valueOf(id)).longValue()); + } Assert.assertEquals(1000, chainBaseManager.getAccountStore().get(contractAddress).getBalance()); + Assert.assertEquals(1000, + worldStateQueryInstance.getAccount(contractAddress).getBalance()); String selectorStr = "transferTokenTo(address,trcToken,uint256)"; @@ -141,15 +163,23 @@ public void TransferTokenTest() .generateTriggerSmartContractAndGetTransaction(Hex.decode(OWNER_ADDRESS), contractAddress, input, triggerCallValue, feeLimit, tokenValue, id); + VmStateTestUtil.runConstantCall(chainBaseManager, worldStateCallBack, transaction); runtime = TvmTestUtils.processTransactionAndReturnRuntime(transaction, dbManager, null); + worldStateQueryInstance = flushTrieAndGetQueryInstance(); Assert.assertNull(runtime.getRuntimeError()); Assert.assertEquals(9, chainBaseManager.getAccountStore().get(Hex.decode(TRANSFER_TO)).getAssetV2MapForTest() .get(String.valueOf(id)).longValue()); + Assert.assertEquals(9, + worldStateQueryInstance.getAccount(Hex.decode(TRANSFER_TO)).getAssetV2MapForTest() + .get(String.valueOf(id)).longValue()); Assert.assertEquals(100 + tokenValue - 9, chainBaseManager.getAccountStore().get(contractAddress) .getAssetV2MapForTest().get(String.valueOf(id)).longValue()); + Assert.assertEquals(100 + tokenValue - 9, + worldStateQueryInstance.getAccount(contractAddress) + .getAssetV2MapForTest().get(String.valueOf(id)).longValue()); long energyCostWhenExist = runtime.getResult().getEnergyUsed(); // 3.Test transferToken To Non-exist address @@ -163,13 +193,20 @@ public void TransferTokenTest() triggerCallValue, feeLimit, tokenValue, id); runtime = TvmTestUtils.processTransactionAndReturnRuntime(transaction, dbManager, null); + worldStateQueryInstance = flushTrieAndGetQueryInstance(); Assert.assertNull(runtime.getRuntimeError()); Assert.assertEquals(100 + tokenValue * 2 - 18, chainBaseManager.getAccountStore().get(contractAddress).getAssetV2MapForTest() .get(String.valueOf(id)).longValue()); + Assert.assertEquals(100 + tokenValue * 2 - 18, + worldStateQueryInstance.getAccount(contractAddress).getAssetV2MapForTest() + .get(String.valueOf(id)).longValue()); Assert.assertEquals(9, chainBaseManager.getAccountStore().get(ecKey.getAddress()).getAssetV2MapForTest() .get(String.valueOf(id)).longValue()); + Assert.assertEquals(9, + worldStateQueryInstance.getAccount(ecKey.getAddress()).getAssetV2MapForTest() + .get(String.valueOf(id)).longValue()); long energyCostWhenNonExist = runtime.getResult().getEnergyUsed(); //4.Test Energy Assert.assertEquals(energyCostWhenNonExist - energyCostWhenExist, @@ -184,10 +221,16 @@ public void TransferTokenTest() .generateTriggerSmartContractAndGetTransaction(Hex.decode(OWNER_ADDRESS), contractAddress, input, triggerCallValue, feeLimit, 0, 0); + // test state call + VmStateTestUtil.runConstantCall(chainBaseManager, worldStateCallBack, transaction); runtime = TvmTestUtils.processTransactionAndReturnRuntime(transaction, dbManager, null); + + worldStateQueryInstance = flushTrieAndGetQueryInstance(); Assert.assertNull(runtime.getRuntimeError()); Assert.assertEquals(19, chainBaseManager.getAccountStore().get(Hex.decode(TRANSFER_TO)).getBalance()); + Assert.assertEquals(19, + worldStateQueryInstance.getAccount(Hex.decode(TRANSFER_TO)).getBalance()); energyCostWhenExist = runtime.getResult().getEnergyUsed(); //6. Test transfer Trx with non-exsit account @@ -200,10 +243,15 @@ public void TransferTokenTest() .generateTriggerSmartContractAndGetTransaction(Hex.decode(OWNER_ADDRESS), contractAddress, input, triggerCallValue, feeLimit, 0, 0); + VmStateTestUtil.runConstantCall(chainBaseManager, worldStateCallBack, transaction); runtime = TvmTestUtils.processTransactionAndReturnRuntime(transaction, dbManager, null); + + worldStateQueryInstance = flushTrieAndGetQueryInstance(); Assert.assertNull(runtime.getRuntimeError()); Assert.assertEquals(9, chainBaseManager.getAccountStore().get(ecKey.getAddress()).getBalance()); + Assert.assertEquals(9, + worldStateQueryInstance.getAccount(ecKey.getAddress()).getBalance()); energyCostWhenNonExist = runtime.getResult().getEnergyUsed(); //7.test energy @@ -219,6 +267,8 @@ public void TransferTokenTest() .generateTriggerSmartContractAndGetTransaction(Hex.decode(OWNER_ADDRESS), contractAddress, input, triggerCallValue, feeLimit, 0, 0); + Assert.assertTrue(VmStateTestUtil.runConstantCall( + chainBaseManager, worldStateCallBack, transaction).getRuntimeError().contains("failed")); runtime = TvmTestUtils.processTransactionAndReturnRuntime(transaction, dbManager, null); Assert.assertTrue(runtime.getRuntimeError().contains("failed")); @@ -234,6 +284,7 @@ public void TransferTokenTest() .generateTriggerSmartContractAndGetTransaction(Hex.decode(OWNER_ADDRESS), contractAddress, triggerData, triggerCallValue, feeLimit, tokenValue, id); + VmStateTestUtil.runConstantCall(chainBaseManager, worldStateCallBack, transaction); runtime = TvmTestUtils.processTransactionAndReturnRuntime(transaction, dbManager, null); Assert.assertEquals("endowment out of long range", runtime.getRuntimeError()); @@ -292,9 +343,21 @@ private byte[] deployTransferContract(long id) long tokenValue = 100; long tokenId = id; - return TvmTestUtils + // test state deploy contract + Transaction tx = TvmTestUtils.generateDeploySmartContractAndGetTransaction( + contractName, address, ABI, code, value, + feeLimit, consumeUserResourcePercent, tokenValue, tokenId, null); + VmStateTestUtil.runConstantCall(chainBaseManager, worldStateCallBack, tx); + + byte[] contractAddress = TvmTestUtils .deployContractWholeProcessReturnContractAddress(contractName, address, ABI, code, value, feeLimit, consumeUserResourcePercent, null, tokenValue, tokenId, repository, null); + return contractAddress; + } + + private WorldStateQueryInstance flushTrieAndGetQueryInstance() { + TrieImpl2 trie = VmStateTestUtil.flushTrie(worldStateCallBack); + return new WorldStateQueryInstance(trie.getRootHashByte32(), chainBaseManager); } } diff --git a/framework/src/test/java/org/tron/common/runtime/vm/VoteTest.java b/framework/src/test/java/org/tron/common/runtime/vm/VoteTest.java index 1d85e9a7eab..2596dce72c2 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/VoteTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/VoteTest.java @@ -28,6 +28,7 @@ import org.tron.common.runtime.RuntimeImpl; import org.tron.common.runtime.TVMTestResult; import org.tron.common.runtime.TvmTestUtils; +import org.tron.common.runtime.VmStateTestUtil; import org.tron.common.utils.Commons; import org.tron.common.utils.FileUtil; import org.tron.common.utils.StringUtil; @@ -35,6 +36,7 @@ import org.tron.common.utils.client.utils.AbiUtil; import org.tron.common.utils.client.utils.DataWord; import org.tron.consensus.dpos.MaintenanceManager; +import org.tron.core.ChainBaseManager; import org.tron.core.Constant; import org.tron.core.Wallet; import org.tron.core.capsule.AccountCapsule; @@ -45,6 +47,7 @@ import org.tron.core.db.Manager; import org.tron.core.db.TransactionTrace; import org.tron.core.service.MortgageService; +import org.tron.core.state.WorldStateCallBack; import org.tron.core.store.StoreFactory; import org.tron.core.vm.config.ConfigLoader; import org.tron.core.vm.config.VMConfig; @@ -275,6 +278,8 @@ private static Consumer getSmallerConsumer(long expected) { private static MortgageService mortgageService; private static byte[] owner; private static Repository rootRepository; + private static ChainBaseManager chainBaseManager; + private static WorldStateCallBack worldStateCallBack; @Before public void init() throws Exception { @@ -285,6 +290,9 @@ public void init() throws Exception { manager = context.getBean(Manager.class); maintenanceManager = context.getBean(MaintenanceManager.class); consensusService = context.getBean(ConsensusService.class); + chainBaseManager = context.getBean(ChainBaseManager.class); + worldStateCallBack = context.getBean(WorldStateCallBack.class); + worldStateCallBack.setExecute(true); consensusService.start(); mortgageService = context.getBean(MortgageService.class); owner = Hex.decode(Wallet.getAddressPreFixString() @@ -346,6 +354,8 @@ private TVMTestResult triggerContract(byte[] contractAddr, TransactionCapsule trxCap = new TransactionCapsule( TvmTestUtils.generateTriggerSmartContractAndGetTransaction( owner, contractAddr, Hex.decode(hexInput), 0, fee)); + VmStateTestUtil.runConstantCall( + chainBaseManager, worldStateCallBack, trxCap.getInstance()); TransactionTrace trace = new TransactionTrace(trxCap, StoreFactory.getInstance(), new RuntimeImpl()); trxCap.setTrxTrace(trace); diff --git a/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java index 95577f46e50..61d4ab6e2a4 100644 --- a/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java @@ -16,6 +16,8 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Resource; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -31,6 +33,9 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.DynamicPropertiesStateStore; import org.tron.core.store.DynamicPropertiesStore; import org.tron.protos.Protocol; import org.tron.protos.Protocol.AccountType; @@ -47,6 +52,8 @@ public class DelegateResourceActuatorTest extends BaseTest { private static final String OWNER_ADDRESS_INVALID = "aaaa"; private static final String OWNER_ACCOUNT_INVALID; private static final long initBalance = 1000_000_000_000L; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -61,6 +68,7 @@ public class DelegateResourceActuatorTest extends BaseTest { */ @Before public void createAccountCapsule() { + worldStateCallBack.setExecute(true); dbManager.getDynamicPropertiesStore().saveTotalNetWeight(0L); dbManager.getDynamicPropertiesStore().saveUnfreezeDelayDays(1L); dbManager.getDynamicPropertiesStore().saveAllowNewResourceModel(1L); @@ -93,6 +101,11 @@ public void createAccountCapsule() { dbManager.getDelegatedResourceAccountIndexStore().unDelegateV2(owner, receiver); } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + public void freezeBandwidthForOwner() { AccountCapsule ownerCapsule = dbManager.getAccountStore().get(ByteArray.fromHexString(OWNER_ADDRESS)); @@ -332,10 +345,17 @@ public void testDelegateResourceForBandwidth() { AccountCapsule ownerCapsule = dbManager.getAccountStore().get(owner); - assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth()); - assertEquals(initBalance - delegateBalance, + Assert.assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth()); + Assert.assertEquals(initBalance - delegateBalance, ownerCapsule.getFrozenV2BalanceForBandwidth()); - assertEquals(initBalance, ownerCapsule.getTronPower()); + Assert.assertEquals(initBalance, ownerCapsule.getTronPower()); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + ownerCapsule = queryInstance.getAccount(owner); + Assert.assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth()); + Assert.assertEquals(initBalance - delegateBalance, + ownerCapsule.getFrozenV2BalanceForBandwidth()); + Assert.assertEquals(initBalance, ownerCapsule.getTronPower()); AccountCapsule receiverCapsule = dbManager.getAccountStore().get(receiver); @@ -344,14 +364,30 @@ public void testDelegateResourceForBandwidth() { assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy()); assertEquals(0L, receiverCapsule.getTronPower()); + receiverCapsule = queryInstance.getAccount(receiver); + Assert.assertEquals(delegateBalance, + receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForBandwidth()); + Assert.assertEquals(0L, + receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy()); + Assert.assertEquals(0L, receiverCapsule.getTronPower()); + DelegatedResourceCapsule delegatedResourceCapsule = dbManager.getDelegatedResourceStore() .get(DelegatedResourceCapsule .createDbKeyV2(ByteArray.fromHexString(OWNER_ADDRESS), ByteArray.fromHexString(RECEIVER_ADDRESS), false)); - assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth()); + Assert.assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth()); + + delegatedResourceCapsule = queryInstance.getDelegatedResource(DelegatedResourceCapsule + .createDbKeyV2(ByteArray.fromHexString(OWNER_ADDRESS), + ByteArray.fromHexString(RECEIVER_ADDRESS), false)); + Assert.assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth()); + long totalNetWeightAfter = dbManager.getDynamicPropertiesStore().getTotalNetWeight(); - assertEquals(totalNetWeightBefore, totalNetWeightAfter); + Assert.assertEquals(totalNetWeightBefore, totalNetWeightAfter); + DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(queryInstance); + Assert.assertEquals(totalNetWeightBefore, stateStore.getTotalNetWeight()); + //check DelegatedResourceAccountIndex DelegatedResourceAccountIndexCapsule ownerIndexCapsule = dbManager @@ -887,4 +923,13 @@ public void testDelegateResourceNoFreeze123() { Assert.assertEquals(true, bSuccess); } + + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } } diff --git a/framework/src/test/java/org/tron/core/actuator/FreezeBalanceActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/FreezeBalanceActuatorTest.java index 18eef50c36f..690d530a227 100644 --- a/framework/src/test/java/org/tron/core/actuator/FreezeBalanceActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/FreezeBalanceActuatorTest.java @@ -6,7 +6,9 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import java.util.List; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -23,8 +25,12 @@ import org.tron.core.capsule.TransactionResultCapsule; import org.tron.core.config.Parameter.ChainConstant; import org.tron.core.config.args.Args; +import org.tron.core.db.Manager; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.DynamicPropertiesStateStore; import org.tron.protos.Protocol.AccountType; import org.tron.protos.Protocol.Transaction.Result.code; import org.tron.protos.contract.AssetIssueContractOuterClass; @@ -39,6 +45,8 @@ public class FreezeBalanceActuatorTest extends BaseTest { private static final String OWNER_ADDRESS_INVALID = "aaaa"; private static final String OWNER_ACCOUNT_INVALID; private static final long initBalance = 10_000_000_000L; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -53,6 +61,7 @@ public class FreezeBalanceActuatorTest extends BaseTest { */ @Before public void createAccountCapsule() { + worldStateCallBack.setExecute(true); AccountCapsule ownerCapsule = new AccountCapsule( ByteString.copyFromUtf8("owner"), @@ -70,6 +79,11 @@ public void createAccountCapsule() { dbManager.getAccountStore().put(receiverCapsule.getAddress().toByteArray(), receiverCapsule); } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private Any getContractForBandwidth(String ownerAddress, long frozenBalance, long duration) { return Any.pack( FreezeBalanceContract.newBuilder() @@ -144,6 +158,14 @@ public void testFreezeBalanceForBandwidth() { - TRANSFER_FEE); Assert.assertEquals(owner.getFrozenBalance(), frozenBalance); Assert.assertEquals(frozenBalance, owner.getTronPower()); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getBalance(), initBalance - frozenBalance + - TRANSFER_FEE); + Assert.assertEquals(owner.getFrozenBalance(), frozenBalance); + Assert.assertEquals(frozenBalance, owner.getTronPower()); + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -170,6 +192,14 @@ public void testFreezeBalanceForEnergy() { Assert.assertEquals(0L, owner.getFrozenBalance()); Assert.assertEquals(frozenBalance, owner.getEnergyFrozenBalance()); Assert.assertEquals(frozenBalance, owner.getTronPower()); + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getBalance(), initBalance - frozenBalance + - TRANSFER_FEE); + Assert.assertEquals(0L, owner.getFrozenBalance()); + Assert.assertEquals(frozenBalance, owner.getEnergyFrozenBalance()); + Assert.assertEquals(frozenBalance, owner.getTronPower()); + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -232,21 +262,43 @@ public void testFreezeDelegatedBalanceForBandwidth() { Assert.assertEquals(frozenBalance, owner.getDelegatedFrozenBalanceForBandwidth()); Assert.assertEquals(frozenBalance, owner.getTronPower()); + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getBalance(), initBalance - frozenBalance - TRANSFER_FEE); + Assert.assertEquals(0L, owner.getFrozenBalance()); + Assert.assertEquals(frozenBalance, owner.getDelegatedFrozenBalanceForBandwidth()); + Assert.assertEquals(frozenBalance, owner.getTronPower()); + AccountCapsule receiver = dbManager.getAccountStore().get(ByteArray.fromHexString(RECEIVER_ADDRESS)); Assert.assertEquals(frozenBalance, receiver.getAcquiredDelegatedFrozenBalanceForBandwidth()); Assert.assertEquals(0L, receiver.getAcquiredDelegatedFrozenBalanceForEnergy()); Assert.assertEquals(0L, receiver.getTronPower()); + receiver = queryInstance.getAccount(ByteArray.fromHexString(RECEIVER_ADDRESS)); + Assert.assertEquals(frozenBalance, receiver.getAcquiredDelegatedFrozenBalanceForBandwidth()); + Assert.assertEquals(0L, receiver.getAcquiredDelegatedFrozenBalanceForEnergy()); + Assert.assertEquals(0L, receiver.getTronPower()); + DelegatedResourceCapsule delegatedResourceCapsule = dbManager.getDelegatedResourceStore() .get(DelegatedResourceCapsule .createDbKey(ByteArray.fromHexString(OWNER_ADDRESS), ByteArray.fromHexString(RECEIVER_ADDRESS))); Assert.assertEquals(frozenBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth()); + + delegatedResourceCapsule = queryInstance.getDelegatedResource(DelegatedResourceCapsule + .createDbKey(ByteArray.fromHexString(OWNER_ADDRESS), + ByteArray.fromHexString(RECEIVER_ADDRESS))); + Assert.assertEquals(frozenBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth()); + long totalNetWeightAfter = dbManager.getDynamicPropertiesStore().getTotalNetWeight(); Assert.assertEquals(totalNetWeightBefore + frozenBalance / 1000_000L, totalNetWeightAfter); + DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(queryInstance); + Assert.assertEquals(totalNetWeightBefore + frozenBalance / 1000_000L, + stateStore.getTotalNetWeight()); + //check DelegatedResourceAccountIndex DelegatedResourceAccountIndexCapsule delegatedResourceAccountIndexCapsuleOwner = dbManager .getDelegatedResourceAccountIndexStore().get(ByteArray.fromHexString(OWNER_ADDRESS)); @@ -256,6 +308,17 @@ public void testFreezeDelegatedBalanceForBandwidth() { Assert.assertTrue(delegatedResourceAccountIndexCapsuleOwner.getToAccountsList() .contains(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)))); + delegatedResourceAccountIndexCapsuleOwner = queryInstance.getDelegatedResourceAccountIndex( + ByteArray.fromHexString(OWNER_ADDRESS)); + + Assert.assertEquals(0, + delegatedResourceAccountIndexCapsuleOwner.getFromAccountsList().size()); + Assert.assertEquals(1, + delegatedResourceAccountIndexCapsuleOwner.getToAccountsList().size()); + Assert.assertEquals(true, + delegatedResourceAccountIndexCapsuleOwner.getToAccountsList() + .contains(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)))); + DelegatedResourceAccountIndexCapsule delegatedResourceAccountIndexCapsuleReceiver = dbManager .getDelegatedResourceAccountIndexStore().get(ByteArray.fromHexString(RECEIVER_ADDRESS)); Assert @@ -266,6 +329,15 @@ public void testFreezeDelegatedBalanceForBandwidth() { Assert.assertTrue(delegatedResourceAccountIndexCapsuleReceiver.getFromAccountsList() .contains(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)))); + delegatedResourceAccountIndexCapsuleReceiver = queryInstance.getDelegatedResourceAccountIndex( + ByteArray.fromHexString(RECEIVER_ADDRESS)); + Assert.assertEquals(0, + delegatedResourceAccountIndexCapsuleReceiver.getToAccountsList().size()); + Assert.assertEquals(1, + delegatedResourceAccountIndexCapsuleReceiver.getFromAccountsList().size()); + Assert.assertTrue(delegatedResourceAccountIndexCapsuleReceiver.getFromAccountsList() + .contains(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)))); + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); @@ -758,4 +830,13 @@ public void testFreezeBalanceForTronPowerWithOldTronPowerAfterNewResourceModel() } } + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } + } diff --git a/framework/src/test/java/org/tron/core/actuator/FreezeBalanceV2ActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/FreezeBalanceV2ActuatorTest.java index 86b0e3143ab..975977a7fcc 100644 --- a/framework/src/test/java/org/tron/core/actuator/FreezeBalanceV2ActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/FreezeBalanceV2ActuatorTest.java @@ -5,7 +5,9 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -20,6 +22,8 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; import org.tron.protos.Protocol.AccountType; import org.tron.protos.Protocol.Transaction.Result.code; import org.tron.protos.contract.AssetIssueContractOuterClass; @@ -34,6 +38,8 @@ public class FreezeBalanceV2ActuatorTest extends BaseTest { private static final String OWNER_ADDRESS_INVALID = "aaaa"; private static final String OWNER_ACCOUNT_INVALID; private static final long initBalance = 10_000_000_000L; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -48,6 +54,7 @@ public class FreezeBalanceV2ActuatorTest extends BaseTest { */ @Before public void createAccountCapsule() { + worldStateCallBack.setExecute(true); dbManager.getDynamicPropertiesStore().saveUnfreezeDelayDays(1L); dbManager.getDynamicPropertiesStore().saveAllowNewResourceModel(1L); @@ -68,6 +75,11 @@ public void createAccountCapsule() { dbManager.getAccountStore().put(receiverCapsule.getAddress().toByteArray(), receiverCapsule); } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private Any getContractV2ForBandwidth(String ownerAddress, long frozenBalance) { return Any.pack( BalanceContract.FreezeBalanceV2Contract.newBuilder() @@ -136,6 +148,12 @@ public void testFreezeBalanceForBandwidth() { - TRANSFER_FEE); Assert.assertEquals(owner.getFrozenV2BalanceForBandwidth(), frozenBalance); Assert.assertEquals(frozenBalance, owner.getTronPower()); + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getBalance(), initBalance - frozenBalance - TRANSFER_FEE); + Assert.assertEquals(owner.getFrozenV2BalanceForBandwidth(), frozenBalance); + Assert.assertEquals(frozenBalance, owner.getTronPower()); + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -161,6 +179,12 @@ public void testFreezeBalanceForEnergy() { Assert.assertEquals(0L, owner.getAllFrozenBalanceForBandwidth()); Assert.assertEquals(frozenBalance, owner.getAllFrozenBalanceForEnergy()); Assert.assertEquals(frozenBalance, owner.getTronPower()); + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getBalance(), initBalance - frozenBalance - TRANSFER_FEE); + Assert.assertEquals(0L, owner.getAllFrozenBalanceForBandwidth()); + Assert.assertEquals(frozenBalance, owner.getAllFrozenBalanceForEnergy()); + Assert.assertEquals(frozenBalance, owner.getTronPower()); } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -366,6 +390,11 @@ public void testFreezeBalanceForEnergyWithOldTronPowerAfterNewResourceModel() { Assert.assertEquals(100L, owner.getInstance().getOldTronPower()); Assert.assertEquals(100L, owner.getAllTronPower()); + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(100L, owner.getInstance().getOldTronPower()); + Assert.assertEquals(100L, owner.getAllTronPower()); + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -397,9 +426,26 @@ public void testFreezeBalanceForTronPowerWithOldTronPowerAfterNewResourceModel() Assert.assertEquals(100L, owner.getInstance().getOldTronPower()); Assert.assertEquals(100L, owner.getTronPower()); Assert.assertEquals(frozenBalance + 100, owner.getAllTronPower()); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + + Assert.assertEquals(100L, owner.getInstance().getOldTronPower()); + Assert.assertEquals(100L, owner.getTronPower()); + Assert.assertEquals(frozenBalance + 100, owner.getAllTronPower()); } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } } + private WorldStateQueryInstance getQueryInstance() { + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance( + worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } + + } diff --git a/framework/src/test/java/org/tron/core/actuator/TransferActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/TransferActuatorTest.java index 12df9031ca8..98ac18c8808 100644 --- a/framework/src/test/java/org/tron/core/actuator/TransferActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/TransferActuatorTest.java @@ -6,8 +6,10 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import java.util.Date; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -24,6 +26,8 @@ import org.tron.core.exception.ContractValidateException; import org.tron.core.exception.ReceiptCheckErrException; import org.tron.core.exception.VMIllegalException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; import org.tron.core.store.StoreFactory; import org.tron.core.vm.repository.RepositoryImpl; import org.tron.protos.Protocol.AccountType; @@ -44,6 +48,8 @@ public class TransferActuatorTest extends BaseTest { private static final String OWNER_ACCOUNT_INVALID; private static final String OWNER_NO_BALANCE; private static final String To_ACCOUNT_INVALID; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -61,6 +67,7 @@ public class TransferActuatorTest extends BaseTest { */ @Before public void createCapsule() { + worldStateCallBack.setExecute(true); AccountCapsule ownerCapsule = new AccountCapsule( ByteString.copyFromUtf8("owner"), @@ -77,6 +84,11 @@ public void createCapsule() { dbManager.getAccountStore().put(toAccountCapsule.getAddress().toByteArray(), toAccountCapsule); } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private Any getContract(long count, byte[] address) { long nowTime = new Date().getTime(); return Any.pack( @@ -121,6 +133,12 @@ public void rightTransfer() { Assert.assertEquals(owner.getBalance(), OWNER_BALANCE - AMOUNT - TRANSFER_FEE); Assert.assertEquals(toAccount.getBalance(), TO_BALANCE + AMOUNT); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(owner.getBalance(), + queryInstance.getAccount(owner.createDbKey()).getBalance()); + Assert.assertEquals(toAccount.getBalance(), + queryInstance.getAccount(toAccount.createDbKey()).getBalance()); Assert.assertTrue(true); } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); @@ -147,6 +165,11 @@ public void perfectTransfer() { Assert.assertEquals(owner.getBalance(), 0); Assert.assertEquals(toAccount.getBalance(), TO_BALANCE + OWNER_BALANCE); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(owner.getBalance(), + queryInstance.getAccount(owner.createDbKey()).getBalance()); + Assert.assertEquals(toAccount.getBalance(), + queryInstance.getAccount(toAccount.createDbKey()).getBalance()); Assert.assertTrue(true); } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); @@ -514,4 +537,14 @@ public void transferToSmartContractAddress() } } + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance( + worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } + } diff --git a/framework/src/test/java/org/tron/core/actuator/TransferAssetActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/TransferAssetActuatorTest.java index 8c936f16dc5..459566d5b2e 100755 --- a/framework/src/test/java/org/tron/core/actuator/TransferAssetActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/TransferAssetActuatorTest.java @@ -20,8 +20,10 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -39,6 +41,8 @@ import org.tron.core.exception.ContractValidateException; import org.tron.core.exception.ReceiptCheckErrException; import org.tron.core.exception.VMIllegalException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; import org.tron.core.store.StoreFactory; import org.tron.core.vm.config.VMConfig; import org.tron.core.vm.repository.RepositoryImpl; @@ -70,6 +74,9 @@ public class TransferAssetActuatorTest extends BaseTest { private static final int VOTE_SCORE = 2; private static final String DESCRIPTION = "TRX"; private static final String URL = "https://tron.network"; + private static Any contract; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -87,6 +94,7 @@ public class TransferAssetActuatorTest extends BaseTest { */ @Before public void createCapsule() { + worldStateCallBack.setExecute(true); AccountCapsule toAccountCapsule = new AccountCapsule( ByteString.copyFrom(ByteArray.fromHexString(TO_ADDRESS)), @@ -96,6 +104,11 @@ public void createCapsule() { } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private boolean isNullOrZero(Long value) { if (null == value || value == 0) { return true; @@ -292,6 +305,21 @@ public void SameTokenNameCloseSuccessTransfer() { toAccount.getAssetV2MapForTest().get(String.valueOf(tokenIdNum)).longValue(), 100L); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(OWNER_ASSET_BALANCE - 100, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + Assert.assertEquals(100L, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + + Assert.assertEquals(OWNER_ASSET_BALANCE - 100, + queryInstance.getAccount(owner.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + Assert.assertEquals(100L, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -329,6 +357,21 @@ public void SameTokenNameOpenSuccessTransfer() { toAccount.getInstance().getAssetV2Map().get(String.valueOf(tokenIdNum)).longValue(), 100L); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(OWNER_ASSET_BALANCE - 100, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + Assert.assertEquals(100L, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + + Assert.assertEquals(OWNER_ASSET_BALANCE - 100, + queryInstance.getAccount(owner.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + Assert.assertEquals(100L, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -366,6 +409,22 @@ public void SameTokenNameCloseSuccessTransfer2() { Assert.assertEquals( toAccount.getAssetV2MapForTest().get(String.valueOf(tokenIdNum)).longValue(), OWNER_ASSET_BALANCE); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(0L, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + Assert.assertEquals(OWNER_ASSET_BALANCE, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + + Assert.assertEquals(0L, + queryInstance.getAccount(owner.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + Assert.assertEquals(OWNER_ASSET_BALANCE, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -405,6 +464,21 @@ public void OldNotUpdateSuccessTransfer2() { Assert.assertEquals( toAccount.getAssetV2MapForTest().get(String.valueOf(tokenIdNum)).longValue(), OWNER_ASSET_BALANCE); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(0L, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + Assert.assertEquals(OWNER_ASSET_BALANCE, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + + Assert.assertEquals(0L, + queryInstance.getAccount(owner.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + Assert.assertEquals(OWNER_ASSET_BALANCE, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -441,6 +515,20 @@ public void SameTokenNameOpenSuccessTransfer2() { Assert.assertEquals( toAccount.getAssetV2MapForTest().get(String.valueOf(tokenIdNum)).longValue(), OWNER_ASSET_BALANCE); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(0L, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + Assert.assertEquals(OWNER_ASSET_BALANCE, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + + Assert.assertEquals(0L, + queryInstance.getAccount(owner.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + Assert.assertEquals(OWNER_ASSET_BALANCE, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -1431,10 +1519,34 @@ public void transferToContractAddress() Assert.assertEquals( toAccount.getInstance().getAssetV2Map().get(String.valueOf(tokenIdNum)).longValue(), 100L); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(OWNER_ASSET_BALANCE - 100, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + Assert.assertEquals(100L, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenIdNum)).longValue()); + + Assert.assertEquals(OWNER_ASSET_BALANCE - 100, + queryInstance.getAccount(owner.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); + Assert.assertEquals(100L, + queryInstance.getAccount(toAccount.createDbKey()).getAssetV2( + String.valueOf(tokenIdNum))); } catch (ContractValidateException e) { Assert.assertTrue(e.getMessage().contains("Cannot transfer")); } catch (ContractExeException e) { Assert.assertFalse(e instanceof ContractExeException); } } + + + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } } diff --git a/framework/src/test/java/org/tron/core/actuator/UnfreezeAssetActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/UnfreezeAssetActuatorTest.java index 7471e9ba20f..9d30f48edfd 100644 --- a/framework/src/test/java/org/tron/core/actuator/UnfreezeAssetActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/UnfreezeAssetActuatorTest.java @@ -2,7 +2,9 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -17,6 +19,8 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Account.Frozen; import org.tron.protos.Protocol.AccountType; @@ -34,6 +38,9 @@ public class UnfreezeAssetActuatorTest extends BaseTest { private static final long initBalance = 10_000_000_000L; private static final long frozenBalance = 1_000_000_000L; private static final String assetName = "testCoin"; + private static final String assetID = "123456"; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -47,12 +54,18 @@ public class UnfreezeAssetActuatorTest extends BaseTest { */ @Before public void createAccountCapsule() { + worldStateCallBack.setExecute(true); } @Before public void createAsset() { } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private Any getContract(String ownerAddress) { return Any.pack( UnfreezeAssetContract.newBuilder() @@ -242,7 +255,13 @@ public void SameTokenNameActiveInitAndAcitveUnfreezeAsset() { //V2 Assert.assertEquals(owner.getAssetV2MapForTest().get(String.valueOf(tokenId)).longValue(), frozenBalance); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(frozenBalance, + queryInstance.getAccount(owner.createDbKey()).getAssetV2MapForTest() + .get(String.valueOf(tokenId)).longValue()); Assert.assertEquals(owner.getFrozenSupplyCount(), 1); + Assert.assertEquals(1, + queryInstance.getAccount(owner.createDbKey()).getFrozenSupplyCount()); } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -398,4 +417,13 @@ public void commonErrorCheck() { actuatorTest.setNullDBManagerMsg("No account store or dynamic store!"); actuatorTest.nullDBManger(); } + + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } } \ No newline at end of file diff --git a/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceActuatorTest.java index e9f1d934e5a..5364618eb30 100644 --- a/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceActuatorTest.java @@ -6,7 +6,9 @@ import com.google.protobuf.ByteString; import java.util.ArrayList; import java.util.List; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -22,6 +24,9 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.DynamicPropertiesStateStore; import org.tron.protos.Protocol.AccountType; import org.tron.protos.Protocol.Transaction.Result.code; import org.tron.protos.Protocol.Vote; @@ -38,6 +43,9 @@ public class UnfreezeBalanceActuatorTest extends BaseTest { private static final String OWNER_ACCOUNT_INVALID; private static final long initBalance = 10_000_000_000L; private static final long frozenBalance = 1_000_000_000L; + private static final long smallTatalResource = 100L; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -52,6 +60,7 @@ public class UnfreezeBalanceActuatorTest extends BaseTest { */ @Before public void createAccountCapsule() { + worldStateCallBack.setExecute(true); AccountCapsule ownerCapsule = new AccountCapsule(ByteString.copyFromUtf8("owner"), ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)), AccountType.Normal, initBalance); @@ -63,6 +72,11 @@ public void createAccountCapsule() { dbManager.getAccountStore().put(receiverCapsule.getAddress().toByteArray(), receiverCapsule); } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private Any getContractForBandwidth(String ownerAddress) { return Any.pack(UnfreezeBalanceContract.newBuilder() .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(ownerAddress))).build()); @@ -130,11 +144,24 @@ public void testUnfreezeBalanceForBandwidth() { Assert.assertEquals(owner.getBalance(), initBalance + frozenBalance); Assert.assertEquals(0, owner.getFrozenBalance()); Assert.assertEquals(0L, owner.getTronPower()); + WorldStateQueryInstance instance = getQueryInstance(); + Assert.assertEquals(owner.getBalance(), + instance.getAccount(owner.createDbKey()).getBalance()); + Assert.assertEquals(owner.getFrozenBalance(), + instance.getAccount(owner.createDbKey()).getFrozenBalance()); + Assert.assertEquals(owner.getTronPower(), + instance.getAccount(owner.createDbKey()).getTronPower()); + long totalNetWeightAfter = dbManager.getDynamicPropertiesStore().getTotalNetWeight(); Assert.assertEquals(totalNetWeightBefore, totalNetWeightAfter + frozenBalance / 1000_000L); + try (DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(instance)) { + Assert.assertEquals(totalNetWeightBefore, + stateStore.getTotalNetWeight() + frozenBalance / 1000_000L); + } + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -200,6 +227,9 @@ public void testUnfreezeSelfAndOthersForBandwidth() { actuator1.validate(); actuator1.execute(ret1); long afterWeight1 = dbManager.getDynamicPropertiesStore().getTotalNetWeight(); + WorldStateQueryInstance instance = getQueryInstance(); + DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(instance); + Assert.assertEquals(1, stateStore.getTotalNetWeight()); Assert.assertEquals(1, afterWeight1); Assert.assertEquals(code.SUCESS, ret1.getInstance().getRet()); } catch (ContractValidateException e) { @@ -218,6 +248,9 @@ public void testUnfreezeSelfAndOthersForBandwidth() { actuator.validate(); actuator.execute(ret); long afterWeight = dbManager.getDynamicPropertiesStore().getTotalNetWeight(); + WorldStateQueryInstance instance = getQueryInstance(); + DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(instance); + Assert.assertEquals(0, stateStore.getTotalNetWeight()); Assert.assertEquals(0, afterWeight); Assert.assertEquals(code.SUCESS, ret.getInstance().getRet()); } catch (ContractValidateException | ContractExeException e) { @@ -329,6 +362,17 @@ public void testUnfreezeDelegatedBalanceForBandwidth() { Assert.assertEquals(0L, ownerResult.getTronPower()); Assert.assertEquals(0L, ownerResult.getDelegatedFrozenBalanceForBandwidth()); Assert.assertEquals(0L, receiverResult.getAllFrozenBalanceForBandwidth()); + WorldStateQueryInstance queryInstance = getQueryInstance(); + Assert.assertEquals(initBalance + frozenBalance, queryInstance + .getAccount(ByteArray.fromHexString(OWNER_ADDRESS)).getBalance()); + Assert.assertEquals(0L, queryInstance + .getAccount(ByteArray.fromHexString(OWNER_ADDRESS)).getTronPower()); + Assert.assertEquals(0L, queryInstance + .getAccount(ByteArray.fromHexString(OWNER_ADDRESS)) + .getDelegatedFrozenBalanceForBandwidth()); + Assert.assertEquals(0L, queryInstance + .getAccount(ByteArray.fromHexString(RECEIVER_ADDRESS)) + .getAllFrozenBalanceForBandwidth()); //check DelegatedResourceAccountIndex DelegatedResourceAccountIndexCapsule delegatedResourceAccountIndexCapsuleOwner = dbManager @@ -345,6 +389,20 @@ public void testUnfreezeDelegatedBalanceForBandwidth() { Assert.assertEquals(0, delegatedResourceAccountIndexCapsuleReceiver.getFromAccountsList().size()); + delegatedResourceAccountIndexCapsuleOwner = queryInstance + .getDelegatedResourceAccountIndex(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(0, + delegatedResourceAccountIndexCapsuleOwner.getFromAccountsList().size()); + Assert.assertEquals(0, + delegatedResourceAccountIndexCapsuleOwner.getToAccountsList().size()); + + delegatedResourceAccountIndexCapsuleReceiver = queryInstance + .getDelegatedResourceAccountIndex(ByteArray.fromHexString(RECEIVER_ADDRESS)); + Assert.assertEquals(0, + delegatedResourceAccountIndexCapsuleReceiver.getToAccountsList().size()); + Assert.assertEquals(0, + delegatedResourceAccountIndexCapsuleReceiver.getFromAccountsList().size()); + } catch (ContractValidateException | ContractExeException e) { Assert.fail(); } @@ -1174,5 +1232,14 @@ public void testUnfreezeBalanceForTronPowerWithOldTronPowerAfterNewResourceModel } } + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } + } diff --git a/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceV2ActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceV2ActuatorTest.java index 749052736e5..adee10fe97b 100644 --- a/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceV2ActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/UnfreezeBalanceV2ActuatorTest.java @@ -6,7 +6,9 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -20,6 +22,9 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; +import org.tron.core.state.WorldStateCallBack; +import org.tron.core.state.WorldStateQueryInstance; +import org.tron.core.state.store.DynamicPropertiesStateStore; import org.tron.protos.Protocol.AccountType; import org.tron.protos.Protocol.Transaction.Result.code; import org.tron.protos.Protocol.Vote; @@ -36,6 +41,8 @@ public class UnfreezeBalanceV2ActuatorTest extends BaseTest { private static final String OWNER_ACCOUNT_INVALID; private static final long initBalance = 10_000_000_000L; private static final long frozenBalance = 1_000_000_000L; + @Resource + private WorldStateCallBack worldStateCallBack; static { Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); @@ -50,6 +57,7 @@ public class UnfreezeBalanceV2ActuatorTest extends BaseTest { */ @Before public void createAccountCapsule() { + worldStateCallBack.setExecute(true); dbManager.getDynamicPropertiesStore().saveUnfreezeDelayDays(1L); dbManager.getDynamicPropertiesStore().saveAllowNewResourceModel(1L); @@ -64,6 +72,11 @@ public void createAccountCapsule() { dbManager.getAccountStore().put(receiverCapsule.getAddress().toByteArray(), receiverCapsule); } + @After + public void reset() { + worldStateCallBack.setExecute(false); + } + private Any getContractForBandwidthV2(String ownerAddress, long unfreezeBalance) { return Any.pack( BalanceContract.UnfreezeBalanceV2Contract.newBuilder() @@ -155,9 +168,17 @@ public void testUnfreezeBalanceForBandwidth() { Assert.assertEquals(100, owner.getFrozenV2BalanceForBandwidth()); Assert.assertEquals(100L, owner.getTronPower()); + WorldStateQueryInstance instance = getQueryInstance(); + owner = instance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(100, owner.getFrozenV2BalanceForBandwidth()); + Assert.assertEquals(100L, owner.getTronPower()); + long totalNetWeightAfter = dbManager.getDynamicPropertiesStore().getTotalNetWeight(); Assert.assertEquals(totalNetWeightBefore - 1000, totalNetWeightAfter); + DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(instance); + Assert.assertEquals(totalNetWeightAfter, stateStore.getTotalNetWeight()); + } catch (Exception e) { Assert.assertFalse(e instanceof ContractValidateException); Assert.assertFalse(e instanceof ContractExeException); @@ -195,8 +216,18 @@ public void testUnfreezeBalanceForEnergy() { //Assert.assertEquals(owner.getBalance(), initBalance + frozenBalance); Assert.assertEquals(100, owner.getAllFrozenBalanceForEnergy()); Assert.assertEquals(100, owner.getTronPower()); + + WorldStateQueryInstance instance = getQueryInstance(); + owner = instance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(100, owner.getAllFrozenBalanceForEnergy()); + Assert.assertEquals(100, owner.getTronPower()); + long totalEnergyWeightAfter = dbManager.getDynamicPropertiesStore().getTotalEnergyWeight(); Assert.assertEquals(totalEnergyWeightBefore - 1000, totalEnergyWeightAfter); + + DynamicPropertiesStateStore stateStore = new DynamicPropertiesStateStore(instance); + Assert.assertEquals(totalEnergyWeightAfter, stateStore.getTotalEnergyWeight()); + } catch (Exception e) { Assert.assertFalse(e instanceof ContractValidateException); Assert.assertFalse(e instanceof ContractExeException); @@ -314,6 +345,20 @@ public void testVotes() { for (Vote vote : accountCapsule.getVotesList()) { Assert.assertEquals(vote.getVoteCount(), 250); } + WorldStateQueryInstance queryInstance = getQueryInstance(); + votesCapsule = queryInstance.getVotes(ownerAddressBytes); + Assert.assertNotNull(votesCapsule); + for (Vote vote : votesCapsule.getOldVotes()) { + Assert.assertEquals(vote.getVoteCount(), 500); + } + for (Vote vote : votesCapsule.getNewVotes()) { + Assert.assertEquals(vote.getVoteCount(), 250); + } + accountCapsule = queryInstance.getAccount(ownerAddressBytes); + for (Vote vote : accountCapsule.getVotesList()) { + Assert.assertEquals(vote.getVoteCount(), 250); + } + } catch (ContractValidateException | ContractExeException e) { Assert.fail("cannot run here."); } @@ -333,6 +378,17 @@ public void testVotes() { Assert.assertEquals(0, votesCapsule.getNewVotes().size()); accountCapsule = dbManager.getAccountStore().get(ownerAddressBytes); Assert.assertEquals(0, accountCapsule.getVotesList().size()); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + votesCapsule = queryInstance.getVotes(ownerAddressBytes); + Assert.assertNotNull(votesCapsule); + for (Vote vote : votesCapsule.getOldVotes()) { + Assert.assertEquals(vote.getVoteCount(), 500); + } + Assert.assertEquals(0, votesCapsule.getNewVotes().size()); + accountCapsule = queryInstance.getAccount(ownerAddressBytes); + Assert.assertEquals(0, accountCapsule.getVotesList().size()); + } catch (ContractValidateException | ContractExeException e) { Assert.fail("cannot run here."); } @@ -402,6 +458,12 @@ public void testUnfreezeBalanceForEnergyWithOldTronPowerAfterNewResourceModel() Assert.assertEquals(owner.getVotesList().size(), 0L); Assert.assertEquals(owner.getInstance().getOldTronPower(), -1L); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getVotesList().size(), 0L); + Assert.assertEquals(owner.getInstance().getOldTronPower(), -1L); + } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -439,6 +501,12 @@ public void testUnfreezeBalanceForEnergyWithoutOldTronPowerAfterNewResourceModel Assert.assertEquals(owner.getVotesList().size(), 1L); Assert.assertEquals(owner.getInstance().getOldTronPower(), -1L); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getVotesList().size(), 1L); + Assert.assertEquals(owner.getInstance().getOldTronPower(), -1L); + } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -476,6 +544,11 @@ public void testUnfreezeBalanceForTronPowerWithOldTronPowerAfterNewResourceModel Assert.assertEquals(owner.getVotesList().size(), 0L); Assert.assertEquals(owner.getInstance().getOldTronPower(), -1L); + + WorldStateQueryInstance queryInstance = getQueryInstance(); + owner = queryInstance.getAccount(ByteArray.fromHexString(OWNER_ADDRESS)); + Assert.assertEquals(owner.getVotesList().size(), 0L); + Assert.assertEquals(owner.getInstance().getOldTronPower(), -1L); } catch (ContractValidateException e) { Assert.assertFalse(e instanceof ContractValidateException); } catch (ContractExeException e) { @@ -793,6 +866,15 @@ public void testUnfreezeBalanceUnfreezeCount() { } + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } + } diff --git a/framework/src/test/java/org/tron/core/db2/RevokingDbWithCacheNewValueTest.java b/framework/src/test/java/org/tron/core/db2/RevokingDbWithCacheNewValueTest.java index 2290df86978..92945698480 100644 --- a/framework/src/test/java/org/tron/core/db2/RevokingDbWithCacheNewValueTest.java +++ b/framework/src/test/java/org/tron/core/db2/RevokingDbWithCacheNewValueTest.java @@ -26,6 +26,7 @@ import org.tron.core.db2.SnapshotRootTest.ProtoCapsuleTest; import org.tron.core.db2.core.SnapshotManager; import org.tron.core.exception.RevokingStoreIllegalStateException; +import org.tron.core.state.WorldStateCallBack; @Slf4j public class RevokingDbWithCacheNewValueTest { @@ -472,6 +473,7 @@ public static class TestRevokingTronStore extends TronStoreWithRevoking result = db.prefixQuery("b".getBytes()); + Assert.assertEquals(3, result.size()); + Assert.assertEquals(value2, result.get(key2)); + Assert.assertEquals(value4, result.get(key4)); + Assert.assertEquals(value5, result.get(key5)); + db.close(); + } catch (RocksDBException | IOException e) { + Assert.fail(); + } finally { + FileUtil.deleteDir(parentPath.toFile()); + } + } +} diff --git a/framework/src/test/java/org/tron/core/state/WorldStateGenesisTest.java b/framework/src/test/java/org/tron/core/state/WorldStateGenesisTest.java new file mode 100644 index 00000000000..214a7c2170e --- /dev/null +++ b/framework/src/test/java/org/tron/core/state/WorldStateGenesisTest.java @@ -0,0 +1,91 @@ +package org.tron.core.state; + +import com.google.protobuf.ByteString; +import java.io.IOException; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.TemporaryFolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.tron.common.application.Application; +import org.tron.common.application.ApplicationFactory; +import org.tron.common.application.TronApplicationContext; +import org.tron.common.config.DbBackupConfig; +import org.tron.common.crypto.ECKey; +import org.tron.common.utils.Sha256Hash; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.config.DefaultConfig; +import org.tron.core.config.args.Args; +import org.tron.core.exception.HeaderNotFound; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class WorldStateGenesisTest { + private static TronApplicationContext context; + private static Application appTest; + private static ChainBaseManager chainBaseManager; + private static WorldStateGenesis worldStateGenesis; + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + /** + * init logic. + */ + @Before + public void init() throws IOException { + + Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()}, + "config-localtest.conf"); + // allow account root + Args.getInstance().setAllowAccountStateRoot(1); + // init dbBackupConfig to avoid NPE + Args.getInstance().dbBackupConfig = DbBackupConfig.getInstance(); + context = new TronApplicationContext(DefaultConfig.class); + appTest = ApplicationFactory.create(context); + appTest.startup(); + chainBaseManager = context.getBean(ChainBaseManager.class); + worldStateGenesis = context.getBean(WorldStateGenesis.class); + } + + @After + public void destroy() { + context.destroy(); + Args.clearParam(); + } + + @Test + public void testResetArchiveRoot() throws HeaderNotFound { + // test ignore genesis reset + worldStateGenesis.resetArchiveRoot(); + Assert.assertNotEquals(Bytes32.ZERO, chainBaseManager.getHead().getArchiveRoot()); + + // test reset + BlockCapsule parentBlock = chainBaseManager.getHead(); + BlockCapsule blockCapsule = + new BlockCapsule( + parentBlock.getNum() + 1, + Sha256Hash.wrap(parentBlock.getBlockId().getByteString()), + System.currentTimeMillis(), + ByteString.copyFrom( + ECKey.fromPrivate( + org.tron.common.utils.ByteArray.fromHexString( + Args.getLocalWitnesses().getPrivateKey())) + .getAddress())); + blockCapsule.setMerkleRoot(); + blockCapsule.setArchiveRoot(Bytes32.random().toArray()); + chainBaseManager.getBlockStore().put(blockCapsule.getBlockId().getBytes(), blockCapsule); + + worldStateGenesis.resetArchiveRoot(); + Assert.assertEquals(Bytes32.ZERO, chainBaseManager.getHead().getArchiveRoot()); + } + +} diff --git a/framework/src/test/java/org/tron/core/state/WorldStateQueryInstanceTest.java b/framework/src/test/java/org/tron/core/state/WorldStateQueryInstanceTest.java new file mode 100644 index 00000000000..ae15cbb6e5b --- /dev/null +++ b/framework/src/test/java/org/tron/core/state/WorldStateQueryInstanceTest.java @@ -0,0 +1,369 @@ +package org.tron.core.state; + +import static org.tron.core.state.WorldStateCallBack.fix32; + +import com.beust.jcommander.internal.Lists; +import com.google.common.primitives.Longs; +import com.google.protobuf.ByteString; +import java.io.IOException; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.ethereum.trie.MerkleStorage; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.TemporaryFolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.tron.common.application.TronApplicationContext; +import org.tron.common.config.DbBackupConfig; +import org.tron.common.crypto.ECKey; +import org.tron.common.runtime.vm.DataWord; +import org.tron.common.utils.Utils; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.AssetIssueCapsule; +import org.tron.core.capsule.CodeCapsule; +import org.tron.core.capsule.ContractCapsule; +import org.tron.core.capsule.ContractStateCapsule; +import org.tron.core.capsule.DelegatedResourceAccountIndexCapsule; +import org.tron.core.capsule.DelegatedResourceCapsule; +import org.tron.core.capsule.VotesCapsule; +import org.tron.core.capsule.WitnessCapsule; +import org.tron.core.config.DefaultConfig; +import org.tron.core.config.args.Args; +import org.tron.core.db.TronStoreWithRevoking; +import org.tron.core.db2.core.Chainbase; +import org.tron.core.exception.BadItemException; +import org.tron.core.exception.ItemNotFoundException; +import org.tron.core.state.store.AccountStateStore; +import org.tron.core.state.store.AssetIssueV2StateStore; +import org.tron.core.state.store.DelegationStateStore; +import org.tron.core.state.store.DynamicPropertiesStateStore; +import org.tron.core.state.store.StorageRowStateStore; +import org.tron.core.state.trie.TrieImpl2; +import org.tron.core.vm.program.Storage; +import org.tron.protos.Protocol; +import org.tron.protos.contract.SmartContractOuterClass; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class WorldStateQueryInstanceTest { + + private WorldStateQueryInstance instance; + private TrieImpl2 trieImpl2; + + private static TronApplicationContext context; + private static ChainBaseManager chainBaseManager; + private static MerkleStorage merkleStorage; + + private static final ECKey ecKey = new ECKey(Utils.getRandom()); + private static final byte[] address = ecKey.getAddress(); + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void init() throws IOException { + Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString(), + "--p2p-disable", "true"}, "config-localtest.conf"); + // allow account root + Args.getInstance().setAllowAccountStateRoot(1); + // init dbBackupConfig to avoid NPE + Args.getInstance().dbBackupConfig = DbBackupConfig.getInstance(); + context = new TronApplicationContext(DefaultConfig.class); + chainBaseManager = context.getBean(ChainBaseManager.class); + merkleStorage = context.getBean(MerkleStorage.class); + } + + @After + public void destroy() { + context.destroy(); + Args.clearParam(); + } + + @Test + public void testGet() { + trieImpl2 = new TrieImpl2(merkleStorage); + testGetAccount(); + testGetAccountAsset(); + testGetContractState(); + testGetContract(); + testGetCode(); + testGetAssetIssue(); + testGetWitness(); + testGetDelegatedResource(); + testGetDelegation(); + testGetDelegatedResourceAccountIndex(); + testGetVotes(); + testGetDynamicProperty(); + testGetStorageRow(); + } + + private void testGetAccount() { + byte[] key = address; + byte[] value = Protocol.Account.newBuilder().setAddress(ByteString.copyFrom(address)).build() + .toByteArray(); + trieImpl2.put(StateType.encodeKey(StateType.Account, key), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + try (AccountStateStore store = new AccountStateStore(instance)) { + Assert.assertArrayEquals(value, store.get(key).getData()); + testUnsupportedOperation(store, key); + Assert.assertEquals(store.getDbName(),(Bytes32.wrap(root).toHexString())); + } + } + + private void testGetAccountAsset() { + long tokenId = 1000001; + long amount = 100; + trieImpl2.put( + fix32(StateType.encodeKey(StateType.AccountAsset, + com.google.common.primitives.Bytes.concat(address, + Longs.toByteArray(tokenId)))), Bytes.of(Longs.toByteArray(amount))); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertEquals(amount, instance.getAccountAsset( + Protocol.Account.newBuilder().setAddress(ByteString.copyFrom(address)).build(), + tokenId)); + Assert.assertEquals(instance.getRootHash(),trieImpl2.getRootHashByte32()); + trieImpl2.put( + fix32(StateType.encodeKey(StateType.AccountAsset, + com.google.common.primitives.Bytes.concat( + address, Longs.toByteArray(tokenId)))), UInt256.ZERO); + trieImpl2.commit(); + trieImpl2.flush(); + instance = new WorldStateQueryInstance(trieImpl2.getRootHashByte32(), + chainBaseManager); + Assert.assertEquals(0, instance.getAccountAsset( + Protocol.Account.newBuilder().setAddress(ByteString.copyFrom(address)).build(), + tokenId)); + Assert.assertFalse(instance.hasAssetV2( + Protocol.Account.newBuilder().setAddress(ByteString.copyFrom(address)).build(), + tokenId)); + + } + + private void testGetContractState() { + byte[] value = new ContractStateCapsule(1).getData(); + trieImpl2.put(StateType.encodeKey(StateType.ContractState, address), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getContractState(address).getData()); + } + + private void testGetContract() { + byte[] value = new ContractCapsule(SmartContractOuterClass.SmartContract.newBuilder() + .setContractAddress(ByteString.copyFrom(address)).build()).getData(); + trieImpl2.put(StateType.encodeKey(StateType.Contract, address), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getContract(address).getData()); + } + + private void testGetCode() { + byte[] value = new CodeCapsule("code".getBytes()).getData(); + trieImpl2.put(StateType.encodeKey(StateType.Code, address), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getCode(address).getData()); + } + + private void testGetAssetIssue() { + String tokenId = "100001"; + byte[] value = new AssetIssueCapsule(address, tokenId, "token1", "test", 100, 100).getData(); + trieImpl2.put(StateType.encodeKey(StateType.AssetIssue, tokenId.getBytes()), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + try (AssetIssueV2StateStore store = new AssetIssueV2StateStore(instance)) { + Assert.assertArrayEquals(value, store.get(tokenId.getBytes()).getData()); + testUnsupportedOperation(store, tokenId.getBytes()); + Assert.assertEquals(store.getDbName(),(Bytes32.wrap(root).toHexString())); + } + } + + private void testGetWitness() { + byte[] value = new WitnessCapsule(ByteString.copyFrom(ecKey.getPubKey()), "http://").getData(); + trieImpl2.put(StateType.encodeKey(StateType.Witness, address), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getWitness(address).getData()); + } + + private void testGetDelegatedResource() { + byte[] value = new DelegatedResourceCapsule(ByteString.copyFrom(address), + ByteString.copyFrom(address)).getData(); + byte[] key = DelegatedResourceCapsule.createDbKey(address, address); + trieImpl2.put(StateType.encodeKey(StateType.DelegatedResource, key), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getDelegatedResource(key).getData()); + } + + private void testGetDelegation() { + byte[] value = "test".getBytes(); + byte[] key = address; + trieImpl2.put(StateType.encodeKey(StateType.Delegation, address), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + try (DelegationStateStore store = new DelegationStateStore(instance)) { + Assert.assertArrayEquals(value, store.get(key).getData()); + testUnsupportedOperation(store, key); + Assert.assertEquals(store.getDbName(),(Bytes32.wrap(root).toHexString())); + } + } + + private void testGetDelegatedResourceAccountIndex() { + byte[] value = new DelegatedResourceAccountIndexCapsule(ByteString.copyFrom(address)).getData(); + trieImpl2.put(StateType.encodeKey(StateType.DelegatedResourceAccountIndex, address), + Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getDelegatedResourceAccountIndex(address).getData()); + } + + private void testGetVotes() { + byte[] value = new VotesCapsule(ByteString.copyFrom(address), Lists.newArrayList()).getData(); + trieImpl2.put(StateType.encodeKey(StateType.Votes, address), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + Assert.assertArrayEquals(value, instance.getVotes(address).getData()); + } + + private void testGetDynamicProperty() { + byte[] key = "key".getBytes(); + byte[] value = "test".getBytes(); + trieImpl2.put(StateType.encodeKey(StateType.Properties, key), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + try (DynamicPropertiesStateStore store = new DynamicPropertiesStateStore(instance)) { + try { + Assert.assertArrayEquals(value, store.get(key).getData()); + } catch (ItemNotFoundException e) { + Assert.fail(); + } + try { + Assert.assertArrayEquals(value, store.get("not-key".getBytes()).getData()); + Assert.fail(); + } catch (ItemNotFoundException e) { + Assert.assertTrue(true); + } + + testUnsupportedOperation(store, key); + Assert.assertEquals(store.getDbName(), Bytes32.wrap(root).toHexString()); + } + } + + private void testGetStorageRow() { + trieImpl2 = new TrieImpl2(merkleStorage); + byte[] key = address; + byte[] value = "test".getBytes(); + trieImpl2.put(StateType.encodeKey(StateType.StorageRow, key), Bytes.wrap(value)); + trieImpl2.commit(); + trieImpl2.flush(); + byte[] root = trieImpl2.getRootHash(); + instance = new WorldStateQueryInstance(Bytes32.wrap(root), chainBaseManager); + try (StorageRowStateStore store = new StorageRowStateStore(instance)) { + Assert.assertArrayEquals(value, store.get(key).getData()); + testUnsupportedOperation(store, key); + Assert.assertEquals(store.getDbName(), Bytes32.wrap(root).toHexString()); + try { + Storage storage = new Storage(address, store); + storage.put(new DataWord(0), new DataWord(0)); + storage.commit(); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + } + } + + private void testUnsupportedOperation(TronStoreWithRevoking store, byte[] key) { + try { + store.put(key, null); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + Assert.assertFalse(store.has("not-key".getBytes())); + + try { + store.delete(key); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + + try { + store.setCursor(Chainbase.Cursor.HEAD); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + + try { + store.iterator(); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + + try { + store.prefixQuery(key); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + + try { + store.size(); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + + try { + store.isNotEmpty(); + Assert.fail(); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } + + try { + store.of(null); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(true); + } catch (BadItemException e) { + Assert.fail(); + } + } + +} diff --git a/framework/src/test/java/org/tron/core/state/WorldStateQueryTest.java b/framework/src/test/java/org/tron/core/state/WorldStateQueryTest.java new file mode 100644 index 00000000000..ccc7cba3a46 --- /dev/null +++ b/framework/src/test/java/org/tron/core/state/WorldStateQueryTest.java @@ -0,0 +1,596 @@ +package org.tron.core.state; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.TemporaryFolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.tron.common.application.Application; +import org.tron.common.application.ApplicationFactory; +import org.tron.common.application.TronApplicationContext; +import org.tron.common.config.DbBackupConfig; +import org.tron.common.crypto.ECKey; +import org.tron.common.runtime.TvmTestUtils; +import org.tron.common.runtime.vm.DataWord; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.PublicMethod; +import org.tron.common.utils.Sha256Hash; +import org.tron.common.utils.WalletUtil; +import org.tron.core.ChainBaseManager; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.AssetIssueCapsule; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.capsule.ContractCapsule; +import org.tron.core.capsule.TransactionCapsule; +import org.tron.core.config.DefaultConfig; +import org.tron.core.config.args.Args; +import org.tron.core.db.Manager; +import org.tron.core.db2.ISession; +import org.tron.core.exception.JsonRpcInternalException; +import org.tron.core.exception.JsonRpcInvalidParamsException; +import org.tron.core.exception.JsonRpcInvalidRequestException; +import org.tron.core.exception.StoreException; +import org.tron.core.services.jsonrpc.TronJsonRpc; +import org.tron.core.services.jsonrpc.types.CallArguments; +import org.tron.core.state.store.StorageRowStateStore; +import org.tron.core.vm.program.Storage; +import org.tron.protos.Protocol; +import org.tron.protos.Protocol.Transaction.Contract.ContractType; +import org.tron.protos.contract.AssetIssueContractOuterClass; +import org.tron.protos.contract.BalanceContract; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class WorldStateQueryTest { + private static TronApplicationContext context; + private static Application appTest; + private static ChainBaseManager chainBaseManager; + private static Manager manager; + + private static TronJsonRpc tronJsonRpc; + private static final long TOKEN_ID1 = 1000001L; + private static final long TOKEN_ID2 = 1000002L; + + private static ECKey account1Prikey = new ECKey(); + private static ECKey account2Prikey = new ECKey(); + + byte[] contractAddress; + private static WorldStateCallBack worldStateCallBack; + + @ClassRule + public static final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + /** + * init logic. + */ + @BeforeClass + public static void init() throws IOException { + + Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()}, + "config-localtest.conf"); + // disable p2p + Args.getInstance().setP2pDisable(true); + // allow account root + Args.getInstance().setAllowAccountStateRoot(1); + // init dbBackupConfig to avoid NPE + Args.getInstance().dbBackupConfig = DbBackupConfig.getInstance(); + context = new TronApplicationContext(DefaultConfig.class); + appTest = ApplicationFactory.create(context); + appTest.initServices(Args.getInstance()); + appTest.startServices(); + appTest.startup(); + chainBaseManager = context.getBean(ChainBaseManager.class); + // open account asset optimize + worldStateCallBack = context.getBean(WorldStateCallBack.class); + worldStateCallBack.setExecute(true); + chainBaseManager.getDynamicPropertiesStore().setAllowAssetOptimization(1); + worldStateCallBack.setExecute(false); + manager = context.getBean(Manager.class); + tronJsonRpc = context.getBean(TronJsonRpc.class); + + Protocol.Account acc = Protocol.Account.newBuilder() + .setAccountName(ByteString.copyFrom(Objects.requireNonNull(ByteArray.fromString("acc")))) + .setAddress(ByteString.copyFrom(account1Prikey.getAddress())) + .setType(Protocol.AccountType.AssetIssue) + .setBalance(10000000000000000L).build(); + Protocol.Account sun = Protocol.Account.newBuilder() + .setAccountName(ByteString.copyFrom(Objects.requireNonNull(ByteArray.fromString("sun")))) + .setAddress(ByteString.copyFrom(account2Prikey.getAddress())) + .setType(Protocol.AccountType.AssetIssue) + .setBalance(10000000000000000L).build(); + chainBaseManager.getAccountStore().put(account1Prikey.getAddress(), new AccountCapsule(acc)); + chainBaseManager.getAccountStore().put(account2Prikey.getAddress(), new AccountCapsule(sun)); + + } + + @AfterClass + public static void destroy() { + context.destroy(); + Args.clearParam(); + } + + public void createAsset() { + Assert.assertEquals(TOKEN_ID1,manager.getDynamicPropertiesStore().getTokenIdNum() + 1); + manager.getDynamicPropertiesStore().saveTokenIdNum(TOKEN_ID1); + AssetIssueContractOuterClass.AssetIssueContract assetIssueContract = + AssetIssueContractOuterClass.AssetIssueContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom(account1Prikey.getAddress())) + .setName(ByteString.copyFrom(ByteArray.fromString("token1"))) + .setId(Long.toString(TOKEN_ID1)) + .setTotalSupply(10) + .setTrxNum(10) + .setNum(1) + .setStartTime(1) + .setEndTime(2) + .setVoteScore(2) + .setDescription(ByteString.copyFrom(ByteArray.fromString("token1"))) + .setUrl(ByteString.copyFrom(ByteArray.fromString("https://tron.network"))) + .build(); + AssetIssueCapsule assetIssueCapsule = new AssetIssueCapsule(assetIssueContract); + manager.getAssetIssueV2Store() + .put(assetIssueCapsule.createDbV2Key(), assetIssueCapsule); + + Assert.assertEquals(TOKEN_ID2,manager.getDynamicPropertiesStore().getTokenIdNum() + 1); + manager.getDynamicPropertiesStore().saveTokenIdNum(TOKEN_ID2); + AssetIssueContractOuterClass.AssetIssueContract assetIssueContract2 = + AssetIssueContractOuterClass.AssetIssueContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom(account1Prikey.getAddress())) + .setName(ByteString.copyFrom(ByteArray.fromString("token2"))) + .setId(Long.toString(TOKEN_ID2)) + .setTotalSupply(10) + .setTrxNum(10) + .setNum(1) + .setStartTime(1) + .setEndTime(2) + .setVoteScore(2) + .setDescription(ByteString.copyFrom(ByteArray.fromString("token2"))) + .setUrl(ByteString.copyFrom(ByteArray.fromString("https://tron.network"))) + .build(); + AssetIssueCapsule assetIssueCapsule2 = new AssetIssueCapsule(assetIssueContract2); + manager.getAssetIssueV2Store() + .put(assetIssueCapsule2.createDbV2Key(), assetIssueCapsule2); + AccountCapsule ownerCapsule = manager.getAccountStore() + .get(account1Prikey.getAddress()); + ownerCapsule.addAssetV2(Long.toString(TOKEN_ID1).getBytes(), 1000); + ownerCapsule.addAssetV2(Long.toString(TOKEN_ID2).getBytes(), 5000); + manager.getAccountStore().put(ownerCapsule.getAddress().toByteArray(), ownerCapsule); + + } + + @Test + public void testTransfer() throws InterruptedException, JsonRpcInvalidParamsException { + manager.initGenesis(); + List blockCapsules = chainBaseManager + .getBlockStore().getBlockByLatestNum(1); + BlockCapsule blockCapsule = blockCapsules.get(0); + try { + manager.pushBlock(buildTransferBlock(blockCapsule)); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Thread.sleep(3000); + blockCapsules = chainBaseManager.getBlockStore().getBlockByLatestNum(1); + blockCapsule = blockCapsules.get(0); + Bytes32 rootHash = blockCapsule.getArchiveRoot(); + WorldStateQueryInstance worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + checkAccount(worldStateQueryInstance, blockCapsule.getNum()); + + try { + manager.pushBlock(buildTransferBlock(blockCapsule)); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Thread.sleep(3000); + + blockCapsules = chainBaseManager.getBlockStore().getBlockByLatestNum(1); + blockCapsule = blockCapsules.get(0); + rootHash = blockCapsule.getArchiveRoot(); + worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + checkAccount(worldStateQueryInstance, blockCapsule.getNum()); + } + + @Test + public void testTransferAsset() throws InterruptedException, JsonRpcInvalidParamsException { + createAsset(); + manager.initGenesis(); + List blockCapsules = chainBaseManager + .getBlockStore().getBlockByLatestNum(1); + BlockCapsule blockCapsule = blockCapsules.get(0); + try { + manager.pushBlock(buildTransferAssetBlock(blockCapsule)); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Thread.sleep(3000); + blockCapsules = chainBaseManager.getBlockStore().getBlockByLatestNum(1); + blockCapsule = blockCapsules.get(0); + Bytes32 rootHash = blockCapsule.getArchiveRoot(); + WorldStateQueryInstance worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + checkAccount(worldStateQueryInstance, blockCapsule.getNum()); + + try { + manager.pushBlock(buildTransferAssetBlock(blockCapsule)); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Thread.sleep(3000); + + blockCapsules = chainBaseManager.getBlockStore().getBlockByLatestNum(1); + blockCapsule = blockCapsules.get(0); + rootHash = blockCapsule.getArchiveRoot(); + worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + checkAccount(worldStateQueryInstance, blockCapsule.getNum()); + } + + @Test + public void testContract() throws InterruptedException, JsonRpcInvalidParamsException, + JsonRpcInvalidRequestException, JsonRpcInternalException { + manager.initGenesis(); + List blockCapsules = chainBaseManager + .getBlockStore().getBlockByLatestNum(1); + BlockCapsule blockCapsule = blockCapsules.get(0); + try { + manager.pushBlock(buildContractBlock(blockCapsule)); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Thread.sleep(3000); + blockCapsules = chainBaseManager.getBlockStore().getBlockByLatestNum(1); + blockCapsule = blockCapsules.get(0); + Bytes32 rootHash = blockCapsule.getArchiveRoot(); + WorldStateQueryInstance worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + Assert.assertArrayEquals(chainBaseManager.getContractStore().get(contractAddress).getData(), + worldStateQueryInstance.getContract(contractAddress).getData()); + checkAccount(worldStateQueryInstance, blockCapsule.getNum()); + + try { + manager.pushBlock(buildTriggerBlock(blockCapsule)); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Thread.sleep(3000); + + blockCapsules = chainBaseManager.getBlockStore().getBlockByLatestNum(1); + blockCapsule = blockCapsules.get(0); + rootHash = blockCapsule.getArchiveRoot(); + worldStateQueryInstance = ChainBaseManager.fetch(rootHash); + ContractCapsule contractCapsule = worldStateQueryInstance.getContract(contractAddress); + try (StorageRowStateStore store = new StorageRowStateStore(worldStateQueryInstance)) { + Storage storage = new Storage(contractAddress, store); + storage.setContractVersion(contractCapsule.getInstance().getVersion()); + + DataWord value1 = storage.getValue(new DataWord(ByteArray.fromHexString("0"))); + + DataWord value2 = storage.getValue(new DataWord(ByteArray.fromHexString("0"))); + Assert.assertArrayEquals(value1.getData(), value2.getData()); + } + checkAccount(worldStateQueryInstance, blockCapsule.getNum()); + Assert.assertEquals(tronJsonRpc.getABIOfSmartContract(ByteArray.toHexString(contractAddress), + ByteArray.toJsonHex(blockCapsule.getNum())), + tronJsonRpc.getABIOfSmartContract(ByteArray.toHexString(contractAddress), + "latest")); + + Assert.assertEquals(tronJsonRpc.getStorageAt(ByteArray.toHexString(contractAddress), + "0x0", ByteArray.toJsonHex(blockCapsule.getNum())), + tronJsonRpc.getStorageAt(ByteArray.toHexString(contractAddress), + "0x0", "latest")); + + byte[] triggerData = TvmTestUtils.parseAbi("increment()", null); + CallArguments callArguments = new CallArguments(); + callArguments.setFrom(ByteArray.toHexString(account1Prikey.getAddress())); + callArguments.setTo(ByteArray.toHexString(contractAddress)); + callArguments.setGas("0x0"); + callArguments.setGasPrice("0x0"); + callArguments.setValue("0x0"); + callArguments.setData(ByteArray.toHexString(triggerData)); + + Assert.assertEquals(tronJsonRpc.getCall(callArguments, ByteArray.toJsonHex( + blockCapsule.getNum())), tronJsonRpc.getCall(callArguments, "latest")); + + + } + + private void checkAccount(WorldStateQueryInstance worldStateQueryInstance, long blockNum) + throws JsonRpcInvalidParamsException { + AccountCapsule account1Capsule = chainBaseManager.getAccountStore() + .get(account1Prikey.getAddress()); + AccountCapsule account2Capsule = chainBaseManager.getAccountStore() + .get(account2Prikey.getAddress()); + Assert.assertEquals(account1Capsule.getBalance(), + worldStateQueryInstance.getAccount(account1Prikey.getAddress()).getBalance()); + Assert.assertEquals(account2Capsule.getBalance(), + worldStateQueryInstance.getAccount(account2Prikey.getAddress()).getBalance()); + Assert.assertEquals(account1Capsule.getAssetMapV2(), + worldStateQueryInstance.getAccount(account1Prikey.getAddress()).getAssetMapV2()); + Assert.assertEquals(account2Capsule.getAssetMapV2(), + worldStateQueryInstance.getAccount(account2Prikey.getAddress()).getAssetMapV2()); + Assert.assertEquals(tronJsonRpc.getTrxBalance( + ByteArray.toHexString(account1Prikey.getAddress()), + ByteArray.toJsonHex(blockNum)), + tronJsonRpc.getTrxBalance( + ByteArray.toHexString(account1Prikey.getAddress()), "latest")); + + Assert.assertEquals(tronJsonRpc.getToken10( + ByteArray.toHexString(account1Prikey.getAddress()), + ByteArray.toJsonHex(blockNum)), + tronJsonRpc.getToken10( + ByteArray.toHexString(account1Prikey.getAddress()), "latest")); + + Assert.assertEquals(tronJsonRpc.getToken10( + ByteArray.toHexString(account2Prikey.getAddress()), + ByteArray.toJsonHex(blockNum)), + tronJsonRpc.getToken10( + ByteArray.toHexString(account2Prikey.getAddress()), "latest")); + + Map asset = new HashMap<>(); + for (TronJsonRpc.Token10Result t : tronJsonRpc.getToken10( + ByteArray.toHexString(account2Prikey.getAddress()), "latest")) { + asset.put(Long.toString(ByteArray.jsonHexToLong(t.getKey())), + ByteArray.jsonHexToLong(t.getValue())); + } + Assert.assertEquals(account2Capsule.getAssetMapV2(), asset); + + Assert.assertEquals(tronJsonRpc.getToken10( + ByteArray.toHexString(account1Prikey.getAddress()), "latest"), + tronJsonRpc.getToken10( + ByteArray.toHexString(account1Prikey.getAddress()), ByteArray.toJsonHex(blockNum))); + + Assert.assertEquals(tronJsonRpc.getToken10ById(ByteArray.toHexString( + account2Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID1), + ByteArray.toJsonHex(blockNum)), + tronJsonRpc.getToken10ById(ByteArray.toHexString( + account2Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID1), + "latest")); + + Assert.assertEquals(tronJsonRpc.getToken10ById(ByteArray.toHexString( + account2Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID2), + ByteArray.toJsonHex(blockNum)), + tronJsonRpc.getToken10ById(ByteArray.toHexString( + account2Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID2), + "latest")); + + Assert.assertEquals(account2Capsule.getAssetV2(Long.toString(TOKEN_ID2)), + ByteArray.jsonHexToLong(tronJsonRpc.getToken10ById(ByteArray.toHexString( + account2Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID2), + ByteArray.toJsonHex(blockNum)).getValue())); + + List list = new ArrayList<>(); + list.add(tronJsonRpc.getToken10ById(ByteArray.toHexString( + account1Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID1), + ByteArray.toJsonHex(blockNum))); + list.add(tronJsonRpc.getToken10ById(ByteArray.toHexString( + account1Prikey.getAddress()), ByteArray.toJsonHex(TOKEN_ID2), + ByteArray.toJsonHex(blockNum))); + + Assert.assertEquals(list, tronJsonRpc.getToken10( + ByteArray.toHexString(account1Prikey.getAddress()), ByteArray.toJsonHex(blockNum))); + try { + Assert.assertNotNull(worldStateQueryInstance.getBlockByNum(blockNum)); + } catch (StoreException e) { + Assert.fail(); + } + } + + private BlockCapsule buildTransferBlock(BlockCapsule parentBlock) { + BalanceContract.TransferContract transferContract = BalanceContract + .TransferContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom(account1Prikey.getAddress())) + .setToAddress(ByteString.copyFrom(account2Prikey.getAddress())) + .setAmount(1).build(); + + Protocol.Transaction.raw.Builder transactionBuilder = Protocol + .Transaction.raw.newBuilder().addContract( + Protocol.Transaction.Contract.newBuilder() + .setType(ContractType.TransferContract).setParameter( + Any.pack(transferContract)).build()); + Protocol.Transaction transaction = Protocol.Transaction.newBuilder() + .setRawData(transactionBuilder.build()).build(); + + TransactionCapsule transactionCapsule = setAndSignTx(transaction, parentBlock, account1Prikey); + + BlockCapsule blockCapsule = + new BlockCapsule( + parentBlock.getNum() + 1, + Sha256Hash.wrap(parentBlock.getBlockId().getByteString()), + System.currentTimeMillis(), + ByteString.copyFrom( + ECKey.fromPrivate( + org.tron.common.utils.ByteArray.fromHexString( + Args.getLocalWitnesses().getPrivateKey())) + .getAddress())); + blockCapsule.addTransaction(transactionCapsule); + blockCapsule.setMerkleRoot(); + blockCapsule.sign( + ByteArray.fromHexString(Args.getLocalWitnesses().getPrivateKey())); + return blockCapsule; + } + + private BlockCapsule buildTransferAssetBlock(BlockCapsule parentBlock) { + TransactionCapsule transactionCapsule = buildTransferAsset(TOKEN_ID1, 5, parentBlock); + TransactionCapsule transactionCapsule2 = buildTransferAsset(TOKEN_ID2, 10, parentBlock); + BlockCapsule blockCapsule = new BlockCapsule(parentBlock.getNum() + 1, + Sha256Hash.wrap(parentBlock.getBlockId().getByteString()), System.currentTimeMillis(), + ByteString.copyFrom(ECKey.fromPrivate(ByteArray.fromHexString( + Args.getLocalWitnesses().getPrivateKey())).getAddress())); + blockCapsule.addTransaction(transactionCapsule); + blockCapsule.addTransaction(transactionCapsule2); + blockCapsule.setMerkleRoot(); + blockCapsule.sign(ByteArray.fromHexString(Args.getLocalWitnesses().getPrivateKey())); + return blockCapsule; + } + + private TransactionCapsule buildTransferAsset(long token, long amt, BlockCapsule parentBlock) { + AssetIssueContractOuterClass.TransferAssetContract transferAssetContract = + AssetIssueContractOuterClass.TransferAssetContract.newBuilder() + .setAssetName(ByteString.copyFrom(Long.toString(token).getBytes())) + .setOwnerAddress(ByteString.copyFrom(account1Prikey.getAddress())) + .setToAddress(ByteString.copyFrom(account2Prikey.getAddress())) + .setAmount(amt) + .build(); + + Protocol.Transaction.raw.Builder transactionBuilder = Protocol.Transaction.raw.newBuilder() + .addContract(Protocol.Transaction.Contract.newBuilder() + .setType(ContractType.TransferAssetContract) + .setParameter(Any.pack(transferAssetContract)) + .build()); + Protocol.Transaction transaction = Protocol.Transaction.newBuilder() + .setRawData(transactionBuilder.build()).build(); + return setAndSignTx(transaction, parentBlock, account1Prikey); + } + + private BlockCapsule buildContractBlock(BlockCapsule parentBlock) { + long value = 0L; + long feeLimit = 1_000_000_000L; + long consumeUserResourcePercent = 0L; + String contractName = "increment"; + String ABI = "[]"; + String code = "60806040526000805534801561001457600080fd5b50610181806100246000396000f3fe608060" + + "405234801561001057600080fd5b50600436106100415760003560e01c806342cbb15c146100465780636d4c" + + "e63c14610064578063d09de08a14610082575b600080fd5b61004e61008c565b60405161005b91906100cd56" + + "5b60405180910390f35b61006c610094565b60405161007991906100cd565b60405180910390f35b61008a61" + + "009d565b005b600043905090565b60008054905090565b60016000546100ac9190610117565b600081905550" + + "565b6000819050919050565b6100c7816100b4565b82525050565b60006020820190506100e2600083018461" + + "00be565b92915050565b7f4e487b710000000000000000000000000000000000000000000000000000000060" + + "0052601160045260246000fd5b6000610122826100b4565b915061012d836100b4565b925082820190508082" + + "1115610145576101446100e8565b5b9291505056fea26469706673582212207c5e242c88722ac1f7f5f1ea67" + + "0cf1a784cad42b058651ceaf6fe0fc10ebff8264736f6c63430008110033"; + String libraryAddressPair = null; + Protocol.Transaction transaction = TvmTestUtils.generateDeploySmartContractAndGetTransaction( + contractName, account1Prikey.getAddress(), ABI, code, value, feeLimit, + consumeUserResourcePercent, libraryAddressPair); + TransactionCapsule transactionCapsule = new TransactionCapsule(transaction); + transactionCapsule.setResultCode(Protocol.Transaction.Result.contractResult.SUCCESS); + + transactionCapsule = setAndSignTx(transactionCapsule.getInstance(), + parentBlock, account1Prikey); + + + BlockCapsule blockCapsule = + new BlockCapsule( + parentBlock.getNum() + 1, + Sha256Hash.wrap(parentBlock.getBlockId().getByteString()), + System.currentTimeMillis(), + ByteString.copyFrom( + ECKey.fromPrivate( + org.tron.common.utils.ByteArray.fromHexString( + Args.getLocalWitnesses().getPrivateKey())) + .getAddress())); + blockCapsule.addTransaction(transactionCapsule); + blockCapsule.setMerkleRoot(); + blockCapsule.sign( + ByteArray.fromHexString(Args.getLocalWitnesses().getPrivateKey())); + contractAddress = WalletUtil.generateContractAddress(transactionCapsule.getInstance()); + return blockCapsule; + } + + private BlockCapsule buildTriggerBlock(BlockCapsule parentBlock) { + long value = 0L; + long feeLimit = 1_000_000_000L; + byte[] triggerData = TvmTestUtils.parseAbi("increment()", null); + Protocol.Transaction transaction = TvmTestUtils.generateTriggerSmartContractAndGetTransaction( + account1Prikey.getAddress(), contractAddress, triggerData, value, feeLimit); + + TransactionCapsule transactionCapsule = new TransactionCapsule(transaction); + transactionCapsule.setResultCode(Protocol.Transaction.Result.contractResult.SUCCESS); + + transactionCapsule = setAndSignTx(transactionCapsule.getInstance(), + parentBlock, account1Prikey); + + BlockCapsule blockCapsule = + new BlockCapsule( + parentBlock.getNum() + 1, + Sha256Hash.wrap(parentBlock.getBlockId().getByteString()), + System.currentTimeMillis(), + ByteString.copyFrom( + ECKey.fromPrivate( + org.tron.common.utils.ByteArray.fromHexString( + Args.getLocalWitnesses().getPrivateKey())) + .getAddress())); + blockCapsule.addTransaction(transactionCapsule); + blockCapsule.setMerkleRoot(); + blockCapsule.sign( + ByteArray.fromHexString(Args.getLocalWitnesses().getPrivateKey())); + return blockCapsule; + } + + private TransactionCapsule setAndSignTx(Protocol.Transaction transaction, + BlockCapsule parentBlock, ECKey account) { + TransactionCapsule transactionCapsule = new TransactionCapsule(transaction); + transactionCapsule.setReference(parentBlock.getNum(), parentBlock.getBlockId().getBytes()); + transactionCapsule.setExpiration(parentBlock.getTimeStamp() + 60 * 60 * 1000); + return new TransactionCapsule(PublicMethod.signTransaction(account, + transactionCapsule.getInstance())); + } + + @Test + public void mockSuicide() { + worldStateCallBack.setExecute(true); + byte[] address = ByteArray.fromHexString("41B68F8AEC4279E8527D678A52BFDFD23B1AA69729"); + Protocol.Account.Builder builder = Protocol.Account.newBuilder(); + builder.setAddress(ByteString.copyFrom(address)); + builder.setBalance(100L); + builder.putAssetV2("1000001", 10); + builder.putAssetV2("1000002", 20); + + AccountCapsule capsule = new AccountCapsule(builder.build()); + try (ISession tmpSession = manager.getRevokingStore().buildSession()) { + chainBaseManager.getAccountStore().put(capsule.createDbKey(), capsule); + tmpSession.commit(); + } + + try (ISession tmpSession = manager.getRevokingStore().buildSession()) { + chainBaseManager.getAccountStore().delete(capsule.createDbKey()); + tmpSession.commit(); + } + + builder = Protocol.Account.newBuilder(); + builder.setAddress(ByteString.copyFrom(address)); + builder.setBalance(200L); + builder.putAssetV2("1000003", 30); + + capsule = new AccountCapsule(builder.build()); + try (ISession tmpSession = manager.getRevokingStore().buildSession()) { + chainBaseManager.getAccountStore().put(capsule.createDbKey(), capsule); + tmpSession.commit(); + } + + WorldStateQueryInstance instance = getQueryInstance(); + AccountCapsule fromState = instance.getAccount(address); + AccountCapsule fromDb = chainBaseManager.getAccountStore().get(address); + Assert.assertEquals(fromState.getBalance(), 200L); + Assert.assertEquals(fromState.getBalance(), fromDb.getBalance()); + + Map assetV2 = new HashMap<>(); + assetV2.put("1000003", 30L); + + Assert.assertEquals(assetV2, fromDb.getAssetV2MapForTest()); + + Assert.assertEquals(assetV2, fromState.getAssetV2MapForTest()); + worldStateCallBack.setExecute(false); + } + + private WorldStateQueryInstance getQueryInstance() { + Assert.assertNotNull(worldStateCallBack.getTrie()); + worldStateCallBack.clear(); + worldStateCallBack.getTrie().commit(); + worldStateCallBack.getTrie().flush(); + return new WorldStateQueryInstance(worldStateCallBack.getTrie().getRootHashByte32(), + chainBaseManager); + } + +} diff --git a/framework/src/test/java/org/tron/core/tire/Trie2Test.java b/framework/src/test/java/org/tron/core/tire/Trie2Test.java new file mode 100644 index 00000000000..cc852b2a8c2 --- /dev/null +++ b/framework/src/test/java/org/tron/core/tire/Trie2Test.java @@ -0,0 +1,651 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.core.tire; + +import static org.tron.core.state.WorldStateCallBack.fix32; +import static org.tron.core.state.WorldStateQueryInstance.ADDRESS_SIZE; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TreeMap; +import java.util.stream.Collectors; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.bouncycastle.util.Arrays; +import org.hyperledger.besu.ethereum.trie.BranchNode; +import org.hyperledger.besu.ethereum.trie.CompactEncoding; +import org.hyperledger.besu.ethereum.trie.LeafNode; +import org.hyperledger.besu.ethereum.trie.MerkleStorage; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.RocksDBConfiguration; +import org.hyperledger.besu.storage.RocksDBConfigurationBuilder; +import org.hyperledger.besu.storage.RocksDBKeyValueStorage; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.tron.common.utils.ByteArray; +import org.tron.core.state.StateType; +import org.tron.core.state.trie.TrieImpl2; + +public class Trie2Test { + + @Rule + public final TemporaryFolder folder = new TemporaryFolder(); + + private static final Bytes c = Bytes.wrap("c".getBytes(StandardCharsets.UTF_8)); + private static final Bytes ca = Bytes.wrap("ca".getBytes(StandardCharsets.UTF_8)); + private static final Bytes cat = Bytes.wrap("cat".getBytes(StandardCharsets.UTF_8)); + private static final Bytes dog = Bytes.wrap("dog".getBytes(StandardCharsets.UTF_8)); + private static final Bytes doge = Bytes.wrap("doge".getBytes(StandardCharsets.UTF_8)); + private static final Bytes test = Bytes.wrap("test".getBytes(StandardCharsets.UTF_8)); + private static final Bytes dude = Bytes.wrap("dude".getBytes(StandardCharsets.UTF_8)); + + private KeyValueStorage createStore() { + try { + return new RocksDBKeyValueStorage(config()); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private RocksDBConfiguration config() throws IOException { + return new RocksDBConfigurationBuilder().databaseDir(folder.newFolder().toPath()).build(); + } + + @Test + public void test() { + TrieImpl2 trie = new TrieImpl2(createStore()); + trie.put(Bytes.of(1), c); + Assert.assertEquals(trie.get(new byte[]{1}), c); + trie.put(Bytes.of(1, 0), ca); + String hash1 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + trie.put(new byte[]{1, 1}, cat); + String hash2 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + Assert.assertNotEquals(hash1, hash2); + trie.put(new byte[]{1, 2}, dog); + String hash3 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + trie.put(Bytes.of(5), doge); + String hash4 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + Assert.assertNotEquals(hash3, hash4); + trie.put(Bytes.of(6).toArrayUnsafe(), doge); + String hash5 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + Assert.assertNotEquals(hash4, hash5); + trie.put(Bytes.of(7).toArrayUnsafe(), doge); + trie.put(Bytes.of(11), doge); + trie.put(Bytes.of(12).toArrayUnsafe(), dude); + trie.put(Bytes.of(13).toArrayUnsafe(), test); + String hash9 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + Assert.assertEquals(trie.get(new byte[]{1, 0}), ca); + trie.delete(Bytes.of(13).toArrayUnsafe()); + String hash10 = ByteArray.toHexString(trie.getRootHash()); + trie.commit(); + Assert.assertNotEquals(hash9, hash10); + byte[] rootHash = trie.getRootHash(); + trie.flush(); + TrieImpl2 trieCopy = new TrieImpl2(trie.getMerkleStorage(), + Bytes32.wrap(ByteArray.fromHexString(hash3))); + Assert.assertNull(trieCopy.get(Bytes.of(5).toArrayUnsafe())); + Assert.assertEquals(trieCopy.get(new byte[]{1, 2}), dog); + trieCopy.put(Bytes.of(5).toArrayUnsafe(), doge); + trieCopy.put(new byte[]{1, 2}, cat); + trieCopy.commit(); + byte[] rootHash2 = trieCopy.getRootHash(); + Assert.assertFalse(Arrays.areEqual(rootHash, rootHash2)); + Assert.assertEquals(trieCopy.get(Bytes.of(5).toArrayUnsafe()), doge); + Assert.assertEquals(trieCopy.get(new byte[]{1, 2}), cat); + Assert.assertEquals(trie.get(new byte[]{1, 2}), dog); + } + + @Test + public void canReloadTrieFromHash() { + final Bytes key1 = Bytes.of(1, 5, 8, 9); + final Bytes key2 = Bytes.of(1, 6, 1, 2); + final Bytes key3 = Bytes.of(1, 6, 1, 3); + + TrieImpl2 trie = new TrieImpl2(createStore()); + + // Push some values into the trie and commit changes so nodes are persisted + final Bytes value1 = Bytes.of("value1".getBytes(StandardCharsets.UTF_8)); + trie.put(key1, value1); + final byte[] hash1 = trie.getRootHash(); + // put data into pendingUpdates + trie.commit(); + + final Bytes value2 = Bytes.wrap("value2".getBytes(StandardCharsets.UTF_8)); + trie.put(key2, value2); + final Bytes value3 = Bytes.wrap("value3".getBytes(StandardCharsets.UTF_8)); + trie.put(key3, value3); + final byte[] hash2 = trie.getRootHash(); + // put data into pendingUpdates + trie.commit(); + + final Bytes value4 = Bytes.wrap("value4".getBytes(StandardCharsets.UTF_8)); + trie.put(key1, value4); + trie.put(Bytes.of(1, 2, 3, 4, 5), UInt256.ZERO); + final byte[] hash3 = trie.getRootHash(); + // put data into pendingUpdates + trie.commit(); + + // Check the root hashes for 3 tries are all distinct + Assert.assertNotEquals(Bytes.of(hash1), Bytes.of(hash2)); + Assert.assertNotEquals(Bytes.of(hash1), Bytes.of(hash3)); + Assert.assertNotEquals(Bytes.of(hash2), Bytes.of(hash3)); + + // And that we can retrieve the last value we set for key1 + Assert.assertEquals(trie.get(key1), value4); + + // Create new tries from root hashes and check that we find expected values + trie = new TrieImpl2(trie.getMerkleStorage(), Bytes32.wrap(hash1)); + Assert.assertEquals(trie.get(key1), value1); + Assert.assertNull(trie.get(key2)); + Assert.assertNull(trie.get(key3)); + + trie = new TrieImpl2(trie.getMerkleStorage(), Bytes32.wrap(hash2)); + Assert.assertEquals(trie.get(key1), value1); + Assert.assertEquals(trie.get(key2), value2); + Assert.assertEquals(trie.get(key3), value3); + + trie = new TrieImpl2(trie.getMerkleStorage(), Bytes32.wrap(hash3)); + Assert.assertEquals(trie.get(key1), value4); + Assert.assertEquals(trie.get(key2), value2); + Assert.assertEquals(trie.get(key3), value3); + + // Commit changes to storage, and create new tries from roothash and new storage instance + trie.flush(); + final MerkleStorage newMerkleStorage = trie.getMerkleStorage(); + trie = new TrieImpl2(newMerkleStorage, Bytes32.wrap(hash1)); + Assert.assertEquals(trie.get(key1), value1); + Assert.assertNull(trie.get(key2)); + Assert.assertNull(trie.get(key3)); + + trie = new TrieImpl2(newMerkleStorage, Bytes32.wrap(hash2)); + Assert.assertEquals(trie.get(key1), value1); + Assert.assertEquals(trie.get(key2), value2); + Assert.assertEquals(trie.get(key3), value3); + + trie = new TrieImpl2(newMerkleStorage, Bytes32.wrap(hash3)); + Assert.assertEquals(trie.get(key1), value4); + Assert.assertEquals(trie.get(key2), value2); + Assert.assertEquals(trie.get(key3), value3); + Assert.assertEquals(trie.get(Bytes.of(1, 2, 3, 4, 5)), UInt256.ZERO); + final byte[] key4 = Bytes.of(1, 3, 4, 6, 7, 9).toArray(); + final byte[] key5 = Bytes.of(1, 3, 4, 6, 3, 9).toArray(); + final byte[] key6 = Bytes.of(1, 3, 4, 6, 8, 9).toArray(); + final Bytes key7 = Bytes.of(2); + final Bytes key8 = Bytes32.random(); + final byte[] key9 = Bytes.wrap(key8, key7).toArray(); + trie.put(key4, Bytes.of("value5".getBytes(StandardCharsets.UTF_8))); + trie.put(key5, Bytes.of("value6".getBytes(StandardCharsets.UTF_8))); + trie.put(key6, Bytes.of("value7".getBytes(StandardCharsets.UTF_8))); + trie.put(key5, Bytes.of("value8".getBytes(StandardCharsets.UTF_8))); + trie.put(key7, Bytes.of("value9".getBytes(StandardCharsets.UTF_8))); + trie.put(key8, Bytes.of("value10".getBytes(StandardCharsets.UTF_8))); + trie.put(key9, Bytes.of("value11".getBytes(StandardCharsets.UTF_8))); + Random r = new SecureRandom(); + List rl = new ArrayList<>(); + for (int i = 1; i <= 1000; i++) { + byte[] array = new byte[i % 256 + 8]; + r.nextBytes(array); + Bytes bytes = Bytes.wrap(array); + rl.add(bytes); + trie.put(bytes, Bytes32.random()); + } + rl.addAll(java.util.Arrays.asList(Bytes.wrap(key1), Bytes.of(1, 2, 3, 4, 5), + Bytes.wrap(key2), Bytes.wrap(key3), Bytes.wrap(key4), + Bytes.wrap(key5), + Bytes.wrap(key6), key7, key8, Bytes.wrap(key9))); + trie.commit(); + trie.flush(); + + List keys = new ArrayList<>(); + trie.visitAll((N) -> { + if (N instanceof BranchNode && N.getValue().isPresent()) { + Bytes k = CompactEncoding.pathToBytes( + Bytes.concatenate(N.getLocation().orElse(Bytes.EMPTY), + Bytes.of(CompactEncoding.LEAF_TERMINATOR))); + keys.add(k); + } + + if (N instanceof LeafNode) { + Bytes k = CompactEncoding.pathToBytes( + Bytes.concatenate(N.getLocation().orElse(Bytes.EMPTY), + N.getPath())); + keys.add(k); + } + }); + + Collections.sort(keys); + Collections.sort(rl); + rl = rl.stream().distinct().collect(Collectors.toList()); + Assert.assertEquals(trie.get(key7), + Bytes.of("value9".getBytes(StandardCharsets.UTF_8))); + Assert.assertEquals(keys.size(), rl.size()); + Assert.assertEquals(keys, rl); + } + + @Test + public void test1() { + TrieImpl2 trie = new TrieImpl2(); + int n = 100; + for (int i = 1; i < n; i++) { + trie.put(Bytes.of(i), Bytes.of(i)); + } + byte[] rootHash1 = trie.getRootHash(); + + TrieImpl2 trie2 = new TrieImpl2(); + for (int i = 1; i < n; i++) { + trie2.put(Bytes.of(i), Bytes.of(i)); + } + byte[] rootHash2 = trie2.getRootHash(); + Assert.assertArrayEquals(rootHash1, rootHash2); + } + + @Test + public void test2() { + TrieImpl2 trie = new TrieImpl2(); + int n = 100; + for (int i = 1; i < n; i++) { + trie.put(Bytes.of(i), Bytes.of(i)); + } + trie.commit(); + trie.flush(); + byte[] rootHash = trie.getRootHash(); + TrieImpl2 trieCopy = new TrieImpl2(trie.getMerkleStorage(), Bytes32.wrap(rootHash)); + for (int i = 1; i < n; i++) { + Assert.assertEquals(trieCopy.get(Bytes.of(i)), Bytes.of(i)); + } + for (int i = 1; i < n; i++) { + for (int j = 1; j < n; j++) { + if (i != j) { + Assert.assertNotEquals(trieCopy.get(Bytes.of(i)), trieCopy.get(Bytes.of(j))); + } + } + } + } + + @Test + public void testOrder() { + TrieImpl2 trie = new TrieImpl2(); + int n = 100; + List value = new ArrayList<>(); + for (int i = 1; i < n; i++) { + value.add(i); + trie.put(Bytes.of(i), Bytes.of(i)); + } + trie.put(Bytes.of(10), Bytes.of(10)); + value.add(10); + trie.commit(); + trie.flush(); + byte[] rootHash1 = trie.getRootHash(); + Collections.shuffle(value); + TrieImpl2 trie2 = new TrieImpl2(); + for (int i : value) { + trie2.put(Bytes.of(i), Bytes.of(i)); + } + trie2.commit(); + trie2.flush(); + byte[] rootHash2 = trie2.getRootHash(); + Assert.assertArrayEquals(rootHash1, rootHash2); + } + + @Test + public void testRange() { + TrieImpl2 trie = new TrieImpl2(); + + Bytes add1 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a1abc"); + Bytes add2 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a3456"); + Bytes add3 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a3433"); + Bytes add4 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a3422"); + + Bytes asset1 = Bytes.ofUnsignedLong(100001); + Bytes asset2 = Bytes.ofUnsignedLong(100002); + Bytes asset3 = Bytes.ofUnsignedLong(700001); + Bytes asset4 = Bytes.ofUnsignedLong(130001); + Bytes asset5 = Bytes.ofUnsignedLong(105001); + + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), Bytes32.random()), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), add2), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), Bytes32.random()), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), add4), Bytes32.random()); + + trie.put(Bytes.wrap(Bytes.of(StateType.Account.value()), Bytes32.random()), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.Account.value()), add2), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.Account.value()), Bytes32.random()), Bytes32.random()); + + trie.put(Bytes.wrap(Bytes.of(StateType.Code.value()), Bytes32.random()), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.Code.value()), add3), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.Code.value()), add4), Bytes32.random()); + + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, asset1)), + Bytes.ofUnsignedLong(5)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, asset2)), + Bytes.ofUnsignedLong(10)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, asset3)), + Bytes.ofUnsignedLong(15)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, asset4)), + Bytes.ofUnsignedLong(25)); + + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add2, asset4)), + Bytes.ofUnsignedLong(5)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add2, asset5)), + Bytes.ofUnsignedLong(25)); + + + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add3, asset2)), + Bytes.ofUnsignedLong(5)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add3, asset4)), + Bytes.ofUnsignedLong(10)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add4, asset1)), + Bytes.ofUnsignedLong(15)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add4, asset3)), + Bytes.ofUnsignedLong(25)); + + + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), Bytes32.random()), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), Bytes32.random()), Bytes32.random()); + trie.put(Bytes.wrap(Bytes.of(StateType.UNDEFINED.value()), Bytes32.random()), Bytes32.random()); + + trie.commit(); + trie.flush(); + + Bytes32 hash = trie.getRootHashByte32(); + + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, asset1)), + Bytes.ofUnsignedLong(50)); + trie.put(fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, asset3)), + Bytes.ofUnsignedLong(20)); + + + Bytes32 min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, + Bytes.ofUnsignedLong(0))); + + Bytes32 max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), add1, + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + + TreeMap asset = new TrieImpl2(trie.getMerkleStorage(), hash) + .entriesFrom(min, max); + + Map assets = new TreeMap<>(); + assets.put(String.valueOf(asset1.toLong()), 5L); + assets.put(String.valueOf(asset2.toLong()), 10L); + assets.put(String.valueOf(asset3.toLong()), 15L); + assets.put(String.valueOf(asset4.toLong()), 25L); + + Map actual = new TreeMap<>(); + + asset.forEach((k, v) -> + actual.put(String.valueOf(k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong()), + v.toLong()) + ); + + Assert.assertEquals(assets, actual); + + assets.put(String.valueOf(asset1.toLong()), 50L); + assets.put(String.valueOf(asset3.toLong()), 20L); + + trie.commit(); + trie.flush(); + asset = trie.entriesFrom(min, max); + actual.clear(); + + asset.forEach((k, v) -> + actual.put(String.valueOf(k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong()), + v.toLong()) + ); + } + + @Test + public void testRange2() { + TrieImpl2 trie = new TrieImpl2(); + trie.put(Bytes.fromHexString("0x1445584348414e47455f42414c414e43455f4c494d4954"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x144d454d4f5f4645455f484953544f5259"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a0b070b2b58f4328e293dc9d6012f59c263d3a1df6"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a025a3aae39b24a257f95769c701e8d6978ebe9fc5"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x18a08beaa1a8e2d45367af7bae7c490b9932a4fa4301"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a0ec6525979a351a54fa09fea64beb4cce33ffbb7a"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x04"), Bytes32.random()); + trie.put(Bytes.fromHexString("0x01a05430a3f089154e9e182ddd6fe136a62321af22a7"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x14544f54414c5f4e45545f4c494d4954"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a00a9309758508413039e4bc5a3d113f3ecc55031d"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a0fab5fbf6afb681e4e37e9d33bddb7e923d6132e5"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x034465766163636f756e74"), Bytes32.random()); + trie.put(Bytes.fromHexString("0x01a08beaa1a8e2d45367af7bae7c490b9932a4fa4301"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x18a06a17a49648a8ad32055c06f60fa14ae46df94cc1"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a0807337f180b62a77576377c1d0c9c24df5c0dd62"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x18a05430a3f089154e9e182ddd6fe136a62321af22a7"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x03426c61636b686f6c65"), Bytes32.random()); + trie.put(Bytes.fromHexString("0x035a696f6e"), Bytes32.random()); + trie.put(Bytes.fromHexString("0x0353756e"), Bytes32.random()); + trie.put(Bytes.fromHexString("0x18a014eebe4d30a6acb505c8b00b218bdc4733433c68"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x18a00a9309758508413039e4bc5a3d113f3ecc55031d"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x18a0807337f180b62a77576377c1d0c9c24df5c0dd62"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x01a06a17a49648a8ad32055c06f60fa14ae46df94cc1"), Bytes32.ZERO); + trie.put(Bytes.fromHexString( + "0x02a0f9490505f11ffb8d7e3df9789e63ab8709cf457200000000000f42410000"), + Bytes.fromHexString("0x0000000000000064")); + trie.put(Bytes.fromHexString("0x07a0f9490505f11ffb8d7e3df9789e63ab8709cf4572"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x08a0f9490505f11ffb8d7e3df9789e63ab8709cf4572"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x0531303030303031"), Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x14414c4c4f575f54564d5f5452414e534645525f5452433130"), + Bytes32.ZERO); + trie.put(Bytes.fromHexString("0x14414c4c4f575f41535345545f4f5054494d495a4154494f4e"), + Bytes.fromHexString("0x0000000000000001")); + + trie.put(Bytes.fromHexString( + "0x02a0abd4b9367799eaa3197fecb144eb71de1e049abc00000000000f42410000"), + Bytes.fromHexString("0x0000000005f5e09c")); + trie.put(Bytes.fromHexString( + "0x02a0abd4b9367799eaa3197fecb144eb71de1e049abc00000000000f42420000"), + Bytes.fromHexString("0x000000000000009c")); + trie.put(Bytes.fromHexString( + "0x02a0f9490505f11ffb8d7e3df9789e63ab8709cf457200000000000f42410000"), + Bytes.fromHexString("0x0000000000000063")); + trie.put(Bytes.fromHexString( + "0x02a0f9490505f11ffb8d7e3df9789e63ab8709cf457200000000000f42510000"), + Bytes.fromHexString("0x0000000000000049")); + trie.put(Bytes.fromHexString( + "0x02a0548794500882809695a8a687866e76d4271a1abc00000000000f42410000"), + Bytes.fromHexString("0x0000000000000009")); + trie.put(Bytes.fromHexString( + "0x02a0abd4b9367799eaa3197fecb144eb71de1e049abc00000000000f42410000"), + Bytes.fromHexString("0x0000000005f5e094")); + trie.put(Bytes.fromHexString( + "0x02a03c612889142e98bb2f4bc40af2d2b77730baff7a00000000000f55780000"), + Bytes.fromHexString("0x01")); + + Map assets = new HashMap<>(); + // 1. address is before head, not exit + Bytes32 min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("413c6120b82a61d0e0bb0c4d4ebfae56cb664ba5a6"), + Bytes.ofUnsignedLong(0))); + + Bytes32 max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("413c6120b82a61d0e0bb0c4d4ebfae56cb664ba5a6"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertTrue(assets.isEmpty()); + + // 2. address is head + min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa03c612889142e98bb2f4bc40af2d2b77730baff7a"), + Bytes.ofUnsignedLong(0))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa03c612889142e98bb2f4bc40af2d2b77730baff7a"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertEquals(Bytes.fromHexString("0x01").toLong(), + assets.get(Bytes.fromHexString("0x00000000000f5578").toLong()).longValue()); + Assert.assertEquals(1, assets.size()); + assets.clear(); + + // 3. address is second + min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa0548794500882809695a8a687866e76d4271a1abc"), + Bytes.ofUnsignedLong(0))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa0548794500882809695a8a687866e76d4271a1abc"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertEquals(Bytes.fromHexString("0x0000000000000009").toLong(), + assets.get(Bytes.fromHexString("0x00000000000f4241").toLong()).longValue()); + Assert.assertEquals(1, assets.size()); + assets.clear(); + + // 4. address is inside, not exit + + min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa08c6120b82a61d0e0bb0c4d4ebfae56cb664ba5a8"), + Bytes.ofUnsignedLong(0))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa08c6120b82a61d0e0bb0c4d4ebfae56cb664ba5a8"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertTrue(assets.isEmpty()); + + // 5. address is third + + min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa0abd4b9367799eaa3197fecb144eb71de1e049abc"), + Bytes.ofUnsignedLong(0))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa0abd4b9367799eaa3197fecb144eb71de1e049abc"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertEquals(Bytes.fromHexString("0x0000000005f5e094").toLong(), + assets.get(Bytes.fromHexString("0x00000000000f4241").toLong()).longValue()); + Assert.assertEquals(Bytes.fromHexString("0x000000000000009c").toLong(), + assets.get(Bytes.fromHexString("0x00000000000f4242").toLong()).longValue()); + Assert.assertEquals(2, assets.size()); + assets.clear(); + + // 6. address is last + min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa0f9490505f11ffb8d7e3df9789e63ab8709cf4572"), + Bytes.ofUnsignedLong(0))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa0f9490505f11ffb8d7e3df9789e63ab8709cf4572"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertEquals(Bytes.fromHexString("0x0000000000000063").toLong(), + assets.get(Bytes.fromHexString("0x00000000000f4241").toLong()).longValue()); + Assert.assertEquals(Bytes.fromHexString("0x0000000000000049").toLong(), + assets.get(Bytes.fromHexString("0x00000000000f4251").toLong()).longValue()); + Assert.assertEquals(2, assets.size()); + assets.clear(); + + // 7. address is after last, not exit + min = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa1f9490505f11ffb8d7e3df9789e63ab8709cf4572"), + Bytes.ofUnsignedLong(0))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.AccountAsset.value()), + Bytes.fromHexString("0xa1f9490505f11ffb8d7e3df9789e63ab8709cf4572"), + Bytes.ofUnsignedLong(Long.MAX_VALUE))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertTrue(assets.isEmpty()); + + // 8. not fix32,should not error + min = fix32(Bytes.wrap(Bytes.of(StateType.Witness.value()), + Bytes.fromHexString("0xa0"))); + + max = fix32(Bytes.wrap(Bytes.of(StateType.Witness.value()), + Bytes.fromHexString("0xa1"))); + trie.entriesFrom(min, max).forEach((k, v) -> assets.put( + k.slice(Byte.BYTES + ADDRESS_SIZE, Long.BYTES).toLong(), + v.toLong())); + Assert.assertTrue(assets.isEmpty()); + } + + @Test + public void testEqual() throws IOException { + TrieImpl2 trie = new TrieImpl2(folder.newFolder().toPath().toString()); + TrieImpl2 trie2 = new TrieImpl2(folder.newFolder().toPath().toString()); + Bytes k1 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a1abc"); + Bytes k2 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a3456"); + Bytes v1 = Bytes32.random(); + Bytes v2 = Bytes32.random(); + trie.put(k1, v1); + trie.put(k2, v2); + trie2.put(k2, v2); + trie2.put(k1, v1); + Assert.assertEquals(trie2, trie); + Assert.assertEquals(trie.hashCode(), trie2.hashCode()); + Assert.assertEquals(trie.toString(), trie2.toString()); + trie.commit(); + TrieImpl2 trie3 = new TrieImpl2(trie.getMerkleStorage(), trie2.getRootHashByte32()); + Assert.assertEquals(trie3, trie); + } + + @Test + public void testRootHash() throws IOException { + TrieImpl2 trie = new TrieImpl2(folder.newFolder().toPath().toString()); + Bytes k1 = Bytes.fromHexString("41548794500882809695a8a687866e76d4271a1abc"); + Bytes v1 = Bytes.wrap(new byte[]{0}); + Bytes v2 = Bytes.wrap(new byte[]{0}); + trie.put(k1, v1); + trie.commit(); + Bytes32 hash1 = trie.getRootHashByte32(); + trie.put(k1, v2); + trie.commit(); + Bytes32 hash2 = trie.getRootHashByte32(); + Assert.assertEquals(hash1, hash2); + trie.put(k1, Bytes.ofUnsignedLong(0)); + trie.commit(); + Bytes32 hash3 = trie.getRootHashByte32(); + Assert.assertNotEquals(hash1, hash3); + + } + +} diff --git a/framework/src/test/resources/config-localtest.conf b/framework/src/test/resources/config-localtest.conf index d7f573fe90e..ba5ae49c919 100644 --- a/framework/src/test/resources/config-localtest.conf +++ b/framework/src/test/resources/config-localtest.conf @@ -52,6 +52,15 @@ storage { // }, ] + stateRoot.switch = true + stateGenesis.directory = "state-genesis" + stateRoot.db = { + maxOpenFiles = 5000 + writeBufferSize = 10485760 // 10 MB = 10 * 1024 * 1024 B + cacheCapacity = 10485760 // 10 MB = 10 * 1024 * 1024 B + cacheIndexAndFilter = true + } + } node.discovery = { @@ -116,14 +125,21 @@ node { ] http { + fullNodeEnable = true fullNodePort = 8090 + solidityEnable = true solidityPort = 8091 + PBFTEnable = true + PBFTPort = 8092 } rpc { port = 50051 - # default value is 50061 - # solidityPort = 50061 + #solidityPort = 50061 + #PBFTPort = 50071 + fullNodeEnable = true + solidityEnable = true + PBFTEnable = true # Number of gRPC thread, default availableProcessors / 2 # thread = 16 diff --git a/framework/src/test/resources/config-test.conf b/framework/src/test/resources/config-test.conf index db24bb2a8a0..43ed0000cce 100644 --- a/framework/src/test/resources/config-test.conf +++ b/framework/src/test/resources/config-test.conf @@ -73,7 +73,17 @@ storage { merkleRoot = { reward-vi = e0ebe2f3243391ed674dff816a07f589a3279420d6d88bc823b6a9d5778337ce - } + } + + stateRoot.switch = true + stateGenesis.directory = "state-genesis" + stateRoot.db = { + maxOpenFiles = 5000 + writeBufferSize = 10485760 // 10 MB = 10 * 1024 * 1024 B + cacheCapacity = 10485760 // 10 MB = 10 * 1024 * 1024 B + cacheIndexAndFilter = true + } + } diff --git a/plugins/README.md b/plugins/README.md index 0db6f2e6143..2c657711e18 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -34,8 +34,7 @@ DB convert provides a helper which can convert LevelDB data to RocksDB data, par - ``: Input path for leveldb, default: output-directory/database. - ``: Output path for rocksdb, default: output-directory-dst/database. -- `--safe`: In safe mode, read data from leveldb then put into rocksdb, it's a very time-consuming procedure. If not, just change engine.properties from leveldb to rocksdb, rocksdb - is compatible with leveldb for the current version. This may not be the case in the future, default: false. +- `--safe`: It is deprecated, now must is in the safe mode. - `-h | --help`: Provide the help info. ### Examples: @@ -145,3 +144,39 @@ NOTE: large db may GC overhead limit exceeded. - ``: Source path for database. Default: output-directory/database - `--db`: db name. - `-h | --help`: provide the help info + +## DB Prune + +Prune tool is only used for pruning the MPT data for archive node. When a Fullnode sets `stateRoot.switch = true`, +it is a archive node and will store the history data in `stateGenesis.directory(default: output-directory/state-genesis/world-state-trie)`, +the data volume of this database grows fast and may reach terabyte levels in a few months. +But not all the archive node want to reserve the whole history data, some may only want to reserve recently history data like three month, +this tool can split the trie data and generate a new database which only contain the latest MPT as you specified. +When prune finished, you shoud replace the `state-genesis` by `state-directory-pruned`, +prune may take a long time depend on the number of MPT that you want reserved. + + + +### Available parameters: + +- `-c | --config`: config file, Default: config.conf. +- `-d | --output-directory`: src output directory, Default: output-directory. +- `-p | --state-directory-pruned`: pruned state directory, Default: state-genesis-pruned. +- `-n | --number-reserved`: the number of recent trie data should be reserved. +- `-k | --check`: the switch whether check the data correction after prune +- `-h | --help`: provide the help info + +### Examples: + +Execute move command. +```shell script +# full command + java -jar Toolkit.jar db prune [-hk] [-c=] -n= + [-p=] [-d=] +# 1. split and get pruned data + java -jar Toolkit.jar db prune -d ./output-directory -p ./state-genesis-pruned -c ./config.conf -n 1 -k +# 2. mv the prev state db away + mv ./output-directory/state-genesis /backup +# 3. replace and rename the pruned dir + mv ./state-genesis-pruned ./output-directory/state-genesis +``` diff --git a/plugins/build.gradle b/plugins/build.gradle index 7f226d7099c..9b0d9e598d6 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -32,10 +32,11 @@ dependencies { compile group: 'com.typesafe', name: 'config', version: '1.3.2' compile group: 'me.tongfei', name: 'progressbar', version: '0.9.3' compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.69' - compile group: 'org.rocksdb', name: 'rocksdbjni', version: '5.15.10' + compile group: 'org.rocksdb', name: 'rocksdbjni', version: '7.7.3' compile 'io.github.tronprotocol:leveldbjni-all:1.18.2' compile 'io.github.tronprotocol:leveldb:1.18.2' compile project(":protocol") + compile project(":state-trie-jdk8") } check.dependsOn 'lint' diff --git a/plugins/src/main/java/org/tron/plugins/Constant.java b/plugins/src/main/java/org/tron/plugins/Constant.java new file mode 100644 index 00000000000..52e0c789f8b --- /dev/null +++ b/plugins/src/main/java/org/tron/plugins/Constant.java @@ -0,0 +1,14 @@ +package org.tron.plugins; + +public class Constant { + + public static final String PROPERTIES_CONFIG_KEY = "storage.properties"; + public static final String DB_DIRECTORY_CONFIG_KEY = "storage.db.directory"; + public static final String DEFAULT_DB_DIRECTORY = "database"; + + public static final String STATE_GENESIS_PATH_KEY = "storage.stateGenesis.directory"; + public static final String STATE_GENESIS_SWITCH_KEY = "storage.stateRoot.switch"; + public static final String STATE_GENESIS_META_FILE = "genesis.properties"; + public static final String STATE_TRIE_DB_NAME = "world-state-trie"; + +} diff --git a/plugins/src/main/java/org/tron/plugins/Db.java b/plugins/src/main/java/org/tron/plugins/Db.java index 84654dca934..5e9416dbe32 100644 --- a/plugins/src/main/java/org/tron/plugins/Db.java +++ b/plugins/src/main/java/org/tron/plugins/Db.java @@ -12,7 +12,8 @@ DbConvert.class, DbLite.class, DbCopy.class, - DbRoot.class + DbRoot.class, + Pruner.class }, commandListHeading = "%nCommands:%n%nThe most commonly used db commands are:%n" ) diff --git a/plugins/src/main/java/org/tron/plugins/DbConvert.java b/plugins/src/main/java/org/tron/plugins/DbConvert.java index a75b235bbcf..66f9207fa6c 100644 --- a/plugins/src/main/java/org/tron/plugins/DbConvert.java +++ b/plugins/src/main/java/org/tron/plugins/DbConvert.java @@ -50,12 +50,13 @@ public class DbConvert implements Callable { private File dest; @CommandLine.Option(names = {"--safe"}, + defaultValue = "true", description = "In safe mode, read data from leveldb then put rocksdb." + "If not, just change engine.properties from leveldb to rocksdb," + "rocksdb is compatible with leveldb for current version." + "This may not be the case in the future." + "Default: ${DEFAULT-VALUE}") - private boolean safe; + private boolean safe = true; @CommandLine.Option(names = {"-h", "--help"}) private boolean help; @@ -94,6 +95,12 @@ public Integer call() throws Exception { } final long time = System.currentTimeMillis(); List services = new ArrayList<>(); + if (!safe) { + logger.info("set safe mode in rocksdb version {}", RocksDB.rocksdbVersion()); + spec.commandLine().getOut().format("set safe mode in rocksdb version %s", + RocksDB.rocksdbVersion()).println(); + safe = true; + } files.forEach(f -> services.add( new DbConverter(src.getPath(), dest.getPath(), f.getName(), safe))); cpList.forEach(f -> services.add( diff --git a/plugins/src/main/java/org/tron/plugins/DbLite.java b/plugins/src/main/java/org/tron/plugins/DbLite.java index 732d4913021..6a01d86aec3 100644 --- a/plugins/src/main/java/org/tron/plugins/DbLite.java +++ b/plugins/src/main/java/org/tron/plugins/DbLite.java @@ -352,7 +352,7 @@ private void generateInfoProperties(String propertyfile, long num) } } - private long getLatestBlockHeaderNum(String databaseDir) throws IOException, RocksDBException { + public long getLatestBlockHeaderNum(String databaseDir) throws IOException, RocksDBException { // query latest_block_header_number from checkpoint first final String latestBlockHeaderNumber = "latest_block_header_number"; DBInterface checkpointDb = getCheckpointDb(databaseDir); @@ -653,7 +653,7 @@ private boolean isLite(String databaseDir) throws RocksDBException, IOException return getSecondBlock(databaseDir) > 1; } - private long getSecondBlock(String databaseDir) throws RocksDBException, IOException { + public long getSecondBlock(String databaseDir) throws RocksDBException, IOException { long num = 0; DBInterface sourceBlockIndexDb = DbTool.getDB(databaseDir, BLOCK_INDEX_DB_NAME); DBIterator iterator = sourceBlockIndexDb.iterator(); diff --git a/plugins/src/main/java/org/tron/plugins/Pruner.java b/plugins/src/main/java/org/tron/plugins/Pruner.java new file mode 100644 index 00000000000..a9504689bd5 --- /dev/null +++ b/plugins/src/main/java/org/tron/plugins/Pruner.java @@ -0,0 +1,552 @@ +package org.tron.plugins; + +import static org.tron.plugins.Constant.DB_DIRECTORY_CONFIG_KEY; +import static org.tron.plugins.Constant.PROPERTIES_CONFIG_KEY; +import static org.tron.plugins.Constant.STATE_GENESIS_META_FILE; +import static org.tron.plugins.Constant.STATE_GENESIS_PATH_KEY; +import static org.tron.plugins.Constant.STATE_TRIE_DB_NAME; + +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import me.tongfei.progressbar.ProgressBar; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.trie.KeyValueMerkleStorage; +import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie; +import org.hyperledger.besu.ethereum.trie.MerkleStorage; +import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.storage.RocksDBConfigurationBuilder; +import org.hyperledger.besu.storage.RocksDBKeyValueStorage; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.tron.plugins.utils.ByteArray; +import org.tron.plugins.utils.FileUtils; +import org.tron.plugins.utils.db.DBInterface; +import org.tron.plugins.utils.db.DbTool; +import org.tron.protos.Protocol; +import picocli.CommandLine; +import picocli.CommandLine.Option; + +@Slf4j(topic = "prune") +@CommandLine.Command(name = "prune", description = "A helper to prune the archive db.") +public class Pruner implements Callable { + + private static final byte[] IN_USE = Bytes.of(1).toArrayUnsafe(); + + private static final String BLOCK_DB_NAME = "block"; + private static final String BLOCK_INDEX_DB_NAME = "block-index"; + private static final String TRIE_DB_NAME = "world-state-trie"; + private static final String DEFAULT_STATE_GENESIS_DIRECTORY = "state-genesis"; + private static Path RESERVED_KEY_STORE_PATH; + + private final ReadWriteLock pendingMarksLock = new ReentrantReadWriteLock(); + private final Set pendingMarks = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + private static final int DEFAULT_OPS_PER_TRANSACTION = 10_000; + + private MerkleStorage srcMerkleStorage; + private KeyValueStorage srcKvStorage; + private MerkleStorage destMerkleStorage; + private KeyValueStorage destKvStorage; + private RocksDB destDb; + private KeyValueStorage markedKeyStore; + + final ThreadPoolExecutor markingExecutorService = + new ThreadPoolExecutor( + 0, + Runtime.getRuntime().availableProcessors() * 2, + 5L, + TimeUnit.SECONDS, + new LinkedBlockingDeque<>(100), + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat(this.getClass().getSimpleName() + "-mark-%d") + .build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + + final ThreadPoolExecutor checkExecutorService = + new ThreadPoolExecutor( + 0, + Runtime.getRuntime().availableProcessors() * 2, + 5L, + TimeUnit.SECONDS, + new LinkedBlockingDeque<>(100), + new ThreadFactoryBuilder() + .setDaemon(true) + // .setPriority(Thread.MIN_PRIORITY) + .setNameFormat(this.getClass().getSimpleName() + "-check-%d") + .build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + + @CommandLine.Spec + CommandLine.Model.CommandSpec spec; + + @CommandLine.Option(names = {"-d", "--output-directory"}, + required = true, + defaultValue = "output-directory", + converter = DbMove.PathConverter.class, + order = 1, + description = "source output directory. Default: ${DEFAULT-VALUE}") + static Path srcDirectory; + + @Option(names = {"-p", "--state-directory-pruned"}, + required = true, + defaultValue = "state-genesis-pruned", + order = 2, + description = "pruned state directory. Default: ${DEFAULT-VALUE}") + private String prunedDir; + + @CommandLine.Option(names = {"-c", "--config"}, + required = true, + defaultValue = "config.conf", + converter = ConfigConverter.class, + order = 0, + description = "config file. Default: ${DEFAULT-VALUE}") + static Config config; + + @CommandLine.Option( + names = {"-n", "--number-reserved"}, + required = true, + order = 3, + description = "the number of block state data need to be reserved.") + private long reserveNumber; + + @CommandLine.Option( + names = {"-k", "--check"}, + order = 4, + description = "check trie data integrity") + private boolean check; + + @Option(names = {"-h", "--help"}) + static boolean help; + + + @Override + public Integer call() throws Exception { + if (help) { + spec.commandLine().usage(System.out); + return 0; + } + + if (!checkPrunedDir(prunedDir)) { + return 300; + } + + String msg; + Path databasePath = Paths.get(srcDirectory.toString(), + config.getString(DB_DIRECTORY_CONFIG_KEY)); + Path prunedPath = Paths.get(prunedDir); + Path statePath; + if (config.hasPath(STATE_GENESIS_PATH_KEY)) { + statePath = Paths.get(srcDirectory.toString(), config.getString(STATE_GENESIS_PATH_KEY)); + } else { + statePath = Paths.get(srcDirectory.toString(), DEFAULT_STATE_GENESIS_DIRECTORY); + } + + // check reserve number + Map result = checkAndGetBlockNumberRange( + databasePath.toString(), statePath.toString(), reserveNumber); + if (result == null || result.size() == 0) { + return 404; + } + + // init persistant storage, only for once + initKeyValueStorage(statePath.toString()); + initReservedKeyStore(prunedPath); + + // mark the trie data + long startIndex = result.get("end") - reserveNumber + 1; + long endIndex = result.get("end"); + print(String.format("Prune begin, start number: %d, end number: %d", + startIndex, endIndex), false); + long startTime = System.currentTimeMillis(); + for (long index = startIndex; index <= endIndex; index++) { + Bytes32 root = Bytes32.wrap( + getBlockByNumber(index).getBlockHeader().getArchiveRoot().toByteArray()); + MerklePatriciaTrie srcTrie = getTrie(srcMerkleStorage, root); + print("marking block number: " + index + ", root: " + root, false); + + srcTrie.visitAll((node) -> { + markNodes(node.getHash()); + }, markingExecutorService).join(); + } + + // wait markingExecutorService task finished + awaitTask(markingExecutorService); + + // flush the final task data + flushPendingMarks(); + print(String.format("mark trie finish, cost: %d ms", + System.currentTimeMillis() - startTime), false); + + // copy the marked data to dest merkle store + copy(); + + // check trie data correction + if (check) { + destMerkleStorage = new KeyValueMerkleStorage(getDestKvStorage(prunedDir)); + for (long index = startIndex; index <= endIndex; index++) { + Bytes32 root = Bytes32.wrap( + getBlockByNumber(index).getBlockHeader().getArchiveRoot().toByteArray()); + startTime = System.currentTimeMillis(); + MerklePatriciaTrie destTrie = getTrie( + destMerkleStorage, root); + destTrie.visitAll(node -> { + node.getHash(); + node.getValue(); + }, checkExecutorService).join(); + } + awaitTask(checkExecutorService); + msg = String.format("check trie data correction, cost: %d", + System.currentTimeMillis() - startTime); + print(msg, false); + destKvStorage.close(); + } + + // copy state genesis data + if (!copyStateGenesis(statePath, prunedPath)) { + return 501; + } + + // generate state meta file + Protocol.Block stateGenesisBlock = getBlockByNumber(startIndex); + if (!generateMetaFile(prunedPath, stateGenesisBlock)) { + return 501; + } + + // release resource + releaseResource(); + + msg = "prune success!"; + print(msg, false); + + return 0; + } + + private void copy() throws RocksDBException { + print("copy marked data start.", false); + // init destDb + initDestDb(prunedDir); + AtomicLong count = new AtomicLong(); + long start = System.currentTimeMillis(); + markedKeyStore.streamKeys().parallel().forEach(key -> { + Optional v = + Optional.ofNullable(srcKvStorage.get(key)).orElse(Optional.empty()); + try { + if (v.isPresent()) { + destDb.put(key, v.get()); + } + } catch (RocksDBException e) { + print(String.format("copy marked data failed, err: %s", e.getMessage()), true); + throw new RuntimeException(e); + } + count.incrementAndGet(); + }); + destDb.close(); + print(String.format("copy marked data finish, record number: %d, cost: %d", + count.get(), System.currentTimeMillis() - start), false); + } + + private void markNodes(final Bytes32 hash) { + markThenMaybeFlush(() -> pendingMarks.add(hash), 1); + } + + private void markThenMaybeFlush(final Runnable nodeMarker, final int numberOfNodes) { + // We use the read lock here because pendingMarks is threadsafe and we want to allow all the + // marking threads access simultaneously. + final Lock markLock = pendingMarksLock.readLock(); + markLock.lock(); + try { + nodeMarker.run(); + } finally { + markLock.unlock(); + } + + // However, when the size of pendingMarks grows too large, we want all the threads to stop + // adding because we're going to clear the set. + // Therefore, we need to take out a write lock. + if (pendingMarks.size() >= DEFAULT_OPS_PER_TRANSACTION) { + final Lock flushLock = pendingMarksLock.writeLock(); + flushLock.lock(); + try { + // Check once again that the condition holds. If it doesn't, that means another thread + // already flushed them. + if (pendingMarks.size() >= DEFAULT_OPS_PER_TRANSACTION) { + flushPendingMarks(); + } + } finally { + flushLock.unlock(); + } + } + } + + private void flushPendingMarks() { + final KeyValueStorageTransaction transaction = markedKeyStore.startTransaction(); + pendingMarks.forEach(node -> transaction.put(node.toArrayUnsafe(), IN_USE)); + transaction.commit(); + pendingMarks.clear(); + } + + private void awaitTask(ThreadPoolExecutor executor) throws InterruptedException { + Thread.sleep(10000); + while (executor.getActiveCount() > 0 || executor.getQueue().size() > 0) { + Thread.sleep(10000); + } + executor.shutdown(); + } + + private boolean copyStateGenesis(Path statePath, Path prunedPath) { + List services = new ArrayList<>(); + Arrays.stream(Objects.requireNonNull(statePath.toFile().listFiles())) + .filter(File::isDirectory) + .filter(f -> !STATE_TRIE_DB_NAME.equals(f.getName())) + .forEach(f -> services.add( + new DbCopy.DbCopier( + statePath.toFile().getPath(), prunedPath.toFile().getPath(), f.getName()))); + List fails = ProgressBar + .wrap(services.stream(), "copy task") + .parallel() + .map( + dbCopier -> { + try { + return dbCopier.doCopy() ? null : dbCopier.name(); + } catch (Exception e) { + print(String.format("copy state genesis failed, db: %s, err: %s", + dbCopier.name(), e.getMessage()), true); + return dbCopier.name(); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return fails.isEmpty(); + } + + private boolean generateMetaFile(Path prunedPath, Protocol.Block block) { + String propertyfile = Paths.get(prunedPath.toString(), + STATE_GENESIS_META_FILE).toString(); + if (!FileUtils.createFileIfNotExists(propertyfile)) { + print("Create properties file failed.", true); + return false; + } + + Map properties = new HashMap<>(); + properties.put("height", + String.valueOf(block.getBlockHeader().getRawData().getNumber())); + properties.put("hash", + Bytes32.wrap(block.getBlockHeader().getArchiveRoot().toByteArray()).toHexString()); + properties.put("time", + String.valueOf(block.getBlockHeader().getRawData().getTimestamp())); + + if (!FileUtils.writeProperties(propertyfile, properties)) { + print("Write properties file failed.", true); + return false; + } + return true; + } + + private void initKeyValueStorage(String stateDir) { + if (srcKvStorage == null) { + Path triePath = Paths.get(stateDir, TRIE_DB_NAME); + srcKvStorage = new RocksDBKeyValueStorage( + new RocksDBConfigurationBuilder() + .databaseDir(triePath) + .build()); + srcMerkleStorage = new KeyValueMerkleStorage(srcKvStorage); + } + } + + private RocksDB initDestDb(String prunedDirectory) throws RocksDBException { + Path triePath = Paths.get(prunedDirectory, TRIE_DB_NAME); + destDb = RocksDB.open(triePath.toString()); + return destDb; + } + + private KeyValueStorage getDestKvStorage(String prunedDirectory) { + Path triePath = Paths.get(prunedDirectory, TRIE_DB_NAME); + destKvStorage = new RocksDBKeyValueStorage( + new RocksDBConfigurationBuilder().databaseDir(triePath).build()); + return destKvStorage; + } + + private MerklePatriciaTrie getTrie( + MerkleStorage merkleStorage, Bytes32 root) { + return new StoredMerklePatriciaTrie<>( + merkleStorage::get, root, + Function.identity(), + Function.identity()); + } + + private Protocol.Block getBlockByNumber(long blockNumber) throws IOException { + try { + DBInterface blockDb = getDb(BLOCK_DB_NAME); + DBInterface blockIndexDb = getDb(BLOCK_INDEX_DB_NAME); + byte[] value = blockDb.get(blockIndexDb.get(ByteArray.fromLong(blockNumber))); + if (value == null || value.length == 0) { + throw new IOException("can not find block, number: " + blockNumber); + } + return Protocol.Block.parseFrom(value); + } catch (IOException | RocksDBException e) { + throw new IOException("get block number failed, " + e.getMessage()); + } + } + + /** + * get db object (can not get state db) + */ + private DBInterface getDb(String dbName) throws IOException, RocksDBException { + File dbDir = getAndCheckDbPath(srcDirectory.toString(), dbName, config).toFile(); + Path dbParentPath = dbDir.toPath().getParent(); + return DbTool.getDB(dbParentPath.toString(), dbName); + } + + private Path getDbPath(String outputDir, String dbName, Config config) { + String confPath = String.format("%s.%s", PROPERTIES_CONFIG_KEY, dbName); + if (config.hasPath(confPath)) { + return Paths.get(config.getString(confPath)); + } else { + return Paths.get(outputDir, config.getString(DB_DIRECTORY_CONFIG_KEY), dbName); + } + } + + private Path getAndCheckDbPath(String outputDir, String dbName, Config config) + throws IOException { + File file = getDbPath(outputDir, dbName, config).toFile(); + if (!file.exists() || !file.isDirectory()) { + String errMsg = String.format("%s database does not exist.", BLOCK_DB_NAME); + print(errMsg, true); + throw new IOException(errMsg); + } + return file.toPath(); + } + + private Map checkAndGetBlockNumberRange( + String databaseDir, String stateDir, long reserveNumber) { + Map result = Maps.newHashMap(); + String errMsg; + if (reserveNumber < 1) { + errMsg = "reserveNumber must bigger than 0"; + print(errMsg, true); + return null; + } + try { + long latestBlockNumber = new DbLite().getLatestBlockHeaderNum(databaseDir); + long stateInitNumber = getStateInitNumber(stateDir); + if (stateInitNumber == -1) { + return null; + } + if (reserveNumber > Math.subtractExact(latestBlockNumber, stateInitNumber)) { + errMsg = String.format("reserveNumber is bigger than the block gap. " + + "reserveNumber: %d, latestBlockNumber: %d, earliestBlockNumber: %d", + reserveNumber, latestBlockNumber, stateInitNumber); + print(errMsg, true); + return null; + } + result.put("start", stateInitNumber); + result.put("end", latestBlockNumber); + } catch (IOException | RocksDBException e) { + errMsg = String.format("checkReserveNumber failed, err: %s", e.getMessage()); + print(errMsg, true); + return null; + } + return result; + } + + private long getStateInitNumber(String stateDir) { + File f = Paths.get(stateDir, STATE_GENESIS_META_FILE).toFile(); + if (!f.exists()) { + String err = "state genesis meta file not exist."; + print(err, true); + return -1; + } + return Long.parseLong(FileUtils.readProperty(f.getPath(), "height")); + } + + private void initReservedKeyStore(Path path) { + RESERVED_KEY_STORE_PATH = Paths.get(path.toString(), + String.format(".reserved-keys-%s", System.currentTimeMillis())); + markedKeyStore = new RocksDBKeyValueStorage( + new RocksDBConfigurationBuilder().databaseDir(RESERVED_KEY_STORE_PATH).build()); + } + + private void destoryPrunedKeyStore() throws IOException { + markedKeyStore.close(); + FileUtils.deleteDir(RESERVED_KEY_STORE_PATH.toFile()); + } + + private void releaseResource() throws IOException { + DbTool.close(); + destoryPrunedKeyStore(); + srcKvStorage.close(); + destDb.close(); + } + + private void print(String msg, boolean err) { + if (err) { + spec.commandLine().getErr().println( + spec.commandLine().getColorScheme().errorText(msg)); + } else { + spec.commandLine().getOut().println(msg); + } + } + + private boolean checkPrunedDir(String prunedDir) { + File file = Paths.get(prunedDir).toFile(); + if (file.exists()) { + print("Pruned output path is already exist!", true); + return false; + } else if (!file.mkdirs()) { + print("Pruned output path create failed!", true); + return false; + } + return true; + } + + static class ConfigConverter implements CommandLine.ITypeConverter { + ConfigConverter() { + } + + public Config convert(String value) throws Exception { + if (help) { + return null; + } + File file = Paths.get(value).toFile(); + if (file.exists() && file.isFile()) { + return ConfigFactory.parseFile(Paths.get(value).toFile()); + } else { + throw new IOException("Parse Config [" + value + "] failed!"); + } + } + } + +} \ No newline at end of file diff --git a/plugins/src/main/java/org/tron/plugins/comparator/MarketOrderPriceComparatorForRockDB.java b/plugins/src/main/java/org/tron/plugins/comparator/MarketOrderPriceComparatorForRockDB.java index cd718bdd2d7..388711f99c6 100644 --- a/plugins/src/main/java/org/tron/plugins/comparator/MarketOrderPriceComparatorForRockDB.java +++ b/plugins/src/main/java/org/tron/plugins/comparator/MarketOrderPriceComparatorForRockDB.java @@ -1,11 +1,11 @@ package org.tron.plugins.comparator; +import java.nio.ByteBuffer; +import org.rocksdb.AbstractComparator; import org.rocksdb.ComparatorOptions; -import org.rocksdb.DirectSlice; -import org.rocksdb.util.DirectBytewiseComparator; import org.tron.plugins.utils.MarketUtils; -public class MarketOrderPriceComparatorForRockDB extends DirectBytewiseComparator { +public class MarketOrderPriceComparatorForRockDB extends AbstractComparator { public MarketOrderPriceComparatorForRockDB(final ComparatorOptions copt) { super(copt); @@ -17,21 +17,16 @@ public String name() { } @Override - public int compare(final DirectSlice a, final DirectSlice b) { + public int compare(final ByteBuffer a, final ByteBuffer b) { return MarketUtils.comparePriceKey(convertDataToBytes(a), convertDataToBytes(b)); } /** * DirectSlice.data().array will throw UnsupportedOperationException. * */ - public byte[] convertDataToBytes(DirectSlice directSlice) { - int capacity = directSlice.data().capacity(); - byte[] bytes = new byte[capacity]; - - for (int i = 0; i < capacity; i++) { - bytes[i] = directSlice.get(i); - } - + public byte[] convertDataToBytes(ByteBuffer buf) { + byte[] bytes = new byte[buf.remaining()]; + buf.get(bytes); return bytes; } diff --git a/plugins/src/main/java/org/tron/plugins/utils/FileUtils.java b/plugins/src/main/java/org/tron/plugins/utils/FileUtils.java index b07b4469dc3..ff3eb1971e0 100644 --- a/plugins/src/main/java/org/tron/plugins/utils/FileUtils.java +++ b/plugins/src/main/java/org/tron/plugins/utils/FileUtils.java @@ -19,6 +19,7 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.List; +import java.util.Map; import java.util.Properties; import lombok.extern.slf4j.Slf4j; @@ -66,6 +67,23 @@ public static boolean writeProperty(String file, String key, String value) { return true; } + public static boolean writeProperties(String file, Map kv) { + try (OutputStream o = new FileOutputStream(file); + FileInputStream f = new FileInputStream(file); + BufferedWriter w = new BufferedWriter(new OutputStreamWriter(o, StandardCharsets.UTF_8)); + BufferedReader r = new BufferedReader(new InputStreamReader(f, StandardCharsets.UTF_8)) + ) { + Properties properties = new Properties(); + properties.load(r); + kv.forEach(properties::setProperty); + properties.store(w, "Generated by the application. PLEASE DO NOT EDIT! "); + } catch (Exception e) { + logger.warn("writeProperty", e); + return false; + } + return true; + } + /** * delete directory. diff --git a/protocol/src/main/protos/core/Tron.proto b/protocol/src/main/protos/core/Tron.proto index 41ef968d907..a999f4b969a 100644 --- a/protocol/src/main/protos/core/Tron.proto +++ b/protocol/src/main/protos/core/Tron.proto @@ -514,6 +514,7 @@ message BlockHeader { } raw raw_data = 1; bytes witness_signature = 2; + bytes archive_root = 3; } // block diff --git a/settings.gradle b/settings.gradle index eb304444378..d91187499c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,5 @@ include 'common' include 'example:actuator-example' include 'crypto' include 'plugins' +include 'state-trie-jdk8' diff --git a/state-trie-jdk8/build.gradle b/state-trie-jdk8/build.gradle new file mode 100644 index 00000000000..5ca8eafc88e --- /dev/null +++ b/state-trie-jdk8/build.gradle @@ -0,0 +1,50 @@ +plugins { + id 'java' + id 'idea' +} +group 'io.github.tronprotocol' +version '1.0.0' + +repositories { + mavenCentral() +} + +repositories { + mavenCentral() + mavenLocal() +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + + +dependencies { + compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.69' + compile group: 'org.connid', name: 'framework', version: '1.3.2' + compile group: 'org.jetbrains', name: 'annotations', version: '23.1.0' + compile group: 'io.netty', name: 'netty-buffer', version: '4.1.27.Final' + compile group: 'io.vertx', name: 'vertx-core', version: '4.3.7' + compile group: 'org.immutables', name: 'value', version: '2.9.3' + compile group: 'org.immutables', name: 'value-annotations', version: '2.9.3' + compile group: 'org.rocksdb', name: 'rocksdbjni', version: '7.7.3' + + + testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.9.1' + testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.9.1' + testCompile group: 'org.mockito', name: 'mockito-junit-jupiter', version: '4.11.0' + testCompile group: 'junit', name: 'junit', version: '4.13.1' + testCompile group: 'org.assertj', name: 'assertj-core', version: '3.24.1' +} + +tasks.matching { it instanceof Test }.all { + testLogging.events = ["failed", "passed", "skipped"] +} + + +test { + testLogging { + exceptionFormat = 'full' + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/AbstractBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/AbstractBytes.java new file mode 100644 index 00000000000..9b7cfe5a83e --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/AbstractBytes.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +/** + * An abstract {@link Bytes} value that provides implementations of {@link #equals(Object)}, {@link #hashCode()} and + * {@link #toString()}. + */ +public abstract class AbstractBytes implements Bytes { + + static final String HEX_CODE_AS_STRING = "0123456789abcdef"; + static final char[] HEX_CODE = HEX_CODE_AS_STRING.toCharArray(); + + private Integer hashCode; + + /** + * Compare this value and the provided one for equality. + * + *

+ * Two {@link Bytes} values are equal is they have contain the exact same bytes. + * + * @param obj The object to test for equality with. + * @return {@code true} if this value and {@code obj} are equal. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes)) { + return false; + } + + Bytes other = (Bytes) obj; + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (this.get(i) != other.get(i)) { + return false; + } + } + + return true; + } + + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + get(i); + } + return result; + } + + @Override + public int hashCode() { + if (this.hashCode == null) { + this.hashCode = computeHashcode(); + } + return this.hashCode; + } + + @Override + public String toString() { + return toHexString(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes.java new file mode 100644 index 00000000000..fec9addaa4c --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.vertx.core.buffer.Buffer; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.Arrays; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +class ArrayWrappingBytes extends AbstractBytes { + + protected final byte[] bytes; + protected final int offset; + protected final int length; + + ArrayWrappingBytes(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + ArrayWrappingBytes(byte[] bytes, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + if (bytes.length > 0) { + checkElementIndex(offset, bytes.length); + } + checkArgument( + offset + length <= bytes.length, + "Provided length %s is too big: the value has only %s bytes from offset %s", + length, + bytes.length - offset, + offset); + + this.bytes = bytes; + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @Override + public byte get(int i) { + // Check bounds because while the array access would throw, the error message would be confusing + // for the caller. + checkElementIndex(i, size()); + return bytes[offset + i]; + } + + @Override + public Bytes slice(int i, int length) { + if (i == 0 && length == this.length) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + + return length == Bytes32.SIZE ? new ArrayWrappingBytes32(bytes, offset + i) + : new ArrayWrappingBytes(bytes, offset + i, length); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + if (offset == 0 && length == bytes.length) { + return this; + } + return new ArrayWrappingBytes(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return new MutableArrayWrappingBytes(toArray()); + } + + @Override + public int commonPrefixLength(Bytes other) { + if (!(other instanceof ArrayWrappingBytes)) { + return super.commonPrefixLength(other); + } + ArrayWrappingBytes o = (ArrayWrappingBytes) other; + int i = 0; + while (i < length && i < o.length && bytes[offset + i] == o.bytes[o.offset + i]) { + i++; + } + return i; + } + + @Override + public void update(MessageDigest digest) { + digest.update(bytes, offset, length); + } + + @Override + public void copyTo(MutableBytes destination, int destinationOffset) { + if (!(destination instanceof MutableArrayWrappingBytes)) { + super.copyTo(destination, destinationOffset); + return; + } + + int size = size(); + if (size == 0) { + return; + } + + checkElementIndex(destinationOffset, destination.size()); + checkArgument( + destination.size() - destinationOffset >= size, + "Cannot copy %s bytes, destination has only %s bytes from index %s", + size, + destination.size() - destinationOffset, + destinationOffset); + + MutableArrayWrappingBytes d = (MutableArrayWrappingBytes) destination; + System.arraycopy(bytes, offset, d.bytes, d.offset + destinationOffset, size); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + byteBuffer.put(bytes, offset, length); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBytes(bytes, offset, length); + } + + + @Override + public byte[] toArray() { + return Arrays.copyOfRange(bytes, offset, offset + length); + } + + @Override + public byte[] toArrayUnsafe() { + if (offset == 0 && length == bytes.length) { + return bytes; + } + return toArray(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes32.java new file mode 100644 index 00000000000..9c80382aeb6 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes32.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + +final class ArrayWrappingBytes32 extends ArrayWrappingBytes implements Bytes32 { + + ArrayWrappingBytes32(byte[] bytes) { + this(checkLength(bytes), 0); + } + + ArrayWrappingBytes32(byte[] bytes, int offset) { + super(checkLength(bytes, offset), offset, SIZE); + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes) { + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return bytes; + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes, int offset) { + checkArgument( + bytes.length - offset >= SIZE, + "Expected at least %s bytes from offset %s but got only %s", + SIZE, + offset, + bytes.length - offset); + return bytes; + } + + @Override + public Bytes32 copy() { + if (offset == 0 && length == bytes.length) { + return this; + } + return new ArrayWrappingBytes32(toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return new MutableArrayWrappingBytes32(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes48.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes48.java new file mode 100644 index 00000000000..d950af5caaa --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ArrayWrappingBytes48.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + +final class ArrayWrappingBytes48 extends ArrayWrappingBytes implements Bytes48 { + + ArrayWrappingBytes48(byte[] bytes) { + this(checkLength(bytes), 0); + } + + ArrayWrappingBytes48(byte[] bytes, int offset) { + super(checkLength(bytes, offset), offset, SIZE); + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes) { + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return bytes; + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes, int offset) { + checkArgument( + bytes.length - offset >= SIZE, + "Expected at least %s bytes from offset %s but got only %s", + SIZE, + offset, + bytes.length - offset); + return bytes; + } + + @Override + public Bytes48 copy() { + if (offset == 0 && length == bytes.length) { + return this; + } + return new ArrayWrappingBytes48(toArray()); + } + + @Override + public MutableBytes48 mutableCopy() { + return new MutableArrayWrappingBytes48(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/BufferWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/BufferWrappingBytes.java new file mode 100644 index 00000000000..0a8d84b3e5d --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/BufferWrappingBytes.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.vertx.core.buffer.Buffer; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +class BufferWrappingBytes extends AbstractBytes { + + protected final Buffer buffer; + + BufferWrappingBytes(Buffer buffer) { + this.buffer = buffer; + } + + BufferWrappingBytes(Buffer buffer, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = buffer.length(); + checkElementIndex(offset, bufferLength + 1); + checkArgument( + offset + length <= bufferLength, + "Provided length %s is too big: the buffer has size %s and has only %s bytes from %s", + length, + bufferLength, + bufferLength - offset, + offset); + + if (offset == 0 && length == bufferLength) { + this.buffer = buffer; + } else { + this.buffer = buffer.slice(offset, offset + length); + } + } + + @Override + public int size() { + return buffer.length(); + } + + @Override + public byte get(int i) { + return buffer.getByte(i); + } + + @Override + public int getInt(int i) { + return buffer.getInt(i); + } + + @Override + public long getLong(int i) { + return buffer.getLong(i); + } + + @Override + public Bytes slice(int i, int length) { + int size = buffer.length(); + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + i + length <= size, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + return new BufferWrappingBytes(buffer.slice(i, i + length)); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBuffer(this.buffer); + } + + @Override + public byte[] toArray() { + return buffer.getBytes(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufWrappingBytes.java new file mode 100644 index 00000000000..c63892bf1c8 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufWrappingBytes.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +class ByteBufWrappingBytes extends AbstractBytes { + + protected final ByteBuf byteBuf; + + ByteBufWrappingBytes(ByteBuf byteBuf) { + this.byteBuf = byteBuf; + } + + ByteBufWrappingBytes(ByteBuf byteBuf, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = byteBuf.capacity(); + checkElementIndex(offset, bufferLength + 1); + checkArgument( + offset + length <= bufferLength, + "Provided length %s is too big: the buffer has size %s and has only %s bytes from %s", + length, + bufferLength, + bufferLength - offset, + offset); + + if (offset == 0 && length == bufferLength) { + this.byteBuf = byteBuf; + } else { + this.byteBuf = byteBuf.slice(offset, length); + } + } + + @Override + public int size() { + return byteBuf.capacity(); + } + + @Override + public byte get(int i) { + return byteBuf.getByte(i); + } + + @Override + public int getInt(int i) { + return byteBuf.getInt(i); + } + + @Override + public long getLong(int i) { + return byteBuf.getLong(i); + } + + @Override + public Bytes slice(int i, int length) { + int size = byteBuf.capacity(); + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + i + length <= size, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + return new ByteBufWrappingBytes(byteBuf.slice(i, length)); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBuffer(Buffer.buffer(this.byteBuf)); + } + + @Override + public byte[] toArray() { + int size = byteBuf.capacity(); + byte[] array = new byte[size]; + byteBuf.getBytes(0, array); + return array; + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufferWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufferWrappingBytes.java new file mode 100644 index 00000000000..b2517537b15 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufferWrappingBytes.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +class ByteBufferWrappingBytes extends AbstractBytes { + + protected final ByteBuffer byteBuffer; + protected final int offset; + protected final int length; + + ByteBufferWrappingBytes(ByteBuffer byteBuffer) { + this(byteBuffer, 0, byteBuffer.limit()); + } + + ByteBufferWrappingBytes(ByteBuffer byteBuffer, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = byteBuffer.capacity(); + if (bufferLength > 0) { + checkElementIndex(offset, bufferLength); + } + checkArgument( + offset + length <= bufferLength, + "Provided length %s is too big: the value has only %s bytes from offset %s", + length, + bufferLength - offset, + offset); + + this.byteBuffer = byteBuffer; + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @Override + public int getInt(int i) { + return byteBuffer.getInt(offset + i); + } + + @Override + public long getLong(int i) { + return byteBuffer.getLong(offset + i); + } + + @Override + public byte get(int i) { + return byteBuffer.get(offset + i); + } + + @Override + public Bytes slice(int i, int length) { + if (i == 0 && length == this.length) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + + if (length == 32) { + return new ByteBufferWrappingBytes32(byteBuffer, offset + i, length); + } + + return new ByteBufferWrappingBytes(byteBuffer, offset + i, length); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + if (offset == 0 && length == byteBuffer.limit()) { + return this; + } + return new ArrayWrappingBytes(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return new MutableArrayWrappingBytes(toArray()); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + byteBuffer.put(this.byteBuffer); + } + + @Override + public byte[] toArray() { + if (!byteBuffer.hasArray()) { + return super.toArray(); + } + int arrayOffset = byteBuffer.arrayOffset(); + return Arrays.copyOfRange(byteBuffer.array(), arrayOffset + offset, arrayOffset + offset + length); + } + + @Override + public byte[] toArrayUnsafe() { + if (!byteBuffer.hasArray()) { + return toArray(); + } + byte[] array = byteBuffer.array(); + if (array.length != length || byteBuffer.arrayOffset() != 0) { + return toArray(); + } + return array; + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufferWrappingBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufferWrappingBytes32.java new file mode 100644 index 00000000000..d62b9e29d47 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ByteBufferWrappingBytes32.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.nio.ByteBuffer; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + +class ByteBufferWrappingBytes32 extends ByteBufferWrappingBytes implements Bytes32 { + + ByteBufferWrappingBytes32(ByteBuffer byteBuffer) { + this(byteBuffer, 0, byteBuffer.limit()); + } + + ByteBufferWrappingBytes32(ByteBuffer byteBuffer, int offset, int length) { + super(byteBuffer, offset, length); + checkArgument(length == SIZE, "Expected %s bytes but got %s", SIZE, length); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes32 copy() { + if (offset == 0 && length == byteBuffer.limit()) { + return this; + } + return new ArrayWrappingBytes32(toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return new MutableArrayWrappingBytes32(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes.java new file mode 100644 index 00000000000..9493c622673 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes.java @@ -0,0 +1,1706 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.math.BigInteger; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Random; + +import static java.lang.String.format; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static org.apache.tuweni.bytes.Checks.*; + +/** + * A value made of bytes. + * + *

+ * This interface makes no thread-safety guarantee, and a {@link Bytes} value is generally not thread safe. However, + * specific implementations may be thread-safe. For instance, the value returned by {@link #copy} is guaranteed to be + * thread-safe as it is immutable. + */ +public interface Bytes extends Comparable { + + /** + * The empty value (with 0 bytes). + */ + Bytes EMPTY = wrap(new byte[0]); + + /** + * Wrap the provided byte array as a {@link Bytes} value. + * + *

+ * Note that value is not copied and thus any future update to {@code value} will be reflected in the returned value. + * + * @param value The value to wrap. + * @return A {@link Bytes} value wrapping {@code value}. + */ + static Bytes wrap(byte[] value) { + return wrap(value, 0, value.length); + } + + /** + * Wrap a slice of a byte array as a {@link Bytes} value. + * + *

+ * Note that value is not copied and thus any future update to {@code value} within the slice will be reflected in the + * returned value. + * + * @param value The value to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, o, l).get(0) == value[o]}. + * @param length The length of the resulting value. + * @return A {@link Bytes} value that expose the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + length} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > value.length}. + */ + static Bytes wrap(byte[] value, int offset, int length) { + checkNotNull(value); + if (length == 32) { + return new ArrayWrappingBytes32(value, offset); + } + return new ArrayWrappingBytes(value, offset, length); + } + + /** + * Wrap the provided byte array as a {@link Bytes} value, encrypted in memory. + * + * + * @param value The value to secure. + * @return A {@link Bytes} value securing {@code value}. + */ + static Bytes secure(byte[] value) { + return secure(value, 0, value.length); + } + + /** + * Wrap a slice of a byte array as a {@link Bytes} value, encrypted in memory. + * + * + * @param value The value to secure. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, o, l).get(0) == value[o]}. + * @param length The length of the resulting value. + * @return A {@link Bytes} value that holds securely the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + length} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > value.length}. + */ + static Bytes secure(byte[] value, int offset, int length) { + checkNotNull(value); + return new GuardedByteArrayBytes(value, offset, length); + } + + /** + * Wrap a list of other values into a concatenated view. + * + *

+ * Note that the values are not copied and thus any future update to the values will be reflected in the returned + * value. If copying the inputs is desired, use {@link #concatenate(Bytes...)}. + * + * @param values The values to wrap. + * @return A value representing a view over the concatenation of all {@code values}. + * @throws IllegalArgumentException if the result overflows an int. + */ + static Bytes wrap(Bytes... values) { + return ConcatenatedBytes.wrap(values); + } + + /** + * Wrap a list of other values into a concatenated view. + * + *

+ * Note that the values are not copied and thus any future update to the values will be reflected in the returned + * value. If copying the inputs is desired, use {@link #concatenate(Bytes...)}. + * + * @param values The values to wrap. + * @return A value representing a view over the concatenation of all {@code values}. + * @throws IllegalArgumentException if the result overflows an int. + */ + static Bytes wrap(List values) { + return ConcatenatedBytes.wrap(values); + } + + /** + * Create a value containing the concatenation of the values provided. + * + * @param values The values to copy and concatenate. + * @return A value containing the result of concatenating the value from {@code values} in their provided order. + * @throws IllegalArgumentException if the result overflows an int. + */ + static Bytes concatenate(List values) { + if (values.size() == 0) { + return EMPTY; + } + + int size; + try { + size = values.stream().mapToInt(Bytes::size).reduce(0, Math::addExact); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Combined length of values is too long (> Integer.MAX_VALUE)"); + } + + MutableBytes result = MutableBytes.create(size); + int offset = 0; + for (Bytes value : values) { + value.copyTo(result, offset); + offset += value.size(); + } + return result; + } + + /** + * Create a value containing the concatenation of the values provided. + * + * @param values The values to copy and concatenate. + * @return A value containing the result of concatenating the value from {@code values} in their provided order. + * @throws IllegalArgumentException if the result overflows an int. + */ + static Bytes concatenate(Bytes... values) { + if (values.length == 0) { + return EMPTY; + } + + int size; + try { + size = Arrays.stream(values).mapToInt(Bytes::size).reduce(0, Math::addExact); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Combined length of values is too long (> Integer.MAX_VALUE)"); + } + + MutableBytes result = MutableBytes.create(size); + int offset = 0; + for (Bytes value : values) { + value.copyTo(result, offset); + offset += value.size(); + } + return result; + } + + /** + * Wrap a full Vert.x {@link Buffer} as a {@link Bytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param buffer The buffer to wrap. + * @return A {@link Bytes} value. + */ + static Bytes wrapBuffer(Buffer buffer) { + checkNotNull(buffer); + if (buffer.length() == 0) { + return EMPTY; + } + return new BufferWrappingBytes(buffer); + } + + /** + * Wrap a slice of a Vert.x {@link Buffer} as a {@link Bytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param buffer The buffer to wrap. + * @param offset The offset in {@code buffer} from which to expose the bytes in the returned value. That is, + * {@code wrapBuffer(buffer, i, 1).get(0) == buffer.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link Bytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (buffer.length() > 0 && offset >= + * buffer.length())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > buffer.length()}. + */ + static Bytes wrapBuffer(Buffer buffer, int offset, int size) { + checkNotNull(buffer); + if (size == 0) { + return EMPTY; + } + return new BufferWrappingBytes(buffer, offset, size); + } + + /** + * Wrap a full Netty {@link ByteBuf} as a {@link Bytes} value. + * + *

+ * Note that any change to the content of the byteBuf may be reflected in the returned value. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @return A {@link Bytes} value. + */ + static Bytes wrapByteBuf(ByteBuf byteBuf) { + checkNotNull(byteBuf); + if (byteBuf.capacity() == 0) { + return EMPTY; + } + return new ByteBufWrappingBytes(byteBuf); + } + + /** + * Wrap a slice of a Netty {@link ByteBuf} as a {@link Bytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @param offset The offset in {@code byteBuf} from which to expose the bytes in the returned value. That is, + * {@code wrapByteBuf(byteBuf, i, 1).get(0) == byteBuf.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link Bytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuf.capacity() > 0 && offset >= + * byteBuf.capacity())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuf.capacity()}. + */ + static Bytes wrapByteBuf(ByteBuf byteBuf, int offset, int size) { + checkNotNull(byteBuf); + if (size == 0) { + return EMPTY; + } + return new ByteBufWrappingBytes(byteBuf, offset, size); + } + + /** + * Wrap a full Java NIO {@link ByteBuffer} as a {@link Bytes} value. + * + *

+ * Note that any change to the content of the byteBuf may be reflected in the returned value. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @return A {@link Bytes} value. + */ + static Bytes wrapByteBuffer(ByteBuffer byteBuffer) { + checkNotNull(byteBuffer); + if (byteBuffer.limit() == 0) { + return EMPTY; + } + return new ByteBufferWrappingBytes(byteBuffer); + } + + /** + * Wrap a slice of a Java NIO {@link ByteBuf} as a {@link Bytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @param offset The offset in {@code byteBuffer} from which to expose the bytes in the returned value. That is, + * {@code wrapByteBuffer(byteBuffer, i, 1).get(0) == byteBuffer.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link Bytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuffer.limit() > 0 && offset >= + * byteBuf.limit())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuffer.limit()}. + */ + static Bytes wrapByteBuffer(ByteBuffer byteBuffer, int offset, int size) { + checkNotNull(byteBuffer); + if (size == 0) { + return EMPTY; + } + return new ByteBufferWrappingBytes(byteBuffer, offset, size); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes that must compose the returned value. + * @return A value containing the specified bytes. + */ + static Bytes of(byte... bytes) { + return wrap(bytes); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes. + * @return A value containing bytes are the one from {@code bytes}. + * @throws IllegalArgumentException if any of the specified would be truncated when storing as a byte. + */ + static Bytes of(int... bytes) { + byte[] result = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + checkArgument(b == (((byte) b) & 0xff), "%sth value %s does not fit a byte", i + 1, b); + result[i] = (byte) b; + } + return Bytes.wrap(result); + } + + /** + * Return a 2-byte value corresponding to the provided value interpreted as an unsigned short. + * + * @param value The value, which must be no larger than an unsigned short. + * @return A 2 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an unsigned 2-byte short + * (that is, if {@code value >= (1 << 16)}). + */ + static Bytes ofUnsignedShort(int value) { + return ofUnsignedShort(value, BIG_ENDIAN); + } + + /** + * Return a 2-byte value corresponding to the provided value interpreted as an unsigned short. + * + * @param value The value, which must be no larger than an unsigned short. + * @param order The byte-order for the integer encoding. + * @return A 2 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an unsigned 2-byte short + * (that is, if {@code value >= (1 << 16)}). + */ + static Bytes ofUnsignedShort(int value, ByteOrder order) { + checkArgument( + value >= 0 && value <= BytesValues.MAX_UNSIGNED_SHORT, + "Value %s cannot be represented as an unsigned short (it is negative or too big)", + value); + byte[] res = new byte[2]; + if (order == BIG_ENDIAN) { + res[0] = (byte) ((value >> 8) & 0xFF); + res[1] = (byte) (value & 0xFF); + } else { + res[0] = (byte) (value & 0xFF); + res[1] = (byte) ((value >> 8) & 0xFF); + } + return Bytes.wrap(res); + } + + /** + * Return a 4-byte value corresponding to the provided value interpreted as an unsigned int. + * + * @param value The value, which must be no larger than an unsigned int. + * @return A 4 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an unsigned 4-byte int + * (that is, if {@code value >= (1L << 32)}). + */ + static Bytes ofUnsignedInt(long value) { + return ofUnsignedInt(value, BIG_ENDIAN); + } + + /** + * Return a 4-byte value corresponding to the provided value interpreted as an unsigned int. + * + * @param value The value, which must be no larger than an unsigned int. + * @param order The byte-order for the integer encoding. + * @return A 4 bytes value corresponding to the encoded {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an unsigned 4-byte int + * (that is, if {@code value >= (1L << 32)}). + */ + static Bytes ofUnsignedInt(long value, ByteOrder order) { + checkArgument( + value >= 0 && value <= BytesValues.MAX_UNSIGNED_INT, + "Value %s cannot be represented as an unsigned int (it is negative or too big)", + value); + byte[] res = new byte[4]; + if (order == BIG_ENDIAN) { + res[0] = (byte) ((value >> 24) & 0xFF); + res[1] = (byte) ((value >> 16) & 0xFF); + res[2] = (byte) ((value >> 8) & 0xFF); + res[3] = (byte) ((value) & 0xFF); + } else { + res[0] = (byte) ((value) & 0xFF); + res[1] = (byte) ((value >> 8) & 0xFF); + res[2] = (byte) ((value >> 16) & 0xFF); + res[3] = (byte) ((value >> 24) & 0xFF); + } + return Bytes.wrap(res); + } + + /** + * Return an 8-byte value corresponding to the provided value interpreted as an unsigned long. + * + * @param value The value, which will be interpreted as an unsigned long. + * @return A 8 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an unsigned 8-byte int + * (that is, if {@code value >= (1L << 64)}). + */ + static Bytes ofUnsignedLong(long value) { + return ofUnsignedLong(value, BIG_ENDIAN); + } + + /** + * Return an 8-byte value corresponding to the provided value interpreted as an unsigned long. + * + * @param value The value, which will be interpreted as an unsigned long. + * @param order The byte-order for the integer encoding. + * @return A 8 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an unsigned 8-byte int + * (that is, if {@code value >= (1L << 64)}). + */ + static Bytes ofUnsignedLong(long value, ByteOrder order) { + byte[] res = new byte[8]; + if (order == BIG_ENDIAN) { + res[0] = (byte) ((value >> 56) & 0xFF); + res[1] = (byte) ((value >> 48) & 0xFF); + res[2] = (byte) ((value >> 40) & 0xFF); + res[3] = (byte) ((value >> 32) & 0xFF); + res[4] = (byte) ((value >> 24) & 0xFF); + res[5] = (byte) ((value >> 16) & 0xFF); + res[6] = (byte) ((value >> 8) & 0xFF); + res[7] = (byte) (value & 0xFF); + } else { + res[0] = (byte) ((value) & 0xFF); + res[1] = (byte) ((value >> 8) & 0xFF); + res[2] = (byte) ((value >> 16) & 0xFF); + res[3] = (byte) ((value >> 24) & 0xFF); + res[4] = (byte) ((value >> 32) & 0xFF); + res[5] = (byte) ((value >> 40) & 0xFF); + res[6] = (byte) ((value >> 48) & 0xFF); + res[7] = (byte) ((value >> 56) & 0xFF); + } + return Bytes.wrap(res); + } + + /** + * Return the smallest bytes value whose bytes correspond to the provided long. That is, the returned value may be of + * size less than 8 if the provided long has leading zero bytes. + * + * @param value The long from which to create the bytes value. + * @return The minimal bytes representation corresponding to {@code l}. + */ + static Bytes minimalBytes(long value) { + if (value == 0) { + return Bytes.EMPTY; + } + + int zeros = Long.numberOfLeadingZeros(value); + int resultBytes = 8 - (zeros / 8); + + byte[] result = new byte[resultBytes]; + int shift = 0; + for (int i = 0; i < resultBytes; i++) { + result[resultBytes - i - 1] = (byte) ((value >> shift) & 0xFF); + shift += 8; + } + return Bytes.wrap(result); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value. + * + *

+ * This method is lenient in that {@code str} may of an odd length, in which case it will behave exactly as if it had + * an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation. + */ + static Bytes fromHexStringLenient(CharSequence str) { + checkNotNull(str); + return BytesValues.fromHexString(str, -1, true); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value of the provided size. + * + *

+ * This method allows for {@code str} to have an odd length, in which case it will behave exactly as if it had an + * additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @param destinationSize The size of the returned value, which must be big enough to hold the bytes represented by + * {@code str}. If it is strictly bigger those bytes from {@code str}, the returned value will be left padded + * with zeros. + * @return A value of size {@code destinationSize} corresponding to {@code str} potentially left-padded. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, + * represents more bytes than {@code destinationSize} or {@code destinationSize < 0}. + */ + static Bytes fromHexStringLenient(CharSequence str, int destinationSize) { + checkNotNull(str); + checkArgument(destinationSize >= 0, "Invalid negative destination size %s", destinationSize); + return BytesValues.fromHexString(str, destinationSize, true); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value. + * + *

+ * This method requires that {@code str} have an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, or is of + * an odd length. + */ + static Bytes fromHexString(CharSequence str) { + checkNotNull(str); + return BytesValues.fromHexString(str, -1, false); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value. + * + *

+ * This method requires that {@code str} have an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @param destinationSize The size of the returned value, which must be big enough to hold the bytes represented by + * {@code str}. If it is strictly bigger those bytes from {@code str}, the returned value will be left padded + * with zeros. + * @return A value of size {@code destinationSize} corresponding to {@code str} potentially left-padded. + * @throws IllegalArgumentException if {@code str} does correspond to a valid hexadecimal representation, or is of an + * odd length. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, or is of + * an odd length, or represents more bytes than {@code destinationSize} or {@code destinationSize < 0}. + */ + static Bytes fromHexString(CharSequence str, int destinationSize) { + checkNotNull(str); + checkArgument(destinationSize >= 0, "Invalid negative destination size %s", destinationSize); + return BytesValues.fromHexString(str, destinationSize, false); + } + + /** + * Parse a base 64 string into a {@link Bytes} value. + * + * @param str The base 64 string to parse. + * @return The value corresponding to {@code str}. + */ + static Bytes fromBase64String(CharSequence str) { + return Bytes.wrap(Base64.getDecoder().decode(str.toString())); + } + + /** + * Generate random bytes. + * + * @param size The number of bytes to generate. + * @return A value containing the desired number of random bytes. + */ + static Bytes random(int size) { + return random(size, new SecureRandom()); + } + + /** + * Generate random bytes. + * + * @param size The number of bytes to generate. + * @param generator The generator for random bytes. + * @return A value containing the desired number of random bytes. + */ + static Bytes random(int size, Random generator) { + byte[] array = new byte[size]; + generator.nextBytes(array); + return Bytes.wrap(array); + } + + /** + * Generate a bytes object filled with the same byte. + * + * @param b the byte to fill the Bytes with + * @param size the size of the object + * @return a value filled with a fixed byte + */ + static Bytes repeat(byte b, int size) { + return new ConstantBytesValue(b, size); + } + + /** + * Splits a Bytes object into Bytes32 objects. If the last element is not exactly 32 bytes, it is right padded with + * zeros. + * + * @param bytes the bytes object to segment + * @return an array of Bytes32 objects + */ + static Bytes32[] segment(Bytes bytes) { + int segments = (int) Math.ceil(bytes.size() / 32.0); + Bytes32[] result = new Bytes32[segments]; + for (int i = 0; i < segments; i++) { + result[i] = Bytes32.rightPad(bytes.slice(i * 32, Math.min(32, bytes.size() - i * 32))); + } + return result; + } + + /** + * + * Provides the number of bytes this value represents. + * + * @return The number of bytes this value represents. + */ + int size(); + + /** + * Retrieve a byte in this value. + * + * @param i The index of the byte to fetch within the value (0-indexed). + * @return The byte at index {@code i} in this value. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()}. + */ + byte get(int i); + + /** + * Retrieve the 4 bytes starting at the provided index in this value as an integer. + * + * @param i The index from which to get the int, which must less than or equal to {@code size() - 4}. + * @return An integer whose value is the 4 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 4}. + */ + default int getInt(int i) { + return getInt(i, BIG_ENDIAN); + } + + /** + * Retrieve the 4 bytes starting at the provided index in this value as an integer. + * + * @param i The index from which to get the int, which must less than or equal to {@code size() - 4}. + * @param order The byte-order for decoding the integer. + * @return An integer whose value is the 4 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 4}. + */ + default int getInt(int i, ByteOrder order) { + int size = size(); + checkElementIndex(i, size); + if (i > (size - 4)) { + throw new IndexOutOfBoundsException( + format("Value of size %s has not enough bytes to read a 4 bytes int from index %s", size, i)); + } + + int value = 0; + if (order == BIG_ENDIAN) { + value |= ((int) get(i) & 0xFF) << 24; + value |= ((int) get(i + 1) & 0xFF) << 16; + value |= ((int) get(i + 2) & 0xFF) << 8; + value |= ((int) get(i + 3) & 0xFF); + } else { + value |= ((int) get(i + 3) & 0xFF) << 24; + value |= ((int) get(i + 2) & 0xFF) << 16; + value |= ((int) get(i + 1) & 0xFF) << 8; + value |= ((int) get(i) & 0xFF); + } + return value; + } + + /** + * The value corresponding to interpreting these bytes as an integer. + * + * @return An value corresponding to this value interpreted as an integer. + * @throws IllegalArgumentException if {@code size() > 4}. + */ + default int toInt() { + return toInt(BIG_ENDIAN); + } + + /** + * The value corresponding to interpreting these bytes as an integer. + * + * @param order The byte-order for decoding the integer. + * @return An value corresponding to this value interpreted as an integer. + * @throws IllegalArgumentException if {@code size() > 4}. + */ + default int toInt(ByteOrder order) { + int size = size(); + checkArgument(size <= 4, "Value of size %s has more than 4 bytes", size()); + if (size == 0) { + return 0; + } + if (order == BIG_ENDIAN) { + int i = size; + int value = ((int) get(--i) & 0xFF); + if (i == 0) { + return value; + } + value |= ((int) get(--i) & 0xFF) << 8; + if (i == 0) { + return value; + } + value |= ((int) get(--i) & 0xFF) << 16; + if (i == 0) { + return value; + } + return value | ((int) get(--i) & 0xFF) << 24; + } else { + int i = 0; + int value = ((int) get(i) & 0xFF); + if (++i == size) { + return value; + } + value |= ((int) get(i++) & 0xFF) << 8; + if (i == size) { + return value; + } + value |= ((int) get(i++) & 0xFF) << 16; + if (i == size) { + return value; + } + return value | ((int) get(i) & 0xFF) << 24; + } + } + + /** + * Whether this value contains no bytes. + * + * @return true if the value contains no bytes + */ + default boolean isEmpty() { + return size() == 0; + } + + /** + * Retrieves the 8 bytes starting at the provided index in this value as a long. + * + * @param i The index from which to get the long, which must less than or equal to {@code size() - 8}. + * @return A long whose value is the 8 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 8}. + */ + default long getLong(int i) { + return getLong(i, BIG_ENDIAN); + } + + /** + * Retrieves the 8 bytes starting at the provided index in this value as a long. + * + * @param i The index from which to get the long, which must less than or equal to {@code size() - 8}. + * @param order The byte-order for decoding the integer. + * @return A long whose value is the 8 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 8}. + */ + default long getLong(int i, ByteOrder order) { + int size = size(); + checkElementIndex(i, size); + if (i > (size - 8)) { + throw new IndexOutOfBoundsException( + format("Value of size %s has not enough bytes to read a 8 bytes long from index %s", size, i)); + } + + long value = 0; + if (order == BIG_ENDIAN) { + value |= ((long) get(i) & 0xFF) << 56; + value |= ((long) get(i + 1) & 0xFF) << 48; + value |= ((long) get(i + 2) & 0xFF) << 40; + value |= ((long) get(i + 3) & 0xFF) << 32; + value |= ((long) get(i + 4) & 0xFF) << 24; + value |= ((long) get(i + 5) & 0xFF) << 16; + value |= ((long) get(i + 6) & 0xFF) << 8; + value |= ((long) get(i + 7) & 0xFF); + } else { + value |= ((long) get(i + 7) & 0xFF) << 56; + value |= ((long) get(i + 6) & 0xFF) << 48; + value |= ((long) get(i + 5) & 0xFF) << 40; + value |= ((long) get(i + 4) & 0xFF) << 32; + value |= ((long) get(i + 3) & 0xFF) << 24; + value |= ((long) get(i + 2) & 0xFF) << 16; + value |= ((long) get(i + 1) & 0xFF) << 8; + value |= ((long) get(i) & 0xFF); + } + return value; + } + + /** + * The value corresponding to interpreting these bytes as a long. + * + * @return An value corresponding to this value interpreted as a long. + * @throws IllegalArgumentException if {@code size() > 8}. + */ + default long toLong() { + return toLong(BIG_ENDIAN); + } + + /** + * The value corresponding to interpreting these bytes as a long. + * + * @param order The byte-order for decoding the integer. + * @return An value corresponding to this value interpreted as a long. + * @throws IllegalArgumentException if {@code size() > 8}. + */ + default long toLong(ByteOrder order) { + int size = size(); + checkArgument(size <= 8, "Value of size %s has more than 8 bytes", size()); + if (size == 0) { + return 0; + } + if (order == BIG_ENDIAN) { + int i = size; + long value = ((long) get(--i) & 0xFF); + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 8; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 16; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 24; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 32; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 40; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 48; + if (i == 0) { + return value; + } + return value | ((long) get(--i) & 0xFF) << 56; + } else { + int i = 0; + long value = ((long) get(i) & 0xFF); + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 8; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 16; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 24; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 32; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 40; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 48; + if (++i == size) { + return value; + } + return value | ((long) get(i) & 0xFF) << 56; + } + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement signed integer. + */ + default BigInteger toBigInteger() { + return toBigInteger(BIG_ENDIAN); + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @param order The byte-order for decoding the integer. + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement signed integer. + */ + default BigInteger toBigInteger(ByteOrder order) { + if (size() == 0) { + return BigInteger.ZERO; + } + return new BigInteger((order == BIG_ENDIAN) ? toArrayUnsafe() : reverse().toArrayUnsafe()); + } + + /** + * The BigInteger corresponding to interpreting these bytes as an unsigned integer. + * + * @return A positive (or zero) {@link BigInteger} corresponding to interpreting these bytes as an unsigned integer. + */ + default BigInteger toUnsignedBigInteger() { + return toUnsignedBigInteger(BIG_ENDIAN); + } + + /** + * The BigInteger corresponding to interpreting these bytes as an unsigned integer. + * + * @param order The byte-order for decoding the integer. + * @return A positive (or zero) {@link BigInteger} corresponding to interpreting these bytes as an unsigned integer. + */ + default BigInteger toUnsignedBigInteger(ByteOrder order) { + return new BigInteger(1, (order == BIG_ENDIAN) ? toArrayUnsafe() : reverse().toArrayUnsafe()); + } + + /** + * Whether this value has only zero bytes. + * + * @return {@code true} if all the bits of this value are zeros. + */ + default boolean isZero() { + for (int i = size() - 1; i >= 0; --i) { + if (get(i) != 0) + return false; + } + return true; + } + + /** + * Whether the bytes start with a zero bit value. + * + * @return true if the first bit equals zero + */ + default boolean hasLeadingZero() { + return size() > 0 && (get(0) & 0x80) == 0; + } + + /** + * Provides the number of zero bits preceding the highest-order ("leftmost") one-bit, or {@code size() * 8} if all + * bits * are zero. + * + * @return The number of zero bits preceding the highest-order ("leftmost") one-bit, or {@code size() * 8} if all bits + * are zero. + */ + default int numberOfLeadingZeros() { + int size = size(); + for (int i = 0; i < size; i++) { + byte b = get(i); + if (b == 0) { + continue; + } + + return (i * 8) + Integer.numberOfLeadingZeros(b & 0xFF) - 3 * 8; + } + return size * 8; + } + + /** + * Whether the bytes start with a zero byte value. + * + * @return true if the first byte equals zero + */ + default boolean hasLeadingZeroByte() { + return size() > 0 && get(0) == 0; + } + + /** + * Provides the number of leading zero bytes of the value + * + * @return The number of leading zero bytes of the value. + */ + default int numberOfLeadingZeroBytes() { + int size = size(); + for (int i = 0; i < size; i++) { + if (get(i) != 0) { + return i; + } + } + return size; + } + + /** + * Provides the number of trailing zero bytes of the value. + * + * @return The number of trailing zero bytes of the value. + */ + default int numberOfTrailingZeroBytes() { + int size = size(); + for (int i = size; i >= 1; i--) { + if (get(i - 1) != 0) { + return size - i; + } + } + return size; + } + + /** + * Provides the number of bits following and including the highest-order ("leftmost") one-bit, or zero if all bits are + * zero. + * + * @return The number of bits following and including the highest-order ("leftmost") one-bit, or zero if all bits are + * zero. + */ + default int bitLength() { + int size = size(); + for (int i = 0; i < size; i++) { + byte b = get(i); + if (b == 0) + continue; + + return (size * 8) - (i * 8) - (Integer.numberOfLeadingZeros(b & 0xFF) - 3 * 8); + } + return 0; + } + + /** + * Return a bit-wise AND of these bytes and the supplied bytes. + * + * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise AND. + */ + default Bytes and(Bytes other) { + return and(other, MutableBytes.create(Math.max(size(), other.size()))); + } + + /** + * Calculate a bit-wise AND of these bytes and the supplied bytes. + * + *

+ * If this value or the supplied value are shorter in length than the output vector, then they will be zero-padded to + * the left. Likewise, if either this value or the supplied valid is longer in length than the output vector, then + * they will be truncated to the left. + * + * @param other The bytes to perform the operation with. + * @param result The mutable output vector for the result. + * @param The {@link MutableBytes} value type. + * @return The {@code result} output vector. + */ + default T and(Bytes other, T result) { + checkNotNull(other); + checkNotNull(result); + int rSize = result.size(); + int offsetSelf = rSize - size(); + int offsetOther = rSize - other.size(); + for (int i = 0; i < rSize; i++) { + byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); + byte b2 = (i < offsetOther) ? 0x00 : other.get(i - offsetOther); + result.set(i, (byte) (b1 & b2)); + } + return result; + } + + /** + * Return a bit-wise OR of these bytes and the supplied bytes. + * + *

+ * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise OR. + */ + default Bytes or(Bytes other) { + return or(other, MutableBytes.create(Math.max(size(), other.size()))); + } + + /** + * Calculate a bit-wise OR of these bytes and the supplied bytes. + * + *

+ * If this value or the supplied value are shorter in length than the output vector, then they will be zero-padded to + * the left. Likewise, if either this value or the supplied valid is longer in length than the output vector, then + * they will be truncated to the left. + * + * @param other The bytes to perform the operation with. + * @param result The mutable output vector for the result. + * @param The {@link MutableBytes} value type. + * @return The {@code result} output vector. + */ + default T or(Bytes other, T result) { + checkNotNull(other); + checkNotNull(result); + int rSize = result.size(); + int offsetSelf = rSize - size(); + int offsetOther = rSize - other.size(); + for (int i = 0; i < rSize; i++) { + byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); + byte b2 = (i < offsetOther) ? 0x00 : other.get(i - offsetOther); + result.set(i, (byte) (b1 | b2)); + } + return result; + } + + /** + * Return a bit-wise XOR of these bytes and the supplied bytes. + * + *

+ * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise XOR. + */ + default Bytes xor(Bytes other) { + return xor(other, MutableBytes.create(Math.max(size(), other.size()))); + } + + /** + * Calculate a bit-wise XOR of these bytes and the supplied bytes. + * + *

+ * If this value or the supplied value are shorter in length than the output vector, then they will be zero-padded to + * the left. Likewise, if either this value or the supplied valid is longer in length than the output vector, then + * they will be truncated to the left. + * + * @param other The bytes to perform the operation with. + * @param result The mutable output vector for the result. + * @param The {@link MutableBytes} value type. + * @return The {@code result} output vector. + */ + default T xor(Bytes other, T result) { + checkNotNull(other); + checkNotNull(result); + int rSize = result.size(); + int offsetSelf = rSize - size(); + int offsetOther = rSize - other.size(); + for (int i = 0; i < rSize; i++) { + byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); + byte b2 = (i < offsetOther) ? 0x00 : other.get(i - offsetOther); + result.set(i, (byte) (b1 ^ b2)); + } + return result; + } + + /** + * Return a bit-wise NOT of these bytes. + * + * @return The result of a bit-wise NOT. + */ + default Bytes not() { + return not(MutableBytes.create(size())); + } + + /** + * Calculate a bit-wise NOT of these bytes. + * + *

+ * If this value is shorter in length than the output vector, then it will be zero-padded to the left. Likewise, if + * this value is longer in length than the output vector, then it will be truncated to the left. + * + * @param result The mutable output vector for the result. + * @param The {@link MutableBytes} value type. + * @return The {@code result} output vector. + */ + default T not(T result) { + checkNotNull(result); + int rSize = result.size(); + int offsetSelf = rSize - size(); + for (int i = 0; i < rSize; i++) { + byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); + result.set(i, (byte) ~b1); + } + return result; + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + default Bytes shiftRight(int distance) { + return shiftRight(distance, MutableBytes.create(size())); + } + + /** + * Shift all bits in this value to the right. + * + *

+ * If this value is shorter in length than the output vector, then it will be zero-padded to the left. Likewise, if + * this value is longer in length than the output vector, then it will be truncated to the left (after shifting). + * + * @param distance The number of bits to shift by. + * @param result The mutable output vector for the result. + * @param The {@link MutableBytes} value type. + * @return The {@code result} output vector. + */ + default T shiftRight(int distance, T result) { + checkNotNull(result); + int rSize = result.size(); + int offsetSelf = rSize - size(); + + int d = distance / 8; + int s = distance % 8; + int resIdx = rSize - 1; + for (int i = rSize - 1 - d; i >= 0; i--) { + byte res; + if (i < offsetSelf) { + res = 0; + } else { + int selfIdx = i - offsetSelf; + int leftSide = (get(selfIdx) & 0xFF) >>> s; + int rightSide = (selfIdx == 0) ? 0 : get(selfIdx - 1) << (8 - s); + res = (byte) (leftSide | rightSide); + } + result.set(resIdx--, res); + } + for (; resIdx >= 0; resIdx--) { + result.set(resIdx, (byte) 0); + } + return result; + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + default Bytes shiftLeft(int distance) { + return shiftLeft(distance, MutableBytes.create(size())); + } + + /** + * Shift all bits in this value to the left. + * + *

+ * If this value is shorter in length than the output vector, then it will be zero-padded to the left. Likewise, if + * this value is longer in length than the output vector, then it will be truncated to the left. + * + * @param distance The number of bits to shift by. + * @param result The mutable output vector for the result. + * @param The {@link MutableBytes} value type. + * @return The {@code result} output vector. + */ + default T shiftLeft(int distance, T result) { + checkNotNull(result); + int size = size(); + int rSize = result.size(); + int offsetSelf = rSize - size; + + int d = distance / 8; + int s = distance % 8; + int resIdx = 0; + for (int i = d; i < rSize; i++) { + byte res; + if (i < offsetSelf) { + res = 0; + } else { + int selfIdx = i - offsetSelf; + int leftSide = get(selfIdx) << s; + int rightSide = (selfIdx == size - 1) ? 0 : (get(selfIdx + 1) & 0xFF) >>> (8 - s); + res = (byte) (leftSide | rightSide); + } + result.set(resIdx++, res); + } + for (; resIdx < rSize; resIdx++) { + result.set(resIdx, (byte) 0); + } + return result; + } + + /** + * Create a new value representing (a view of) a slice of the bytes of this value. + * + *

+ * Please note that the resulting slice is only a view and as such maintains a link to the underlying full value. So + * holding a reference to the returned slice may hold more memory than the slide represents. Use {@link #copy} on the + * returned slice if that is not what you want. + * + * @param i The start index for the slice. + * @return A new value providing a view over the bytes from index {@code i} (included) to the end. + * @throws IndexOutOfBoundsException if {@code i < 0}. + */ + default Bytes slice(int i) { + if (i == 0) { + return this; + } + int size = size(); + if (i >= size) { + return EMPTY; + } + return slice(i, size - i); + } + + /** + * Create a new value representing (a view of) a slice of the bytes of this value. + * + *

+ * Please note that the resulting slice is only a view and as such maintains a link to the underlying full value. So + * holding a reference to the returned slice may hold more memory than the slide represents. Use {@link #copy} on the + * returned slice if that is not what you want. + * + * @param i The start index for the slice. + * @param length The length of the resulting value. + * @return A new value providing a view over the bytes from index {@code i} (included) to {@code i + length} + * (excluded). + * @throws IllegalArgumentException if {@code length < 0}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()} or {i + length > size()} . + */ + Bytes slice(int i, int length); + + /** + * Return a value equivalent to this one but guaranteed to 1) be deeply immutable (i.e. the underlying value will be + * immutable) and 2) to not retain more bytes than exposed by the value. + * + * @return A value, equals to this one, but deeply immutable and that doesn't retain any "unreachable" bytes. For + * performance reasons, this is allowed to return this value however if it already fit those constraints. + */ + Bytes copy(); + + /** + * Return a new mutable value initialized with the content of this value. + * + * @return A mutable copy of this value. This will copy bytes, modifying the returned value will not modify + * this value. + */ + MutableBytes mutableCopy(); + + /** + * Copy the bytes of this value to the provided mutable one, which must have the same size. + * + * @param destination The mutable value to which to copy the bytes to, which must have the same size as this value. If + * you want to copy value where size differs, you should use {@link #slice} and/or + * {@link MutableBytes#mutableSlice} and apply the copy to the result. + * @throws IllegalArgumentException if {@code this.size() != destination.size()}. + */ + default void copyTo(MutableBytes destination) { + checkNotNull(destination); + checkArgument( + destination.size() == size(), + "Cannot copy %s bytes to destination of non-equal size %s", + size(), + destination.size()); + copyTo(destination, 0); + } + + /** + * Copy the bytes of this value to the provided mutable one from a particular offset. + * + *

+ * This is a (potentially slightly more efficient) shortcut for {@code + * copyTo(destination.mutableSlice(destinationOffset, this.size()))}. + * + * @param destination The mutable value to which to copy the bytes to, which must have enough bytes from + * {@code destinationOffset} for the copied value. + * @param destinationOffset The offset in {@code destination} at which the copy starts. + * @throws IllegalArgumentException if the destination doesn't have enough room, that is if {@code + * this.size() > (destination.size() - destinationOffset)}. + */ + default void copyTo(MutableBytes destination, int destinationOffset) { + checkNotNull(destination); + + // Special casing an empty source or the following checks might throw (even though we have + // nothing to copy anyway) and this gets inconvenient for generic methods using copyTo() as + // they may have to special case empty values because of this. As an example, + // concatenate(EMPTY, EMPTY) would need to be special cased without this. + int size = size(); + if (size == 0) { + return; + } + + checkElementIndex(destinationOffset, destination.size()); + checkArgument( + destination.size() - destinationOffset >= size, + "Cannot copy %s bytes, destination has only %s bytes from index %s", + size, + destination.size() - destinationOffset, + destinationOffset); + + destination.set(destinationOffset, this); + } + + /** + * Append the bytes of this value to the {@link ByteBuffer}. + * + * @param byteBuffer The {@link ByteBuffer} to which to append this value. + * @throws BufferOverflowException If the writer attempts to write more than the provided buffer can hold. + * @throws ReadOnlyBufferException If the provided buffer is read-only. + */ + default void appendTo(ByteBuffer byteBuffer) { + checkNotNull(byteBuffer); + for (int i = 0; i < size(); i++) { + byteBuffer.put(get(i)); + } + } + + /** + * Append the bytes of this value to the provided Vert.x {@link Buffer}. + * + *

+ * Note that since a Vert.x {@link Buffer} will grow as necessary, this method never fails. + * + * @param buffer The {@link Buffer} to which to append this value. + */ + default void appendTo(Buffer buffer) { + checkNotNull(buffer); + for (int i = 0; i < size(); i++) { + buffer.appendByte(get(i)); + } + } + + /** + * Append this value as a sequence of hexadecimal characters. + * + * @param appendable The appendable + * @param The appendable type. + * @return The appendable. + */ + default T appendHexTo(T appendable) { + try { + appendable.append(toFastHex(false)); + return appendable; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + default String toFastHex(boolean prefix) { + + int offset = prefix ? 2 : 0; + + int resultSize = (size() * 2) + offset; + + char[] result = new char[resultSize]; + + if (prefix) { + result[0] = '0'; + result[1] = 'x'; + } + + for (int i = 0; i < size(); i++) { + byte b = get(i); + int pos = i * 2; + result[pos + offset] = AbstractBytes.HEX_CODE_AS_STRING.charAt(b >> 4 & 15); + result[pos + offset + 1] = AbstractBytes.HEX_CODE_AS_STRING.charAt(b & 15); + } + + return new String(result); + + } + + /** + * Return the number of bytes in common between this set of bytes and another. + * + * @param other The bytes to compare to. + * @return The number of common bytes. + */ + default int commonPrefixLength(Bytes other) { + checkNotNull(other); + int ourSize = size(); + int otherSize = other.size(); + int i = 0; + while (i < ourSize && i < otherSize && get(i) == other.get(i)) { + i++; + } + return i; + } + + /** + * Return a slice over the common prefix between this set of bytes and another. + * + * @param other The bytes to compare to. + * @return A slice covering the common prefix. + */ + default Bytes commonPrefix(Bytes other) { + return slice(0, commonPrefixLength(other)); + } + + /** + * Return a slice of representing the same value but without any leading zero bytes. + * + * @return {@code value} if its left-most byte is non zero, or a slice that exclude any leading zero bytes. + */ + default Bytes trimLeadingZeros() { + int size = size(); + for (int i = 0; i < size; i++) { + if (get(i) != 0) { + return slice(i); + } + } + return Bytes.EMPTY; + } + + /** + * Return a slice of representing the same value but without any trailing zero bytes. + * + * @return {@code value} if its right-most byte is non zero, or a slice that exclude any trailing zero bytes. + */ + default Bytes trimTrailingZeros() { + int size = size(); + for (int i = size - 1; i >= 0; i--) { + if (get(i) != 0) { + return slice(0, i + 1); + } + } + return Bytes.EMPTY; + } + + /** + * Update the provided message digest with the bytes of this value. + * + * @param digest The digest to update. + */ + default void update(MessageDigest digest) { + checkNotNull(digest); + digest.update(toArrayUnsafe()); + } + + /** + * Computes the reverse array of bytes of the current bytes. + * + * @return a new Bytes value, containing the bytes in reverse order + */ + default Bytes reverse() { + byte[] reverse = new byte[size()]; + for (int i = 0; i < size(); i++) { + reverse[size() - i - 1] = get(i); + } + return Bytes.wrap(reverse); + } + + /** + * Extract the bytes of this value into a byte array. + * + * @return A byte array with the same content than this value. + */ + default byte[] toArray() { + return toArray(BIG_ENDIAN); + } + + /** + * Extract the bytes of this value into a byte array. + * + * @param byteOrder the byte order to apply : big endian or little endian + * @return A byte array with the same content than this value. + */ + default byte[] toArray(ByteOrder byteOrder) { + int size = size(); + byte[] array = new byte[size]; + if (byteOrder == BIG_ENDIAN) { + for (int i = 0; i < size; i++) { + array[i] = get(i); + } + } else { + for (int i = 0; i < size(); i++) { + array[size() - i - 1] = get(i); + } + } + return array; + } + + /** + * Get the bytes represented by this value as byte array. + * + *

+ * Contrarily to {@link #toArray()}, this may avoid allocating a new array and directly return the backing array of + * this value if said value is array backed and doing so is possible. As such, modifications to the returned array may + * or may not impact this value. As such, this method should be used with care and hence the "unsafe" moniker. + * + * @return A byte array with the same content than this value, which may or may not be the direct backing of this + * value. + */ + default byte[] toArrayUnsafe() { + return toArray(); + } + + /** + * Return the hexadecimal string representation of this value. + * + * @return The hexadecimal representation of this value, starting with "0x". + */ + @Override + String toString(); + + /** + * Provides this value represented as hexadecimal, starting with "0x". + * + * @return This value represented as hexadecimal, starting with "0x". + */ + default String toHexString() { + return toFastHex(true); + } + + /** + * Provides this value represented as hexadecimal, with no prefix + * + * @return This value represented as hexadecimal, with no prefix. + */ + default String toUnprefixedHexString() { + return toFastHex(false); + } + + default String toEllipsisHexString() { + int size = size(); + if (size < 6) { + return toHexString(); + } + char[] result = new char[12]; + result[0] = '0'; + result[1] = 'x'; + for (int i = 0; i < 2; i++) { + byte b = get(i); + int pos = (i * 2) + 2; + result[pos] = AbstractBytes.HEX_CODE_AS_STRING.charAt(b >> 4 & 15); + result[pos + 1] = AbstractBytes.HEX_CODE_AS_STRING.charAt(b & 15); + } + result[6] = '.'; + result[7] = '.'; + for (int i = 0; i < 2; i++) { + byte b = get(i + size - 2); + int pos = (i * 2) + 8; + result[pos] = AbstractBytes.HEX_CODE_AS_STRING.charAt(b >> 4 & 15); + result[pos + 1] = AbstractBytes.HEX_CODE_AS_STRING.charAt(b & 15); + } + return new String(result); + } + + /** + * Provides this value represented as a minimal hexadecimal string (without any leading zero) + * + * @return This value represented as a minimal hexadecimal string (without any leading zero). + */ + default String toShortHexString() { + String hex = toFastHex(false); + + int i = 0; + while (i < hex.length() && hex.charAt(i) == '0') { + i++; + } + return "0x" + hex.substring(i); + } + + /** + * Provides this value represented as a minimal hexadecimal string (without any leading zero, except if it's valued + * zero or empty, in which case it returns 0x0). + * + * @return This value represented as a minimal hexadecimal string (without any leading zero, except if it's valued + * zero or empty, in which case it returns 0x0). + */ + default String toQuantityHexString() { + if (Bytes.EMPTY.equals(this)) { + return "0x0"; + } + String hex = toFastHex(false); + + int i = 0; + while (i < hex.length() - 1 && hex.charAt(i) == '0') { + i++; + } + return "0x" + hex.substring(i); + } + + /** + * Provides this value represented as base 64 + * + * @return This value represented as base 64. + */ + default String toBase64String() { + return Base64.getEncoder().encodeToString(toArrayUnsafe()); + } + + @Override + default int compareTo(Bytes b) { + checkNotNull(b); + + int bitLength = bitLength(); + int sizeCmp = Integer.compare(bitLength, b.bitLength()); + if (sizeCmp != 0) { + return sizeCmp; + } + // same bitlength and is zeroes only, return 0. + if (bitLength == 0) { + return 0; + } + + for (int i = 0; i < size(); i++) { + int cmp = Integer.compare(get(i) & 0xff, b.get(i) & 0xff); + if (cmp != 0) { + return cmp; + } + } + return 0; + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes32.java new file mode 100644 index 00000000000..e4380b787e0 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes32.java @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.security.SecureRandom; +import java.util.Random; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkNotNull; + +/** + * A {@link Bytes} value that is guaranteed to contain exactly 32 bytes. + */ +public interface Bytes32 extends Bytes { + /** The number of bytes in this value - i.e. 32 */ + int SIZE = 32; + + /** A {@code Bytes32} containing all zero bytes */ + Bytes32 ZERO = Bytes32.repeat((byte) 0); + + /** + * Generate a bytes object filled with the same byte. + * + * @param b the byte to fill the Bytes with + * @return a value filled with a fixed byte + */ + static Bytes32 repeat(byte b) { + return new ConstantBytes32Value(b); + } + + /** + * Wrap the provided byte array, which must be of length 32, as a {@link Bytes32}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} will be reflected in the + * returned value. + * + * @param bytes The bytes to wrap. + * @return A {@link Bytes32} wrapping {@code value}. + * @throws IllegalArgumentException if {@code value.length != 32}. + */ + static Bytes32 wrap(byte[] bytes) { + checkNotNull(bytes); + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return wrap(bytes, 0); + } + + /** + * Wrap a slice/sub-part of the provided array as a {@link Bytes32}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} within the wrapped parts + * will be reflected in the returned value. + * + * @param bytes The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link Bytes32} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 32 > value.length}. + */ + static Bytes32 wrap(byte[] bytes, int offset) { + checkNotNull(bytes); + return new ArrayWrappingBytes32(bytes, offset); + } + + /** + * Secures the provided byte array, which must be of length 32, as a {@link Bytes32}. + * + * @param bytes The bytes to secure. + * @return A {@link Bytes32} securing {@code value}. + * @throws IllegalArgumentException if {@code value.length != 32}. + */ + static Bytes32 secure(byte[] bytes) { + checkNotNull(bytes); + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return secure(bytes, 0); + } + + /** + * Secures a slice/sub-part of the provided array as a {@link Bytes32}. + * + * @param bytes The bytes to secure. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link Bytes32} that holds securely the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 32 > value.length}. + */ + static Bytes32 secure(byte[] bytes, int offset) { + checkNotNull(bytes); + return new GuardedByteArrayBytes32(bytes, offset); + } + + /** + * Wrap a the provided value, which must be of size 32, as a {@link Bytes32}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} will be reflected in the + * returned value. + * + * @param value The bytes to wrap. + * @return A {@link Bytes32} that exposes the bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() != 32}. + */ + static Bytes32 wrap(Bytes value) { + checkNotNull(value); + if (value instanceof Bytes32) { + return (Bytes32) value; + } + checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); + return new DelegatingBytes32(value); + } + + /** + * Wrap a slice/sub-part of the provided value as a {@link Bytes32}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} within the wrapped parts + * will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value.get(i)}. + * @return A {@link Bytes32} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.size() > 0 && offset >= + * value.size())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 32 > value.size()}. + */ + static Bytes32 wrap(Bytes value, int offset) { + checkNotNull(value); + Bytes slice = value.slice(offset, Bytes32.SIZE); + if (slice instanceof Bytes32) { + return (Bytes32) slice; + } + return new DelegatingBytes32(slice); + } + + /** + * Left pad a {@link Bytes} value with a fill byte to create a {@link Bytes32}. + * + * @param value The bytes value pad. + * @param fill the byte to fill with + * @return A {@link Bytes32} that exposes the left-padded bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() > 32}. + */ + static Bytes32 leftPad(Bytes value, byte fill) { + checkNotNull(value); + if (value instanceof Bytes32) { + return (Bytes32) value; + } + checkArgument(value.size() <= SIZE, "Expected at most %s bytes but got %s", SIZE, value.size()); + MutableBytes32 result = MutableBytes32.create(); + result.fill(fill); + value.copyTo(result, SIZE - value.size()); + return result; + } + + /** + * Left pad a {@link Bytes} value with zero bytes to create a {@link Bytes32}. + * + * @param value The bytes value pad. + * @return A {@link Bytes32} that exposes the left-padded bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() > 32}. + */ + static Bytes32 leftPad(Bytes value) { + checkNotNull(value); + if (value instanceof Bytes32) { + return (Bytes32) value; + } + checkArgument(value.size() <= SIZE, "Expected at most %s bytes but got %s", SIZE, value.size()); + MutableBytes32 result = MutableBytes32.create(); + value.copyTo(result, SIZE - value.size()); + return result; + } + + /** + * Right pad a {@link Bytes} value with zero bytes to create a {@link Bytes32}. + * + * @param value The bytes value pad. + * @return A {@link Bytes32} that exposes the rightw-padded bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() > 32}. + */ + static Bytes32 rightPad(Bytes value) { + checkNotNull(value); + if (value instanceof Bytes32) { + return (Bytes32) value; + } + checkArgument(value.size() <= SIZE, "Expected at most %s bytes but got %s", SIZE, value.size()); + MutableBytes32 result = MutableBytes32.create(); + value.copyTo(result, 0); + return result; + } + + /** + * Parse a hexadecimal string into a {@link Bytes32}. + * + *

+ * This method is lenient in that {@code str} may of an odd length, in which case it will behave exactly as if it had + * an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 32 bytes, in which case the result is left padded with zeros (see {@link #fromHexStringStrict} if + * this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or + * contains more than 32 bytes. + */ + static Bytes32 fromHexStringLenient(CharSequence str) { + checkNotNull(str); + return wrap(BytesValues.fromRawHexString(str, SIZE, true)); + } + + /** + * Parse a hexadecimal string into a {@link Bytes32}. + * + *

+ * This method is strict in that {@code str} must of an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 32 bytes, in which case the result is left padded with zeros (see {@link #fromHexStringStrict} if + * this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, is of an + * odd length, or contains more than 32 bytes. + */ + static Bytes32 fromHexString(CharSequence str) { + checkNotNull(str); + return wrap(BytesValues.fromRawHexString(str, SIZE, false)); + } + + /** + * Generate random bytes. + * + * @return A value containing random bytes. + */ + static Bytes32 random() { + return random(new SecureRandom()); + } + + /** + * Generate random bytes. + * + * @param generator The generator for random bytes. + * @return A value containing random bytes. + */ + static Bytes32 random(Random generator) { + byte[] array = new byte[32]; + generator.nextBytes(array); + return wrap(array); + } + + /** + * Parse a hexadecimal string into a {@link Bytes32}. + * + *

+ * This method is extra strict in that {@code str} must of an even length and the provided representation must have + * exactly 32 bytes. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, is of an + * odd length or does not contain exactly 32 bytes. + */ + static Bytes32 fromHexStringStrict(CharSequence str) { + checkNotNull(str); + return wrap(BytesValues.fromRawHexString(str, -1, false)); + } + + @Override + default int size() { + return SIZE; + } + + /** + * Return a bit-wise AND of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise AND. + */ + default Bytes32 and(Bytes32 other) { + return and(other, MutableBytes32.create()); + } + + /** + * Return a bit-wise OR of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise OR. + */ + default Bytes32 or(Bytes32 other) { + return or(other, MutableBytes32.create()); + } + + /** + * Return a bit-wise XOR of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise XOR. + */ + default Bytes32 xor(Bytes32 other) { + return xor(other, MutableBytes32.create()); + } + + @Override + default Bytes32 not() { + return not(MutableBytes32.create()); + } + + @Override + default Bytes32 shiftRight(int distance) { + return shiftRight(distance, MutableBytes32.create()); + } + + @Override + default Bytes32 shiftLeft(int distance) { + return shiftLeft(distance, MutableBytes32.create()); + } + + @Override + Bytes32 copy(); + + @Override + MutableBytes32 mutableCopy(); +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes48.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes48.java new file mode 100644 index 00000000000..30d7c5a8e83 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Bytes48.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.security.SecureRandom; +import java.util.Random; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkNotNull; + +/** + * A {@link Bytes} value that is guaranteed to contain exactly 48 bytes. + */ +public interface Bytes48 extends Bytes { + /** The number of bytes in this value - i.e. 48 */ + int SIZE = 48; + + /** A {@code Bytes48} containing all zero bytes */ + Bytes48 ZERO = wrap(new byte[SIZE]); + + /** + * Wrap the provided byte array, which must be of length 48, as a {@link Bytes48}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} will be reflected in the + * returned value. + * + * @param bytes The bytes to wrap. + * @return A {@link Bytes48} wrapping {@code value}. + * @throws IllegalArgumentException if {@code value.length != 48}. + */ + static Bytes48 wrap(byte[] bytes) { + checkNotNull(bytes); + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return wrap(bytes, 0); + } + + /** + * Wrap a slice/sub-part of the provided array as a {@link Bytes48}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} within the wrapped parts + * will be reflected in the returned value. + * + * @param bytes The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link Bytes48} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 48} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 48 > value.length}. + */ + static Bytes48 wrap(byte[] bytes, int offset) { + checkNotNull(bytes); + return new ArrayWrappingBytes48(bytes, offset); + } + + /** + * Wrap a the provided value, which must be of size 48, as a {@link Bytes48}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} will be reflected in the + * returned value. + * + * @param value The bytes to wrap. + * @return A {@link Bytes48} that exposes the bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() != 48}. + */ + static Bytes48 wrap(Bytes value) { + checkNotNull(value); + if (value instanceof Bytes48) { + return (Bytes48) value; + } + checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); + return new DelegatingBytes48(value); + } + + /** + * Wrap a slice/sub-part of the provided value as a {@link Bytes48}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} within the wrapped parts + * will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value.get(i)}. + * @return A {@link Bytes48} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 48} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.size() > 0 && offset >= + * value.size())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 48 > value.size()}. + */ + static Bytes48 wrap(Bytes value, int offset) { + checkNotNull(value); + if (value instanceof Bytes48) { + return (Bytes48) value; + } + Bytes slice = value.slice(offset, Bytes48.SIZE); + if (slice instanceof Bytes48) { + return (Bytes48) slice; + } + return new DelegatingBytes48(Bytes48.wrap(slice)); + } + + /** + * Left pad a {@link Bytes} value with zero bytes to create a {@link Bytes48}. + * + * @param value The bytes value pad. + * @return A {@link Bytes48} that exposes the left-padded bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() > 48}. + */ + static Bytes48 leftPad(Bytes value) { + checkNotNull(value); + if (value instanceof Bytes48) { + return (Bytes48) value; + } + checkArgument(value.size() <= SIZE, "Expected at most %s bytes but got %s", SIZE, value.size()); + MutableBytes48 result = MutableBytes48.create(); + value.copyTo(result, SIZE - value.size()); + return result; + } + + + /** + * Right pad a {@link Bytes} value with zero bytes to create a {@link Bytes48}. + * + * @param value The bytes value pad. + * @return A {@link Bytes48} that exposes the rightw-padded bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() > 48}. + */ + static Bytes48 rightPad(Bytes value) { + checkNotNull(value); + if (value instanceof Bytes48) { + return (Bytes48) value; + } + checkArgument(value.size() <= SIZE, "Expected at most %s bytes but got %s", SIZE, value.size()); + MutableBytes48 result = MutableBytes48.create(); + value.copyTo(result, 0); + return result; + } + + /** + * Parse a hexadecimal string into a {@link Bytes48}. + * + *

+ * This method is lenient in that {@code str} may of an odd length, in which case it will behave exactly as if it had + * an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 48 bytes, in which case the result is left padded with zeros (see {@link #fromHexStringStrict} if + * this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or + * contains more than 48 bytes. + */ + static Bytes48 fromHexStringLenient(CharSequence str) { + checkNotNull(str); + return wrap(BytesValues.fromRawHexString(str, SIZE, true)); + } + + /** + * Parse a hexadecimal string into a {@link Bytes48}. + * + *

+ * This method is strict in that {@code str} must of an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 48 bytes, in which case the result is left padded with zeros (see {@link #fromHexStringStrict} if + * this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, is of an + * odd length, or contains more than 48 bytes. + */ + static Bytes48 fromHexString(CharSequence str) { + checkNotNull(str); + return wrap(BytesValues.fromRawHexString(str, SIZE, false)); + } + + /** + * Generate random bytes. + * + * @return A value containing random bytes. + */ + static Bytes48 random() { + return random(new SecureRandom()); + } + + /** + * Generate random bytes. + * + * @param generator The generator for random bytes. + * @return A value containing random bytes. + */ + static Bytes48 random(Random generator) { + byte[] array = new byte[48]; + generator.nextBytes(array); + return wrap(array); + } + + /** + * Parse a hexadecimal string into a {@link Bytes48}. + * + *

+ * This method is extra strict in that {@code str} must of an even length and the provided representation must have + * exactly 48 bytes. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, is of an + * odd length or does not contain exactly 48 bytes. + */ + static Bytes48 fromHexStringStrict(CharSequence str) { + checkNotNull(str); + return wrap(BytesValues.fromRawHexString(str, -1, false)); + } + + @Override + default int size() { + return SIZE; + } + + /** + * Return a bit-wise AND of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise AND. + */ + default Bytes48 and(Bytes48 other) { + return and(other, MutableBytes48.create()); + } + + /** + * Return a bit-wise OR of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise OR. + */ + default Bytes48 or(Bytes48 other) { + return or(other, MutableBytes48.create()); + } + + /** + * Return a bit-wise XOR of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise XOR. + */ + default Bytes48 xor(Bytes48 other) { + return xor(other, MutableBytes48.create()); + } + + @Override + default Bytes48 not() { + return not(MutableBytes48.create()); + } + + @Override + default Bytes48 shiftRight(int distance) { + return shiftRight(distance, MutableBytes48.create()); + } + + @Override + default Bytes48 shiftLeft(int distance) { + return shiftLeft(distance, MutableBytes48.create()); + } + + @Override + Bytes48 copy(); + + @Override + MutableBytes48 mutableCopy(); +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/BytesValues.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/BytesValues.java new file mode 100644 index 00000000000..5ff0948989b --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/BytesValues.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + +final class BytesValues { + private BytesValues() {} + + static final int MAX_UNSIGNED_SHORT = (1 << 16) - 1; + static final long MAX_UNSIGNED_INT = (1L << 32) - 1; + static final long MAX_UNSIGNED_LONG = Long.MAX_VALUE; + + static Bytes fromHexString(CharSequence str, int destSize, boolean lenient) { + return Bytes.wrap(fromRawHexString(str, destSize, lenient)); + } + + static byte[] fromRawHexString(CharSequence str, int destSize, boolean lenient) { + int len = str.length(); + CharSequence hex = str; + if (len >= 2 && str.charAt(0) == '0' && str.charAt(1) == 'x') { + hex = str.subSequence(2, len); + len -= 2; + } + + int idxShift = 0; + if ((len & 0x01) != 0) { + if (!lenient) { + throw new IllegalArgumentException("Invalid odd-length hex binary representation"); + } + + hex = "0" + hex; + len += 1; + idxShift = 1; + } + + int size = len >> 1; + if (destSize < 0) { + destSize = size; + } else { + checkArgument(size <= destSize, "Hex value is too large: expected at most %s bytes but got %s", destSize, size); + } + + byte[] out = new byte[destSize]; + + int destOffset = (destSize - size); + for (int i = destOffset, j = 0; j < len; i++) { + int h = Character.digit(hex.charAt(j), 16); + if (h == -1) { + throw new IllegalArgumentException( + String + .format( + "Illegal character '%c' found at index %d in hex binary representation", + hex.charAt(j), + j - idxShift)); + } + j++; + int l = Character.digit(hex.charAt(j), 16); + if (l == -1) { + throw new IllegalArgumentException( + String + .format( + "Illegal character '%c' found at index %d in hex binary representation", + hex.charAt(j), + j - idxShift)); + } + j++; + out[i] = (byte) ((h << 4) + l); + } + return out; + } + +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Checks.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Checks.java new file mode 100644 index 00000000000..ae6306c88ed --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/Checks.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import com.google.errorprone.annotations.FormatMethod; + +import javax.annotation.Nullable; + +public class Checks { + + static void checkNotNull(@Nullable Object object) { + if (object == null) { + throw new NullPointerException("argument cannot be null"); + } + } + + static void checkElementIndex(int index, int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("index is out of bounds"); + } + } + + @FormatMethod + static void checkArgument(boolean condition, String message, Object... args) { + if (!condition) { + throw new IllegalArgumentException(String.format(message, args)); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java new file mode 100644 index 00000000000..9b91fb2cf30 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConcatenatedBytes.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + + +import java.security.MessageDigest; +import java.util.List; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +final class ConcatenatedBytes extends AbstractBytes { + + private final Bytes[] values; + private final int size; + + private ConcatenatedBytes(Bytes[] values, int totalSize) { + this.values = values; + this.size = totalSize; + } + + static Bytes wrap(Bytes... values) { + if (values.length == 0) { + return EMPTY; + } + if (values.length == 1) { + return values[0]; + } + + int count = 0; + int totalSize = 0; + + for (Bytes value : values) { + int size = value.size(); + try { + totalSize = Math.addExact(totalSize, size); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Combined length of values is too long (> Integer.MAX_VALUE)"); + } + if (value instanceof ConcatenatedBytes) { + count += ((ConcatenatedBytes) value).values.length; + } else if (size != 0) { + count += 1; + } + } + + if (count == 0) { + return Bytes.EMPTY; + } + if (count == values.length) { + return new ConcatenatedBytes(values, totalSize); + } + + Bytes[] concatenated = new Bytes[count]; + int i = 0; + for (Bytes value : values) { + if (value instanceof ConcatenatedBytes) { + Bytes[] subvalues = ((ConcatenatedBytes) value).values; + System.arraycopy(subvalues, 0, concatenated, i, subvalues.length); + i += subvalues.length; + } else if (value.size() != 0) { + concatenated[i++] = value; + } + } + return new ConcatenatedBytes(concatenated, totalSize); + } + + static Bytes wrap(List values) { + if (values.size() == 0) { + return EMPTY; + } + if (values.size() == 1) { + return values.get(0); + } + + int count = 0; + int totalSize = 0; + + for (Bytes value : values) { + int size = value.size(); + try { + totalSize = Math.addExact(totalSize, size); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Combined length of values is too long (> Integer.MAX_VALUE)"); + } + if (value instanceof ConcatenatedBytes) { + count += ((ConcatenatedBytes) value).values.length; + } else if (size != 0) { + count += 1; + } + } + + if (count == 0) { + return Bytes.EMPTY; + } + if (count == values.size()) { + return new ConcatenatedBytes(values.toArray(new Bytes[0]), totalSize); + } + + Bytes[] concatenated = new Bytes[count]; + int i = 0; + for (Bytes value : values) { + if (value instanceof ConcatenatedBytes) { + Bytes[] subvalues = ((ConcatenatedBytes) value).values; + System.arraycopy(subvalues, 0, concatenated, i, subvalues.length); + i += subvalues.length; + } else if (value.size() != 0) { + concatenated[i++] = value; + } + } + return new ConcatenatedBytes(concatenated, totalSize); + } + + @Override + public int size() { + return size; + } + + @Override + public byte get(int i) { + checkElementIndex(i, size); + for (Bytes value : values) { + int vSize = value.size(); + if (i < vSize) { + return value.get(i); + } + i -= vSize; + } + throw new IllegalStateException("element sizes do not match total size"); + } + + @Override + public Bytes slice(int i, final int length) { + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + (i + length) <= size, + "Provided length %s is too large: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + int j = 0; + int vSize; + while (true) { + vSize = values[j].size(); + if (i < vSize) { + break; + } + i -= vSize; + ++j; + } + + if ((i + length) < vSize) { + return values[j].slice(i, length); + } + + int remaining = length - (vSize - i); + Bytes firstValue = this.values[j].slice(i); + int firstOffset = j; + + while (remaining > 0) { + if (++j >= this.values.length) { + throw new IllegalStateException("element sizes do not match total size"); + } + vSize = this.values[j].size(); + if (length < vSize + firstValue.size()) { + break; + } + remaining -= vSize; + } + + Bytes[] combined = new Bytes[j - firstOffset + 1]; + combined[0] = firstValue; + if (remaining > 0) { + if (combined.length > 2) { + System.arraycopy(this.values, firstOffset + 1, combined, 1, combined.length - 2); + } + combined[combined.length - 1] = this.values[j].slice(0, remaining); + } else if (combined.length > 1) { + System.arraycopy(this.values, firstOffset + 1, combined, 1, combined.length - 1); + } + return new ConcatenatedBytes(combined, length); + } + + @Override + public Bytes copy() { + return mutableCopy(); + } + + @Override + public MutableBytes mutableCopy() { + if (size == 0) { + return MutableBytes.EMPTY; + } + MutableBytes result = MutableBytes.create(size); + copyToUnchecked(result, 0); + return result; + } + + @Override + public void copyTo(MutableBytes destination, int destinationOffset) { + if (size == 0) { + return; + } + + checkElementIndex(destinationOffset, destination.size()); + checkArgument( + destination.size() - destinationOffset >= size, + "Cannot copy %s bytes, destination has only %s bytes from index %s", + size, + destination.size() - destinationOffset, + destinationOffset); + + copyToUnchecked(destination, destinationOffset); + } + + @Override + public void update(MessageDigest digest) { + for (Bytes value : values) { + value.update(digest); + } + } + + @Override + public byte[] toArray() { + if (size == 0) { + return new byte[0]; + } + + MutableBytes result = MutableBytes.create(size); + copyToUnchecked(result, 0); + return result.toArrayUnsafe(); + } + + private void copyToUnchecked(MutableBytes destination, int destinationOffset) { + int offset = 0; + for (Bytes value : values) { + int vSize = value.size(); + if ((offset + vSize) > size) { + throw new IllegalStateException("element sizes do not match total size"); + } + value.copyTo(destination, destinationOffset); + offset += vSize; + destinationOffset += vSize; + } + } + + @Override + public int hashCode() { + return computeHashcode(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConstantBytes32Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConstantBytes32Value.java new file mode 100644 index 00000000000..7a007e26773 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConstantBytes32Value.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.util.Arrays; + +/** + * A Bytes value with just one constant value throughout. Ideal to avoid allocating large byte arrays filled with the + * same byte. + */ +class ConstantBytes32Value extends AbstractBytes implements Bytes32 { + + private final byte value; + + public ConstantBytes32Value(byte b) { + this.value = b; + } + + @Override + public int size() { + return 32; + } + + @Override + public byte get(int i) { + return this.value; + } + + @Override + public Bytes slice(int i, int length) { + if (length == 32) { + return this; + } + return new ConstantBytesValue(this.value, length); + } + + @Override + public Bytes32 copy() { + return new ConstantBytes32Value(this.value); + } + + @Override + public MutableBytes32 mutableCopy() { + byte[] mutable = new byte[32]; + Arrays.fill(mutable, this.value); + return new MutableArrayWrappingBytes32(mutable); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConstantBytesValue.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConstantBytesValue.java new file mode 100644 index 00000000000..5578687e165 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/ConstantBytesValue.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.util.Arrays; + +/** + * A Bytes value with just one constant value throughout. Ideal to avoid allocating large byte arrays filled with the + * same byte. + */ +class ConstantBytesValue extends AbstractBytes { + + private final int size; + private final byte value; + + public ConstantBytesValue(byte b, int size) { + this.value = b; + this.size = size; + } + + @Override + public int size() { + return this.size; + } + + @Override + public byte get(int i) { + return this.value; + } + + @Override + public Bytes slice(int i, int length) { + return new ConstantBytesValue(this.value, length); + } + + @Override + public Bytes copy() { + return new ConstantBytesValue(this.value, this.size); + } + + @Override + public MutableBytes mutableCopy() { + byte[] mutable = new byte[this.size]; + Arrays.fill(mutable, this.value); + return new MutableArrayWrappingBytes(mutable); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes.java new file mode 100644 index 00000000000..472cb40cceb --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes.java @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + + +import io.vertx.core.buffer.Buffer; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; + +/** + * A class that holds and delegates all operations to its inner bytes field. + * + *

+ * This class may be used to create more types that represent bytes, but need a different name for business logic. + */ +public class DelegatingBytes extends AbstractBytes implements Bytes { + + final Bytes delegate; + + protected DelegatingBytes(Bytes delegate) { + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public byte get(int i) { + return delegate.get(i); + } + + @Override + public Bytes slice(int index, int length) { + return delegate.slice(index, length); + } + + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public byte[] toArray() { + return delegate.toArray(); + } + + @Override + public byte[] toArray(ByteOrder byteOrder) { + return delegate.toArray(byteOrder); + } + + @Override + public byte[] toArrayUnsafe() { + return delegate.toArrayUnsafe(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public int getInt(int i) { + return delegate.getInt(i); + } + + @Override + public int getInt(int i, ByteOrder order) { + return delegate.getInt(i, order); + } + + @Override + public int toInt() { + return delegate.toInt(); + } + + @Override + public int toInt(ByteOrder order) { + return delegate.toInt(order); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public long getLong(int i) { + return delegate.getLong(i); + } + + @Override + public long getLong(int i, ByteOrder order) { + return delegate.getLong(i, order); + } + + @Override + public long toLong() { + return delegate.toLong(); + } + + @Override + public long toLong(ByteOrder order) { + return delegate.toLong(order); + } + + @Override + public BigInteger toBigInteger() { + return delegate.toBigInteger(); + } + + @Override + public BigInteger toBigInteger(ByteOrder order) { + return delegate.toBigInteger(order); + } + + @Override + public BigInteger toUnsignedBigInteger() { + return delegate.toUnsignedBigInteger(); + } + + @Override + public BigInteger toUnsignedBigInteger(ByteOrder order) { + return delegate.toUnsignedBigInteger(order); + } + + @Override + public boolean isZero() { + return delegate.isZero(); + } + + @Override + public boolean hasLeadingZero() { + return delegate.hasLeadingZero(); + } + + @Override + public int numberOfLeadingZeros() { + return delegate.numberOfLeadingZeros(); + } + + @Override + public boolean hasLeadingZeroByte() { + return delegate.hasLeadingZeroByte(); + } + + @Override + public int numberOfLeadingZeroBytes() { + return delegate.numberOfLeadingZeroBytes(); + } + + @Override + public int numberOfTrailingZeroBytes() { + return delegate.numberOfTrailingZeroBytes(); + } + + @Override + public int bitLength() { + return delegate.bitLength(); + } + + @Override + public Bytes and(Bytes other) { + return delegate.and(other); + } + + @Override + public T and(Bytes other, T result) { + return delegate.and(other, result); + } + + @Override + public Bytes or(Bytes other) { + return delegate.or(other); + } + + @Override + public T or(Bytes other, T result) { + return delegate.or(other, result); + } + + @Override + public Bytes xor(Bytes other) { + return delegate.xor(other); + } + + @Override + public T xor(Bytes other, T result) { + return delegate.xor(other, result); + } + + @Override + public T shiftRight(int distance, T result) { + return delegate.shiftRight(distance, result); + } + + @Override + public T shiftLeft(int distance, T result) { + return delegate.shiftLeft(distance, result); + } + + @Override + public Bytes slice(int i) { + return delegate.slice(i); + } + + @Override + public void copyTo(MutableBytes destination) { + delegate.copyTo(destination); + } + + @Override + public void copyTo(MutableBytes destination, int destinationOffset) { + delegate.copyTo(destination, destinationOffset); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + delegate.appendTo(byteBuffer); + } + + @Override + public void appendTo(Buffer buffer) { + delegate.appendTo(buffer); + } + + @Override + public T appendHexTo(T appendable) { + return delegate.appendHexTo(appendable); + } + + @Override + public int commonPrefixLength(Bytes other) { + return delegate.commonPrefixLength(other); + } + + @Override + public Bytes commonPrefix(Bytes other) { + return delegate.commonPrefix(other); + } + + @Override + public Bytes trimLeadingZeros() { + return delegate.trimLeadingZeros(); + } + + @Override + public void update(MessageDigest digest) { + delegate.update(digest); + } + + @Override + public Bytes reverse() { + return delegate.reverse(); + } + + @Override + public String toHexString() { + return delegate.toHexString(); + } + + @Override + public String toUnprefixedHexString() { + return delegate.toUnprefixedHexString(); + } + + @Override + public String toEllipsisHexString() { + return delegate.toEllipsisHexString(); + } + + @Override + public String toShortHexString() { + return delegate.toShortHexString(); + } + + @Override + public String toQuantityHexString() { + return delegate.toQuantityHexString(); + } + + @Override + public String toBase64String() { + return delegate.toBase64String(); + } + + @Override + public int compareTo(Bytes b) { + return delegate.compareTo(b); + } + + @Override + public boolean equals(final Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java new file mode 100644 index 00000000000..14832cb6f78 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + + + +/** + * A class that holds and delegates all operations to its inner bytes field. + * + *

+ * This class may be used to create more types that represent 32 bytes, but need a different name for business logic. + */ +public class DelegatingBytes32 extends DelegatingBytes implements Bytes32 { + + protected DelegatingBytes32(Bytes delegate) { + super(delegate); + } + + @Override + public int size() { + return Bytes32.SIZE; + } + + @Override + public Bytes32 copy() { + return Bytes32.wrap(toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return MutableBytes32.wrap(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java new file mode 100644 index 00000000000..de4b295ae60 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + + + +/** + * A class that holds and delegates all operations to its inner bytes field. + * + *

+ * This class may be used to create more types that represent 48 bytes, but need a different name for business logic. + */ +public class DelegatingBytes48 extends DelegatingBytes implements Bytes48 { + + protected DelegatingBytes48(Bytes delegate) { + super(delegate); + } + + @Override + public int size() { + return Bytes48.SIZE; + } + + @Override + public Bytes48 copy() { + return Bytes48.wrap(toArray()); + } + + @Override + public MutableBytes48 mutableCopy() { + return MutableBytes48.wrap(toArray()); + } + +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes.java new file mode 100644 index 00000000000..4f5d2786937 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + + +import io.vertx.core.buffer.Buffer; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; + + +/** + * A class that holds and delegates all operations to its inner bytes field. + * + *

+ * This class may be used to create more types that represent bytes, but need a different name for business logic. + */ +public class DelegatingMutableBytes implements MutableBytes { + + final MutableBytes delegate; + + protected DelegatingMutableBytes(MutableBytes delegate) { + this.delegate = delegate; + } + + @Override + public void set(int i, byte b) { + delegate.set(i, b); + } + + @Override + public void set(int i, Bytes b) { + delegate.set(i, b); + } + + @Override + public void setInt(int i, int value) { + delegate.setInt(i, value); + } + + @Override + public void setLong(int i, long value) { + delegate.setLong(i, value); + } + + @Override + public MutableBytes increment() { + return delegate.increment(); + } + + @Override + public MutableBytes decrement() { + return delegate.decrement(); + } + + @Override + public MutableBytes mutableSlice(int i, int length) { + return delegate.mutableSlice(i, length); + } + + @Override + public void fill(byte b) { + delegate.fill(b); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public byte get(int i) { + return delegate.get(i); + } + + @Override + public int getInt(int i) { + return delegate.getInt(i); + } + + @Override + public int toInt() { + return delegate.toInt(); + } + + @Override + public long getLong(int i) { + return delegate.getLong(i); + } + + @Override + public long toLong() { + return delegate.toLong(); + } + + @Override + public BigInteger toBigInteger() { + return delegate.toBigInteger(); + } + + @Override + public BigInteger toUnsignedBigInteger() { + return delegate.toUnsignedBigInteger(); + } + + @Override + public boolean isZero() { + return delegate.isZero(); + } + + @Override + public int numberOfLeadingZeros() { + return delegate.numberOfLeadingZeros(); + } + + @Override + public int numberOfLeadingZeroBytes() { + return delegate.numberOfLeadingZeroBytes(); + } + + @Override + public boolean hasLeadingZeroByte() { + return delegate.hasLeadingZeroByte(); + } + + @Override + public boolean hasLeadingZero() { + return delegate.hasLeadingZero(); + } + + @Override + public int bitLength() { + return delegate.bitLength(); + } + + @Override + public Bytes and(Bytes other) { + return delegate.and(other); + } + + @Override + public T and(Bytes other, T result) { + return delegate.and(other, result); + } + + @Override + public Bytes or(Bytes other) { + return delegate.or(other); + } + + @Override + public T or(Bytes other, T result) { + return delegate.or(other, result); + } + + @Override + public Bytes xor(Bytes other) { + return delegate.xor(other); + } + + @Override + public T xor(Bytes other, T result) { + return delegate.xor(other, result); + } + + @Override + public T not(T result) { + return delegate.not(result); + } + + @Override + public T shiftRight(int distance, T result) { + return delegate.shiftRight(distance, result); + } + + @Override + public T shiftLeft(int distance, T result) { + return delegate.shiftLeft(distance, result); + } + + @Override + public Bytes slice(int index) { + return delegate.slice(index); + } + + @Override + public Bytes slice(int index, int length) { + return delegate.slice(index, length); + } + + @Override + public Bytes copy() { + return Bytes.wrap(delegate.toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(delegate.toArray()); + } + + @Override + public void copyTo(MutableBytes destination) { + delegate.copyTo(destination); + } + + @Override + public void copyTo(MutableBytes destination, int destinationOffset) { + delegate.copyTo(destination, destinationOffset); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + delegate.appendTo(byteBuffer); + } + + @Override + public void appendTo(Buffer buffer) { + delegate.appendTo(buffer); + } + + @Override + public int commonPrefixLength(Bytes other) { + return delegate.commonPrefixLength(other); + } + + @Override + public Bytes commonPrefix(Bytes other) { + return delegate.commonPrefix(other); + } + + @Override + public void update(MessageDigest digest) { + delegate.update(digest); + } + + @Override + public byte[] toArray() { + return delegate.toArray(); + } + + @Override + public byte[] toArrayUnsafe() { + return delegate.toArrayUnsafe(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public String toHexString() { + return delegate.toHexString(); + } + + @Override + public String toShortHexString() { + return delegate.toShortHexString(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes32.java new file mode 100644 index 00000000000..009b719a2c3 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes32.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + + +final class DelegatingMutableBytes32 extends DelegatingMutableBytes implements MutableBytes32 { + + private DelegatingMutableBytes32(MutableBytes delegate) { + super(delegate); + } + + static MutableBytes32 delegateTo(MutableBytes value) { + checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); + return new DelegatingMutableBytes32(value); + } + + @Override + public Bytes32 copy() { + return Bytes32.wrap(delegate.toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return MutableBytes32.wrap(delegate.toArray()); + } + +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes48.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes48.java new file mode 100644 index 00000000000..faf84c16930 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/DelegatingMutableBytes48.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + + +final class DelegatingMutableBytes48 extends DelegatingMutableBytes implements MutableBytes48 { + + private DelegatingMutableBytes48(MutableBytes delegate) { + super(delegate); + } + + static MutableBytes48 delegateTo(MutableBytes value) { + checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); + return new DelegatingMutableBytes48(value); + } + + @Override + public Bytes48 copy() { + return Bytes48.wrap(delegate.toArray()); + } + + @Override + public MutableBytes48 mutableCopy() { + return MutableBytes48.wrap(delegate.toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/GuardedByteArrayBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/GuardedByteArrayBytes.java new file mode 100644 index 00000000000..8a05703030c --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/GuardedByteArrayBytes.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.vertx.core.buffer.Buffer; +import org.identityconnectors.common.security.GuardedByteArray; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +class GuardedByteArrayBytes extends AbstractBytes { + + protected final GuardedByteArray bytes; + protected final int offset; + protected final int length; + + GuardedByteArrayBytes(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + GuardedByteArrayBytes(byte[] bytes, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + if (bytes.length > 0) { + checkElementIndex(offset, bytes.length); + } + checkArgument( + offset + length <= bytes.length, + "Provided length %s is too big: the value has only %s bytes from offset %s", + length, + bytes.length - offset, + offset); + + this.bytes = new GuardedByteArray(bytes); + this.bytes.makeReadOnly(); + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @Override + public byte get(int i) { + // Check bounds because while the array access would throw, the error message would be confusing + // for the caller. + checkElementIndex(i, size()); + AtomicReference b = new AtomicReference<>(); + bytes.access(bytes -> b.set(bytes[offset + i])); + return b.get(); + } + + @Override + public Bytes slice(int i, int length) { + if (i == 0 && length == this.length) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + AtomicReference clearBytes = new AtomicReference<>(); + bytes.access(data -> { + byte[] result = new byte[length]; + System.arraycopy(data, offset + i, result, 0, length); + clearBytes.set(result); + }); + + return length == Bytes32.SIZE ? new ArrayWrappingBytes32(clearBytes.get()) + : new ArrayWrappingBytes(clearBytes.get(), 0, length); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + return new ArrayWrappingBytes(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return new MutableArrayWrappingBytes(toArray()); + } + + @Override + public void update(MessageDigest digest) { + digest.update(toArray(), offset, length); + } + + @Override + public void copyTo(MutableBytes destination, int destinationOffset) { + if (!(destination instanceof MutableArrayWrappingBytes)) { + super.copyTo(destination, destinationOffset); + return; + } + + int size = size(); + if (size == 0) { + return; + } + + checkElementIndex(destinationOffset, destination.size()); + checkArgument( + destination.size() - destinationOffset >= size, + "Cannot copy %s bytes, destination has only %s bytes from index %s", + size, + destination.size() - destinationOffset, + destinationOffset); + + MutableArrayWrappingBytes d = (MutableArrayWrappingBytes) destination; + System.arraycopy(toArray(), offset, d.bytes, d.offset + destinationOffset, size); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + byteBuffer.put(toArray(), offset, length); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBytes(toArray(), offset, length); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof GuardedByteArrayBytes)) { + return super.equals(obj); + } + GuardedByteArrayBytes other = (GuardedByteArrayBytes) obj; + if (length != other.length) { + return false; + } + for (int i = 0; i < length; ++i) { + if (get(offset + i) != other.get(other.offset + i)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int result = 1; + int size = size(); + for (int i = 0; i < size; i++) { + result = 31 * result + get(offset + i); + } + return result; + } + + @Override + public byte[] toArray() { + AtomicReference clearBytes = new AtomicReference<>(); + bytes.access(data -> { + byte[] result = new byte[length]; + System.arraycopy(data, offset, result, 0, length); + clearBytes.set(result); + }); + return clearBytes.get(); + } + + @Override + public byte[] toArrayUnsafe() { + return toArray(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/GuardedByteArrayBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/GuardedByteArrayBytes32.java new file mode 100644 index 00000000000..8c0aee7b6c5 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/GuardedByteArrayBytes32.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkArgument; + +final class GuardedByteArrayBytes32 extends GuardedByteArrayBytes implements Bytes32 { + + GuardedByteArrayBytes32(byte[] bytes) { + this(checkLength(bytes), 0); + } + + GuardedByteArrayBytes32(byte[] bytes, int offset) { + super(checkLength(bytes, offset), offset, SIZE); + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes) { + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return bytes; + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes, int offset) { + checkArgument( + bytes.length - offset >= SIZE, + "Expected at least %s bytes from offset %s but got only %s", + SIZE, + offset, + bytes.length - offset); + return bytes; + } + + @Override + public Bytes32 copy() { + return new ArrayWrappingBytes32(toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return new MutableArrayWrappingBytes32(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes.java new file mode 100644 index 00000000000..48eb1b6a90f --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.util.Arrays; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +class MutableArrayWrappingBytes extends ArrayWrappingBytes implements MutableBytes { + + MutableArrayWrappingBytes(byte[] bytes) { + super(bytes); + } + + MutableArrayWrappingBytes(byte[] bytes, int offset, int length) { + super(bytes, offset, length); + } + + @Override + public void set(int i, byte b) { + // Check bounds because while the array access would throw, the error message would be confusing + // for the caller. + checkElementIndex(i, length); + bytes[offset + i] = b; + } + + @Override + public void set(int i, Bytes b) { + byte[] bytesArray = b.toArrayUnsafe(); + System.arraycopy(bytesArray, 0, bytes, offset + i, bytesArray.length); + } + + @Override + public MutableBytes increment() { + for (int i = length - 1; i >= offset; --i) { + if (bytes[i] == (byte) 0xFF) { + bytes[i] = (byte) 0x00; + } else { + ++bytes[i]; + break; + } + } + return this; + } + + @Override + public MutableBytes decrement() { + for (int i = length - 1; i >= offset; --i) { + if (bytes[i] == (byte) 0x00) { + bytes[i] = (byte) 0xFF; + } else { + --bytes[i]; + break; + } + } + return this; + } + + @Override + public MutableBytes mutableSlice(int i, int length) { + if (i == 0 && length == this.length) + return this; + if (length == 0) + return MutableBytes.EMPTY; + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Specified length %s is too large: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + return length == Bytes32.SIZE ? new MutableArrayWrappingBytes32(bytes, offset + i) + : new MutableArrayWrappingBytes(bytes, offset + i, length); + } + + @Override + public void fill(byte b) { + Arrays.fill(bytes, offset, offset + length, b); + } + + @Override + public Bytes copy() { + return new ArrayWrappingBytes(toArray()); + } + + @Override + public int hashCode() { + return computeHashcode(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes32.java new file mode 100644 index 00000000000..0cf5096287a --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes32.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +final class MutableArrayWrappingBytes32 extends MutableArrayWrappingBytes implements MutableBytes32 { + + MutableArrayWrappingBytes32(byte[] bytes) { + this(bytes, 0); + } + + MutableArrayWrappingBytes32(byte[] bytes, int offset) { + super(bytes, offset, SIZE); + } + + @Override + public Bytes32 copy() { + return new ArrayWrappingBytes32(toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return new MutableArrayWrappingBytes32(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes48.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes48.java new file mode 100644 index 00000000000..25bbaeb9865 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableArrayWrappingBytes48.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +final class MutableArrayWrappingBytes48 extends MutableArrayWrappingBytes implements MutableBytes48 { + + MutableArrayWrappingBytes48(byte[] bytes) { + this(bytes, 0); + } + + MutableArrayWrappingBytes48(byte[] bytes, int offset) { + super(bytes, offset, SIZE); + } + + @Override + public Bytes48 copy() { + return new ArrayWrappingBytes48(toArray()); + } + + @Override + public MutableBytes48 mutableCopy() { + return new MutableArrayWrappingBytes48(toArray()); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBufferWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBufferWrappingBytes.java new file mode 100644 index 00000000000..63d79296496 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBufferWrappingBytes.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.vertx.core.buffer.Buffer; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +final class MutableBufferWrappingBytes extends BufferWrappingBytes implements MutableBytes { + + MutableBufferWrappingBytes(Buffer buffer) { + super(buffer); + } + + MutableBufferWrappingBytes(Buffer buffer, int offset, int length) { + super(buffer, offset, length); + } + + @Override + public void set(int i, byte b) { + buffer.setByte(i, b); + } + + @Override + public void set(int i, Bytes b) { + byte[] bytes = b.toArrayUnsafe(); + buffer.setBytes(i, bytes); + } + + @Override + public void setInt(int i, int value) { + buffer.setInt(i, value); + } + + @Override + public void setLong(int i, long value) { + buffer.setLong(i, value); + } + + @Override + public MutableBytes mutableSlice(int i, int length) { + int size = size(); + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return MutableBytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + i + length <= size, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + return new MutableBufferWrappingBytes(buffer.slice(i, i + length)); + } + + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public int hashCode() { + return computeHashcode(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableByteBufWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableByteBufWrappingBytes.java new file mode 100644 index 00000000000..392b8bf8d80 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableByteBufWrappingBytes.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.ByteBuf; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +final class MutableByteBufWrappingBytes extends ByteBufWrappingBytes implements MutableBytes { + + MutableByteBufWrappingBytes(ByteBuf buffer) { + super(buffer); + } + + MutableByteBufWrappingBytes(ByteBuf buffer, int offset, int length) { + super(buffer, offset, length); + } + + @Override + public void clear() { + byteBuf.setZero(0, byteBuf.capacity()); + } + + @Override + public void set(int i, byte b) { + byteBuf.setByte(i, b); + } + + @Override + public void set(int i, Bytes b) { + byte[] bytes = b.toArrayUnsafe(); + byteBuf.setBytes(i, bytes); + } + + @Override + public void setInt(int i, int value) { + byteBuf.setInt(i, value); + } + + @Override + public void setLong(int i, long value) { + byteBuf.setLong(i, value); + } + + @Override + public MutableBytes mutableSlice(int i, int length) { + int size = size(); + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return MutableBytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + i + length <= size, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + return new MutableByteBufWrappingBytes(byteBuf.slice(i, length)); + } + + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public int hashCode() { + return computeHashcode(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableByteBufferWrappingBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableByteBufferWrappingBytes.java new file mode 100644 index 00000000000..10c8d05eb9f --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableByteBufferWrappingBytes.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.nio.ByteBuffer; + +import static org.apache.tuweni.bytes.Checks.checkArgument; +import static org.apache.tuweni.bytes.Checks.checkElementIndex; + +public class MutableByteBufferWrappingBytes extends ByteBufferWrappingBytes implements MutableBytes { + + MutableByteBufferWrappingBytes(ByteBuffer byteBuffer) { + super(byteBuffer); + } + + MutableByteBufferWrappingBytes(ByteBuffer byteBuffer, int offset, int length) { + super(byteBuffer, offset, length); + } + + @Override + public void setInt(int i, int value) { + byteBuffer.putInt(offset + i, value); + } + + @Override + public void setLong(int i, long value) { + byteBuffer.putLong(offset + i, value); + } + + @Override + public void set(int i, byte b) { + byteBuffer.put(offset + i, b); + } + + @Override + public void set(int i, Bytes b) { + byte[] bytes = b.toArrayUnsafe(); + int byteIndex = 0; + int thisIndex = offset + i; + int end = bytes.length; + while (byteIndex < end) { + byteBuffer.put(thisIndex++, bytes[byteIndex++]); + } + } + + @Override + public MutableBytes mutableSlice(int i, int length) { + if (i == 0 && length == this.length) { + return this; + } + if (length == 0) { + return MutableBytes.EMPTY; + } + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + + return new MutableByteBufferWrappingBytes(byteBuffer, offset + i, length); + } + + @Override + public Bytes copy() { + return new ArrayWrappingBytes(toArray()); + } + + @Override + public int hashCode() { + return computeHashcode(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes.java new file mode 100644 index 00000000000..9501009a2a7 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes.java @@ -0,0 +1,374 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +import java.nio.ByteBuffer; + +import static java.lang.String.format; +import static org.apache.tuweni.bytes.Checks.*; + +/** + * A mutable {@link Bytes} value. + */ +public interface MutableBytes extends Bytes { + + /** + * The empty value (with 0 bytes). + */ + MutableBytes EMPTY = wrap(new byte[0]); + + /** + * Create a new mutable byte value. + * + * @param size The size of the returned value. + * @return A {@link MutableBytes} value. + */ + static MutableBytes create(int size) { + if (size == 32) { + return MutableBytes32.create(); + } + return new MutableArrayWrappingBytes(new byte[size]); + } + + /** + * Wrap a byte array in a {@link MutableBytes} value. + * + * @param value The value to wrap. + * @return A {@link MutableBytes} value wrapping {@code value}. + */ + static MutableBytes wrap(byte[] value) { + checkNotNull(value); + return new MutableArrayWrappingBytes(value); + } + + /** + * Wrap a slice of a byte array as a {@link MutableBytes} value. + * + *

+ * Note that value is not copied and thus any future update to {@code value} within the slice will be reflected in the + * returned value. + * + * @param value The value to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, o, l).get(0) == value[o]}. + * @param length The length of the resulting value. + * @return A {@link Bytes} value that expose the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + length} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > value.length}. + */ + static MutableBytes wrap(byte[] value, int offset, int length) { + checkNotNull(value); + if (length == 32) { + return new MutableArrayWrappingBytes32(value, offset); + } + return new MutableArrayWrappingBytes(value, offset, length); + } + + /** + * Wrap a full Vert.x {@link Buffer} as a {@link MutableBytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param buffer The buffer to wrap. + * @return A {@link MutableBytes} value. + */ + static MutableBytes wrapBuffer(Buffer buffer) { + checkNotNull(buffer); + if (buffer.length() == 0) { + return EMPTY; + } + return new MutableBufferWrappingBytes(buffer); + } + + /** + * Wrap a slice of a Vert.x {@link Buffer} as a {@link MutableBytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value, and any change to the + * returned value will be reflected in the buffer. + * + * @param buffer The buffer to wrap. + * @param offset The offset in {@code buffer} from which to expose the bytes in the returned value. That is, + * {@code wrapBuffer(buffer, i, 1).get(0) == buffer.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link MutableBytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (buffer.length() > 0 && offset >= + * buffer.length())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > buffer.length()}. + */ + static MutableBytes wrapBuffer(Buffer buffer, int offset, int size) { + checkNotNull(buffer); + if (size == 0) { + return EMPTY; + } + return new MutableBufferWrappingBytes(buffer, offset, size); + } + + /** + * Wrap a full Netty {@link ByteBuf} as a {@link MutableBytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @return A {@link MutableBytes} value. + */ + static MutableBytes wrapByteBuf(ByteBuf byteBuf) { + checkNotNull(byteBuf); + if (byteBuf.capacity() == 0) { + return EMPTY; + } + return new MutableByteBufWrappingBytes(byteBuf); + } + + /** + * Wrap a slice of a Netty {@link ByteBuf} as a {@link MutableBytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value, and any change to the + * returned value will be reflected in the buffer. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @param offset The offset in {@code byteBuf} from which to expose the bytes in the returned value. That is, + * {@code wrapByteBuf(byteBuf, i, 1).get(0) == byteBuf.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link MutableBytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuf.capacity() > 0 && offset >= + * byteBuf.capacity())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuf.capacity()}. + */ + static MutableBytes wrapByteBuf(ByteBuf byteBuf, int offset, int size) { + checkNotNull(byteBuf); + if (size == 0) { + return EMPTY; + } + return new MutableByteBufWrappingBytes(byteBuf, offset, size); + } + + /** + * Wrap a full Java NIO {@link ByteBuffer} as a {@link MutableBytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @return A {@link MutableBytes} value. + */ + static MutableBytes wrapByteBuffer(ByteBuffer byteBuffer) { + checkNotNull(byteBuffer); + if (byteBuffer.limit() == 0) { + return EMPTY; + } + return new MutableByteBufferWrappingBytes(byteBuffer); + } + + /** + * Wrap a slice of a Java NIO {@link ByteBuffer} as a {@link MutableBytes} value. + * + *

+ * Note that any change to the content of the buffer may be reflected in the returned value, and any change to the + * returned value will be reflected in the buffer. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @param offset The offset in {@code byteBuffer} from which to expose the bytes in the returned value. That is, + * {@code wrapByteBuffer(byteBuffer, i, 1).get(0) == byteBuffer.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link MutableBytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuffer.limit() > 0 && offset >= + * byteBuffer.limit())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuffer.limit()}. + */ + static MutableBytes wrapByteBuffer(ByteBuffer byteBuffer, int offset, int size) { + checkNotNull(byteBuffer); + if (size == 0) { + return EMPTY; + } + return new MutableByteBufferWrappingBytes(byteBuffer, offset, size); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes that must compose the returned value. + * @return A value containing the specified bytes. + */ + static MutableBytes of(byte... bytes) { + return wrap(bytes); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes. + * @return A value containing bytes are the one from {@code bytes}. + * @throws IllegalArgumentException if any of the specified would be truncated when storing as a byte. + */ + static MutableBytes of(int... bytes) { + byte[] result = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + checkArgument(b == (((byte) b) & 0xff), "%sth value %s does not fit a byte", i + 1, b); + result[i] = (byte) b; + } + return wrap(result); + } + + /** + * Set a byte in this value. + * + * @param i The index of the byte to set. + * @param b The value to set that byte to. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()}. + */ + void set(int i, byte b); + + /** + * Set a byte in this value. + * + * @param offset The offset of the bytes to set. + * @param bytes The value to set bytes to. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()}. + */ + default void set(int offset, Bytes bytes) { + for (int i = 0; i < bytes.size(); i++) { + set(offset + i, bytes.get(i)); + } + } + + /** + * Set the 4 bytes starting at the specified index to the specified integer value. + * + * @param i The index, which must less than or equal to {@code size() - 4}. + * @param value The integer value. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 4}. + */ + default void setInt(int i, int value) { + int size = size(); + checkElementIndex(i, size); + if (i > (size - 4)) { + throw new IndexOutOfBoundsException( + format("Value of size %s has not enough bytes to write a 4 bytes int from index %s", size, i)); + } + + set(i++, (byte) (value >>> 24)); + set(i++, (byte) ((value >>> 16) & 0xFF)); + set(i++, (byte) ((value >>> 8) & 0xFF)); + set(i, (byte) (value & 0xFF)); + } + + /** + * Set the 8 bytes starting at the specified index to the specified long value. + * + * @param i The index, which must less than or equal to {@code size() - 8}. + * @param value The long value. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 8}. + */ + default void setLong(int i, long value) { + int size = size(); + checkElementIndex(i, size); + if (i > (size - 8)) { + throw new IndexOutOfBoundsException( + format("Value of size %s has not enough bytes to write a 8 bytes long from index %s", size, i)); + } + + set(i++, (byte) (value >>> 56)); + set(i++, (byte) ((value >>> 48) & 0xFF)); + set(i++, (byte) ((value >>> 40) & 0xFF)); + set(i++, (byte) ((value >>> 32) & 0xFF)); + set(i++, (byte) ((value >>> 24) & 0xFF)); + set(i++, (byte) ((value >>> 16) & 0xFF)); + set(i++, (byte) ((value >>> 8) & 0xFF)); + set(i, (byte) (value & 0xFF)); + } + + /** + * Increments the value of the bytes by 1, treating the value as big endian. + * + * If incrementing overflows the value then all bits flip, i.e. incrementing 0xFFFF will return 0x0000. + * + * @return this value + */ + default MutableBytes increment() { + for (int i = size() - 1; i >= 0; --i) { + if (get(i) == (byte) 0xFF) { + set(i, (byte) 0x00); + } else { + byte currentValue = get(i); + set(i, ++currentValue); + break; + } + } + return this; + } + + /** + * Decrements the value of the bytes by 1, treating the value as big endian. + * + * If decrementing underflows the value then all bits flip, i.e. decrementing 0x0000 will return 0xFFFF. + * + * @return this value + */ + default MutableBytes decrement() { + for (int i = size() - 1; i >= 0; --i) { + if (get(i) == (byte) 0x00) { + set(i, (byte) 0xFF); + } else { + byte currentValue = get(i); + set(i, --currentValue); + break; + } + } + return this; + } + + /** + * Create a mutable slice of the bytes of this value. + * + *

+ * Note: the resulting slice is only a view over the original value. Holding a reference to the returned slice may + * hold more memory than the slide represents. Use {@link #copy} on the returned slice to avoid this. + * + * @param i The start index for the slice. + * @param length The length of the resulting value. + * @return A new mutable view over the bytes of this value from index {@code i} (included) to index {@code i + length} + * (excluded). + * @throws IllegalArgumentException if {@code length < 0}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()} or {i + length > size()} . + */ + MutableBytes mutableSlice(int i, int length); + + /** + * Fill all the bytes of this value with the specified byte. + * + * @param b The byte to use to fill the value. + */ + default void fill(byte b) { + int size = size(); + for (int i = 0; i < size; i++) { + set(i, b); + } + } + + /** + * Set all bytes in this value to 0. + */ + default void clear() { + fill((byte) 0); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes32.java new file mode 100644 index 00000000000..41c221aa808 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes32.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import static org.apache.tuweni.bytes.Checks.checkNotNull; + +/** + * A mutable {@link Bytes32}, that is a mutable {@link Bytes} value of exactly 32 bytes. + */ +public interface MutableBytes32 extends MutableBytes, Bytes32 { + + /** + * Create a new mutable 32 bytes value. + * + * @return A newly allocated {@link MutableBytes} value. + */ + static MutableBytes32 create() { + return new MutableArrayWrappingBytes32(new byte[SIZE]); + } + + /** + * Wrap a 32 bytes array as a mutable 32 bytes value. + * + * @param value The value to wrap. + * @return A {@link MutableBytes32} wrapping {@code value}. + * @throws IllegalArgumentException if {@code value.length != 32}. + */ + static MutableBytes32 wrap(byte[] value) { + checkNotNull(value); + return new MutableArrayWrappingBytes32(value); + } + + /** + * Wrap a the provided array as a {@link MutableBytes32}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} within the wrapped parts + * will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link MutableBytes32} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 32 > value.length}. + */ + static MutableBytes32 wrap(byte[] value, int offset) { + checkNotNull(value); + return new MutableArrayWrappingBytes32(value, offset); + } + + /** + * Wrap a the provided value, which must be of size 32, as a {@link MutableBytes32}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} will be reflected in the + * returned value. + * + * @param value The bytes to wrap. + * @return A {@link MutableBytes32} that exposes the bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() != 32}. + */ + static MutableBytes32 wrap(MutableBytes value) { + checkNotNull(value); + if (value instanceof MutableBytes32) { + return (MutableBytes32) value; + } + return DelegatingMutableBytes32.delegateTo(value); + } + + /** + * Wrap a slice/sub-part of the provided value as a {@link MutableBytes32}. + * + *

+ * Note that the value is not copied, and thus any future update to {@code value} within the wrapped parts will be + * reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value.get(i)}. + * @return A {@link Bytes32} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.size() > 0 && offset >= + * value.size())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 32 > value.size()}. + */ + static MutableBytes32 wrap(MutableBytes value, int offset) { + checkNotNull(value); + if (value instanceof MutableBytes32) { + return (MutableBytes32) value; + } + MutableBytes slice = value.mutableSlice(offset, Bytes32.SIZE); + if (slice instanceof MutableBytes32) { + return (MutableBytes32) slice; + } + return DelegatingMutableBytes32.delegateTo(slice); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes48.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes48.java new file mode 100644 index 00000000000..c8f51ad2c70 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/bytes/MutableBytes48.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + + +import static org.apache.tuweni.bytes.Checks.checkNotNull; + +/** + * A mutable {@link Bytes48}, that is a mutable {@link Bytes} value of exactly 48 bytes. + */ +public interface MutableBytes48 extends MutableBytes, Bytes48 { + + /** + * Create a new mutable 48 bytes value. + * + * @return A newly allocated {@link MutableBytes} value. + */ + static MutableBytes48 create() { + return new MutableArrayWrappingBytes48(new byte[SIZE]); + } + + /** + * Wrap a 48 bytes array as a mutable 48 bytes value. + * + * @param value The value to wrap. + * @return A {@link MutableBytes48} wrapping {@code value}. + * @throws IllegalArgumentException if {@code value.length != 48}. + */ + static MutableBytes48 wrap(byte[] value) { + checkNotNull(value); + return new MutableArrayWrappingBytes48(value); + } + + /** + * Wrap a the provided array as a {@link MutableBytes48}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} within the wrapped parts + * will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link MutableBytes48} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 48} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 48 > value.length}. + */ + static MutableBytes48 wrap(byte[] value, int offset) { + checkNotNull(value); + return new MutableArrayWrappingBytes48(value, offset); + } + + /** + * Wrap a the provided value, which must be of size 48, as a {@link MutableBytes48}. + * + *

+ * Note that value is not copied, only wrapped, and thus any future update to {@code value} will be reflected in the + * returned value. + * + * @param value The bytes to wrap. + * @return A {@link MutableBytes48} that exposes the bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() != 48}. + */ + static MutableBytes48 wrap(MutableBytes value) { + checkNotNull(value); + if (value instanceof MutableBytes48) { + return (MutableBytes48) value; + } + return DelegatingMutableBytes48.delegateTo(value); + } + + /** + * Wrap a slice/sub-part of the provided value as a {@link MutableBytes48}. + * + *

+ * Note that the value is not copied, and thus any future update to {@code value} within the wrapped parts will be + * reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other + * words, you will have {@code wrap(value, i).get(0) == value.get(i)}. + * @return A {@link Bytes48} that exposes the bytes of {@code value} from {@code offset} (inclusive) to + * {@code offset + 48} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.size() > 0 && offset >= + * value.size())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 48 > value.size()}. + */ + static MutableBytes48 wrap(MutableBytes value, int offset) { + checkNotNull(value); + if (value instanceof MutableBytes48) { + return (MutableBytes48) value; + } + MutableBytes slice = value.mutableSlice(offset, Bytes48.SIZE); + if (slice instanceof MutableBytes48) { + return (MutableBytes48) slice; + } + return DelegatingMutableBytes48.delegateTo(slice); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt256Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt256Value.java new file mode 100644 index 00000000000..cf55c329d9d --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt256Value.java @@ -0,0 +1,405 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes32; + +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +/** + * Base class for {@link UInt256Value}. + * + *

+ * This class is abstract as it is not meant to be used directly, but it has no abstract methods. As mentioned in + * {@link UInt256Value}, this is used to create strongly-typed type aliases of {@link UInt256}. In other words, this + * allow to "tag" numbers with the unit of what they represent for the type-system, which can help clarity, but also + * forbid mixing numbers that are mean to be of different units (the strongly-typed part). + * + *

+ * This class implements {@link UInt256Value}, but also adds a few operations that take a {@link UInt256} directly, for + * instance {@link #multiply(UInt256)}. The rational is that multiplying a given quantity of something by a "raw" number + * is always meaningful, and return a new quantity of the same thing. + * + * @param The concrete type of the value. + */ +public abstract class BaseUInt256Value> implements UInt256Value { + + private final UInt256 value; + private final Function ctor; + + /** + * @param value The value to instantiate this {@code UInt256Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt256Value(UInt256 value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = value; + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt256Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt256Value(long value, Function ctor) { + requireNonNull(ctor); + this.value = UInt256.valueOf(value); + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt256Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt256Value(BigInteger value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = UInt256.valueOf(value); + this.ctor = ctor; + } + + /** + * Return a copy of this value, or itself if immutable. + * + *

+ * The default implementation of this method returns a copy using the constructor for the concrete type and the bytes + * returned from {@link #toBytes()}. Most implementations will want to override this method to instead return + * {@code this}. + * + * @return A copy of this value, or itself if immutable. + */ + @Override + public T copy() { + return ctor.apply(value); + } + + /** + * Return the zero value for this type. + * + *

+ * The default implementation of this method returns a value obtained from calling the concrete type constructor with + * an argument of {@link UInt256#ZERO}. Most implementations will want to override this method to instead return a + * static constant. + * + * @return The zero value for this type. + */ + protected T zero() { + return ctor.apply(UInt256.ZERO); + } + + /** + * Return the max value for this type. + * + *

+ * The default implementation of this method returns a value obtained from calling the concrete type constructor with + * an argument of {@link UInt256#MAX_VALUE}. Most implementations will want to override this method to instead return + * a static constant. + * + * @return The max value for this type. + */ + @Override + public T max() { + return ctor.apply(UInt256.MAX_VALUE); + } + + @Override + public T add(T value) { + return add(value.toUInt256()); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + public T add(UInt256 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T add(long value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T addMod(T value, UInt256 modulus) { + return addMod(value.toUInt256(), modulus); + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + public T addMod(UInt256 value, UInt256 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, UInt256 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, long modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T subtract(T value) { + return subtract(value.toUInt256()); + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + public T subtract(UInt256 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T subtract(long value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T multiply(T value) { + return multiply(value.toUInt256()); + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + public T multiply(UInt256 value) { + if (isZero() || value.isZero()) { + return zero(); + } + if (value.equals(UInt256.ONE)) { + return copy(); + } + if (this.value.equals(UInt256.ONE)) { + return ctor.apply(value); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiply(long value) { + if (value == 0 || isZero()) { + return zero(); + } + if (value == 1) { + return copy(); + } + if (this.value.equals(UInt256.ONE)) { + return ctor.apply(UInt256.valueOf(value)); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiplyMod(T value, UInt256 modulus) { + return multiplyMod(value.toUInt256(), modulus); + } + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + public T multiplyMod(UInt256 value, UInt256 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(long value, UInt256 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(long value, long modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T divide(T value) { + return divide(value.toUInt256()); + } + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + public T divide(UInt256 value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T divide(long value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T divideCeil(T value) { + return divideCeil(value.toUInt256()); + } + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + public T divideCeil(UInt256 value) { + return ctor.apply(this.value.divideCeil(value)); + } + + @Override + public T divideCeil(long value) { + return ctor.apply(this.value.divideCeil(value)); + } + + @Override + public T pow(UInt256 exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T pow(long exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T mod(UInt256 modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public T mod(long modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public T mod0(UInt256 modulus) { + return ctor.apply(this.value.mod0(modulus)); + } + + @Override + public T mod0(long modulus) { + return ctor.apply(this.value.mod0(modulus)); + } + + /** + * Compare two {@link UInt256} values. + * + * @param other The value to compare to. + * @return A negative integer, zero, or a positive integer as this value is less than, equal to, or greater than the + * specified value. + */ + public int compareTo(UInt256 other) { + return this.value.compareTo(other); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof UInt256Value)) { + return false; + } + UInt256Value other = (UInt256Value) obj; + return this.value.equals(other.toUInt256()); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public UInt256 toUInt256() { + return value; + } + + @Override + public Bytes32 toBytes() { + return value; + } + + @Override + public Bytes toMinimalBytes() { + return value.toMinimalBytes(); + } + + @Override + public byte get(int i) { + return value.get(i); + } + + @Override + public Bytes slice(int i, int length) { + return value.slice(i, length); + } + + @Override + public MutableBytes32 mutableCopy() { + return MutableBytes32.wrap(value.toArrayUnsafe()); + } + + @Override + public long toLong(ByteOrder order) { + return value.toLong(order); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt32Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt32Value.java new file mode 100644 index 00000000000..f50b8896292 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt32Value.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; + +import java.math.BigInteger; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +/** + * Base class for {@link UInt32Value}. + * + *

+ * This class is abstract as it is not meant to be used directly, but it has no abstract methods. As mentioned in + * {@link UInt32Value}, this is used to create strongly-typed type aliases of {@link UInt32}. In other words, this allow + * to "tag" numbers with the unit of what they represent for the type-system, which can help clarity, but also forbid + * mixing numbers that are mean to be of different units (the strongly-typed part). + * + *

+ * This class implements {@link UInt32Value}, but also adds a few operations that take a {@link UInt32} directly, for + * instance {@link #multiply(UInt32)}. The rational is that multiplying a given quantity of something by a "raw" number + * is always meaningful, and return a new quantity of the same thing. + * + * @param The concrete type of the value. + */ +public abstract class BaseUInt32Value> implements UInt32Value { + + private final UInt32 value; + private final Function ctor; + + /** + * @param value The value to instantiate this {@code UInt32Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt32Value(UInt32 value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = value; + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt32Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt32Value(int value, Function ctor) { + requireNonNull(ctor); + this.value = UInt32.valueOf(value); + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt32Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt32Value(BigInteger value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = UInt32.valueOf(value); + this.ctor = ctor; + } + + /** + * Return a copy of this value, or itself if immutable. + * + *

+ * The default implementation of this method returns a copy using the constructor for the concrete type and the bytes + * returned from {@link #toBytes()}. Most implementations will want to override this method to instead return + * {@code this}. + * + * @return A copy of this value, or itself if immutable. + */ + protected T copy() { + return ctor.apply(value); + } + + /** + * Return the zero value for this type. + * + *

+ * The default implementation of this method returns a value obtained from calling the concrete type constructor with + * an argument of {@link UInt32#ZERO}. Most implementations will want to override this method to instead return a + * static constant. + * + * @return The zero value for this type. + */ + protected T zero() { + return ctor.apply(UInt32.ZERO); + } + + @Override + public T add(T value) { + return add(value.toUInt32()); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + public T add(UInt32 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T add(int value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T addMod(T value, UInt32 modulus) { + return addMod(value.toUInt32(), modulus); + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + public T addMod(UInt32 value, UInt32 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, UInt32 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, long modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T subtract(T value) { + return subtract(value.toUInt32()); + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + public T subtract(UInt32 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T subtract(int value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T multiply(T value) { + return multiply(value.toUInt32()); + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + public T multiply(UInt32 value) { + if (isZero() || value.isZero()) { + return zero(); + } + if (value.equals(UInt32.ONE)) { + return copy(); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiply(int value) { + if (value == 0 || isZero()) { + return zero(); + } + if (value == 1) { + return copy(); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiplyMod(T value, UInt32 modulus) { + return multiplyMod(value.toUInt32(), modulus); + } + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + public T multiplyMod(UInt32 value, UInt32 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(int value, UInt32 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(int value, int modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T divide(T value) { + return divide(value.toUInt32()); + } + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + public T divide(UInt32 value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T divide(int value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T pow(UInt32 exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T pow(long exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T mod(UInt32 modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public T mod(int modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public int compareTo(T other) { + return compareTo(other.toUInt32()); + } + + /** + * Compare two {@link UInt32} values. + * + * @param other The value to compare to. + * @return A negative integer, zero, or a positive integer as this value is less than, equal to, or greater than the + * specified value. + */ + public int compareTo(UInt32 other) { + return this.value.compareTo(other); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof UInt32Value)) { + return false; + } + UInt32Value other = (UInt32Value) obj; + return this.value.equals(other.toUInt32()); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public UInt32 toUInt32() { + return value; + } + + @Override + public Bytes toBytes() { + return value.toBytes(); + } + + @Override + public Bytes toMinimalBytes() { + return value.toMinimalBytes(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt384Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt384Value.java new file mode 100644 index 00000000000..fdc0432e4b9 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt384Value.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes48; + +import java.math.BigInteger; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +/** + * Base class for {@link UInt384Value}. + * + *

+ * This class is abstract as it is not meant to be used directly, but it has no abstract methods. As mentioned in + * {@link UInt384Value}, this is used to create strongly-typed type aliases of {@link UInt384}. In other words, this + * allow to "tag" numbers with the unit of what they represent for the type-system, which can help clarity, but also + * forbid mixing numbers that are mean to be of different units (the strongly-typed part). + * + *

+ * This class implements {@link UInt384Value}, but also adds a few operations that take a {@link UInt384} directly, for + * instance {@link #multiply(UInt384)}. The rational is that multiplying a given quantity of something by a "raw" number + * is always meaningful, and return a new quantity of the same thing. + * + * @param The concrete type of the value. + */ +public abstract class BaseUInt384Value> implements UInt384Value { + + private final UInt384 value; + private final Function ctor; + + /** + * @param value The value to instantiate this {@code UInt384Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt384Value(UInt384 value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = value; + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt384Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt384Value(long value, Function ctor) { + requireNonNull(ctor); + this.value = UInt384.valueOf(value); + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt384Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt384Value(BigInteger value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = UInt384.valueOf(value); + this.ctor = ctor; + } + + /** + * Return a copy of this value, or itself if immutable. + * + *

+ * The default implementation of this method returns a copy using the constructor for the concrete type and the bytes + * returned from {@link #toBytes()}. Most implementations will want to override this method to instead return + * {@code this}. + * + * @return A copy of this value, or itself if immutable. + */ + protected T copy() { + return ctor.apply(value); + } + + /** + * Return the zero value for this type. + * + *

+ * The default implementation of this method returns a value obtained from calling the concrete type constructor with + * an argument of {@link Bytes48#ZERO}. Most implementations will want to override this method to instead return a + * static constant. + * + * @return The zero value for this type. + */ + protected T zero() { + return ctor.apply(UInt384.ZERO); + } + + @Override + public T add(T value) { + return add(value.toUInt384()); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + public T add(UInt384 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T add(long value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T addMod(T value, UInt384 modulus) { + return addMod(value.toUInt384(), modulus); + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + public T addMod(UInt384 value, UInt384 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, UInt384 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, long modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T subtract(T value) { + return subtract(value.toUInt384()); + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + public T subtract(UInt384 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T subtract(long value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T multiply(T value) { + return multiply(value.toUInt384()); + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + public T multiply(UInt384 value) { + if (isZero() || value.isZero()) { + return zero(); + } + if (value.equals(UInt384.ONE)) { + return copy(); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiply(long value) { + if (value == 0 || isZero()) { + return zero(); + } + if (value == 1) { + return copy(); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiplyMod(T value, UInt384 modulus) { + return multiplyMod(value.toUInt384(), modulus); + } + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + public T multiplyMod(UInt384 value, UInt384 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(long value, UInt384 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(long value, long modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T divide(T value) { + return divide(value.toUInt384()); + } + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + public T divide(UInt384 value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T divide(long value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T pow(UInt384 exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T pow(long exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T mod(UInt384 modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public T mod(long modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public int compareTo(T other) { + return compareTo(other.toUInt384()); + } + + /** + * Compare two {@link UInt384} values. + * + * @param other The value to compare to. + * @return A negative integer, zero, or a positive integer as this value is less than, equal to, or greater than the + * specified value. + */ + public int compareTo(UInt384 other) { + return this.value.compareTo(other); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof UInt384Value)) { + return false; + } + UInt384Value other = (UInt384Value) obj; + return this.value.equals(other.toUInt384()); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public UInt384 toUInt384() { + return value; + } + + @Override + public Bytes48 toBytes() { + return value.toBytes(); + } + + @Override + public Bytes toMinimalBytes() { + return value.toMinimalBytes(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt64Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt64Value.java new file mode 100644 index 00000000000..c216791615e --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BaseUInt64Value.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; + +import java.math.BigInteger; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +/** + * Base class for {@link UInt64Value}. + * + *

+ * This class is abstract as it is not meant to be used directly, but it has no abstract methods. As mentioned in + * {@link UInt64Value}, this is used to create strongly-typed type aliases of {@link UInt64}. In other words, this allow + * to "tag" numbers with the unit of what they represent for the type-system, which can help clarity, but also forbid + * mixing numbers that are mean to be of different units (the strongly-typed part). + * + *

+ * This class implements {@link UInt64Value}, but also adds a few operations that take a {@link UInt64} directly, for + * instance {@link #multiply(UInt64)}. The rational is that multiplying a given quantity of something by a "raw" number + * is always meaningful, and return a new quantity of the same thing. + * + * @param The concrete type of the value. + */ +public abstract class BaseUInt64Value> implements UInt64Value { + + private final UInt64 value; + private final Function ctor; + + /** + * @param value The value to instantiate this {@code UInt64Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt64Value(UInt64 value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = value; + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt64Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt64Value(long value, Function ctor) { + requireNonNull(ctor); + this.value = UInt64.valueOf(value); + this.ctor = ctor; + } + + /** + * @param value An unsigned value to instantiate this {@code UInt64Value} with. + * @param ctor A constructor for the concrete type. + */ + protected BaseUInt64Value(BigInteger value, Function ctor) { + requireNonNull(value); + requireNonNull(ctor); + this.value = UInt64.valueOf(value); + this.ctor = ctor; + } + + /** + * Return a copy of this value, or itself if immutable. + * + *

+ * The default implementation of this method returns a copy using the constructor for the concrete type and the bytes + * returned from {@link #toBytes()}. Most implementations will want to override this method to instead return + * {@code this}. + * + * @return A copy of this value, or itself if immutable. + */ + protected T copy() { + return ctor.apply(value); + } + + /** + * Return the zero value for this type. + * + *

+ * The default implementation of this method returns a value obtained from calling the concrete type constructor with + * an argument of {@link UInt64#ZERO}. Most implementations will want to override this method to instead return a + * static constant. + * + * @return The zero value for this type. + */ + protected T zero() { + return ctor.apply(UInt64.ZERO); + } + + @Override + public T add(T value) { + return add(value.toUInt64()); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + public T add(UInt64 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T add(long value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.add(value)); + } + + @Override + public T addMod(T value, UInt64 modulus) { + return addMod(value.toUInt64(), modulus); + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + public T addMod(UInt64 value, UInt64 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, UInt64 modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T addMod(long value, long modulus) { + return ctor.apply(this.value.addMod(value, modulus)); + } + + @Override + public T subtract(T value) { + return subtract(value.toUInt64()); + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + public T subtract(UInt64 value) { + if (value.isZero()) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T subtract(long value) { + if (value == 0) { + return copy(); + } + return ctor.apply(this.value.subtract(value)); + } + + @Override + public T multiply(T value) { + return multiply(value.toUInt64()); + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + public T multiply(UInt64 value) { + if (isZero() || value.isZero()) { + return zero(); + } + if (value.equals(UInt64.ONE)) { + return copy(); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiply(long value) { + if (value == 0 || isZero()) { + return zero(); + } + if (value == 1) { + return copy(); + } + return ctor.apply(this.value.multiply(value)); + } + + @Override + public T multiplyMod(T value, UInt64 modulus) { + return multiplyMod(value.toUInt64(), modulus); + } + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + public T multiplyMod(UInt64 value, UInt64 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(long value, UInt64 modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T multiplyMod(long value, long modulus) { + return ctor.apply(this.value.multiplyMod(value, modulus)); + } + + @Override + public T divide(T value) { + return divide(value.toUInt64()); + } + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + public T divide(UInt64 value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T divide(long value) { + return ctor.apply(this.value.divide(value)); + } + + @Override + public T pow(UInt64 exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T pow(long exponent) { + return ctor.apply(this.value.pow(exponent)); + } + + @Override + public T mod(UInt64 modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public T mod(long modulus) { + return ctor.apply(this.value.mod(modulus)); + } + + @Override + public int compareTo(T other) { + return compareTo(other.toUInt64()); + } + + /** + * Compare two {@link UInt64} values. + * + * @param other The value to compare to. + * @return A negative integer, zero, or a positive integer as this value is less than, equal to, or greater than the + * specified value. + */ + public int compareTo(UInt64 other) { + return this.value.compareTo(other); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof UInt64Value)) { + return false; + } + UInt64Value other = (UInt64Value) obj; + return this.value.equals(other.toUInt64()); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public UInt64 toUInt64() { + return value; + } + + @Override + public Bytes toBytes() { + return value.toBytes(); + } + + @Override + public Bytes toMinimalBytes() { + return value.toMinimalBytes(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BytesUInt256Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BytesUInt256Value.java new file mode 100644 index 00000000000..cd1ead967e3 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/BytesUInt256Value.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.math.BigInteger; + +public interface BytesUInt256Value> extends Bytes32, UInt256Value { + + @Override + default long toLong() { + return ((Bytes32) this).toLong(); + } + + @Override + default String toHexString() { + return ((Bytes32) this).toHexString(); + } + + @Override + default String toShortHexString() { + return ((Bytes) this).toShortHexString(); + } + + @Override + default BigInteger toBigInteger() { + return ((UInt256Value) this).toBigInteger(); + } + + @Override + default int numberOfLeadingZeros() { + return ((Bytes) this).numberOfLeadingZeros(); + } + + @Override + default boolean isZero() { + return ((Bytes) this).isZero(); + } + + @Override + default int bitLength() { + return ((Bytes) this).bitLength(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256.java new file mode 100644 index 00000000000..c6d060e6178 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256.java @@ -0,0 +1,970 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes; +import org.apache.tuweni.bytes.MutableBytes32; +import org.jetbrains.annotations.Nullable; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * An unsigned 256-bit precision number. + * + * This is a raw {@link UInt256Value} - a 256-bit precision unsigned number of no particular unit. + */ +public final class UInt256 implements UInt256Value { + private final static int MAX_CONSTANT = 64; + private final static BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); + private static UInt256[] CONSTANTS = new UInt256[MAX_CONSTANT + 1]; + static { + CONSTANTS[0] = new UInt256(Bytes32.ZERO); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt256(i); + } + } + + /** The minimum value of a UInt256 */ + public final static UInt256 MIN_VALUE = valueOf(0); + /** The maximum value of a UInt256 */ + public final static UInt256 MAX_VALUE = new UInt256(Bytes32.ZERO.not()); + /** The value 0 */ + public final static UInt256 ZERO = valueOf(0); + /** The value 1 */ + public final static UInt256 ONE = valueOf(1); + + private static final int INTS_SIZE = 32 / 4; + // The mask is used to obtain the value of an int as if it were unsigned. + private static final long LONG_MASK = 0xFFFFFFFFL; + private static final BigInteger P_2_256 = BigInteger.valueOf(2).pow(256); + + // The unsigned int components of the value + private final int[] ints; + private Integer hashCode; + + /** + * Return a {@code UInt256} containing the specified value. + * + * @param value The value to create a {@code UInt256} for. + * @return A {@code UInt256} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt256 valueOf(long value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value <= MAX_CONSTANT) { + return CONSTANTS[(int) value]; + } + return new UInt256(value); + } + + /** + * Return a {@link UInt256} containing the specified value. + * + * @param value the value to create a {@link UInt256} for + * @return a {@link UInt256} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a UInt256 + */ + public static UInt256 valueOf(BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value.bitLength() > 256) { + throw new IllegalArgumentException("Argument is too large to represent a UInt256"); + } + if (value.compareTo(BI_MAX_CONSTANT) <= 0) { + return CONSTANTS[value.intValue()]; + } + int[] ints = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + ints[i] = value.intValue(); + value = value.shiftRight(32); + } + return new UInt256(ints); + } + + /** + * Return a {@link UInt256} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt256}. + * @return A {@link UInt256} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 32}. + */ + public static UInt256 fromBytes(final Bytes bytes) { + if (bytes instanceof UInt256) { + return (UInt256) bytes; + } + if (bytes instanceof Bytes32) { + final byte[] array = bytes.toArrayUnsafe(); + return new UInt256( + new int[] { + (Byte.toUnsignedInt(array[0])) << 24 + | (Byte.toUnsignedInt(array[1]) << 16) + | (Byte.toUnsignedInt(array[2]) << 8) + | (Byte.toUnsignedInt(array[3])), + (Byte.toUnsignedInt(array[4]) << 24) + | (Byte.toUnsignedInt(array[5]) << 16) + | (Byte.toUnsignedInt(array[6]) << 8) + | (Byte.toUnsignedInt(array[7])), + (Byte.toUnsignedInt(array[8]) << 24) + | (Byte.toUnsignedInt(array[9]) << 16) + | (Byte.toUnsignedInt(array[10]) << 8) + | (Byte.toUnsignedInt(array[11])), + (Byte.toUnsignedInt(array[12]) << 24) + | (Byte.toUnsignedInt(array[13]) << 16) + | (Byte.toUnsignedInt(array[14]) << 8) + | (Byte.toUnsignedInt(array[15])), + (Byte.toUnsignedInt(array[16]) << 24) + | (Byte.toUnsignedInt(array[17]) << 16) + | (Byte.toUnsignedInt(array[18]) << 8) + | (Byte.toUnsignedInt(array[19])), + (Byte.toUnsignedInt(array[20]) << 24) + | (Byte.toUnsignedInt(array[21]) << 16) + | (Byte.toUnsignedInt(array[22]) << 8) + | (Byte.toUnsignedInt(array[23])), + (Byte.toUnsignedInt(array[24]) << 24) + | (Byte.toUnsignedInt(array[25]) << 16) + | (Byte.toUnsignedInt(array[26]) << 8) + | (Byte.toUnsignedInt(array[27])), + (Byte.toUnsignedInt(array[28]) << 24) + | (Byte.toUnsignedInt(array[29]) << 16) + | (Byte.toUnsignedInt(array[30]) << 8) + | (Byte.toUnsignedInt(array[31]))}); + } else { + return new UInt256(Bytes32.leftPad(bytes)); + } + } + + /** + * Parse a hexadecimal string into a {@link UInt256}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 32 bytes, in which case the result is left padded with zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or + * contains more than 32 bytes. + */ + public static UInt256 fromHexString(String str) { + return new UInt256(Bytes32.fromHexStringLenient(str)); + } + + private UInt256(Bytes32 bytes) { + this.ints = new int[INTS_SIZE]; + for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { + ints[i] = bytes.getInt(j); + } + } + + private UInt256(long value) { + this.ints = new int[INTS_SIZE]; + this.ints[INTS_SIZE - 2] = (int) ((value >>> 32) & LONG_MASK); + this.ints[INTS_SIZE - 1] = (int) (value & LONG_MASK); + } + + private UInt256(int[] ints) { + this.ints = ints; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isZero() { + if (this == ZERO) { + return true; + } + for (int i = INTS_SIZE - 1; i >= 0; --i) { + if (this.ints[i] != 0) { + return false; + } + } + return true; + } + + @Override + public UInt256 add(UInt256 value) { + if (value.isZero()) { + return this; + } + if (isZero()) { + return value; + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value.ints[INTS_SIZE - 1] & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + (value.ints[i] & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt256(result); + } + + @Override + public UInt256 add(long value) { + if (value == 0) { + return this; + } + if (value > 0 && isZero()) { + return UInt256.valueOf(value); + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + sum = (this.ints[INTS_SIZE - 2] & LONG_MASK) + (value >>> 32) + (sum >>> 32); + result[INTS_SIZE - 2] = (int) (sum & LONG_MASK); + constant &= result[INTS_SIZE - 2] == 0; + long signExtent = (value >> 63) & LONG_MASK; + for (int i = INTS_SIZE - 3; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + signExtent + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt256(result); + } + + @Override + public UInt256 addMod(UInt256 value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt256 + .valueOf(toUnsignedBigInteger().add(value.toUnsignedBigInteger()).mod(modulus.toUnsignedBigInteger())); + } + + @Override + public UInt256 addMod(long value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt256.valueOf(toUnsignedBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toUnsignedBigInteger())); + } + + @Override + public UInt256 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return UInt256.valueOf(toUnsignedBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + @Override + public UInt256 subtract(UInt256 value) { + if (value.isZero()) { + return this; + } + + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + ((~value.ints[INTS_SIZE - 1]) & LONG_MASK) + 1; + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + ((~value.ints[i]) & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt256(result); + } + + @Override + public UInt256 subtract(long value) { + return add(-value); + } + + @Override + public UInt256 multiply(UInt256 value) { + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt256.ONE)) { + return this; + } + if (this.equals(UInt256.ONE)) { + return value; + } + return multiply(this.ints, value.ints); + } + + private static UInt256 multiply(int[] x, int[] y) { + int[] result = new int[INTS_SIZE + INTS_SIZE]; + + long carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + INTS_SIZE - 1; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[INTS_SIZE - 1] & LONG_MASK) + carry; + result[k] = (int) product; + carry = product >>> 32; + } + result[INTS_SIZE - 1] = (int) carry; + + for (int i = INTS_SIZE - 2; i >= 0; i--) { + carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + i; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[i] & LONG_MASK) + (result[k] & LONG_MASK) + carry; + + result[k] = (int) product; + carry = product >>> 32; + } + result[i] = (int) carry; + } + + boolean constant = true; + for (int i = INTS_SIZE; i < (INTS_SIZE + INTS_SIZE) - 1; ++i) { + constant &= (result[i] == 0); + } + if (constant && result[INTS_SIZE + INTS_SIZE - 1] >= 0 && result[INTS_SIZE + INTS_SIZE - 1] <= MAX_CONSTANT) { + return CONSTANTS[result[INTS_SIZE + INTS_SIZE - 1]]; + } + return new UInt256(Arrays.copyOfRange(result, INTS_SIZE, INTS_SIZE + INTS_SIZE)); + } + + @Override + public UInt256 multiply(long value) { + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return this; + } + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + UInt256 other = new UInt256(value); + if (this.equals(UInt256.ONE)) { + return other; + } + return multiply(this.ints, other.ints); + } + + @Override + public UInt256 multiplyMod(UInt256 value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt256.ONE)) { + return mod(modulus); + } + return UInt256 + .valueOf(toUnsignedBigInteger().multiply(value.toUnsignedBigInteger()).mod(modulus.toUnsignedBigInteger())); + } + + @Override + public UInt256 multiplyMod(long value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt256 + .valueOf(toUnsignedBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toUnsignedBigInteger())); + } + + @Override + public UInt256 multiplyMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt256.valueOf(toUnsignedBigInteger().multiply(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + @Override + public UInt256 divide(UInt256 value) { + if (value.isZero()) { + throw new ArithmeticException("divide by zero"); + } + if (value.equals(UInt256.ONE)) { + return this; + } + return UInt256.valueOf(toUnsignedBigInteger().divide(value.toUnsignedBigInteger())); + } + + @Override + public UInt256 divide(long value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return shiftRight(log2(value)); + } + return UInt256.valueOf(toUnsignedBigInteger().divide(BigInteger.valueOf(value))); + } + + public UInt256 sdiv0(UInt256 divisor) { + if (divisor.isZero()) { + return UInt256.ZERO; + } else { + BigInteger result = this.toSignedBigInteger().divide(divisor.toSignedBigInteger()); + Bytes resultBytes = Bytes.wrap(result.toByteArray()); + if (resultBytes.size() > 32) { + resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); + } + return UInt256.fromBytes(Bytes32.leftPad(resultBytes, result.signum() < 0 ? (byte) 0xFF : 0x00)); + } + } + + @Override + public UInt256 divideCeil(UInt256 value) { + return this.divide(value).add(this.mod(value).isZero() ? 0 : 1); + } + + @Override + public UInt256 divideCeil(long value) { + return this.divide(value).add(this.mod(value).isZero() ? 0 : 1); + } + + @Override + public UInt256 pow(UInt256 exponent) { + return UInt256.valueOf(toUnsignedBigInteger().modPow(exponent.toUnsignedBigInteger(), P_2_256)); + } + + @Override + public UInt256 pow(long exponent) { + return UInt256.valueOf(toUnsignedBigInteger().modPow(BigInteger.valueOf(exponent), P_2_256)); + } + + @Override + public UInt256 mod(UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return UInt256.valueOf(toUnsignedBigInteger().mod(modulus.toUnsignedBigInteger())); + } + + @Override + public UInt256 mod(long modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + if (isPowerOf2(modulus)) { + int log2 = log2(modulus); + int d = log2 / 32; + int s = log2 % 32; + assert (d == 0 || d == 1); + + int[] result = new int[INTS_SIZE]; + // Mask the byte at d to only include the s right-most bits + result[INTS_SIZE - 1 - d] = this.ints[INTS_SIZE - 1 - d] & ~(0xFFFFFFFF << s); + if (d != 0) { + result[INTS_SIZE - 1] = this.ints[INTS_SIZE - 1]; + } + return new UInt256(result); + } + return UInt256.valueOf(toUnsignedBigInteger().mod(BigInteger.valueOf(modulus))); + } + + @Override + public UInt256 mod0(UInt256 modulus) { + if (modulus.equals(UInt256.ZERO)) { + return UInt256.ZERO; + } + return mod(modulus); + } + + /** + * Returns a value that is the {@code (this signed mod modulus)}, or 0 if modulus is 0. + * + * @param modulus The modulus. + * @return {@code this signed mod modulus}. + */ + public UInt256 smod0(UInt256 modulus) { + if (modulus.equals(UInt256.ZERO)) { + return UInt256.ZERO; + } + + BigInteger bi = this.toSignedBigInteger(); + BigInteger result = bi.abs().mod(modulus.toSignedBigInteger().abs()); + + if (bi.signum() < 0) { + result = result.negate(); + } + + Bytes resultBytes = Bytes.wrap(result.toByteArray()); + if (resultBytes.size() > 32) { + resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); + } + + return UInt256.fromBytes(Bytes32.leftPad(resultBytes, result.signum() < 0 ? (byte) 0xFF : 0x00)); + } + + @Override + public UInt256 mod0(long modulus) { + if (modulus == 0) { + return UInt256.ZERO; + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + return mod(modulus); + } + + /** + * Return a bit-wise AND of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt256 and(UInt256 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] & value.ints[i]; + } + return new UInt256(result); + } + + /** + * Return a bit-wise AND of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise AND + */ + @Override + public UInt256 and(Bytes32 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(i + 2) & 0xFF) << 8; + other |= ((int) bytes.get(i + 3) & 0xFF); + result[i] = this.ints[i] & other; + } + return new UInt256(result); + } + + /** + * Return a bit-wise OR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt256 or(UInt256 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] | value.ints[i]; + } + return new UInt256(result); + } + + /** + * Return a bit-wise OR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise OR + */ + @Override + public UInt256 or(Bytes32 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + result[i] = this.ints[i] | (((int) bytes.get(j) & 0xFF) << 24); + result[i] |= ((int) bytes.get(j + 1) & 0xFF) << 16; + result[i] |= ((int) bytes.get(j + 2) & 0xFF) << 8; + result[i] |= ((int) bytes.get(j + 3) & 0xFF); + } + return new UInt256(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt256 xor(UInt256 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] ^ value.ints[i]; + } + return new UInt256(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise XOR + */ + @Override + public UInt256 xor(Bytes32 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + result[i] = this.ints[i] ^ (((int) bytes.get(j) & 0xFF) << 24); + result[i] ^= ((int) bytes.get(j + 1) & 0xFF) << 16; + result[i] ^= ((int) bytes.get(j + 2) & 0xFF) << 8; + result[i] ^= ((int) bytes.get(j + 3) & 0xFF); + } + return new UInt256(result); + } + + /** + * Return a bit-wise NOT of this value. + * + * @return the result of a bit-wise NOT + */ + @Override + public UInt256 not() { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = ~(this.ints[i]); + } + return new UInt256(result); + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + @Override + public UInt256 shiftRight(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 256) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = INTS_SIZE; + if (s == 0) { + for (int i = INTS_SIZE - d; i > 0;) { + result[--resIdx] = this.ints[--i]; + } + } else { + for (int i = INTS_SIZE - 1 - d; i >= 0; i--) { + int leftSide = this.ints[i] >>> s; + int rightSide = (i == 0) ? 0 : this.ints[i - 1] << (32 - s); + result[--resIdx] = (leftSide | rightSide); + } + } + return new UInt256(result); + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + @Override + public UInt256 shiftLeft(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 256) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = 0; + if (s == 0) { + for (int i = d; i < INTS_SIZE;) { + result[resIdx++] = this.ints[i++]; + } + } else { + for (int i = d; i < INTS_SIZE; ++i) { + int leftSide = this.ints[i] << s; + int rightSide = (i == INTS_SIZE - 1) ? 0 : (this.ints[i + 1] >>> (32 - s)); + result[resIdx++] = (leftSide | rightSide); + } + } + return new UInt256(result); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof UInt256) { + UInt256 other = (UInt256) object; + for (int i = 0; i < INTS_SIZE; ++i) { + if (this.ints[i] != other.ints[i]) { + return false; + } + } + return true; + } + if (object instanceof Bytes) { + Bytes other = (Bytes) object; + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (this.get(i) != other.get(i)) { + return false; + } + } + + return true; + } + return false; + } + + int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + get(i); + } + return result; + } + + @Override + public int hashCode() { + if (this.hashCode == null) { + this.hashCode = computeHashcode(); + } + return this.hashCode; + } + + @Override + public boolean fitsInt() { + for (int i = 0; i < INTS_SIZE - 1; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 1] >= 0; + } + + @Override + public int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return this.ints[INTS_SIZE - 1]; + } + + @Override + public boolean fitsLong() { + for (int i = 0; i < INTS_SIZE - 2; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 2] >= 0; + } + + @Override + public byte get(int i) { + int whichInt = i / 4; + int whichIndex = 3 - i % 4; + return (byte) ((this.ints[whichInt] >> (8 * whichIndex)) & 0xFF); + } + + @Override + public Bytes32 copy() { + return toBytes(); + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return (((long) this.ints[INTS_SIZE - 2]) << 32) | (((long) (this.ints[INTS_SIZE - 1])) & LONG_MASK); + } + + @Override + public String toString() { + return toHexString(); + } + + @Override + public UInt256 toUInt256() { + return this; + } + + private byte[] toByteArray() { + return new byte[] { + (byte) (ints[0] >> 24), + (byte) (ints[0] >> 16), + (byte) (ints[0] >> 8), + (byte) (ints[0]), + (byte) (ints[1] >> 24), + (byte) (ints[1] >> 16), + (byte) (ints[1] >> 8), + (byte) (ints[1]), + (byte) (ints[2] >> 24), + (byte) (ints[2] >> 16), + (byte) (ints[2] >> 8), + (byte) (ints[2]), + (byte) (ints[3] >> 24), + (byte) (ints[3] >> 16), + (byte) (ints[3] >> 8), + (byte) (ints[3]), + (byte) (ints[4] >> 24), + (byte) (ints[4] >> 16), + (byte) (ints[4] >> 8), + (byte) (ints[4]), + (byte) (ints[5] >> 24), + (byte) (ints[5] >> 16), + (byte) (ints[5] >> 8), + (byte) (ints[5]), + (byte) (ints[6] >> 24), + (byte) (ints[6] >> 16), + (byte) (ints[6] >> 8), + (byte) (ints[6]), + (byte) (ints[7] >> 24), + (byte) (ints[7] >> 16), + (byte) (ints[7] >> 8), + (byte) (ints[7])}; + } + + @Override + public Bytes slice(int i, int length) { + return toBytes().slice(i, length); + } + + @Override + public MutableBytes32 mutableCopy() { + return MutableBytes32.wrap(toByteArray()); + } + + @Override + public Bytes32 toBytes() { + return Bytes32.wrap(toByteArray()); + } + + @Override + public Bytes toMinimalBytes() { + int i = 0; + while (i < INTS_SIZE && this.ints[i] == 0) { + ++i; + } + if (i == INTS_SIZE) { + return Bytes.EMPTY; + } + int firstIntBytes = 4 - (Integer.numberOfLeadingZeros(this.ints[i]) / 8); + int totalBytes = firstIntBytes + ((INTS_SIZE - (i + 1)) * 4); + MutableBytes bytes = MutableBytes.create(totalBytes); + int j = 0; + switch (firstIntBytes) { + case 4: + bytes.set(j++, (byte) (this.ints[i] >>> 24)); + // fall through + case 3: + bytes.set(j++, (byte) ((this.ints[i] >>> 16) & 0xFF)); + // fall through + case 2: + bytes.set(j++, (byte) ((this.ints[i] >>> 8) & 0xFF)); + // fall through + case 1: + bytes.set(j++, (byte) (this.ints[i] & 0xFF)); + } + ++i; + for (; i < INTS_SIZE; ++i, j += 4) { + bytes.setInt(j, this.ints[i]); + } + return bytes; + } + + @Override + public int numberOfLeadingZeros() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (i * 32) + Integer.numberOfLeadingZeros(this.ints[i]); + } + return 256; + } + + @Override + public int bitLength() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (INTS_SIZE * 32) - (i * 32) - Integer.numberOfLeadingZeros(this.ints[i]); + } + return 0; + } + + @Override + public UInt256 max() { + return UInt256.MAX_VALUE; + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(long v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256Value.java new file mode 100644 index 00000000000..c31e2f3b22a --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256Value.java @@ -0,0 +1,597 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.math.BigInteger; +import java.nio.ByteOrder; + +import static java.nio.ByteOrder.BIG_ENDIAN; + + +/** + * Represents a 256-bit (32 bytes) unsigned integer value. + * + *

+ * A {@link UInt256Value} is an unsigned integer value stored with 32 bytes, so whose value can range between 0 and + * 2^256-1. + * + *

+ * This interface defines operations for value types with a 256-bit precision range. The methods provided by this + * interface take parameters of the same type (and also {@code long}. This provides type safety by ensuring calculations + * cannot mix different {@code UInt256Value} types. + * + *

+ * Where only a pure numerical 256-bit value is required, {@link UInt256} should be used. + * + *

+ * It is strongly advised to extend {@link BaseUInt256Value} rather than implementing this interface directly. Doing so + * provides type safety in that quantities of different units cannot be mixed accidentally. + * + * @param The concrete type of the value. + */ +public interface UInt256Value> extends Bytes32 { + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(T value); + + /** + * Returns a value that is {@code (this + value)}. + * + *

+ * This notation can be used in Kotlin with the {@code +} operator. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + default T plus(T value) { + return add(value); + } + + /** + * Returns a value that is {@code (this + value)}. + * + *

+ * This notation can be used in Kotlin with the {@code +} operator. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + default T plus(long value) { + return add(value); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(T value) { + T result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}, or MAX_VALUE if it overflows. + * + * @param value the amount to be added to this value + * @return {@code this + value} or UInt256.MAX + */ + default T addSafe(T value) { + T result = add(value); + if (compareTo(result) > 0) { + return max(); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(long value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(long value) { + T result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}, or MAX_VALUE if it overflows. + * + * @param value the amount to be added to this value + * @return {@code this + value} or UInt256.MAX + */ + default T addSafe(long value) { + T result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + return max(); + } + return result; + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(T value, UInt256 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(long value, UInt256 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T addMod(long value, long modulus); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(T value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T subtractExact(T value) { + T result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(long value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T subtractExact(long value) { + T result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Return the max value for this type. + * + *

+ * The default implementation of this method returns a value obtained from calling the concrete type constructor with + * an argument of {@link UInt256#MAX_VALUE}. Most implementations will want to override this method to instead return + * a static constant. + * + * @return The max value for this type. + */ + public T max(); + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + T multiply(T value); + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + * @throws ArithmeticException {@code value} < 0. + */ + T multiply(long value); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(T value, UInt256 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(long value, UInt256 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} ≤ 0. + */ + T multiplyMod(long value, long modulus); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + T divide(T value); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} ≤ 0. + */ + T divide(long value); + + /** + * Returns a value that is {@code ceiling(this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value + ( this % value == 0 ? 0 : 1)} + * @throws ArithmeticException {@code value} == 0. + */ + T divideCeil(T value); + + /** + * Returns a value that is {@code ceiling(this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value + ( this % value == 0 ? 0 : 1)} + * @throws ArithmeticException {@code value} == 0. + */ + T divideCeil(long value); + + /** + * Returns a value that is {@code (thisexponent mod 2256)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^256}. + * + *

+ * Note that {@code exponent} is an {@link UInt256} rather than of the type {@code T}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 2256} + */ + T pow(UInt256 exponent); + + /** + * Returns a value that is {@code (thisexponent mod 2256)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^256}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 2256} + */ + T pow(long exponent); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} == 0. + */ + T mod(UInt256 modulus); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T mod(long modulus); + + /** + * Returns a value that is {@code (this mod modulus)}, or 0 if modulus is 0. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + */ + T mod0(UInt256 modulus); + + /** + * Returns a value that is {@code (this mod modulus)}, or 0 if modulus is 0. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + */ + T mod0(long modulus); + + /** + * Returns true if the value can fit in an int. + * + * @return True if this value fits a java {@code int} (i.e. is less or equal to {@code Integer.MAX_VALUE}). + */ + default boolean fitsInt() { + return fitsInt(ByteOrder.BIG_ENDIAN); + } + + /** + * Returns true if the value can fit in an int according to the byte order. + * + * @param order the byte order, little or big endian + * @return True if this value fits a java {@code int} (i.e. is less or equal to {@code Integer.MAX_VALUE}). + */ + default boolean fitsInt(ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + // Ints are 4 bytes, so anything but the 4 last bytes must be zeroes + for (int i = 0; i < Bytes32.SIZE - 4; i++) { + if (get(i) != 0) + return false; + } + // Lastly, the left-most byte of the int must not start with a 1. + return get(Bytes32.SIZE - 4) >= 0; + } else { + // Ints are 4 bytes, so only the 4 first bytes must not be zeroes + for (int i = 4; i < Bytes32.SIZE - 4; i++) { + if (get(i) != 0) + return false; + } + // Lastly, the right-most byte of the int must not start with a 1. + return get(3) >= 0; + } + } + + /** + * Provides this value as an int. + * + * @return This value as a java {@code int} assuming it is small enough to fit an {@code int}. + * @throws ArithmeticException If the value does not fit an {@code int}, that is if {@code !fitsInt()}. + */ + default int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return getInt(Bytes32.SIZE - 4); + } + + @Override + default int toInt(ByteOrder order) { + if (!fitsInt(order)) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + if (order == ByteOrder.BIG_ENDIAN) { + return getInt(Bytes32.SIZE - 4, order); + } else { + return getInt(0, order); + } + } + + @Override + default long toLong(ByteOrder order) { + if (!fitsLong(order)) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + if (order == ByteOrder.BIG_ENDIAN) { + return getLong(Bytes32.SIZE - 8, order); + } else { + return getLong(0, order); + } + } + + /** + * Returns true if the value can fit in a long. + * + * @return True if this value fits a java {@code long} (i.e. is less or equal to {@code Long.MAX_VALUE}). + */ + default boolean fitsLong() { + return fitsLong(ByteOrder.BIG_ENDIAN); + } + + /** + * Returns true if the value can fit in a long. + * + * @param order byte order, little or big endian + * @return True if this value fits a java {@code long} (i.e. is less or equal to {@code Long.MAX_VALUE}). + */ + default boolean fitsLong(ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + // Longs are 8 bytes, so anything but the 8 last bytes must be zeroes + for (int i = 0; i < Bytes32.SIZE - 8; i++) { + if (get(i) != 0) + return false; + } + // Lastly, the left-most byte of the long must not start with a 1. + return get(Bytes32.SIZE - 8) >= 0; + } else { + // Longs are 8 bytes, so only the 8 first bytes may not be zeroes + for (int i = 8; i < Bytes32.SIZE; i++) { + if (get(i) != 0) + return false; + } + // Lastly, the left-most byte of the long must not start with a 1. + return get(7) >= 0; + } + } + + /** + * Convert this value to a {@link UInt256}. + * + * @return This value as a {@link UInt256}. + */ + UInt256 toUInt256(); + + /** + * Provides the value as bytes. + * + * @return The value as bytes. + */ + Bytes32 toBytes(); + + /** + * Provides the value as bytes without any leading zero bytes. + * + * @return The value as bytes without any leading zero bytes. + */ + Bytes toMinimalBytes(); + + /** + * Returns true if this value is greater than the other one + * + * @param other the other value being compared + * @return true if this value is greater than the other one, false otherwise + */ + default boolean greaterThan(UInt256Value other) { + return compareTo(other) > 0; + } + + /** + * Returns true if this value is greater or equal than the other one + * + * @param other the other value being compared + * @return true if this value is greater or equal than the other one, false otherwise + */ + default boolean greaterOrEqualThan(UInt256Value other) { + return compareTo(other) >= 0; + } + + /** + * Returns true if this value is less than the other one + * + * @param other the other value being compared + * @return true if this value is less than the other one, false otherwise + */ + default boolean lessThan(UInt256Value other) { + return compareTo(other) < 0; + } + + /** + * Returns true if this value is less or equal than the other one + * + * @param other the other value being compared + * @return true if this value is less or equal than the other one, false otherwise + */ + default boolean lessOrEqualThan(UInt256Value other) { + return compareTo(other) <= 0; + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + default String toDecimalString() { + return toBigInteger().toString(10); + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement signed integer. + */ + default BigInteger toSignedBigInteger() { + return toSignedBigInteger(BIG_ENDIAN); + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @param order The byte-order for decoding the integer. + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement signed integer. + */ + default BigInteger toSignedBigInteger(ByteOrder order) { + if (size() == 0) { + return BigInteger.ZERO; + } + return new BigInteger((order == BIG_ENDIAN) ? toArrayUnsafe() : reverse().toArrayUnsafe()); + } + + /** + * The BigInteger corresponding to interpreting these bytes as an unsigned integer. + * + * @return A positive (or zero) {@link BigInteger} corresponding to interpreting these bytes as an unsigned integer. + */ + @Override + default BigInteger toBigInteger() { + return toUnsignedBigInteger(); + } + + /** + * The BigInteger corresponding to interpreting these bytes as an unsigned integer. + * + * @param order The byte-order for decoding the integer. + * @return A positive (or zero) {@link BigInteger} corresponding to interpreting these bytes as an unsigned integer. + */ + @Override + default BigInteger toBigInteger(ByteOrder order) { + return toUnsignedBigInteger(order); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256s.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256s.java new file mode 100644 index 00000000000..53a26ad5210 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt256s.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +/** Static utility methods on UInt256 values. */ +public final class UInt256s { + private UInt256s() {} + + /** + * Returns the maximum of two UInt256 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The maximum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T max(T v1, T v2) { + return (v1.compareTo(v2)) >= 0 ? v1 : v2; + } + + /** + * Returns the minimum of two UInt256 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The minimum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T min(T v1, T v2) { + return (v1.compareTo(v2)) < 0 ? v1 : v2; + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32.java new file mode 100644 index 00000000000..c93f70c361a --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32.java @@ -0,0 +1,583 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; + +import java.math.BigInteger; +import java.nio.ByteOrder; + +/** + * An unsigned 32-bit precision number. + *

+ * This is a raw {@link UInt32Value} - a 32-bit precision unsigned number of no particular unit. + */ +public final class UInt32 implements UInt32Value { + private final static int MAX_CONSTANT = 0xff; + private static UInt32[] CONSTANTS = new UInt32[MAX_CONSTANT + 1]; + + static { + CONSTANTS[0] = new UInt32(new byte[4]); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt32( + new byte[] { + (byte) ((i >> 24) & 0xff), + (byte) ((i >> 16) & 0xff), + (byte) ((i >> 8) & 0xff), + (byte) ((i >> 0) & 0xff)}); + } + } + + /** + * The minimum value of a UInt32 + */ + public final static UInt32 MIN_VALUE = valueOf(0); + /** + * The maximum value of a UInt32 + */ + public final static UInt32 MAX_VALUE = create(new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); + /** + * The value 0 + */ + public final static UInt32 ZERO = valueOf(0); + /** + * The value 1 + */ + public final static UInt32 ONE = valueOf(1); + + private static final BigInteger P_2_32 = BigInteger.valueOf(2).pow(32); + + private final Bytes value; + + /** + * Return a {@code UInt32} containing the specified value. + * + * @param value The value to create a {@code UInt32} for. + * @return A {@code UInt32} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt32 valueOf(int value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + return create(value); + } + + /** + * Return a {@link UInt32} containing the specified value. + * + * @param value the value to create a {@link UInt32} for + * @return a {@link UInt32} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a UInt32 + */ + public static UInt32 valueOf(BigInteger value) { + if (value.bitLength() > 32) { + throw new IllegalArgumentException("Argument is too large to represent a UInt32"); + } + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + return create(value.toByteArray()); + } + + /** + * Return a {@link UInt32} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt32}. \ * @return A {@link UInt32} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 4}. + */ + public static UInt32 fromBytes(Bytes bytes) { + return fromBytes(bytes, ByteOrder.BIG_ENDIAN); + } + + /** + * Return a {@link UInt32} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt32}. + * @param byteOrder the byte order of the value + * @return A {@link UInt32} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 4}. + */ + public static UInt32 fromBytes(Bytes bytes, ByteOrder byteOrder) { + if (bytes.size() > 4) { + throw new IllegalArgumentException("Argument is greater than 4 bytes"); + } + return create(byteOrder == ByteOrder.LITTLE_ENDIAN ? bytes.reverse() : bytes); + } + + /** + * Parse a hexadecimal string into a {@link UInt32}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 8 bytes, in which case the result is left padded with zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or + * contains more than 8 bytes. + */ + public static UInt32 fromHexString(String str) { + return fromBytes(Bytes.fromHexStringLenient(str)); + } + + private static UInt32 create(Bytes value) { + return create(value.toArrayUnsafe()); + } + + private static UInt32 create(byte[] value) { + if (value.length == 4 && value[0] == 0 && value[1] == 0 && value[2] == 0) { + return CONSTANTS[value[3] & 0xff]; + } + if (value.length == 3) { + value = new byte[] {0, value[0], value[1], value[2]}; + } else if (value.length == 2) { + value = new byte[] {0, 0, value[0], value[1]}; + } else if (value.length == 1) { + value = new byte[] {0, 0, 0, value[0]}; + } else if (value.length == 0) { + value = new byte[4]; + } + return new UInt32(value); + } + + private static UInt32 create(int value) { + if (value >= 0 && value <= MAX_CONSTANT) { + return CONSTANTS[value]; + } + return new UInt32( + new byte[] { + (byte) ((value >> 24) & 0xff), + (byte) ((value >> 16) & 0xff), + (byte) ((value >> 8) & 0xff), + (byte) ((value >> 0) & 0xff)}); + } + + private UInt32(byte[] bytes) { + this.value = Bytes.wrap(bytes); + } + + @Override + public boolean isZero() { + return ZERO.equals(this); + } + + @Override + public UInt32 add(UInt32 value) { + if (value.isZero()) { + return this; + } + if (this.isZero()) { + return value; + } + byte[] result = new byte[4]; + int carry = 0; + for (int i = 3; i >= 0; i--) { + int sum = (this.value.get(i) & 0xff) + (value.value.get(i) & 0xff) + carry; + result[i] = (byte) sum; + carry = sum >>> 8; + } + return create(result); + } + + @Override + public UInt32 add(int value) { + if (value == 0) { + return this; + } + return create(this.value.toInt() + value); + } + + @Override + public UInt32 addMod(UInt32 value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger()).intValue()); + } + + @Override + public UInt32 addMod(long value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create(toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).intValue()); + } + + @Override + public UInt32 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return create(toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus)).intValue()); + } + + @Override + public UInt32 subtract(UInt32 value) { + if (value.isZero()) { + return this; + } + + byte[] result = new byte[4]; + int borrow = 0; + for (int i = 3; 0 <= i; i--) { + int i1 = this.value.get(i) & 0xff; + int i2 = value.value.get(i) & 0xff; + int col = i1 - i2 + borrow; + borrow = (col >> 8); + result[i] = (byte) (col & 0xff); + } + return create(result); + } + + @Override + public UInt32 subtract(int value) { + return subtract(UInt32.create(value)); + } + + @Override + public UInt32 multiply(UInt32 value) { + return create(this.value.toInt() * value.value.toInt()); + } + + @Override + public UInt32 multiply(int value) { + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return this; + } + return multiply(UInt32.valueOf(value)); + } + + @Override + public UInt32 multiplyMod(UInt32 value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (isZero() || value.isZero()) { + return ZERO; + } + if (ONE.equals(value)) { + return mod(modulus); + } + return create(toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger()).intValue()); + } + + @Override + public UInt32 multiplyMod(int value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || this.isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create(toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).intValue()); + } + + @Override + public UInt32 multiplyMod(int value, int modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || this.isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create(toBigInteger().multiply(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus)).intValue()); + } + + @Override + public UInt32 divide(UInt32 value) { + if (value.isZero()) { + throw new ArithmeticException("divide by zero"); + } + + if (value.equals(ONE)) { + return this; + } + return create(toBigInteger().divide(value.toBigInteger()).intValue()); + } + + @Override + public UInt32 divide(int value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return shiftRight(log2(value)); + } + return create(toBigInteger().divide(BigInteger.valueOf(value)).intValue()); + } + + @Override + public UInt32 pow(UInt32 exponent) { + return create(toBigInteger().modPow(exponent.toBigInteger(), P_2_32).intValue()); + } + + @Override + public UInt32 pow(long exponent) { + return create(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_32).intValue()); + } + + @Override + public UInt32 mod(UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return create(Integer.remainderUnsigned(this.value.toInt(), modulus.value.toInt())); + } + + @Override + public UInt32 mod(int modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + return create(Integer.remainderUnsigned(this.value.toInt(), modulus)); + } + + /** + * Return a bit-wise AND of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt32 and(UInt32 value) { + if (this.isZero() || value.isZero()) { + return ZERO; + } + return create(this.value.toInt() & value.value.toInt()); + } + + /** + * Return a bit-wise AND of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise AND + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt32 and(Bytes bytes) { + if (bytes.size() > 4) { + throw new IllegalArgumentException("and with more than 4 bytes"); + } + if (this.isZero()) { + return ZERO; + } + int value = bytes.toInt(); + if (value == 0) { + return ZERO; + } + return create(this.value.toInt() & value); + } + + /** + * Return a bit-wise OR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt32 or(UInt32 value) { + return create(this.value.or(value.value)); + } + + /** + * Return a bit-wise OR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise OR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt32 or(Bytes bytes) { + if (bytes.size() > 4) { + throw new IllegalArgumentException("or with more than 4 bytes"); + } + return create(this.value.or(bytes)); + } + + /** + * Return a bit-wise XOR of this value and the supplied value. + *

+ * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt32 xor(UInt32 value) { + return create(this.value.xor(value.value)); + } + + + /** + * Return a bit-wise XOR of this value and the supplied value. + *

+ * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt32 xor(int value) { + return create(this.value.toInt() ^ value); + } + + /** + * Return a bit-wise XOR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise XOR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt32 xor(Bytes bytes) { + if (bytes.size() > 4) { + throw new IllegalArgumentException("xor with more than 4 bytes"); + } + return create(this.value.xor(bytes).toArrayUnsafe()); + } + + /** + * Return a bit-wise NOT of this value. + * + * @return the result of a bit-wise NOT + */ + public UInt32 not() { + return create(this.value.not()); + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt32 shiftRight(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 32) { + return ZERO; + } + return create(this.value.shiftRight(distance)); + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt32 shiftLeft(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 32) { + return ZERO; + } + return create(this.value.shiftLeft(distance)); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof UInt32)) { + return false; + } + UInt32 other = (UInt32) object; + return this.value.equals(other.value); + } + + @Override + public int hashCode() { + return Long.hashCode(this.value.toInt()); + } + + @Override + public int compareTo(UInt32 other) { + return Long.compareUnsigned(this.value.toInt(), other.value.toInt()); + } + + @Override + public String toString() { + return toHexString(); + } + + @Override + public BigInteger toBigInteger() { + return value.toUnsignedBigInteger(); + } + + @Override + public UInt32 toUInt32() { + return this; + } + + @Override + public Bytes toBytes() { + return value; + } + + @Override + public Bytes toMinimalBytes() { + return value.slice(value.numberOfLeadingZeroBytes()); + } + + @Override + public int numberOfLeadingZeros() { + return value.numberOfLeadingZeros(); + } + + @Override + public int bitLength() { + return 32 - value.numberOfLeadingZeros(); + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(int v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } + +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32Value.java new file mode 100644 index 00000000000..c45b2a9bcae --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32Value.java @@ -0,0 +1,402 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + + +import org.apache.tuweni.bytes.Bytes; + +import java.math.BigInteger; + +/** + * Represents a 32-bit (8 bytes) unsigned integer value. + * + *

+ * A {@link UInt32Value} is an unsigned integer value whose value can range between 0 and 2^32-1. + * + *

+ * This interface defines operations for value types with a 32-bit precision range. The methods provided by this + * interface take parameters of the same type (and also {@code long}. This provides type safety by ensuring calculations + * cannot mix different {@code UInt32Value} types. + * + *

+ * Where only a pure numerical 32-bit value is required, {@link UInt32} should be used. + * + *

+ * It is strongly advised to extend {@link BaseUInt32Value} rather than implementing this interface directly. Doing so + * provides type safety in that quantities of different units cannot be mixed accidentally. + * + * @param The concrete type of the value. + */ +public interface UInt32Value> extends Comparable { + + /** + * Returns true is this is 0. + * + * @return True if this is the value 0. + */ + default boolean isZero() { + return toBytes().isZero(); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(T value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(T value) { + T result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(int value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(int value) { + T result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(T value, UInt32 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(long value, UInt32 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T addMod(long value, long modulus); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(T value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + default T subtractExact(T value) { + T result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(int value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + default T subtractExact(int value) { + T result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + T multiply(T value); + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + * @throws ArithmeticException {@code value} < 0. + */ + T multiply(int value); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(T value, UInt32 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(int value, UInt32 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} ≤ 0. + */ + T multiplyMod(int value, int modulus); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + T divide(T value); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} ≤ 0. + */ + T divide(int value); + + /** + * Returns a value that is {@code (thisexponent mod 232)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^32}. + * + *

+ * Note that {@code exponent} is an {@link UInt32} rather than of the type {@code T}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 232} + */ + T pow(UInt32 exponent); + + /** + * Returns a value that is {@code (thisexponent mod 232)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^32}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 232} + */ + T pow(long exponent); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} == 0. + */ + T mod(UInt32 modulus); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T mod(int modulus); + + /** + * Returns true if this value fits an int. + * + * @return True if this value fits a java {@code int} (i.e. is less or equal to {@code Integer.MAX_VALUE}). + */ + default boolean fitsInt() { + return !UInt32.MAX_VALUE.equals(this); + } + + /** + * Returns this value as an int. + * + * @return This value as a java {@code int} assuming it is small enough to fit an {@code int}. + * @throws ArithmeticException If the value does not fit an {@code int}, that is if {@code + * !fitsInt()}. + */ + default int intValue() { + return toBigInteger().intValueExact(); + } + + /** + * Returns true if this value fits a long. + * + * @return True if this value fits a java {@code long} (i.e. is less or equal to {@code Long.MAX_VALUE}). + */ + default boolean fitsLong() { + return true; + } + + /** + * Returns this value as a long. + * + * @return This value as a java {@code long} assuming it is small enough to fit a {@code long}. + * @throws ArithmeticException If the value does not fit a {@code long}, that is if {@code + * !fitsLong()}. + */ + default long toLong() { + return toBigInteger().longValueExact(); + } + + /** + * Provides this value as a BigInteger. + * + * @return This value as a {@link BigInteger}. + */ + default BigInteger toBigInteger() { + return toBytes().toUnsignedBigInteger(); + } + + /** + * This value represented as an hexadecimal string. + * + *

+ * Note that this representation includes all the 8 underlying bytes, no matter what the integer actually represents + * (in other words, it can have many leading zeros). For a shorter representation that don't include leading zeros, + * use {@link #toShortHexString}. + * + * @return This value represented as an hexadecimal string. + */ + default String toHexString() { + return toBytes().toHexString(); + } + + /** + * Returns this value represented as a minimal hexadecimal string (without any leading zero). + * + * @return This value represented as a minimal hexadecimal string (without any leading zero). + */ + default String toShortHexString() { + return toBytes().toShortHexString(); + } + + /** + * Convert this value to a {@link UInt32}. + * + * @return This value as a {@link UInt32}. + */ + UInt32 toUInt32(); + + /** + * Provides the value as bytes. + * + * @return The value as bytes. + */ + Bytes toBytes(); + + /** + * Provides the value as bytes without any leading zero bytes + * + * @return The value as bytes without any leading zero bytes. + */ + Bytes toMinimalBytes(); + + /** + * Provides the number of zero bits preceding the highest-order ("leftmost") one-bit + * + * @return the number of zero bits preceding the highest-order ("leftmost") one-bit in the binary representation of + * this value, or 32 if the value is equal to zero. + */ + default int numberOfLeadingZeros() { + return toBytes().numberOfLeadingZeros(); + } + + /** + * Provides the number of bits following and including the highest-order ("leftmost") one-bit + * + * @return The number of bits following and including the highest-order ("leftmost") one-bit in the binary + * representation of this value, or zero if all bits are zero. + */ + default int bitLength() { + return toBytes().bitLength(); + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + default String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32s.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32s.java new file mode 100644 index 00000000000..b4f33248d9e --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt32s.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +/** Static utility methods on UInt32 values. */ +public final class UInt32s { + private UInt32s() {} + + /** + * Returns the maximum of two UInt32 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The maximum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T max(T v1, T v2) { + return (v1.compareTo(v2)) >= 0 ? v1 : v2; + } + + /** + * Returns the minimum of two UInt32 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The minimum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T min(T v1, T v2) { + return (v1.compareTo(v2)) < 0 ? v1 : v2; + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384.java new file mode 100644 index 00000000000..3e2872de4ca --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384.java @@ -0,0 +1,786 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes48; +import org.apache.tuweni.bytes.MutableBytes; +import org.apache.tuweni.bytes.MutableBytes48; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * An unsigned 384-bit precision number. + * + * This is a raw {@link UInt384Value} - a 384-bit precision unsigned number of no particular unit. + */ +public final class UInt384 implements UInt384Value { + private final static int MAX_CONSTANT = 64; + private final static BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); + private static UInt384[] CONSTANTS = new UInt384[MAX_CONSTANT + 1]; + static { + CONSTANTS[0] = new UInt384(Bytes48.ZERO); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt384(i); + } + } + + /** The minimum value of a UInt384 */ + public final static UInt384 MIN_VALUE = valueOf(0); + /** The maximum value of a UInt384 */ + public final static UInt384 MAX_VALUE = new UInt384(Bytes48.ZERO.not()); + /** The value 0 */ + public final static UInt384 ZERO = valueOf(0); + /** The value 1 */ + public final static UInt384 ONE = valueOf(1); + + private static final int INTS_SIZE = 48 / 4; + // The mask is used to obtain the value of an int as if it were unsigned. + private static final long LONG_MASK = 0xFFFFFFFFL; + private static final BigInteger P_2_384 = BigInteger.valueOf(2).pow(384); + + // The unsigned int components of the value + private final int[] ints; + + /** + * Return a {@code UInt384} containing the specified value. + * + * @param value The value to create a {@code UInt384} for. + * @return A {@code UInt384} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt384 valueOf(long value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value <= MAX_CONSTANT) { + return CONSTANTS[(int) value]; + } + return new UInt384(value); + } + + /** + * Return a {@link UInt384} containing the specified value. + * + * @param value the value to create a {@link UInt384} for + * @return a {@link UInt384} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a UInt384 + */ + public static UInt384 valueOf(BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value.bitLength() > 384) { + throw new IllegalArgumentException("Argument is too large to represent a UInt384"); + } + if (value.compareTo(BI_MAX_CONSTANT) <= 0) { + return CONSTANTS[value.intValue()]; + } + int[] ints = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + ints[i] = value.intValue(); + value = value.shiftRight(32); + } + return new UInt384(ints); + } + + /** + * Return a {@link UInt384} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt384}. + * @return A {@link UInt384} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 48}. + */ + public static UInt384 fromBytes(Bytes bytes) { + return new UInt384(Bytes48.leftPad(bytes)); + } + + /** + * Parse a hexadecimal string into a {@link UInt384}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 48 bytes, in which case the result is left padded with zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or + * contains more than 48 bytes. + */ + public static UInt384 fromHexString(String str) { + return new UInt384(Bytes48.fromHexStringLenient(str)); + } + + private UInt384(Bytes48 bytes) { + this.ints = new int[INTS_SIZE]; + for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { + ints[i] = bytes.getInt(j); + } + } + + private UInt384(long value) { + this.ints = new int[INTS_SIZE]; + this.ints[INTS_SIZE - 2] = (int) ((value >>> 32) & LONG_MASK); + this.ints[INTS_SIZE - 1] = (int) (value & LONG_MASK); + } + + private UInt384(int[] ints) { + this.ints = ints; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isZero() { + if (this == ZERO) { + return true; + } + for (int i = INTS_SIZE - 1; i >= 0; --i) { + if (this.ints[i] != 0) { + return false; + } + } + return true; + } + + @Override + public UInt384 add(UInt384 value) { + if (value.isZero()) { + return this; + } + if (isZero()) { + return value; + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value.ints[INTS_SIZE - 1] & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + (value.ints[i] & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt384(result); + } + + @Override + public UInt384 add(long value) { + if (value == 0) { + return this; + } + if (value > 0 && isZero()) { + return UInt384.valueOf(value); + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + sum = (this.ints[INTS_SIZE - 2] & LONG_MASK) + (value >>> 32) + (sum >>> 32); + result[INTS_SIZE - 2] = (int) (sum & LONG_MASK); + constant &= result[INTS_SIZE - 2] == 0; + long signExtent = (value >> 63) & LONG_MASK; + for (int i = INTS_SIZE - 3; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + signExtent + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt384(result); + } + + @Override + public UInt384 addMod(UInt384 value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt384.valueOf(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger())); + } + + @Override + public UInt384 addMod(long value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt384.valueOf(toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger())); + } + + @Override + public UInt384 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return UInt384.valueOf(toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + @Override + public UInt384 subtract(UInt384 value) { + if (value.isZero()) { + return this; + } + + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + ((~value.ints[INTS_SIZE - 1]) & LONG_MASK) + 1; + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + ((~value.ints[i]) & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt384(result); + } + + @Override + public UInt384 subtract(long value) { + return add(-value); + } + + @Override + public UInt384 multiply(UInt384 value) { + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt384.ONE)) { + return this; + } + return multiply(this.ints, value.ints); + } + + private static UInt384 multiply(int[] x, int[] y) { + int[] result = new int[INTS_SIZE + INTS_SIZE]; + + long carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + INTS_SIZE - 1; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[INTS_SIZE - 1] & LONG_MASK) + carry; + result[k] = (int) product; + carry = product >>> 32; + } + result[INTS_SIZE - 1] = (int) carry; + + for (int i = INTS_SIZE - 2; i >= 0; i--) { + carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + i; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[i] & LONG_MASK) + (result[k] & LONG_MASK) + carry; + + result[k] = (int) product; + carry = product >>> 32; + } + result[i] = (int) carry; + } + + boolean constant = true; + for (int i = INTS_SIZE; i < (INTS_SIZE + INTS_SIZE) - 2; ++i) { + constant &= (result[i] == 0); + } + if (constant && result[INTS_SIZE + INTS_SIZE - 1] >= 0 && result[INTS_SIZE + INTS_SIZE - 1] <= MAX_CONSTANT) { + return CONSTANTS[result[INTS_SIZE + INTS_SIZE - 1]]; + } + return new UInt384(Arrays.copyOfRange(result, INTS_SIZE, INTS_SIZE + INTS_SIZE)); + } + + @Override + public UInt384 multiply(long value) { + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return this; + } + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + UInt384 other = new UInt384(value); + return multiply(this.ints, other.ints); + } + + @Override + public UInt384 multiplyMod(UInt384 value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt384.ONE)) { + return mod(modulus); + } + return UInt384.valueOf(toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger())); + } + + @Override + public UInt384 multiplyMod(long value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt384.valueOf(toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger())); + } + + @Override + public UInt384 multiplyMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt384.valueOf(toBigInteger().multiply(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + @Override + public UInt384 divide(UInt384 value) { + if (value.isZero()) { + throw new ArithmeticException("divide by zero"); + } + if (value.equals(UInt384.ONE)) { + return this; + } + return UInt384.valueOf(toBigInteger().divide(value.toBigInteger())); + } + + @Override + public UInt384 divide(long value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return shiftRight(log2(value)); + } + return UInt384.valueOf(toBigInteger().divide(BigInteger.valueOf(value))); + } + + @Override + public UInt384 pow(UInt384 exponent) { + return UInt384.valueOf(toBigInteger().modPow(exponent.toBigInteger(), P_2_384)); + } + + @Override + public UInt384 pow(long exponent) { + return UInt384.valueOf(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_384)); + } + + @Override + public UInt384 mod(UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return UInt384.valueOf(toBigInteger().mod(modulus.toBigInteger())); + } + + @Override + public UInt384 mod(long modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + if (isPowerOf2(modulus)) { + int log2 = log2(modulus); + int d = log2 / 32; + int s = log2 % 32; + assert (d == 0 || d == 1); + + int[] result = new int[INTS_SIZE]; + // Mask the byte at d to only include the s right-most bits + result[INTS_SIZE - 1 - d] = this.ints[INTS_SIZE - 1 - d] & ~(0xFFFFFFFF << s); + if (d != 0) { + result[INTS_SIZE - 1] = this.ints[INTS_SIZE - 1]; + } + return new UInt384(result); + } + return UInt384.valueOf(toBigInteger().mod(BigInteger.valueOf(modulus))); + } + + /** + * Return a bit-wise AND of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt384 and(UInt384 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] & value.ints[i]; + } + return new UInt384(result); + } + + /** + * Return a bit-wise AND of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt384 and(Bytes48 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(i + 2) & 0xFF) << 8; + other |= ((int) bytes.get(i + 3) & 0xFF); + result[i] = this.ints[i] & other; + } + return new UInt384(result); + } + + /** + * Return a bit-wise OR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt384 or(UInt384 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] | value.ints[i]; + } + return new UInt384(result); + } + + /** + * Return a bit-wise OR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt384 or(Bytes48 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + result[i] = this.ints[i] | (((int) bytes.get(j) & 0xFF) << 24); + result[i] |= ((int) bytes.get(j + 1) & 0xFF) << 16; + result[i] |= ((int) bytes.get(j + 2) & 0xFF) << 8; + result[i] |= ((int) bytes.get(j + 3) & 0xFF); + } + return new UInt384(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt384 xor(UInt384 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] ^ value.ints[i]; + } + return new UInt384(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt384 xor(Bytes48 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + result[i] = this.ints[i] ^ (((int) bytes.get(j) & 0xFF) << 24); + result[i] ^= ((int) bytes.get(j + 1) & 0xFF) << 16; + result[i] ^= ((int) bytes.get(j + 2) & 0xFF) << 8; + result[i] ^= ((int) bytes.get(j + 3) & 0xFF); + } + return new UInt384(result); + } + + /** + * Return a bit-wise NOT of this value. + * + * @return the result of a bit-wise NOT + */ + public UInt384 not() { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = ~(this.ints[i]); + } + return new UInt384(result); + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt384 shiftRight(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 384) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = INTS_SIZE; + if (s == 0) { + for (int i = INTS_SIZE - d; i > 0;) { + result[--resIdx] = this.ints[--i]; + } + } else { + for (int i = INTS_SIZE - 1 - d; i >= 0; i--) { + int leftSide = this.ints[i] >>> s; + int rightSide = (i == 0) ? 0 : this.ints[i - 1] << (32 - s); + result[--resIdx] = (leftSide | rightSide); + } + } + return new UInt384(result); + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt384 shiftLeft(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 384) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = 0; + if (s == 0) { + for (int i = d; i < INTS_SIZE;) { + result[resIdx++] = this.ints[i++]; + } + } else { + for (int i = d; i < INTS_SIZE; ++i) { + int leftSide = this.ints[i] << s; + int rightSide = (i == INTS_SIZE - 1) ? 0 : (this.ints[i + 1] >>> (32 - s)); + result[resIdx++] = (leftSide | rightSide); + } + } + return new UInt384(result); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof UInt384)) { + return false; + } + UInt384 other = (UInt384) object; + for (int i = 0; i < INTS_SIZE; ++i) { + if (this.ints[i] != other.ints[i]) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < INTS_SIZE; ++i) { + result = 31 * result + this.ints[i]; + } + return result; + } + + @Override + public int compareTo(UInt384 other) { + for (int i = 0; i < INTS_SIZE; ++i) { + int cmp = Long.compare(((long) this.ints[i]) & LONG_MASK, ((long) other.ints[i]) & LONG_MASK); + if (cmp != 0) { + return cmp; + } + } + return 0; + } + + @Override + public boolean fitsInt() { + for (int i = 0; i < INTS_SIZE - 1; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 1] >= 0; + } + + @Override + public int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return this.ints[INTS_SIZE - 1]; + } + + @Override + public boolean fitsLong() { + for (int i = 0; i < INTS_SIZE - 2; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 2] >= 0; + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return (((long) this.ints[INTS_SIZE - 2]) << 32) | (((long) (this.ints[INTS_SIZE - 1])) & LONG_MASK); + } + + @Override + public String toString() { + return toBigInteger().toString(); + } + + @Override + public BigInteger toBigInteger() { + byte[] mag = new byte[48]; + for (int i = 0, j = 0; i < INTS_SIZE; ++i) { + mag[j++] = (byte) (this.ints[i] >>> 24); + mag[j++] = (byte) ((this.ints[i] >>> 16) & 0xFF); + mag[j++] = (byte) ((this.ints[i] >>> 8) & 0xFF); + mag[j++] = (byte) (this.ints[i] & 0xFF); + } + return new BigInteger(1, mag); + } + + @Override + public UInt384 toUInt384() { + return this; + } + + @Override + public Bytes48 toBytes() { + MutableBytes48 bytes = MutableBytes48.create(); + for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { + bytes.setInt(j, this.ints[i]); + } + return bytes; + } + + @Override + public Bytes toMinimalBytes() { + int i = 0; + while (i < INTS_SIZE && this.ints[i] == 0) { + ++i; + } + if (i == INTS_SIZE) { + return Bytes.EMPTY; + } + int firstIntBytes = 4 - (Integer.numberOfLeadingZeros(this.ints[i]) / 8); + int totalBytes = firstIntBytes + ((INTS_SIZE - (i + 1)) * 4); + MutableBytes bytes = MutableBytes.create(totalBytes); + int j = 0; + switch (firstIntBytes) { + case 4: + bytes.set(j++, (byte) (this.ints[i] >>> 24)); + // fall through + case 3: + bytes.set(j++, (byte) ((this.ints[i] >>> 16) & 0xFF)); + // fall through + case 2: + bytes.set(j++, (byte) ((this.ints[i] >>> 8) & 0xFF)); + // fall through + case 1: + bytes.set(j++, (byte) (this.ints[i] & 0xFF)); + } + ++i; + for (; i < INTS_SIZE; ++i, j += 4) { + bytes.setInt(j, this.ints[i]); + } + return bytes; + } + + @Override + public int numberOfLeadingZeros() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (i * 32) + Integer.numberOfLeadingZeros(this.ints[i]); + } + return 384; + } + + @Override + public int bitLength() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (INTS_SIZE * 32) - (i * 32) - Integer.numberOfLeadingZeros(this.ints[i]); + } + return 0; + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(long v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384Value.java new file mode 100644 index 00000000000..62859b6a855 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384Value.java @@ -0,0 +1,421 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes48; + +import java.math.BigInteger; + +/** + * Represents a 384-bit (48 bytes) unsigned integer value. + * + *

+ * A {@link UInt384Value} is an unsigned integer value stored with 48 bytes, so whose value can range between 0 and + * 2^384-1. + * + *

+ * This interface defines operations for value types with a 384-bit precision range. The methods provided by this + * interface take parameters of the same type (and also {@code long}. This provides type safety by ensuring calculations + * cannot mix different {@code UInt384Value} types. + * + *

+ * Where only a pure numerical 384-bit value is required, {@link UInt384} should be used. + * + *

+ * It is strongly advised to extend {@link BaseUInt384Value} rather than implementing this interface directly. Doing so + * provides type safety in that quantities of different units cannot be mixed accidentally. + * + * @param The concrete type of the value. + */ +public interface UInt384Value> extends Comparable { + + /** + * Returns true is this is 0. + * + * @return True if this is the value 0. + */ + default boolean isZero() { + return toBytes().isZero(); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(T value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(T value) { + T result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(long value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(long value) { + T result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(T value, UInt384 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(long value, UInt384 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T addMod(long value, long modulus); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(T value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T subtractExact(T value) { + T result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(long value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T subtractExact(long value) { + T result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + T multiply(T value); + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + * @throws ArithmeticException {@code value} < 0. + */ + T multiply(long value); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(T value, UInt384 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(long value, UInt384 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} ≤ 0. + */ + T multiplyMod(long value, long modulus); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + T divide(T value); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} ≤ 0. + */ + T divide(long value); + + /** + * Returns a value that is {@code (thisexponent mod 2384)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^384}. + * + *

+ * Note that {@code exponent} is an {@link UInt384} rather than of the type {@code T}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 2384} + */ + T pow(UInt384 exponent); + + /** + * Returns a value that is {@code (thisexponent mod 2384)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^384}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 2384} + */ + T pow(long exponent); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} == 0. + */ + T mod(UInt384 modulus); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T mod(long modulus); + + /** + * Returns true if this value fits an int + * + * @return True if this value fits a java {@code int} (i.e. is less or equal to {@code Integer.MAX_VALUE}). + */ + default boolean fitsInt() { + // Ints are 4 bytes, so anything but the 4 last bytes must be zeroes + Bytes48 bytes = toBytes(); + for (int i = 0; i < Bytes48.SIZE - 4; i++) { + if (bytes.get(i) != 0) + return false; + } + // Lastly, the left-most byte of the int must not start with a 1. + return bytes.get(Bytes48.SIZE - 4) >= 0; + } + + /** + * Returns the value as an int. + * + * @return This value as a java {@code int} assuming it is small enough to fit an {@code int}. + * @throws ArithmeticException If the value does not fit an {@code int}, that is if {@code !fitsInt()}. + */ + default int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return toBytes().getInt(Bytes48.SIZE - 4); + } + + /** + * Returns true if this value fits in a long. + * + * @return True if this value fits a java {@code long} (i.e. is less or equal to {@code Long.MAX_VALUE}). + */ + default boolean fitsLong() { + // Longs are 8 bytes, so anything but the 8 last bytes must be zeroes + for (int i = 0; i < Bytes48.SIZE - 8; i++) { + if (toBytes().get(i) != 0) + return false; + } + // Lastly, the left-most byte of the long must not start with a 1. + return toBytes().get(Bytes48.SIZE - 8) >= 0; + } + + /** + * Returns this value as a long. + * + * @return This value as a java {@code long} assuming it is small enough to fit a {@code long}. + * @throws ArithmeticException If the value does not fit a {@code long}, that is if {@code !fitsLong()}. + */ + default long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return toBytes().getLong(Bytes48.SIZE - 8); + } + + /** + * Returns this value as a {@link BigInteger}. + * + * @return This value as a {@link BigInteger}. + */ + default BigInteger toBigInteger() { + return toBytes().toUnsignedBigInteger(); + } + + /** + * This value represented as an hexadecimal string. + * + *

+ * Note that this representation includes all the 48 underlying bytes, no matter what the integer actually represents + * (in other words, it can have many leading zeros). For a shorter representation that don't include leading zeros, + * use {@link #toShortHexString}. + * + * @return This value represented as an hexadecimal string. + */ + default String toHexString() { + return toBytes().toHexString(); + } + + /** + * Returns this value represented as a minimal hexadecimal string (without any leading zero). + * + * @return This value represented as a minimal hexadecimal string (without any leading zero). + */ + default String toShortHexString() { + return toBytes().toShortHexString(); + } + + /** + * Convert this value to a {@link UInt384}. + * + * @return This value as a {@link UInt384}. + */ + UInt384 toUInt384(); + + /** + * Returns the value as bytes + * + * @return The value as bytes. + */ + Bytes48 toBytes(); + + /** + * Retuns the value as bytes without any leading zero bytes. + * + * @return The value as bytes without any leading zero bytes. + */ + Bytes toMinimalBytes(); + + /** + * Returns the number of zero bits preceding the highest-order ("leftmost") one-bit + * + * @return the number of zero bits preceding the highest-order ("leftmost") one-bit in the binary representation of + * this value, or 384 if the value is equal to zero. + */ + default int numberOfLeadingZeros() { + return toBytes().numberOfLeadingZeros(); + } + + /** + * Returns the number of bits following and including the highest-order ("leftmost") one-bit + * + * @return The number of bits following and including the highest-order ("leftmost") one-bit in the binary + * representation of this value, or zero if all bits are zero. + */ + default int bitLength() { + return toBytes().bitLength(); + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + default String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384s.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384s.java new file mode 100644 index 00000000000..c5e8d3ea119 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt384s.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +/** Static utility methods on UInt384 values. */ +public final class UInt384s { + private UInt384s() {} + + /** + * Returns the maximum of two UInt384 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The maximum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T max(T v1, T v2) { + return (v1.compareTo(v2)) >= 0 ? v1 : v2; + } + + /** + * Returns the minimum of two UInt384 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The minimum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T min(T v1, T v2) { + return (v1.compareTo(v2)) < 0 ? v1 : v2; + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64.java new file mode 100644 index 00000000000..40a14b2cba6 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64.java @@ -0,0 +1,581 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; + +import java.math.BigInteger; + +/** + * An unsigned 64-bit precision number. + * + * This is a raw {@link UInt64Value} - a 64-bit precision unsigned number of no particular unit. + */ +public final class UInt64 implements UInt64Value { + private final static int MAX_CONSTANT = 64; + private static UInt64[] CONSTANTS = new UInt64[MAX_CONSTANT + 1]; + static { + CONSTANTS[0] = new UInt64(0); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt64(i); + } + } + + /** The minimum value of a UInt64 */ + public final static UInt64 MIN_VALUE = valueOf(0); + /** The maximum value of a UInt64 */ + public final static UInt64 MAX_VALUE = new UInt64(~0L); + /** The value 0 */ + public final static UInt64 ZERO = valueOf(0); + /** The value 1 */ + public final static UInt64 ONE = valueOf(1); + + private static final BigInteger P_2_64 = BigInteger.valueOf(2).pow(64); + + private final long value; + + /** + * Return a {@code UInt64} containing the specified value. + * + * @param value The value to create a {@code UInt64} for. + * @return A {@code UInt64} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt64 valueOf(long value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + return create(value); + } + + /** + * Return a {@link UInt64} containing a random value. + * + * @return a {@link UInt64} containing a random value + */ + public static UInt64 random() { + return UInt64.fromBytes(Bytes.random(8)); + } + + /** + * Return a {@link UInt64} containing the specified value. + * + * @param value the value to create a {@link UInt64} for + * @return a {@link UInt64} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a UInt64 + */ + public static UInt64 valueOf(BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value.bitLength() > 64) { + throw new IllegalArgumentException("Argument is too large to represent a UInt64"); + } + return create(value.longValue()); + } + + /** + * Return a {@link UInt64} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt64}. + * @return A {@link UInt64} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 8}. + */ + public static UInt64 fromBytes(Bytes bytes) { + if (bytes.size() > 8) { + throw new IllegalArgumentException("Argument is greater than 8 bytes"); + } + return create(bytes.toLong()); + } + + /** + * Parse a hexadecimal string into a {@link UInt64}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain + * less than 8 bytes, in which case the result is left padded with zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or + * contains more than 8 bytes. + */ + public static UInt64 fromHexString(String str) { + return fromBytes(Bytes.fromHexStringLenient(str)); + } + + private static UInt64 create(long value) { + if (value >= 0 && value <= MAX_CONSTANT) { + return CONSTANTS[(int) value]; + } + return new UInt64(value); + } + + private UInt64(long value) { + this.value = value; + } + + @Override + public boolean isZero() { + return this.value == 0; + } + + @Override + public UInt64 add(UInt64 value) { + if (value.value == 0) { + return this; + } + if (this.value == 0) { + return value; + } + return create(this.value + value.value); + } + + @Override + public UInt64 add(long value) { + if (value == 0) { + return this; + } + return create(this.value + value); + } + + @Override + public UInt64 addMod(UInt64 value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger()).longValue()); + } + + @Override + public UInt64 addMod(long value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create(toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).longValue()); + } + + @Override + public UInt64 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return create(toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus)).longValue()); + } + + @Override + public UInt64 subtract(UInt64 value) { + if (value.isZero()) { + return this; + } + return create(this.value - value.value); + } + + @Override + public UInt64 subtract(long value) { + return add(-value); + } + + @Override + public UInt64 multiply(UInt64 value) { + if (this.value == 0 || value.value == 0) { + return ZERO; + } + if (value.value == 1) { + return this; + } + return create(this.value * value.value); + } + + @Override + public UInt64 multiply(long value) { + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + if (value == 0 || this.value == 0) { + return ZERO; + } + if (value == 1) { + return this; + } + return create(this.value * value); + } + + @Override + public UInt64 multiplyMod(UInt64 value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (this.value == 0 || value.value == 0) { + return ZERO; + } + if (value.value == 1) { + return mod(modulus); + } + return create(toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger()).longValue()); + } + + @Override + public UInt64 multiplyMod(long value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || this.value == 0) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create(toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).longValue()); + } + + @Override + public UInt64 multiplyMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || this.value == 0) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create(toBigInteger().multiply(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus)).longValue()); + } + + @Override + public UInt64 divide(UInt64 value) { + if (value.value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value.value == 1) { + return this; + } + return create(toBigInteger().divide(value.toBigInteger()).longValue()); + } + + @Override + public UInt64 divide(long value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return shiftRight(log2(value)); + } + return create(toBigInteger().divide(BigInteger.valueOf(value)).longValue()); + } + + @Override + public UInt64 pow(UInt64 exponent) { + return create(toBigInteger().modPow(exponent.toBigInteger(), P_2_64).longValue()); + } + + @Override + public UInt64 pow(long exponent) { + return create(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_64).longValue()); + } + + @Override + public UInt64 mod(UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return create(toBigInteger().mod(modulus.toBigInteger()).longValue()); + } + + @Override + public UInt64 mod(long modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + return create(this.value % modulus); + } + + /** + * Return a bit-wise AND of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt64 and(UInt64 value) { + if (this.value == 0 || value.value == 0) { + return ZERO; + } + return create(this.value & value.value); + } + + /** + * Return a bit-wise AND of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise AND + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt64 and(Bytes bytes) { + if (bytes.size() > 8) { + throw new IllegalArgumentException("and with more than 8 bytes"); + } + if (this.value == 0) { + return ZERO; + } + long value = bytes.toLong(); + if (value == 0) { + return ZERO; + } + return create(this.value & value); + } + + /** + * Return a bit-wise OR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt64 or(UInt64 value) { + return create(this.value | value.value); + } + + /** + * Return a bit-wise OR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise OR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt64 or(Bytes bytes) { + if (bytes.size() > 8) { + throw new IllegalArgumentException("or with more than 8 bytes"); + } + return create(this.value | bytes.toLong()); + } + + /** + * Return a bit-wise XOR of this value and the supplied value. + * + * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt64 xor(UInt64 value) { + return create(this.value ^ value.value); + } + + /** + * Return a bit-wise XOR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise XOR + * @throws IllegalArgumentException if more than 8 bytes are supplied + */ + public UInt64 xor(Bytes bytes) { + if (bytes.size() > 8) { + throw new IllegalArgumentException("xor with more than 8 bytes"); + } + return create(this.value ^ bytes.toLong()); + } + + /** + * Return a bit-wise NOT of this value. + * + * @return the result of a bit-wise NOT + */ + public UInt64 not() { + return create(~this.value); + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt64 shiftRight(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 64) { + return ZERO; + } + return create(this.value >>> distance); + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt64 shiftLeft(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 64) { + return ZERO; + } + return create(this.value << distance); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof UInt64)) { + return false; + } + UInt64 other = (UInt64) object; + return this.value == other.value; + } + + @Override + public int hashCode() { + return Long.hashCode(this.value); + } + + @Override + public int compareTo(UInt64 other) { + return Long.compareUnsigned(this.value, other.value); + } + + @Override + public boolean fitsInt() { + return this.value >= 0 && this.value <= Integer.MAX_VALUE; + } + + @Override + public int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return (int) this.value; + } + + @Override + public boolean fitsLong() { + return this.value >= 0; + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return this.value; + } + + @Override + public String toString() { + return toBigInteger().toString(); + } + + @Override + public BigInteger toBigInteger() { + byte[] mag = new byte[8]; + mag[0] = (byte) ((this.value >>> 56) & 0xFF); + mag[1] = (byte) ((this.value >>> 48) & 0xFF); + mag[2] = (byte) ((this.value >>> 40) & 0xFF); + mag[3] = (byte) ((this.value >>> 32) & 0xFF); + mag[4] = (byte) ((this.value >>> 24) & 0xFF); + mag[5] = (byte) ((this.value >>> 16) & 0xFF); + mag[6] = (byte) ((this.value >>> 8) & 0xFF); + mag[7] = (byte) (this.value & 0xFF); + return new BigInteger(1, mag); + } + + @Override + public UInt64 toUInt64() { + return this; + } + + @Override + public Bytes toBytes() { + MutableBytes bytes = MutableBytes.create(8); + bytes.setLong(0, this.value); + return bytes; + } + + @Override + public Bytes toMinimalBytes() { + int requiredBytes = 8 - (Long.numberOfLeadingZeros(this.value) / 8); + MutableBytes bytes = MutableBytes.create(requiredBytes); + int j = 0; + switch (requiredBytes) { + case 8: + bytes.set(j++, (byte) (this.value >>> 56)); + // fall through + case 7: + bytes.set(j++, (byte) ((this.value >>> 48) & 0xFF)); + // fall through + case 6: + bytes.set(j++, (byte) ((this.value >>> 40) & 0xFF)); + // fall through + case 5: + bytes.set(j++, (byte) ((this.value >>> 32) & 0xFF)); + // fall through + case 4: + bytes.set(j++, (byte) ((this.value >>> 24) & 0xFF)); + // fall through + case 3: + bytes.set(j++, (byte) ((this.value >>> 16) & 0xFF)); + // fall through + case 2: + bytes.set(j++, (byte) ((this.value >>> 8) & 0xFF)); + // fall through + case 1: + bytes.set(j, (byte) (this.value & 0xFF)); + } + return bytes; + } + + @Override + public int numberOfLeadingZeros() { + return Long.numberOfLeadingZeros(this.value); + } + + @Override + public int bitLength() { + return 64 - Long.numberOfLeadingZeros(this.value); + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(long v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } + +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64Value.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64Value.java new file mode 100644 index 00000000000..6def37a2365 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64Value.java @@ -0,0 +1,415 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + + +import org.apache.tuweni.bytes.Bytes; + +import java.math.BigInteger; + +/** + * Represents a 64-bit (8 bytes) unsigned integer value. + * + *

+ * A {@link UInt64Value} is an unsigned integer value whose value can range between 0 and 2^64-1. + * + *

+ * This interface defines operations for value types with a 64-bit precision range. The methods provided by this + * interface take parameters of the same type (and also {@code long}. This provides type safety by ensuring calculations + * cannot mix different {@code UInt64Value} types. + * + *

+ * Where only a pure numerical 64-bit value is required, {@link UInt64} should be used. + * + *

+ * It is strongly advised to extend {@link BaseUInt64Value} rather than implementing this interface directly. Doing so + * provides type safety in that quantities of different units cannot be mixed accidentally. + * + * @param The concrete type of the value. + */ +public interface UInt64Value> extends Comparable { + + /** + * Returns true if this is 0. + * + * @return True if this is the value 0. + */ + default boolean isZero() { + return toBytes().isZero(); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(T value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(T value) { + T result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value The amount to be added to this value. + * @return {@code this + value} + */ + T add(long value); + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + default T addExact(long value) { + T result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(T value, UInt64 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} == 0. + */ + T addMod(long value, UInt64 modulus); + + /** + * Returns a value equivalent to {@code ((this + value) mod modulus)}. + * + * @param value The amount to be added to this value. + * @param modulus The modulus. + * @return {@code (this + value) mod modulus} + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T addMod(long value, long modulus); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(T value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + default T subtractExact(T value) { + T result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value The amount to be subtracted from this value. + * @return {@code this - value} + */ + T subtract(long value); + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + default T subtractExact(long value) { + T result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + */ + T multiply(T value); + + /** + * Returns a value that is {@code (this * value)}. + * + * @param value The amount to multiply this value by. + * @return {@code this * value} + * @throws ArithmeticException {@code value} < 0. + */ + T multiply(long value); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(T value, UInt64 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. + */ + T multiplyMod(long value, UInt64 modulus); + + /** + * Returns a value that is {@code ((this * value) mod modulus)}. + * + * @param value The amount to multiply this value by. + * @param modulus The modulus. + * @return {@code (this * value) mod modulus} + * @throws ArithmeticException {@code value} < 0 or {@code modulus} ≤ 0. + */ + T multiplyMod(long value, long modulus); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} == 0. + */ + T divide(T value); + + /** + * Returns a value that is {@code (this / value)}. + * + * @param value The amount to divide this value by. + * @return {@code this / value} + * @throws ArithmeticException {@code value} ≤ 0. + */ + T divide(long value); + + /** + * Returns a value that is {@code (thisexponent mod 264)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^64}. + * + *

+ * Note that {@code exponent} is an {@link UInt64} rather than of the type {@code T}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 264} + */ + T pow(UInt64 exponent); + + /** + * Returns a value that is {@code (thisexponent mod 264)} + * + *

+ * This calculates an exponentiation over the modulus of {@code 2^64}. + * + * @param exponent The exponent to which this value is to be raised. + * @return {@code thisexponent mod 264} + */ + T pow(long exponent); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} == 0. + */ + T mod(UInt64 modulus); + + /** + * Returns a value that is {@code (this mod modulus)}. + * + * @param modulus The modulus. + * @return {@code this mod modulus}. + * @throws ArithmeticException {@code modulus} ≤ 0. + */ + T mod(long modulus); + + /** + * Returns true if this value fits an int. + * + * @return True if this value fits a java {@code int} (i.e. is less or equal to {@code Integer.MAX_VALUE}). + */ + default boolean fitsInt() { + // Ints are 4 bytes, so anything but the 4 last bytes must be zeroes + Bytes bytes = toBytes(); + for (int i = 0; i < 8 - 4; i++) { + if (bytes.get(i) != 0) + return false; + } + // Lastly, the left-most byte of the int must not start with a 1. + return bytes.get(4) >= 0; + } + + /** + * Returns this value as an int. + * + * @return This value as a java {@code int} assuming it is small enough to fit an {@code int}. + * @throws ArithmeticException If the value does not fit an {@code int}, that is if {@code + * !fitsInt()}. + */ + default int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return toBytes().getInt(4); + } + + /** + * Returns true if this value fits a long. + * + * @return True if this value fits a java {@code long} (i.e. is less or equal to {@code Long.MAX_VALUE}). + */ + default boolean fitsLong() { + return true; + } + + /** + * Returns this value as a long. + * + * @return This value as a java {@code long} assuming it is small enough to fit a {@code long}. + * @throws ArithmeticException If the value does not fit a {@code long}, that is if {@code + * !fitsLong()}. + */ + default long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return toBytes().getLong(0); + } + + /** + * Returns this value as a BigInteger + * + * @return This value as a {@link BigInteger}. + */ + default BigInteger toBigInteger() { + return toBytes().toUnsignedBigInteger(); + } + + /** + * This value represented as an hexadecimal string. + * + *

+ * Note that this representation includes all the 8 underlying bytes, no matter what the integer actually represents + * (in other words, it can have many leading zeros). For a shorter representation that don't include leading zeros, + * use {@link #toShortHexString}. + * + * @return This value represented as an hexadecimal string. + */ + default String toHexString() { + return toBytes().toHexString(); + } + + /** + * Returns this value represented as a minimal hexadecimal string (without any leading zero) + * + * @return This value represented as a minimal hexadecimal string (without any leading zero). + */ + default String toShortHexString() { + return toBytes().toShortHexString(); + } + + /** + * Convert this value to a {@link UInt64}. + * + * @return This value as a {@link UInt64}. + */ + UInt64 toUInt64(); + + /** + * Returns the value as bytes. + * + * @return The value as bytes. + */ + Bytes toBytes(); + + /** + * Returns the value as bytes without any leading zero bytes. + * + * @return The value as bytes without any leading zero bytes. + */ + Bytes toMinimalBytes(); + + /** + * Returns the number of zero bits preceding the highest-order ("leftmost") one-bit + * + * @return the number of zero bits preceding the highest-order ("leftmost") one-bit in the binary representation of + * this value, or 64 if the value is equal to zero. + */ + default int numberOfLeadingZeros() { + return toBytes().numberOfLeadingZeros(); + } + + /** + * Returns the number of bits following and including the highest-order ("leftmost") one-bit + * + * @return The number of bits following and including the highest-order ("leftmost") one-bit in the binary + * representation of this value, or zero if all bits are zero. + */ + default int bitLength() { + return toBytes().bitLength(); + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + default String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64s.java b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64s.java new file mode 100644 index 00000000000..692e1a12a95 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/apache/tuweni/units/bigints/UInt64s.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +/** Static utility methods on UInt64 values. */ +public final class UInt64s { + private UInt64s() {} + + /** + * Returns the maximum of two UInt64 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The maximum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T max(T v1, T v2) { + return (v1.compareTo(v2)) >= 0 ? v1 : v2; + } + + /** + * Returns the minimum of two UInt64 values. + * + * @param v1 The first value. + * @param v2 The second value. + * @return The minimum of {@code v1} and {@code v2}. + * @param The concrete type of the two values. + */ + public static > T min(T v1, T v2) { + return (v1.compareTo(v2)) < 0 ? v1 : v2; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/BesuProvider.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/BesuProvider.java new file mode 100644 index 00000000000..65dbb72d466 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/BesuProvider.java @@ -0,0 +1,29 @@ +/* + * 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.crypto; + +import java.security.Provider; + +public final class BesuProvider extends Provider { + + private static final String info = "Besu Security Provider v1.0"; + + public static final String PROVIDER_NAME = "Besu"; + + @SuppressWarnings({"unchecked", "removal"}) + public BesuProvider() { + super(PROVIDER_NAME, 1.0, info); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/Hash.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/Hash.java new file mode 100644 index 00000000000..261504c1f28 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/Hash.java @@ -0,0 +1,68 @@ +/* + * 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.crypto; + +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.function.Supplier; + +/** Various utilities for providing hashes (digests) of arbitrary data. */ +public abstract class Hash { + private Hash() {} + + public static final String KECCAK256_ALG = "KECCAK-256"; + + private static final Supplier KECCAK256_SUPPLIER = + Suppliers.memoize(() -> messageDigest(KECCAK256_ALG)); + + private static MessageDigest messageDigest(final String algorithm) { + try { + return MessageDigestFactory.create(algorithm); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Helper method to generate a digest using the provided algorithm. + * + * @param input The input bytes to produce the digest for. + * @param digestSupplier the digest supplier to use + * @return A digest. + */ + private static byte[] digestUsingAlgorithm( + final Bytes input, final Supplier digestSupplier) { + try { + final MessageDigest digest = (MessageDigest) digestSupplier.get().clone(); + input.update(digest); + return digest.digest(); + } catch (final CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + /** + * Digest using keccak-256. + * + * @param input The input bytes to produce the digest for. + * @return A digest. + */ + public static Bytes32 keccak256(final Bytes input) { + return Bytes32.wrap(digestUsingAlgorithm(input, KECCAK256_SUPPLIER)); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java new file mode 100644 index 00000000000..85deeec97d5 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java @@ -0,0 +1,34 @@ +/* + * 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.crypto; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +public class MessageDigestFactory { + + static { + Security.addProvider(new BesuProvider()); + Security.addProvider(new BouncyCastleProvider()); + } + + @SuppressWarnings("DoNotInvokeMessageDigestDirectly") + public static MessageDigest create(final String algorithm) throws NoSuchAlgorithmException { + return MessageDigest.getInstance(algorithm); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java new file mode 100644 index 00000000000..b1b8c1ec051 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPInput.java @@ -0,0 +1,556 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes; +import org.apache.tuweni.bytes.MutableBytes32; +import org.apache.tuweni.units.bigints.UInt256; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.function.Function; + +import static com.google.common.base.Preconditions.checkState; + +abstract class AbstractRLPInput implements RLPInput { + + private final boolean lenient; + + protected long size; // The number of bytes in this rlp-encoded byte string + + // Information on the item the input currently is at (next thing to read). + protected long + currentItem; // Offset in value to the beginning of the item (or value.size() if done) + private RLPDecodingHelpers.Kind currentKind; // Kind of the item. + private long currentPayloadOffset; // Offset to the beginning of the current item payload. + private int currentPayloadSize; // Size of the current item payload. + + // Information regarding opened list. The depth is how many list deep we are, and endOfListOffset + // holds the offset in value at which each list ends (indexed by depth). Allows to know if we're + // at the end of our current list, and if there is any unfinished one. + private int depth; + private long[] endOfListOffset = new long[4]; + + AbstractRLPInput(final boolean lenient) { + this.lenient = lenient; + } + + protected void init(final long inputSize, final boolean shouldFitInputSizeExactly) { + if (inputSize == 0) { + return; + } + + currentItem = 0; + // Initially set the size to the input as prepareCurrentItem() needs it. Once we've prepared the + // top level item, we know where that item ends exactly and can update the size to that more + // precise value (which basically mean we'll throw errors on malformed inputs potentially + // sooner). + size = inputSize; + prepareCurrentItem(); + if (currentKind.isList()) { + size = nextItem(); + } + + // No matter what, if the first item advertise a payload ending after the end of the input, that + // input is corrupted. + if (size > inputSize) { + // Our error message include a snippet of the input and that code assume size is not set + // outside of the input, and that's exactly the case we're testing, so resetting the size + // simply for the sake of the error being properly generated. + final long itemEnd = size; + size = inputSize; + throw corrupted( + "Input doesn't have enough data for RLP encoding: encoding advertise a " + + "payload ending at byte %d but input has size %d", + itemEnd, inputSize); + } + + if (shouldFitInputSizeExactly && inputSize > size) { + throwMalformed( + "Input has extra data after RLP encoding: encoding ends at byte %d but " + + "input has size %d", + size, inputSize); + } + + validateCurrentItem(); + } + + protected abstract byte inputByte(long offset); + + protected abstract Bytes inputSlice(long offset, int length); + + protected abstract Bytes32 inputSlice32(long offset); + + protected abstract String inputHex(long offset, int length); + + protected abstract BigInteger getUnsignedBigInteger(long offset, int length); + + protected abstract int getInt(long offset); + + protected abstract long getLong(long offset); + + /** + * Sets the input to the item provided (an offset to the beginning of an item) and check this is + * valid. + * + * @param item the value to which the current item is to be set. + */ + protected void setTo(final long item) { + currentItem = item; + if (currentItem >= size) { + // Setting somewhat safe values so that multiple calls to setTo(nextItem()) don't do anything + // even when at the end. + currentKind = null; + currentPayloadOffset = item; + currentPayloadSize = 0; + return; + } + prepareCurrentItem(); + validateCurrentItem(); + } + + private void prepareCurrentItem() { + // Sets the kind of the item, the offset at which his payload starts and the size of this + // payload. + try { + final RLPDecodingHelpers.RLPElementMetadata elementMetadata = + RLPDecodingHelpers.rlpElementMetadata(this::inputByte, size, currentItem); + currentKind = elementMetadata.kind; + currentPayloadOffset = elementMetadata.payloadStart; + currentPayloadSize = elementMetadata.payloadSize; + } catch (final RLPException exception) { + final String message = + String.format( + exception.getMessage() + getErrorMessageSuffix(), getErrorMessageSuffixParams()); + throw new RLPException(message, exception); + } + } + + private void validateCurrentItem() { + if (currentKind == RLPDecodingHelpers.Kind.SHORT_ELEMENT) { + // Validate that a single byte SHORT_ELEMENT payload is not <= 0x7F. If it is, is should have + // been written as a BYTE_ELEMENT. + if (currentPayloadSize == 1 + && currentPayloadOffset < size + && (payloadByte(0) & 0xFF) <= 0x7F) { + throwMalformed( + "Malformed RLP item: single byte value 0x%s should have been " + + "written without a prefix", + hex(currentPayloadOffset, currentPayloadOffset + 1)); + } + } + + if (currentPayloadSize > 0 && currentPayloadOffset >= size) { + throw corrupted( + "Invalid RLP item: payload should start at offset %d but input has only " + "%d bytes", + currentPayloadOffset, size); + } + if (size - currentPayloadOffset < currentPayloadSize) { + throw corrupted( + "Invalid RLP item: payload starting at byte %d should be %d bytes long, but input " + + "has only %d bytes from that offset", + currentPayloadOffset, currentPayloadSize, size - currentPayloadOffset); + } + } + + private long nextItem() { + return currentPayloadOffset + currentPayloadSize; + } + + @Override + public boolean isDone() { + // The input is done if we're out of input, but also if we've called leaveList() an appropriate + // amount of times. + return currentItem >= size && depth == 0; + } + + private String hex(final long start, final long taintedEnd) { + final long end = Math.min(taintedEnd, size); + final long size = end - start; + if (size < 10) { + return inputHex(start, Math.toIntExact(size)); + } else { + return String.format("%s...%s", inputHex(start, 4), inputHex(end - 4, 4)); + } + } + + private void throwMalformed(final String msg, final Object... params) { + if (!lenient) throw new MalformedRLPInputException(errorMsg(msg, params)); + } + + private CorruptedRLPInputException corrupted(final String msg, final Object... params) { + throw new CorruptedRLPInputException(errorMsg(msg, params)); + } + + private RLPException error(final String msg, final Object... params) { + throw new RLPException(errorMsg(msg, params)); + } + + private RLPException error(final Throwable cause, final String msg, final Object... params) { + throw new RLPException(errorMsg(msg, params), cause); + } + + private String errorMsg(final String message, final Object... params) { + return String.format( + message + getErrorMessageSuffix(), concatParams(params, getErrorMessageSuffixParams())); + } + + private String getErrorMessageSuffix() { + return " (at bytes %d-%d: %s%s[%s]%s%s)"; + } + + private Object[] getErrorMessageSuffixParams() { + final long start = currentItem; + final long end = Math.min(size, nextItem()); + final long realStart = Math.max(0, start - 4); + final long realEnd = Math.min(size, end + 4); + return new Object[] { + start, + end, + realStart == 0 ? "" : "...", + hex(realStart, start), + hex(start, end), + hex(end, realEnd), + realEnd == size ? "" : "..." + }; + } + + private static Object[] concatParams(final Object[] initial, final Object... others) { + final Object[] params = Arrays.copyOf(initial, initial.length + others.length); + System.arraycopy(others, 0, params, initial.length, others.length); + return params; + } + + private void checkElt(final String what) { + if (currentItem >= size) { + throw error("Cannot read a %s, input is fully consumed", what); + } + if (isEndOfCurrentList()) { + throw error("Cannot read a %s, reached end of current list", what); + } + if (currentKind.isList()) { + throw error("Cannot read a %s, current item is a list", what); + } + } + + private void checkElt(final String what, final int expectedSize) { + checkElt(what); + if (currentPayloadSize != expectedSize) + throw error( + "Cannot read a %s, expecting %d bytes but current element is %d bytes long", + what, expectedSize, currentPayloadSize); + } + + private void checkScalar(final String what) { + checkElt(what); + if (currentPayloadSize > 0 && payloadByte(0) == 0) { + throwMalformed("Invalid scalar, has leading zeros bytes"); + } + } + + private void checkScalar(final String what, final int maxExpectedSize) { + checkScalar(what); + if (currentPayloadSize > maxExpectedSize) + throw error( + "Cannot read a %s, expecting a maximum of %d bytes but current element is %d bytes long", + what, maxExpectedSize, currentPayloadSize); + } + + private byte payloadByte(final int offsetInPayload) { + return inputByte(currentPayloadOffset + offsetInPayload); + } + + private Bytes payloadSlice() { + return inputSlice(currentPayloadOffset, currentPayloadSize); + } + + @Override + public void skipNext() { + setTo(nextItem()); + } + + @Override + public long readLongScalar() { + checkScalar("long scalar", 8); + long res = readGenericLongScalar(); + setTo(nextItem()); + return res; + } + + private long readGenericLongScalar() { + long res = 0; + int shift = 0; + for (int i = 0; i < currentPayloadSize; i++) { + res |= ((long) payloadByte(currentPayloadSize - i - 1) & 0xFF) << shift; + shift += 8; + } + return res; + } + + @Override + public int readIntScalar() { + checkScalar("int scalar", 4); + int res = 0; + int shift = 0; + for (int i = 0; i < currentPayloadSize; i++) { + res |= (payloadByte(currentPayloadSize - i - 1) & 0xFF) << shift; + shift += 8; + } + setTo(nextItem()); + return res; + } + + @Override + public BigInteger readBigIntegerScalar() { + checkScalar("arbitrary precision scalar"); + final BigInteger res = getUnsignedBigInteger(currentPayloadOffset, currentPayloadSize); + setTo(nextItem()); + return res; + } + + private Bytes32 readBytes32Scalar() { + checkScalar("32-bytes scalar", 32); + final MutableBytes32 res = MutableBytes32.create(); + payloadSlice().copyTo(res, res.size() - currentPayloadSize); + setTo(nextItem()); + return res; + } + + @Override + public UInt256 readUInt256Scalar() { + return UInt256.fromBytes(readBytes32Scalar()); + } + + @Override + public byte readByte() { + checkElt("byte", 1); + final byte b = payloadByte(0); + setTo(nextItem()); + return b; + } + + @Override + public short readShort() { + checkElt("2-byte short", 2); + final short s = (short) ((payloadByte(0) << 8) | (payloadByte(1) & 0xFF)); + setTo(nextItem()); + return s; + } + + @Override + public int readInt() { + checkElt("4-byte int", 4); + final int res = getInt(currentPayloadOffset); + setTo(nextItem()); + return res; + } + + @Override + public long readLong() { + checkElt("8-byte long", 8); + final long res = getLong(currentPayloadOffset); + setTo(nextItem()); + return res; + } + + @Override + public InetAddress readInetAddress() { + checkElt("inet address"); + if (currentPayloadSize != 4 && currentPayloadSize != 16) { + throw error( + "Cannot read an inet address, current element is %d bytes long", currentPayloadSize); + } + final byte[] address = new byte[currentPayloadSize]; + for (int i = 0; i < currentPayloadSize; i++) { + address[i] = payloadByte(i); + } + setTo(nextItem()); + try { + return InetAddress.getByAddress(address); + } catch (final UnknownHostException e) { + // InetAddress.getByAddress() only throws for an address of illegal length, and we have + // validated that length already, this this genuinely shouldn't throw. + throw new AssertionError(e); + } + } + + @Override + public Bytes readBytes() { + checkElt("arbitrary bytes value"); + final Bytes res = payloadSlice(); + setTo(nextItem()); + return res; + } + + @Override + public Bytes32 readBytes32() { + checkElt("32 bytes value", 32); + final Bytes32 res = inputSlice32(currentPayloadOffset); + setTo(nextItem()); + return res; + } + + @Override + public T readBytes(final Function mapper) { + final Bytes res = readBytes(); + try { + return mapper.apply(res); + } catch (final Exception e) { + throw error(e, "Problem decoding bytes value"); + } + } + + @Override + public RLPInput readAsRlp() { + if (currentItem >= size) { + throw error("Cannot read current element as RLP, input is fully consumed"); + } + final long next = nextItem(); + final RLPInput res = RLP.input(inputSlice(currentItem, Math.toIntExact(next - currentItem))); + setTo(next); + return res; + } + + @Override + public int enterList() { + return enterList(false); + } + + /** + * Enters the list, but does not return the number of item of the entered list. This prevents + * bouncing all around the file to read values that are probably not even used. + * + * @see #enterList() + * @param skipCount true if the element count is not required. + * @return -1 if skipCount==true, otherwise, the number of item of the entered list. + */ + public int enterList(final boolean skipCount) { + if (currentItem >= size) { + throw error("Cannot enter a lists, input is fully consumed"); + } + if (!currentKind.isList()) { + throw error("Expected current item to be a list, but it is: " + currentKind); + } + + ++depth; + if (depth > endOfListOffset.length) { + endOfListOffset = Arrays.copyOf(endOfListOffset, (endOfListOffset.length * 3) / 2); + } + // The first list element is the beginning of the payload. It's end is the end of this item. + final long listStart = currentPayloadOffset; + final long listEnd = nextItem(); + + if (listEnd > size) { + throw corrupted( + "Invalid RLP item: list payload should end at offset %d but input has only %d bytes", + listEnd, size); + } + + endOfListOffset[depth - 1] = listEnd; + int count = -1; + + if (!skipCount) { + // Count list elements from first one. + count = 0; + setTo(listStart); + while (currentItem < listEnd) { + ++count; + setTo(nextItem()); + } + } + + // And lastly reset on the list first element before returning + setTo(listStart); + return count; + } + + @Override + public void leaveList() { + leaveList(false); + } + + @Override + public void leaveListLenient() { + leaveList(true); + } + + private void leaveList(final boolean ignoreRest) { + checkState(depth > 0, "Not within an RLP list"); + + if (!ignoreRest) { + final long listEndOffset = endOfListOffset[depth - 1]; + if (currentItem < listEndOffset) throw error("Not at the end of the current list"); + } + + --depth; + } + + @Override + public boolean nextIsList() { + return currentKind != null && currentKind.isList(); + } + + @Override + public boolean nextIsNull() { + return currentKind == RLPDecodingHelpers.Kind.SHORT_ELEMENT && currentPayloadSize == 0; + } + + @Override + public int nextSize() { + return currentPayloadSize; + } + + @Override + public int nextOffset() { + return Math.toIntExact(currentPayloadOffset); + } + + @Override + public boolean isEndOfCurrentList() { + return depth > 0 && currentItem >= endOfListOffset[depth - 1]; + } + + @Override + public boolean isZeroLengthString() { + return currentKind == RLPDecodingHelpers.Kind.SHORT_ELEMENT && currentPayloadSize == 0; + } + + @Override + public void reset() { + setTo(0); + } + + @Override + public Bytes currentListAsBytes() { + if (currentItem >= size) { + throw error("Cannot read list, input is fully consumed"); + } + if (!currentKind.isList()) { + throw error("Cannot read list, current item is not a list list"); + } + + final MutableBytes scratch = MutableBytes.create(currentPayloadSize + 10); + final int headerSize = RLPEncodingHelpers.writeListHeader(currentPayloadSize, scratch, 0); + payloadSlice().copyTo(scratch, headerSize); + final Bytes res = scratch.slice(0, currentPayloadSize + headerSize); + + setTo(nextItem()); + return res; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPOutput.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPOutput.java new file mode 100644 index 00000000000..882a4e08eb8 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/AbstractRLPOutput.java @@ -0,0 +1,208 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; +import java.util.function.Consumer; + +import static com.google.common.base.Preconditions.checkState; + +abstract class AbstractRLPOutput implements RLPOutput { + /* + * The algorithm implemented works as follows: + * + * Values written to the output are accumulated in the 'values' list. When a list is started, it + * is indicated by adding a specific marker in that list (LIST_MARKER). + * While this is gathered, we also incrementally compute the size of the payload of every list of + * that output. Those sizes are stored in 'payloadSizes': when all the output has been added, + * payloadSizes[i] will contain the size of the (encoded) payload of the ith list in 'values' + * (that is, the list that starts at the ith LIST_MARKER in 'values'). + * + * With that information gathered, encoded() can write its output in a single walk of 'values': + * values can be encoded directly, and every time we read a list marker, we use the corresponding + * payload size to write the proper prefix and continue. + * + * The main remaining aspect is how the values of 'payloadSizes' are computed. Computing the size + * of a list without nesting inside is easy: simply add the encoded size of any newly added value + * to the running size. The difficulty is with nesting: when we start a new list, we need to + * track both the sizes of the previous list and the new one. To deal with that, we use the small + * stack 'parentListStack': it stores the index in 'payloadSizes' of every currently "open" lists. + * In other words, payloadSizes[parentListStack[stackSize - 1]] corresponds to the size of the + * current list, the one to which newly added value are currently written (until the next call + * to 'endList()' that is, while payloadSizes[parentListStack[stackSize - 2]] would be the size + * of the parent list, .... + * + * Note that when a new value is added, we add its size only the currently running list. We should + * add that size to that of any parent list as well, but we do so indirectly when a list is + * finished: when 'endList()' is called, we add the size of the full list we just finished (and + * whose size we have now completed) to its parent size. + * + * Side-note: this class internally and informally use "element" to refer to a non list items. + */ + + private static final Bytes LIST_MARKER = Bytes.wrap(new byte[0]); + + private final List values = new ArrayList<>(); + // For every value i in values, rlpEncoded.get(i) will be true only if the value stored is an + // already encoded item. + private final BitSet rlpEncoded = new BitSet(); + + // First element is the total size of everything (the encoding may be a single non-list item, so + // this handles that case more easily; we need that value to size out final output). Following + // elements holds the size of the payload of the ith list in 'values'. + private int[] payloadSizes = new int[8]; + private int listsCount = 1; // number of lists current in 'values' + 1. + + private int[] parentListStack = new int[4]; + private int stackSize = 1; + + private int currentList() { + return parentListStack[stackSize - 1]; + } + + @Override + public void writeBytes(final Bytes v) { + checkState( + stackSize > 1 || values.isEmpty(), "Terminated RLP output, cannot add more elements"); + values.add(v); + payloadSizes[currentList()] += RLPEncodingHelpers.elementSize(v); + } + + @Override + public void writeRaw(final Bytes v) { + checkState( + stackSize > 1 || values.isEmpty(), "Terminated RLP output, cannot add more elements"); + values.add(v); + // Mark that last value added as already encoded. + rlpEncoded.set(values.size() - 1); + payloadSizes[currentList()] += v.size(); + } + + @Override + public void startList() { + values.add(LIST_MARKER); + ++listsCount; // we'll add a new element to payloadSizes + ++stackSize; // and to the list stack. + + // Resize our lists if necessary. + if (listsCount > payloadSizes.length) { + payloadSizes = Arrays.copyOf(payloadSizes, (payloadSizes.length * 3) / 2); + } + if (stackSize > parentListStack.length) { + parentListStack = Arrays.copyOf(parentListStack, (parentListStack.length * 3) / 2); + } + + // The new current list size is store in the slot we just made room for by incrementing + // listsCount + parentListStack[stackSize - 1] = listsCount - 1; + } + + @Override + public void endList() { + checkState(stackSize > 1, "LeaveList() called with no prior matching startList()"); + + final int current = currentList(); + final int finishedListSize = RLPEncodingHelpers.listSize(payloadSizes[current]); + --stackSize; + + // We just finished an item of our parent list, add it to that parent list size now. + final int newCurrent = currentList(); + payloadSizes[newCurrent] += finishedListSize; + } + + /** + * Computes the final encoded data size. + * + * @return The size of the RLP-encoded data written to this output. + * @throws IllegalStateException if some opened list haven't been closed (the output is not valid + * as is). + */ + public int encodedSize() { + checkState(stackSize == 1, "A list has been entered (startList()) but not left (endList())"); + return payloadSizes[0]; + } + + /** + * Write the rlp encoded value to the provided {@link MutableBytes} + * + * @param mutableBytes the value to which the rlp-data will be written + */ + public void writeEncoded(final MutableBytes mutableBytes) { + // Special case where we encode only a single non-list item (note that listsCount is initially + // set to 1, so listsCount == 1 really mean no list explicitly added to the output). + if (listsCount == 1) { + // writeBytes make sure we cannot have more than 1 value without a list + assert values.size() == 1; + final Bytes value = values.get(0); + + final int finalOffset; + // Single non-list value. + if (rlpEncoded.get(0)) { + value.copyTo(mutableBytes, 0); + finalOffset = value.size(); + } else { + finalOffset = RLPEncodingHelpers.writeElement(value, mutableBytes, 0); + } + checkState( + finalOffset == mutableBytes.size(), + "Expected single element RLP encode to be of size %s but was of size %s.", + mutableBytes.size(), + finalOffset); + return; + } + + int offset = 0; + int listIdx = 0; + for (int i = 0; i < values.size(); i++) { + final Bytes value = values.get(i); + if (value == LIST_MARKER) { + final int payloadSize = payloadSizes[++listIdx]; + offset = RLPEncodingHelpers.writeListHeader(payloadSize, mutableBytes, offset); + } else if (rlpEncoded.get(i)) { + value.copyTo(mutableBytes, offset); + offset += value.size(); + } else { + offset = RLPEncodingHelpers.writeElement(value, mutableBytes, offset); + } + } + + checkState( + offset == mutableBytes.size(), + "Expected RLP encoding to be of size %s but was of size %s.", + mutableBytes.size(), + offset); + } + + /** + * Check if the incoming value is 0 and writes it as 0x80, per the spec. + * + * @param input The value to check + * @param writer The consumer to write the non-zero output + */ + public void processZeroByte(final Long input, final Consumer writer) { + // If input == 0, encode 0 value as 0x80 + if (input == 0) { + writeRaw(Bytes.of(0x80)); + } else { + writer.accept(input); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/BytesValueRLPInput.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/BytesValueRLPInput.java new file mode 100644 index 00000000000..c7c113027ef --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/BytesValueRLPInput.java @@ -0,0 +1,78 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.math.BigInteger; + +/** An {@link RLPInput} that reads RLP encoded data from a {@link Bytes}. */ +public class BytesValueRLPInput extends AbstractRLPInput { + + // The RLP encoded data. + private final Bytes value; + + public BytesValueRLPInput(final Bytes value, final boolean lenient) { + this(value, lenient, true); + } + + public BytesValueRLPInput( + final Bytes value, final boolean lenient, final boolean shouldFitExactly) { + super(lenient); + this.value = value; + init(value.size(), shouldFitExactly); + } + + @Override + protected byte inputByte(final long offset) { + return value.get((int) offset); + } + + @Override + protected Bytes inputSlice(final long offset, final int length) { + return value.slice(Math.toIntExact(offset), length); + } + + @Override + protected Bytes32 inputSlice32(final long offset) { + return Bytes32.wrap(inputSlice(offset, 32)); + } + + @Override + protected String inputHex(final long offset, final int length) { + return value.slice(Math.toIntExact(offset), length).toString().substring(2); + } + + @Override + protected BigInteger getUnsignedBigInteger(final long offset, final int length) { + return value.slice(Math.toIntExact(offset), length).toUnsignedBigInteger(); + } + + @Override + protected int getInt(final long offset) { + return value.getInt(Math.toIntExact(offset)); + } + + @Override + protected long getLong(final long offset) { + return value.getLong(Math.toIntExact(offset)); + } + + @Override + public Bytes raw() { + return value; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/BytesValueRLPOutput.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/BytesValueRLPOutput.java new file mode 100644 index 00000000000..295fb1d34e8 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/BytesValueRLPOutput.java @@ -0,0 +1,37 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; + +/** An {@link RLPOutput} that writes RLP encoded data to a {@link Bytes}. */ +public class BytesValueRLPOutput extends AbstractRLPOutput { + /** + * Computes the final encoded data. + * + * @return A value containing the data written to this output RLP-encoded. + */ + public Bytes encoded() { + final int size = encodedSize(); + if (size == 0) { + return Bytes.EMPTY; + } + + final MutableBytes output = MutableBytes.create(size); + writeEncoded(output); + return output; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/CorruptedRLPInputException.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/CorruptedRLPInputException.java new file mode 100644 index 00000000000..317dc279381 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/CorruptedRLPInputException.java @@ -0,0 +1,22 @@ +/* + * 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.rlp; + +/** Exception thrown if an RLP input is corrupted and cannot be decoded properly. */ +public class CorruptedRLPInputException extends RLPException { + CorruptedRLPInputException(final String message) { + super(message); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/MalformedRLPInputException.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/MalformedRLPInputException.java new file mode 100644 index 00000000000..1cd4073918c --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/MalformedRLPInputException.java @@ -0,0 +1,25 @@ +/* + * 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.rlp; + +/** + * Exception thrown if an RLP input is strictly malformed, but in such a way that can be processed + * by a lenient RLP decoder. + */ +public class MalformedRLPInputException extends RLPException { + MalformedRLPInputException(final String message) { + super(message); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLP.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLP.java new file mode 100644 index 00000000000..680c57a8160 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLP.java @@ -0,0 +1,212 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; + +import java.util.function.Consumer; + +import static java.lang.String.format; + +/** Static methods to work with RLP encoding/decoding. */ +public abstract class RLP { + private RLP() {} + + /** The RLP encoding of a single empty value, also known as RLP null. */ + public static final Bytes NULL = encodeOne(Bytes.EMPTY); + + public static final Bytes EMPTY_LIST; + + // RLP encoding requires payloads to be less thatn 2^64 bytes in length + // As a result, the longest RLP strings will have a prefix composed of 1 byte encoding the type + // of string followed by at most 8 bytes describing the length of the string + public static final int MAX_PREFIX_SIZE = 9; + + static { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.startList(); + out.endList(); + EMPTY_LIST = out.encoded(); + } + + /** + * Creates a new {@link RLPInput} suitable for decoding the provided RLP encoded value. + * + *

The created input is strict, in that exceptions will be thrown for any malformed input, + * either by this method or by future reads from the returned input. + * + * @param encoded The RLP encoded data for which to create a {@link RLPInput}. + * @return A newly created {@link RLPInput} to decode {@code encoded}. + * @throws MalformedRLPInputException if {@code encoded} doesn't contain a single RLP encoded item + * (item that can be a list itself). Note that more deeply nested corruption/malformation of + * the input will not be detected by this method call, but will be later when the input is + * read. + */ + public static RLPInput input(final Bytes encoded) { + return input(encoded, false); + } + + public static RLPInput input(final Bytes encoded, final boolean lenient) { + return new BytesValueRLPInput(encoded, lenient); + } + + /** + * Creates a {@link RLPOutput}, pass it to the provided consumer for writing, and then return the + * RLP encoded result of that writing. + * + *

This method is a convenience method that is mostly meant for use with class that have a + * method to write to an {@link RLPOutput}. For instance: + * + *

{@code
+   * class Foo {
+   *   public void writeTo(RLPOutput out) {
+   *     //... write some data to out ...
+   *   }
+   * }
+   *
+   * Foo f = ...;
+   * // RLP encode f
+   * Bytes encoded = RLPs.encode(f::writeTo);
+   * }
+ * + * @param writer A method that given an {@link RLPOutput}, writes some data to it. + * @return The RLP encoding of the data written by {@code writer}. + */ + public static Bytes encode(final Consumer writer) { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + writer.accept(out); + return out.encoded(); + } + + /** + * Encodes a single binary value into RLP. + * + *

This is equivalent (but possibly more efficient) to: + * + *

+   * {
+   *   @code
+   *   BytesValueRLPOutput out = new BytesValueRLPOutput();
+   *   out.writeBytes(value);
+   *   return out.encoded();
+   * }
+   * 
+ * + *

So note in particular that the value is encoded as is (and so not as a scalar in + * particular). + * + * @param value The value to encode. + * @return The RLP encoding containing only {@code value}. + */ + public static Bytes encodeOne(final Bytes value) { + if (RLPEncodingHelpers.isSingleRLPByte(value)) return value; + + final MutableBytes res = MutableBytes.create(RLPEncodingHelpers.elementSize(value)); + RLPEncodingHelpers.writeElement(value, res, 0); + return res; + } + + /** + * Decodes an RLP-encoded value assuming it contains a single non-list item. + * + *

This is equivalent (but possibly more efficient) to: + * + *

{@code
+   * return input(value).readBytes();
+   * }
+ * + *

So note in particular that the value is decoded as is (and so not as a scalar in + * particular). + * + * @param encodedValue The encoded RLP value. + * @return The single value encoded in {@code encodedValue}. + * @throws RLPException if {@code encodedValue} is not a valid RLP encoding or if it does not + * contains a single non-list item. + */ + public static Bytes decodeOne(final Bytes encodedValue) { + if (encodedValue.size() == 0) { + throw new RLPException("Invalid empty input for RLP decoding"); + } + + final int prefix = encodedValue.get(0) & 0xFF; + final RLPDecodingHelpers.Kind kind = RLPDecodingHelpers.Kind.of(prefix); + if (kind.isList()) { + throw new RLPException(format("Invalid input: value %s is an RLP list", encodedValue)); + } + + if (kind == RLPDecodingHelpers.Kind.BYTE_ELEMENT) { + return encodedValue; + } + + final int offset; + final int size; + if (kind == RLPDecodingHelpers.Kind.SHORT_ELEMENT) { + offset = 1; + size = prefix - 0x80; + } else { + final int sizeLength = prefix - 0xb7; + if (1 + sizeLength > encodedValue.size()) { + throw new RLPException( + format( + "Malformed RLP input: not enough bytes to read size of " + + "long item in %s: expected %d bytes but only %d", + encodedValue, sizeLength + 1, encodedValue.size())); + } + offset = 1 + sizeLength; + size = RLPDecodingHelpers.extractSize((index) -> encodedValue.get(index), 1, sizeLength); + } + if (offset + size != encodedValue.size()) { + throw new RLPException( + format( + "Malformed RLP input: %s should be of size %d according to " + + "prefix byte but of size %d", + encodedValue, offset + size, encodedValue.size())); + } + return encodedValue.slice(offset, size); + } + + /** + * Validates that the provided value is a valid RLP encoding. + * + * @param encodedValue The value to check. + * @throws RLPException if {@code encodedValue} is not a valid RLP encoding. + */ + public static void validate(final Bytes encodedValue) { + final RLPInput in = input(encodedValue); + while (!in.isDone()) { + if (in.nextIsList()) { + in.enterList(); + } else if (in.isEndOfCurrentList()) { + in.leaveList(); + } else { + // Skip does as much validation as can be done in general, without allocating anything. + in.skipNext(); + } + } + } + + /** + * Given a {@link Bytes} containing rlp-encoded data, determines the full length of the encoded + * value (including the prefix) by inspecting the prefixed metadata. + * + * @param value the rlp-encoded byte string + * @return the length of the encoded data, according to the prefixed metadata + */ + public static int calculateSize(final Bytes value) { + return RLPDecodingHelpers.rlpElementMetadata((index) -> value.get((int) index), value.size(), 0) + .getEncodedSize(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPDecodingHelpers.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPDecodingHelpers.java new file mode 100644 index 00000000000..670a6b83fb6 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPDecodingHelpers.java @@ -0,0 +1,208 @@ +/* + * 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.rlp; + +import java.util.function.IntUnaryOperator; +import java.util.function.LongUnaryOperator; + +/** + * Helper static methods to facilitate RLP decoding within this package. Neither this class + * nor any of its method are meant to be exposed publicly, they are too low level. + */ +class RLPDecodingHelpers { + + /** The kind of items an RLP item can be. */ + enum Kind { + BYTE_ELEMENT, + SHORT_ELEMENT, + LONG_ELEMENT, + SHORT_LIST, + LONG_LIST; + + static Kind of(final int prefix) { + if (prefix <= 0x7F) { + return Kind.BYTE_ELEMENT; + } else if (prefix <= 0xb7) { + return Kind.SHORT_ELEMENT; + } else if (prefix <= 0xbf) { + return Kind.LONG_ELEMENT; + } else if (prefix <= 0xf7) { + return Kind.SHORT_LIST; + } else { + return Kind.LONG_LIST; + } + } + + boolean isList() { + switch (this) { + case SHORT_LIST: + case LONG_LIST: + return true; + default: + return false; + } + } + } + + /** Read from the provided offset a size of the provided length, assuming this is enough bytes. */ + static int extractSize(final IntUnaryOperator getter, final int offset, final int sizeLength) { + int res = 0; + int shift = 0; + for (int i = 0; i < sizeLength; i++) { + res |= (getter.applyAsInt(offset + (sizeLength - 1) - i) & 0xFF) << shift; + shift += 8; + } + return res; + } + + /** Read from the provided offset a size of the provided length, assuming this is enough bytes. */ + private static int extractSizeFromLongItem( + final LongUnaryOperator getter, final long offset, final int sizeLength) { + if (sizeLength > 4) { + throw new RLPException( + "RLP item at offset " + + offset + + " with size value consuming " + + sizeLength + + " bytes exceeds max supported size of " + + Integer.MAX_VALUE); + } + + long res = 0; + int shift = 0; + for (int i = 0; i < sizeLength; i++) { + res |= (getter.applyAsLong(offset + (sizeLength - 1) - i) & 0xFF) << shift; + shift += 8; + } + try { + return Math.toIntExact(res); + } catch (final ArithmeticException e) { + throw new RLPException( + "RLP item at offset " + + offset + + " with size value consuming " + + sizeLength + + " bytes exceeds max supported size of " + + Integer.MAX_VALUE, + e); + } + } + + static RLPElementMetadata rlpElementMetadata( + final LongUnaryOperator byteGetter, final long size, final long elementStart) { + final int prefix = Math.toIntExact(byteGetter.applyAsLong(elementStart)) & 0xFF; + final Kind kind = Kind.of(prefix); + long payloadStart = 0; + int payloadSize = 0; + + switch (kind) { + case BYTE_ELEMENT: + payloadStart = elementStart; + payloadSize = 1; + break; + case SHORT_ELEMENT: + payloadStart = elementStart + 1; + payloadSize = prefix - 0x80; + break; + case LONG_ELEMENT: + final int sizeLengthElt = prefix - 0xb7; + payloadStart = elementStart + 1 + sizeLengthElt; + payloadSize = readLongSize(byteGetter, size, elementStart, sizeLengthElt); + break; + case SHORT_LIST: + payloadStart = elementStart + 1; + payloadSize = prefix - 0xc0; + break; + case LONG_LIST: + final int sizeLengthList = prefix - 0xf7; + payloadStart = elementStart + 1 + sizeLengthList; + payloadSize = readLongSize(byteGetter, size, elementStart, sizeLengthList); + break; + } + + return new RLPElementMetadata(kind, elementStart, payloadStart, payloadSize); + } + + /** The size of the item payload for a "long" item, given the length in bytes of the said size. */ + private static int readLongSize( + final LongUnaryOperator byteGetter, + final long sizeOfRlpEncodedByteString, + final long item, + final int sizeLength) { + // We will read sizeLength bytes from item + 1. There must be enough bytes for this or the input + // is corrupted. + if (sizeOfRlpEncodedByteString - (item + 1) < sizeLength) { + throw new CorruptedRLPInputException( + String.format( + "Invalid RLP item: value of size %d has not enough bytes to read the %d " + + "bytes payload size", + sizeOfRlpEncodedByteString, sizeLength)); + } + + // That size (which is at least 1 byte by construction) shouldn't have leading zeros. + if (byteGetter.applyAsLong(item + 1) == 0) { + throw new MalformedRLPInputException("Malformed RLP item: size of payload has leading zeros"); + } + + final int res = RLPDecodingHelpers.extractSizeFromLongItem(byteGetter, item + 1, sizeLength); + + // We should not have had the size written separately if it was less than 56 bytes long. + if (res < 56) { + throw new MalformedRLPInputException( + String.format("Malformed RLP item: written as a long item, but size %d < 56 bytes", res)); + } + + return res; + } + + static class RLPElementMetadata { + final Kind kind; // The type of rlp element + final long elementStart; // The index at which this element starts + final long payloadStart; // The index at which the payload of this element starts + final int payloadSize; // The size of the paylod + + RLPElementMetadata( + final Kind kind, final long elementStart, final long payloadStart, final int payloadSize) { + this.kind = kind; + this.elementStart = elementStart; + this.payloadStart = payloadStart; + this.payloadSize = payloadSize; + } + + /** + * @return the size of the byte string holding the rlp-encoded value and metadata + */ + int getEncodedSize() { + final long encodedSize = elementEnd() - elementStart + 1; + try { + return Math.toIntExact(encodedSize); + } catch (final ArithmeticException e) { + throw new RLPException( + String.format( + "RLP item exceeds max supported size of %d: %d", Integer.MAX_VALUE, encodedSize), + e); + } + } + + /** + * The index of the last byte of the rlp encoded element at startIndex + * + * @return the index of the last byte of the rlp encoded element at startIndex + */ + long elementEnd() { + return payloadStart + payloadSize - 1; + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPEncodingHelpers.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPEncodingHelpers.java new file mode 100644 index 00000000000..10558778268 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPEncodingHelpers.java @@ -0,0 +1,106 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; + +/** + * Helper static methods to facilitate RLP encoding within this package. Neither this class + * nor any of its method are meant to be exposed publicly, they are too low level. + */ +class RLPEncodingHelpers { + private RLPEncodingHelpers() {} + + static boolean isSingleRLPByte(final Bytes value) { + return value.size() == 1 && value.get(0) >= 0; + } + + static boolean isShortElement(final Bytes value) { + return value.size() <= 55; + } + + static boolean isShortList(final int payloadSize) { + return payloadSize <= 55; + } + + /** The encoded size of the provided value. */ + static int elementSize(final Bytes value) { + if (isSingleRLPByte(value)) return 1; + + if (isShortElement(value)) return 1 + value.size(); + + return 1 + sizeLength(value.size()) + value.size(); + } + + /** The encoded size of a list given the encoded size of its payload. */ + static int listSize(final int payloadSize) { + int size = 1 + payloadSize; + if (!isShortList(payloadSize)) size += sizeLength(payloadSize); + return size; + } + + /** + * Writes the result of encoding the provided value to the provided destination (which must be big + * enough). + */ + static int writeElement(final Bytes value, final MutableBytes dest, final int destOffset) { + final int size = value.size(); + if (isSingleRLPByte(value)) { + dest.set(destOffset, value.get(0)); + return destOffset + 1; + } + + if (isShortElement(value)) { + dest.set(destOffset, (byte) (0x80 + size)); + value.copyTo(dest, destOffset + 1); + return destOffset + 1 + size; + } + + final int offset = writeLongMetadata(0xb7, size, dest, destOffset); + value.copyTo(dest, offset); + return offset + size; + } + + /** + * Writes the encoded header of a list provided its encoded payload size to the provided + * destination (which must be big enough). + */ + static int writeListHeader(final int payloadSize, final MutableBytes dest, final int destOffset) { + if (isShortList(payloadSize)) { + dest.set(destOffset, (byte) (0xc0 + payloadSize)); + return destOffset + 1; + } + + return writeLongMetadata(0xf7, payloadSize, dest, destOffset); + } + + private static int writeLongMetadata( + final int baseCode, final int size, final MutableBytes dest, final int destOffset) { + final int sizeLength = sizeLength(size); + dest.set(destOffset, (byte) (baseCode + sizeLength)); + int shift = 0; + for (int i = 0; i < sizeLength; i++) { + dest.set(destOffset + sizeLength - i, (byte) (size >> shift)); + shift += 8; + } + return destOffset + 1 + sizeLength; + } + + private static int sizeLength(final int size) { + final int zeros = Integer.numberOfLeadingZeros(size); + return 4 - (zeros / 8); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPException.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPException.java new file mode 100644 index 00000000000..a00767053cd --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPException.java @@ -0,0 +1,25 @@ +/* + * 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.rlp; + +public class RLPException extends RuntimeException { + public RLPException(final String message) { + this(message, null); + } + + RLPException(final String message, final Throwable throwable) { + super(message, throwable); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java new file mode 100644 index 00000000000..3419dd4ac9b --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java @@ -0,0 +1,356 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * An input used to decode data in RLP encoding. + * + *

An RLP "value" is fundamentally an {@code Item} defined the following way: + * + *

+ *   Item ::= List | Bytes
+ *   List ::= [ Item, ... , Item ]
+ *   Bytes ::= a binary value (comprised of an arbitrary number of bytes).
+ * 
+ * + *

In other words, RLP encodes binary data organized in arbitrary nested lists. + * + *

A {@link RLPInput} thus provides methods to decode both lists and binary values. A list in the + * input is "entered" by calling {@link #enterList()} and left by calling {@link #leaveList()}. + * Binary values can be read directly with {@link #readBytes()} ()}, but the {@link RLPInput} + * interface provides a wealth of convenience methods to read specific types of data that are in + * specific encoding. + * + *

Amongst the methods to read binary data, some methods are provided to read "scalar". A scalar + * should simply be understood as a positive integer that is encoded with no leading zeros. In other + * word, a method like {@link #readLongScalar()} does not expect an encoded value of exactly 8 bytes + * (by opposition to {@link #readLong}), but rather one that is "up to" 8 bytes. + * + * @see BytesValueRLPInput for a {@link RLPInput} that decode an RLP encoded value stored in a + * {@link Bytes}. + */ +public interface RLPInput { + + /** + * Whether the input has been already fully decoded (has no more data to read). + * + * @return {@code false} if the input has more data to read, {@code true} otherwise. + */ + boolean isDone(); + + /** + * Whether the next element to read from this input is a list. + * + * @return {@code true} if the input is not done and the next item to read is a list. + */ + boolean nextIsList(); + + /** + * Whether the next element to read from this input is an RLP "null" (that is, {@link + * Bytes#EMPTY}). + * + * @return {@code true} if the input is not done and the next item to read is an empty value. + */ + boolean nextIsNull(); + + /** + * Returns the payload size of the next item + * + * @return the payload size of the next item + */ + int nextSize(); + + /** + * Returns the offset of the next item, counting from the start of the RLP Input as a whole. + * + * @return offset from buffer start + */ + int nextOffset(); + + /** + * Whether the input is at the end of a currently entered list, that is if {@link #leaveList()} + * should be the next method called. + * + * @return Whether all elements of the current list have been read but said list haven't been + * "left" yet. + */ + boolean isEndOfCurrentList(); + + /** + * Skips the next item to read in the input. + * + *

Note that if the next item is a list, the whole list is skipped. + */ + void skipNext(); + + /** + * If the next item to read is a list, enter that list, placing the input on the first item of + * that list. + * + * @return The number of item of the entered list. + * @throws RLPException if the next item to read from this input is not a list, or the input is + * corrupted. + */ + int enterList(); + + /** + * Exits the current list after all its items have been consumed. + * + *

Note that this method technically doesn't consume any input but must be called after having + * read the last element of a list. This allow to ensure the structure of the input is indeed the + * one expected. + * + * @throws RLPException if the current list is not finished (it has more items). + */ + void leaveList(); + + /** + * Exits the current list, ignoring any remaining unconsumed elements. + * + *

Note that this method technically doesn't consume any input but must be called after having + * read the last element of a list. This allow to ensure the structure of the input is indeed the + * one expected. + */ + void leaveListLenient(); + + /** + * Reads a non-negative scalar from the input and return it as a long value which is interpreted + * as an unsigned long. + * + * @return The next scalar item of this input as a long value. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is either too big to + * fit a long or has leading zeros. + */ + long readLongScalar(); + + /** + * Reads a scalar from the input and return is as an int value. + * + * @return The next scalar item of this input as an int value. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is either too big to + * fit a long or has leading zeros. + */ + int readIntScalar(); + + /** + * Reads a scalar from the input and return is as a {@link BigInteger}. + * + * @return The next scalar item of this input as a {@link BigInteger}. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item has leading zeros. + */ + BigInteger readBigIntegerScalar(); + + /** + * Reads a scalar from the input and return is as a {@link UInt256}. + * + * @return The next scalar item of this input as a {@link UInt256}. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is either too big to + * fit a {@link UInt256} or has leading zeros. + */ + UInt256 readUInt256Scalar(); + + /** + * Reads the next item of this input (which must be exactly 1 byte) as a byte. + * + * @return The byte corresponding to the next item of this input. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not a single byte + * long. + */ + byte readByte(); + + /** + * Reads the next item of this input (which must be exactly 2-bytes) as a (signed) short. + * + * @return The short corresponding to the next item of this input. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not 2-bytes. + */ + short readShort(); + + /** + * Reads the next item of this input (which must be exactly 4-bytes) as a (signed) int. + * + * @return The int corresponding to the next item of this input. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not 4-bytes. + */ + int readInt(); + + /** + * Reads the next item of this input (which must be exactly 8-bytes) as a (signed) long. + * + * @return The long corresponding to the next item of this input. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not 8-bytes. + */ + long readLong(); + + /** + * Reads the next item of this input (which must be exactly 1 byte) as an unsigned byte. + * + * @return The value of the next item interpreted as an unsigned byte. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not a single byte + * long. + */ + default int readUnsignedByte() { + if (isZeroLengthString()) { + // Decode an empty string (0x80) as an unsigned byte with value 0 + readBytes(); + return 0; + } + return readByte() & 0xFF; + } + + /** + * Reads the next item of this input (which must be exactly 2-bytes) as an unsigned short. + * + * @return The value of the next item interpreted as an unsigned short. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not 2-bytes. + */ + default int readUnsignedShort() { + return readShort() & 0xFFFF; + } + + /** + * Reads the next item of this input (which must be exactly 4-bytes) as an unsigned int. + * + * @return The value of the next item interpreted as an unsigned int. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is not 4-bytes. + */ + default long readUnsignedInt() { + return (readInt()) & 0xFFFFFFFFL; + } + + /** + * Reads an inet address from this input. + * + * @return The inet address corresponding to the next item of this input. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or if the next item is neither 4 nor 16 + * bytes. + */ + InetAddress readInetAddress(); + + /** + * Reads the next item of this input (assuming it is not a list). + * + * @return The next item read of this input. + * @throws RLPException if the next item to read is a list or the input is at the end of its + * current list (and {@link #leaveList()} hasn't been called). + */ + Bytes readBytes(); + + /** + * Reads the next item of this input (assuming it is not a list) that must be exact 32 bytes. + * + * @return The next item read of this input. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or the next element is not exactly 32 + * bytes. + */ + Bytes32 readBytes32(); + + /** + * Reads the next item of this input (assuming it is not a list) and transforms it with the + * provided mapping function. + * + *

Note that the only benefit of this method over calling the mapper function on the result of + * {@link #readBytes()} is that any error thrown by the mapper will be wrapped by a {@link + * RLPException}, which can make error handling more convenient (having a particular decoded value + * not match what is expected is not fundamentally different from trying to read an unsigned short + * from an item with strictly more or less than 2 bytes). + * + * @param mapper The mapper to apply to the read value. + * @param The type of the result. + * @return The next item read from this input, mapped through {@code mapper}. + * @throws RLPException if the next item to read is a list, the input is at the end of its current + * list (and {@link #leaveList()} hasn't been called) or {@code mapper} throws an exception + * when applied. + */ + T readBytes(Function mapper); + + /** + * Returns the current element as a standalone RLP element. + * + *

This method is useful to extract self-contained RLP elements from a list, so they can be + * processed individually later. + * + * @return The current element as a standalone RLP input element. + */ + RLPInput readAsRlp(); + + /** + * Returns a raw {@link Bytes} representation of this RLP. + * + * @return The raw RLP. + */ + Bytes raw(); + + boolean isZeroLengthString(); + + /** Resets this RLP input to the start. */ + void reset(); + + /** + * Returns a raw {@link Bytes} representation of this RLP list. + * + * @return The raw RLP list. + */ + Bytes currentListAsBytes(); + + /** + * Reads a full list from the input given a method that knows how to read its elements. + * + * @param valueReader A method that can decode a single list element. + * @param The type of the elements of the decoded list. + * @return The next list of this input, where elements are decoded using {@code valueReader}. + * @throws RLPException is the next item to read is not a list, of if any error happens when + * applying {@code valueReader} to read elements of the list. + */ + default List readList(final Function valueReader) { + final int size = enterList(); + final List res = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + try { + res.add(valueReader.apply(this)); + } catch (final Exception e) { + throw new RLPException( + String.format( + "Error applying element decoding function on " + "element %d of the list", i), + e); + } + } + leaveList(); + return res; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java new file mode 100644 index 00000000000..4c72656c78e --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java @@ -0,0 +1,302 @@ +/* + * 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.rlp; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; +import org.apache.tuweni.units.bigints.UInt256Value; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * An output used to encode data in RLP encoding. + * + *

An RLP "value" is fundamentally an {@code Item} defined the following way: + * + *

+ *   Item ::= List | Bytes
+ *   List ::= [ Item, ... , Item ]
+ *   Bytes ::= a binary value (comprised of an arbitrary number of bytes).
+ * 
+ * + * In other words, RLP encodes binary data organized in arbitrary nested lists. + * + *

A {@link RLPOutput} thus provides methods to write both lists and binary values. A list is + * started by calling {@link #startList()} and ended by {@link #endList()}. Lists can be nested in + * other lists in arbitrary ways. Binary values can be written directly with {@link + * #writeBytes(Bytes)}, but the {@link RLPOutput} interface provides a wealth of convenience methods + * to write specific types of data with a specific encoding. + * + *

Amongst the methods to write binary data, some methods are provided to write "scalar". A + * scalar should simply be understood as a positive integer that is encoded with no leading zeros. + * In other word, if an integer is written with a "Scalar" method variant, that number will be + * encoded with the minimum number of bytes necessary to represent it. + * + *

The {@link RLPOutput} only defines the interface for writing data meant to be RLP encoded. + * Getting the finally encoded output will depend on the concrete implementation, see {@link + * BytesValueRLPOutput} for instance. + */ +public interface RLPOutput { + + /** Starts a new list. */ + void startList(); + + /** + * Ends the current list. + * + * @throws IllegalStateException if no list has been previously started with {@link #startList()} + * (or any started had already be ended). + */ + void endList(); + + /** + * Writes a new value. + * + * @param v The value to write. + */ + void writeBytes(Bytes v); + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + */ + default void writeUInt256Scalar(final UInt256Value v) { + writeBytes(v.trimLeadingZeros()); + } + + /** + * Writes a RLP "null", that is an empty value. + * + *

This is a shortcut for {@code writeBytes(Bytes.EMPTY)}. + */ + default void writeNull() { + writeBytes(Bytes.EMPTY); + } + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + * @throws IllegalArgumentException if {@code v < 0}. + */ + default void writeIntScalar(final int v) { + writeLongScalar(v); + } + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + */ + default void writeLongScalar(final long v) { + writeBytes(Bytes.minimalBytes(v)); + } + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + */ + default void writeBigIntegerScalar(final BigInteger v) { + if (v.equals(BigInteger.ZERO)) { + writeBytes(Bytes.EMPTY); + return; + } + + final byte[] bytes = v.toByteArray(); + // BigInteger will not include leading zeros by contract, but it always includes at least one + // bit of sign (a zero here since it's positive). What that mean is that if the first 1 of the + // resulting number is exactly on a byte boundary, then the sign bit constraint will make the + // value include one extra byte, which will be zero. In other words, they can be one zero bytes + // in practice we should ignore, but there should never be more than one. + writeBytes( + bytes.length > 1 && bytes[0] == 0 + ? Bytes.wrap(bytes, 1, bytes.length - 1) + : Bytes.wrap(bytes)); + } + + /** + * Writes a single byte value. + * + * @param b The byte to write. + */ + default void writeByte(final byte b) { + writeBytes(Bytes.of(b)); + } + + /** + * Writes a 2-bytes value. + * + *

Note that this is not a "scalar" write: the value will be encoded with exactly 2 bytes. + * + * @param s The 2-bytes short to write. + */ + default void writeShort(final short s) { + final byte[] res = new byte[2]; + res[0] = (byte) (s >> 8); + res[1] = (byte) s; + writeBytes(Bytes.wrap(res)); + } + + /** + * Writes a 4-bytes value. + * + *

Note that this is not a "scalar" write: the value will be encoded with exactly 4 bytes. + * + * @param i The 4-bytes int to write. + */ + default void writeInt(final int i) { + final MutableBytes v = MutableBytes.create(4); + v.setInt(0, i); + writeBytes(v); + } + + /** + * Writes a 8-bytes value. + * + *

Note that this is not a "scalar" write: the value will be encoded with exactly 8 bytes. + * + * @param l The 8-bytes long to write. + */ + default void writeLong(final long l) { + final MutableBytes v = MutableBytes.create(8); + v.setLong(0, l); + writeBytes(v); + } + + /** + * Writes a single byte value. + * + * @param b A value that must fit an unsigned byte. + * @throws IllegalArgumentException if {@code b} does not fit an unsigned byte, that is if either + * {@code b < 0} or {@code b > 0xFF}. + */ + default void writeUnsignedByte(final int b) { + processZeroByte(Long.valueOf(b), a -> writeBytes(Bytes.of(b))); + } + + /** + * Writes a 2-bytes value. + * + * @param s A value that must fit an unsigned 2-bytes short. + * @throws IllegalArgumentException if {@code s} does not fit an unsigned 2-bytes short, that is + * if either {@code s < 0} or {@code s > 0xFFFF}. + */ + default void writeUnsignedShort(final int s) { + processZeroByte(Long.valueOf(s), a -> writeBytes(Bytes.ofUnsignedShort(s).trimLeadingZeros())); + } + + /** + * Writes a 4-bytes value. + * + * @param i A value that must fit an unsigned 4-bytes integer. + * @throws IllegalArgumentException if {@code i} does not fit an unsigned 4-bytes int, that is if + * either {@code i < 0} or {@code i > 0xFFFFFFFFL}. + */ + default void writeUnsignedInt(final long i) { + processZeroByte(i, a -> writeBytes(Bytes.ofUnsignedInt(i).trimLeadingZeros())); + } + + /** + * Writes the byte representation of an inet address (so either 4 or 16 bytes long). + * + * @param address The address to write. + */ + default void writeInetAddress(final InetAddress address) { + writeBytes(Bytes.wrap(address.getAddress())); + } + + /** + * Writes a list of values of a specific class provided a function to write values of that class + * to an {@link RLPOutput}. + * + *

This is a convenience method whose result is equivalent to doing: + * + *

{@code
+   * startList();
+   * for (T v : values) {
+   *   valueWriter.accept(v, this);
+   * }
+   * endList();
+   * }
+ * + * @param values A list of value of type {@code T}. + * @param valueWriter A method that given a value of type {@code T} and an {@link RLPOutput}, + * writes this value to the output. + * @param The type of values to write. + */ + default void writeList(final Iterable values, final BiConsumer valueWriter) { + startList(); + for (final T v : values) { + valueWriter.accept(v, this); + } + endList(); + } + + /** + * Writes an empty list to the output. + * + *

This is a shortcut for doing: + * + *

{@code
+   * startList();
+   * endList();
+   * }
+ */ + default void writeEmptyList() { + startList(); + endList(); + } + + /** + * Writes an already RLP encoded item to the output. + * + *

This method is the functional equivalent of decoding the provided value entirely (to an + * fully formed Java object) and then re-encoding that result to this output. It is however a lot + * more efficient in that it saves most of that decoding/re-encoding work. Please note however + * that this method does validate that the input is a valid RLP encoding. If you can + * guaranteed that the input is valid and do not want this validation step, please have a look at + * {@link #writeRaw(Bytes)}. + * + * @param rlpEncodedValue An already RLP encoded value to write as next item of this output. + */ + default void writeRLPBytes(final Bytes rlpEncodedValue) { + RLP.validate(rlpEncodedValue); + writeRaw(rlpEncodedValue); + } + + /** + * Writes an already RLP encoded item to the output. + * + *

This method is equivalent to {@link #writeRLPBytes(Bytes)}, but is unsafe in that it does + * not do any validation of the its input. As such, it is faster but can silently yield invalid + * RLP output if misused. + * + * @param bytes An already RLP encoded value to write as next item of this output. + */ + void writeRaw(Bytes bytes); + + /** + * Check if the incoming value is 0 and writes it as 0x80, per the spec. + * + * @param input The value to check + * @param writer The consumer to write the non-zero output + */ + void processZeroByte(final Long input, final Consumer writer); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java new file mode 100644 index 00000000000..123abd4fabc --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java @@ -0,0 +1,68 @@ +/* + * 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.trie; + +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; + +public class AllNodesVisitor implements NodeVisitor { + + private final Consumer> handler; + + private ExecutorService executorService; + + AllNodesVisitor(final Consumer> handler) { + this.handler = handler; + } + + AllNodesVisitor(final Consumer> handler, ExecutorService executorService) { + this.handler = handler; + this.executorService = executorService; + } + + @Override + public void visit(final ExtensionNode extensionNode) { + handler.accept(extensionNode); + acceptAndUnload(extensionNode.getChild()); + } + + @Override + public void visit(final BranchNode branchNode) { + handler.accept(branchNode); + if (executorService == null) { + branchNode.getChildren().forEach(this::acceptAndUnload); + } else { + branchNode.getChildren().forEach(child-> { + executorService.submit( () -> { + child.accept(this); + child.unload(); + }); + }); + } + } + + @Override + public void visit(final LeafNode leafNode) { + handler.accept(leafNode); + } + + @Override + public void visit(final NullNode nullNode) {} + + private void acceptAndUnload(final Node storedNode) { + storedNode.accept(this); + storedNode.unload(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/BranchNode.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/BranchNode.java new file mode 100644 index 00000000000..af688a02a5c --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/BranchNode.java @@ -0,0 +1,267 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.function.Function; + +import static org.hyperledger.besu.crypto.Hash.keccak256; + +public class BranchNode implements Node { + public static final byte RADIX = CompactEncoding.LEAF_TERMINATOR; + + @SuppressWarnings("rawtypes") + private static final Node NULL_NODE = NullNode.instance(); + + private final Optional location; + private final ArrayList> children; + private final Optional value; + private final NodeFactory nodeFactory; + private final Function valueSerializer; + private WeakReference rlp; + private SoftReference hash; + private boolean dirty = false; + private boolean needHeal = false; + + BranchNode( + final Bytes location, + final ArrayList> children, + final Optional value, + final NodeFactory nodeFactory, + final Function valueSerializer) { + assert (children.size() == RADIX); + this.location = Optional.ofNullable(location); + this.children = children; + this.value = value; + this.nodeFactory = nodeFactory; + this.valueSerializer = valueSerializer; + } + + BranchNode( + final ArrayList> children, + final Optional value, + final NodeFactory nodeFactory, + final Function valueSerializer) { + assert (children.size() == RADIX); + this.location = Optional.empty(); + this.children = children; + this.value = value; + this.nodeFactory = nodeFactory; + this.valueSerializer = valueSerializer; + } + + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + return visitor.visit(this, path); + } + + @Override + public void accept(final NodeVisitor visitor) { + visitor.visit(this); + } + + @Override + public void accept(final Bytes location, final LocationNodeVisitor visitor) { + visitor.visit(location, this); + } + + @Override + public Optional getLocation() { + return location; + } + + @Override + public Bytes getPath() { + return Bytes.EMPTY; + } + + @Override + public Optional getValue() { + return value; + } + + @Override + public List> getChildren() { + return Collections.unmodifiableList(children); + } + + public Node child(final byte index) { + return children.get(index); + } + + @Override + public Bytes getRlp() { + if (rlp != null) { + final Bytes encoded = rlp.get(); + if (encoded != null) { + return encoded; + } + } + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.startList(); + for (int i = 0; i < RADIX; ++i) { + out.writeRaw(children.get(i).getRlpRef()); + } + if (value.isPresent()) { + out.writeBytes(valueSerializer.apply(value.get())); + } else { + out.writeNull(); + } + out.endList(); + final Bytes encoded = out.encoded(); + rlp = new WeakReference<>(encoded); + return encoded; + } + + @Override + public Bytes getRlpRef() { + if (isReferencedByHash()) { + return RLP.encodeOne(getHash()); + } else { + return getRlp(); + } + } + + @Override + public Bytes32 getHash() { + if (hash != null) { + final Bytes32 hashed = hash.get(); + if (hashed != null) { + return hashed; + } + } + final Bytes32 hashed = keccak256(getRlp()); + hash = new SoftReference<>(hashed); + return hashed; + } + + @Override + public Node replacePath(final Bytes newPath) { + return nodeFactory.createExtension(newPath, this); + } + + public Node replaceChild(final byte index, final Node updatedChild) { + return replaceChild(index, updatedChild, true); + } + + public Node replaceChild( + final byte index, final Node updatedChild, final boolean allowFlatten) { + final ArrayList> newChildren = new ArrayList<>(children); + newChildren.set(index, updatedChild); + + if (updatedChild == NULL_NODE) { + if (value.isPresent() && !hasChildren()) { + return nodeFactory.createLeaf(Bytes.of(index), value.get()); + } else if (!value.isPresent() && allowFlatten) { + final Optional> flattened = maybeFlatten(newChildren); + if (flattened.isPresent()) { + return flattened.get(); + } + } + } + + return nodeFactory.createBranch(newChildren, value); + } + + public Node replaceValue(final V value) { + return nodeFactory.createBranch(children, Optional.of(value)); + } + + public Node removeValue() { + return maybeFlatten(children).orElse(nodeFactory.createBranch(children, Optional.empty())); + } + + private boolean hasChildren() { + for (final Node child : children) { + if (child != NULL_NODE) { + return true; + } + } + return false; + } + + private static Optional> maybeFlatten(final ArrayList> children) { + final int onlyChildIndex = findOnlyChild(children); + if (onlyChildIndex >= 0) { + // replace the path of the only child and return it + final Node onlyChild = children.get(onlyChildIndex); + final Bytes onlyChildPath = onlyChild.getPath(); + final MutableBytes completePath = MutableBytes.create(1 + onlyChildPath.size()); + completePath.set(0, (byte) onlyChildIndex); + onlyChildPath.copyTo(completePath, 1); + return Optional.of(onlyChild.replacePath(completePath)); + } + return Optional.empty(); + } + + private static int findOnlyChild(final ArrayList> children) { + int onlyChildIndex = -1; + assert (children.size() == RADIX); + for (int i = 0; i < RADIX; ++i) { + if (children.get(i) != NULL_NODE) { + if (onlyChildIndex >= 0) { + return -1; + } + onlyChildIndex = i; + } + } + return onlyChildIndex; + } + + @Override + public String print() { + final StringBuilder builder = new StringBuilder(); + builder.append("Branch:"); + builder.append("\n\tRef: ").append(getRlpRef()); + for (int i = 0; i < RADIX; i++) { + final Node child = child((byte) i); + if (!Objects.equals(child, NullNode.instance())) { + final String branchLabel = "[" + Integer.toHexString(i) + "] "; + final String childRep = child.print().replaceAll("\n\t", "\n\t\t"); + builder.append("\n\t").append(branchLabel).append(childRep); + } + } + builder.append("\n\tValue: ").append(getValue().map(Object::toString).orElse("empty")); + return builder.toString(); + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void markDirty() { + dirty = true; + } + + @Override + public boolean isHealNeeded() { + return needHeal; + } + + @Override + public void markHealNeeded() { + this.needHeal = true; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java new file mode 100644 index 00000000000..4e3335e3119 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java @@ -0,0 +1,75 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +public class CommitVisitor implements LocationNodeVisitor { + + private final NodeUpdater nodeUpdater; + + public CommitVisitor(final NodeUpdater nodeUpdater) { + this.nodeUpdater = nodeUpdater; + } + + @Override + public void visit(final Bytes location, final ExtensionNode extensionNode) { + if (!extensionNode.isDirty()) { + return; + } + + final Node child = extensionNode.getChild(); + if (child.isDirty()) { + child.accept(Bytes.concatenate(location, extensionNode.getPath()), this); + } + + maybeStoreNode(location, extensionNode); + } + + @Override + public void visit(final Bytes location, final BranchNode branchNode) { + if (!branchNode.isDirty()) { + return; + } + + for (byte i = 0; i < BranchNode.RADIX; ++i) { + final Node child = branchNode.child(i); + if (child.isDirty()) { + child.accept(Bytes.concatenate(location, Bytes.of(i)), this); + } + } + + maybeStoreNode(location, branchNode); + } + + @Override + public void visit(final Bytes location, final LeafNode leafNode) { + if (!leafNode.isDirty()) { + return; + } + + maybeStoreNode(location, leafNode); + } + + @Override + public void visit(final Bytes location, final NullNode nullNode) {} + + public void maybeStoreNode(final Bytes location, final Node node) { + final Bytes nodeRLP = node.getRlp(); + if (nodeRLP.size() >= 32) { + this.nodeUpdater.store(location, node.getHash(), nodeRLP); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java new file mode 100644 index 00000000000..2622e8a45a8 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java @@ -0,0 +1,123 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; + +import static com.google.common.base.Preconditions.checkArgument; + +public abstract class CompactEncoding { + private CompactEncoding() {} + + public static final byte LEAF_TERMINATOR = 0x10; + + public static Bytes bytesToPath(final Bytes bytes) { + final MutableBytes path = MutableBytes.create(bytes.size() * 2 + 1); + int j = 0; + for (int i = 0; i < bytes.size(); i += 1, j += 2) { + final byte b = bytes.get(i); + path.set(j, (byte) ((b >>> 4) & 0x0f)); + path.set(j + 1, (byte) (b & 0x0f)); + } + path.set(j, LEAF_TERMINATOR); + return path; + } + + public static Bytes pathToBytes(final Bytes path) { + checkArgument(!path.isEmpty(), "Path must not be empty"); + checkArgument(path.get(path.size() - 1) == LEAF_TERMINATOR, "Path must be a leaf path"); + final MutableBytes bytes = MutableBytes.create((path.size() - 1) / 2); + int bytesPos = 0; + for (int pathPos = 0; pathPos < path.size() - 1; pathPos += 2, bytesPos += 1) { + final byte high = path.get(pathPos); + final byte low = path.get(pathPos + 1); + if ((high & 0xf0) != 0 || (low & 0xf0) != 0) { + throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble"); + } + bytes.set(bytesPos, (byte) (high << 4 | low)); + } + return bytes; + } + + public static Bytes encode(final Bytes path) { + int size = path.size(); + final boolean isLeaf = size > 0 && path.get(size - 1) == LEAF_TERMINATOR; + if (isLeaf) { + size = size - 1; + } + + final MutableBytes encoded = MutableBytes.create((size + 2) / 2); + int i = 0; + int j = 0; + + if (size % 2 == 1) { + // add first nibble to magic + final byte high = (byte) (isLeaf ? 0x03 : 0x01); + final byte low = path.get(i++); + if ((low & 0xf0) != 0) { + throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble"); + } + encoded.set(j++, (byte) (high << 4 | low)); + } else { + final byte high = (byte) (isLeaf ? 0x02 : 0x00); + encoded.set(j++, (byte) (high << 4)); + } + + while (i < size) { + final byte high = path.get(i++); + final byte low = path.get(i++); + if ((high & 0xf0) != 0 || (low & 0xf0) != 0) { + throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble"); + } + encoded.set(j++, (byte) (high << 4 | low)); + } + + return encoded; + } + + public static Bytes decode(final Bytes encoded) { + final int size = encoded.size(); + checkArgument(size > 0); + final byte metadata = encoded.get(0); + checkArgument((metadata & 0xc0) == 0, "Invalid compact encoding"); + + final boolean isLeaf = (metadata & 0x20) != 0; + + final int pathLength = ((size - 1) * 2) + (isLeaf ? 1 : 0); + final MutableBytes path; + int i = 0; + + if ((metadata & 0x10) != 0) { + // need to use lower nibble of metadata + path = MutableBytes.create(pathLength + 1); + path.set(i++, (byte) (metadata & 0x0f)); + } else { + path = MutableBytes.create(pathLength); + } + + for (int j = 1; j < size; j++) { + final byte b = encoded.get(j); + path.set(i++, (byte) ((b >>> 4) & 0x0f)); + path.set(i++, (byte) (b & 0x0f)); + } + + if (isLeaf) { + path.set(i, LEAF_TERMINATOR); + } + + return path; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/DefaultNodeFactory.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/DefaultNodeFactory.java new file mode 100644 index 00000000000..e8e01b99258 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/DefaultNodeFactory.java @@ -0,0 +1,71 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Optional; +import java.util.function.Function; + +public class DefaultNodeFactory implements NodeFactory { + @SuppressWarnings("rawtypes") + private static final Node NULL_NODE = NullNode.instance(); + + private final Function valueSerializer; + + public DefaultNodeFactory(final Function valueSerializer) { + this.valueSerializer = valueSerializer; + } + + @Override + public Node createExtension(final Bytes path, final Node child) { + return new ExtensionNode<>(path, child, this); + } + + @SuppressWarnings("unchecked") + @Override + public Node createBranch( + final byte leftIndex, final Node left, final byte rightIndex, final Node right) { + assert (leftIndex <= BranchNode.RADIX); + assert (rightIndex <= BranchNode.RADIX); + assert (leftIndex != rightIndex); + + final ArrayList> children = + new ArrayList<>(Collections.nCopies(BranchNode.RADIX, (Node) NULL_NODE)); + if (leftIndex == BranchNode.RADIX) { + children.set(rightIndex, right); + return createBranch(children, left.getValue()); + } else if (rightIndex == BranchNode.RADIX) { + children.set(leftIndex, left); + return createBranch(children, right.getValue()); + } else { + children.set(leftIndex, left); + children.set(rightIndex, right); + return createBranch(children, Optional.empty()); + } + } + + @Override + public Node createBranch(final ArrayList> children, final Optional value) { + return new BranchNode<>(children, value, this, valueSerializer); + } + + @Override + public Node createLeaf(final Bytes path, final V value) { + return new LeafNode<>(path, value, this, valueSerializer); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/ExtensionNode.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/ExtensionNode.java new file mode 100644 index 00000000000..4677a4004cf --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/ExtensionNode.java @@ -0,0 +1,192 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.hyperledger.besu.crypto.Hash.keccak256; + +public class ExtensionNode implements Node { + + private final Optional location; + private final Bytes path; + private final Node child; + private final NodeFactory nodeFactory; + private WeakReference rlp; + private SoftReference hash; + private boolean dirty = false; + private boolean needHeal = false; + + ExtensionNode( + final Bytes location, + final Bytes path, + final Node child, + final NodeFactory nodeFactory) { + assert (path.size() > 0); + assert (path.get(path.size() - 1) != CompactEncoding.LEAF_TERMINATOR) + : "Extension path ends in a leaf terminator"; + this.location = Optional.ofNullable(location); + this.path = path; + this.child = child; + this.nodeFactory = nodeFactory; + } + + ExtensionNode(final Bytes path, final Node child, final NodeFactory nodeFactory) { + assert (path.size() > 0); + assert (path.get(path.size() - 1) != CompactEncoding.LEAF_TERMINATOR) + : "Extension path ends in a leaf terminator"; + this.location = Optional.empty(); + this.path = path; + this.child = child; + this.nodeFactory = nodeFactory; + } + + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + return visitor.visit(this, path); + } + + @Override + public void accept(final NodeVisitor visitor) { + visitor.visit(this); + } + + @Override + public void accept(final Bytes location, final LocationNodeVisitor visitor) { + visitor.visit(location, this); + } + + @Override + public Optional getLocation() { + return location; + } + + @Override + public Bytes getPath() { + return path; + } + + @Override + public Optional getValue() { + return Optional.empty(); + } + + @Override + public List> getChildren() { + return Collections.singletonList(child); + } + + public Node getChild() { + return child; + } + + @Override + public Bytes getRlp() { + if (rlp != null) { + final Bytes encoded = rlp.get(); + if (encoded != null) { + return encoded; + } + } + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.startList(); + out.writeBytes(CompactEncoding.encode(path)); + out.writeRaw(child.getRlpRef()); + out.endList(); + final Bytes encoded = out.encoded(); + rlp = new WeakReference<>(encoded); + return encoded; + } + + @Override + public Bytes getRlpRef() { + if (isReferencedByHash()) { + return RLP.encodeOne(getHash()); + } else { + return getRlp(); + } + } + + @Override + public Bytes32 getHash() { + if (hash != null) { + final Bytes32 hashed = hash.get(); + if (hashed != null) { + return hashed; + } + } + final Bytes rlp = getRlp(); + final Bytes32 hashed = keccak256(rlp); + hash = new SoftReference<>(hashed); + return hashed; + } + + public Node replaceChild(final Node updatedChild) { + // collapse this extension - if the child is a branch, it will create a new extension + return updatedChild.replacePath(Bytes.concatenate(path, updatedChild.getPath())); + } + + @Override + public Node replacePath(final Bytes path) { + if (path.size() == 0) { + return child; + } + return nodeFactory.createExtension(path, child); + } + + @Override + public String print() { + final StringBuilder builder = new StringBuilder(); + final String childRep = getChild().print().replaceAll("\n\t", "\n\t\t"); + builder + .append("Extension:") + .append("\n\tRef: ") + .append(getRlpRef()) + .append("\n\tPath: ") + .append(CompactEncoding.encode(path)) + .append("\n\t") + .append(childRep); + return builder.toString(); + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void markDirty() { + dirty = true; + } + + @Override + public boolean isHealNeeded() { + return needHeal; + } + + @Override + public void markHealNeeded() { + this.needHeal = true; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/GetVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/GetVisitor.java new file mode 100644 index 00000000000..20397701a46 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/GetVisitor.java @@ -0,0 +1,62 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +class GetVisitor implements PathNodeVisitor { + private final Node NULL_NODE_RESULT = NullNode.instance(); + + @Override + public Node visit(final ExtensionNode extensionNode, final Bytes path) { + final Bytes extensionPath = extensionNode.getPath(); + final int commonPathLength = extensionPath.commonPrefixLength(path); + assert commonPathLength < path.size() + : "Visiting path doesn't end with a non-matching terminator"; + + if (commonPathLength < extensionPath.size()) { + // path diverges before the end of the extension, so it cannot match + return NULL_NODE_RESULT; + } + + return extensionNode.getChild().accept(this, path.slice(commonPathLength)); + } + + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + assert path.size() > 0 : "Visiting path doesn't end with a non-matching terminator"; + + final byte childIndex = path.get(0); + if (childIndex == CompactEncoding.LEAF_TERMINATOR) { + return branchNode; + } + + return branchNode.child(childIndex).accept(this, path.slice(1)); + } + + @Override + public Node visit(final LeafNode leafNode, final Bytes path) { + final Bytes leafPath = leafNode.getPath(); + if (leafPath.commonPrefixLength(path) != leafPath.size()) { + return NULL_NODE_RESULT; + } + return leafNode; + } + + @Override + public Node visit(final NullNode nullNode, final Bytes path) { + return NULL_NODE_RESULT; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/KeyValueMerkleStorage.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/KeyValueMerkleStorage.java new file mode 100644 index 00000000000..d5a06130c6f --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/KeyValueMerkleStorage.java @@ -0,0 +1,66 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorageTransaction; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class KeyValueMerkleStorage implements MerkleStorage { + + protected final KeyValueStorage keyValueStorage; + protected final Map pendingUpdates = new HashMap<>(); + + public KeyValueMerkleStorage(final KeyValueStorage keyValueStorage) { + this.keyValueStorage = keyValueStorage; + } + + @Override + public Optional get(final Bytes location, final Bytes32 hash) { + return pendingUpdates.containsKey(hash) + ? Optional.of(pendingUpdates.get(hash)) + : keyValueStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap); + } + + @Override + public void put(final Bytes location, final Bytes32 hash, final Bytes value) { + pendingUpdates.put(hash, value); + } + + @Override + public void commit() { + if (pendingUpdates.size() == 0) { + // Nothing to do + return; + } + final KeyValueStorageTransaction kvTx = keyValueStorage.startTransaction(); + for (final Map.Entry entry : pendingUpdates.entrySet()) { + kvTx.put(entry.getKey().toArrayUnsafe(), entry.getValue().toArrayUnsafe()); + } + kvTx.commit(); + + pendingUpdates.clear(); + } + + @Override + public void rollback() { + pendingUpdates.clear(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/LeafNode.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/LeafNode.java new file mode 100644 index 00000000000..a3cc33ebfe8 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/LeafNode.java @@ -0,0 +1,177 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import static org.hyperledger.besu.crypto.Hash.keccak256; + +public class LeafNode implements Node { + private final Optional location; + private final Bytes path; + private final V value; + private final NodeFactory nodeFactory; + private final Function valueSerializer; + private WeakReference rlp; + private SoftReference hash; + private boolean dirty = false; + + LeafNode( + final Bytes location, + final Bytes path, + final V value, + final NodeFactory nodeFactory, + final Function valueSerializer) { + this.location = Optional.ofNullable(location); + this.path = path; + this.value = value; + this.nodeFactory = nodeFactory; + this.valueSerializer = valueSerializer; + } + + LeafNode( + final Bytes path, + final V value, + final NodeFactory nodeFactory, + final Function valueSerializer) { + this.location = Optional.empty(); + this.path = path; + this.value = value; + this.nodeFactory = nodeFactory; + this.valueSerializer = valueSerializer; + } + + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + return visitor.visit(this, path); + } + + @Override + public void accept(final NodeVisitor visitor) { + visitor.visit(this); + } + + @Override + public void accept(final Bytes location, final LocationNodeVisitor visitor) { + visitor.visit(location, this); + } + + @Override + public Optional getLocation() { + return location; + } + + @Override + public Bytes getPath() { + return path; + } + + @Override + public Optional getValue() { + return Optional.of(value); + } + + @Override + public List> getChildren() { + return Collections.emptyList(); + } + + @Override + public Bytes getRlp() { + if (rlp != null) { + final Bytes encoded = rlp.get(); + if (encoded != null) { + return encoded; + } + } + + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.startList(); + out.writeBytes(CompactEncoding.encode(path)); + out.writeBytes(valueSerializer.apply(value)); + out.endList(); + final Bytes encoded = out.encoded(); + rlp = new WeakReference<>(encoded); + return encoded; + } + + @Override + public Bytes getRlpRef() { + if (isReferencedByHash()) { + return RLP.encodeOne(getHash()); + } else { + return getRlp(); + } + } + + @Override + public Bytes32 getHash() { + if (hash != null) { + final Bytes32 hashed = hash.get(); + if (hashed != null) { + return hashed; + } + } + final Bytes32 hashed = keccak256(getRlp()); + hash = new SoftReference<>(hashed); + return hashed; + } + + @Override + public Node replacePath(final Bytes path) { + return nodeFactory.createLeaf(path, value); + } + + @Override + public String print() { + return "Leaf:" + + "\n\tRef: " + + getRlpRef() + + "\n\tPath: " + + CompactEncoding.encode(path) + + "\n\tValue: " + + getValue().map(Object::toString).orElse("empty"); + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void markDirty() { + dirty = true; + } + + @Override + public boolean isHealNeeded() { + return false; + } + + @Override + public void markHealNeeded() { + // nothing to do a leaf don't have child + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java new file mode 100644 index 00000000000..2e1f934ea98 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java @@ -0,0 +1,28 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +interface LocationNodeVisitor { + + void visit(Bytes location, ExtensionNode extensionNode); + + void visit(Bytes location, BranchNode branchNode); + + void visit(Bytes location, LeafNode leafNode); + + void visit(Bytes location, NullNode nullNode); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerklePatriciaTrie.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerklePatriciaTrie.java new file mode 100644 index 00000000000..17e593562bf --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerklePatriciaTrie.java @@ -0,0 +1,138 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.hyperledger.besu.crypto.Hash.keccak256; + +/** An Merkle Patricial Trie. */ +public interface MerklePatriciaTrie { + + Bytes EMPTY_TRIE_NODE = RLP.NULL; + Bytes32 EMPTY_TRIE_NODE_HASH = keccak256(EMPTY_TRIE_NODE); + + /** + * Returns an {@code Optional} of value mapped to the hash if it exists; otherwise empty. + * + * @param key The key for the value. + * @return an {@code Optional} of value mapped to the hash if it exists; otherwise empty + */ + Optional get(K key); + + /** + * Returns an {@code Optional} of value mapped to the given path if it exists; otherwise empty. + * + * @param path The path for the value. + * @return an {@code Optional} of value mapped to the given path if it exists; otherwise empty + */ + Optional getPath(final K path); + + /** + * Returns value and ordered proof-related nodes mapped to the hash if it exists; otherwise empty. + * + * @param key The key for the value. + * @return value and ordered proof-related nodes + */ + Proof getValueWithProof(K key); + + /** + * Updates the value mapped to the specified key, creating the mapping if one does not already + * exist. + * + * @param key The key that corresponds to the value to be updated. + * @param value The value to associate the key with. + */ + void put(K key, V value); + + /** + * Updates the value mapped to the specified key, creating the mapping if one does not already + * exist. + * + * @param key The key that corresponds to the value to be updated. + * @param putVisitor custom visitor for the update + */ + void put(K key, PutVisitor putVisitor); + + /** + * Deletes the value mapped to the specified key, if such a value exists (Optional operation). + * + * @param key The key of the value to be deleted. + */ + void remove(K key); + + /** + * Deletes the node mapped to the specified path, if such a node exists (Optional operation). + * + * @param path of the node to be deleted. + * @param removeVisitor custom visitor for the deletion + */ + void removePath(K path, RemoveVisitor removeVisitor); + + /** + * Returns the KECCAK256 hash of the root node of the trie. + * + * @return The KECCAK256 hash of the root node of the trie. + */ + Bytes32 getRootHash(); + + /** + * Commits any pending changes to the underlying storage. + * + * @param nodeUpdater used to store the node values + */ + void commit(NodeUpdater nodeUpdater); + + /** + * Commits any pending changes to the underlying storage. + * + * @param nodeUpdater used to store the node values + * @param commitVisitor custom visitor for the commit + */ + void commit(NodeUpdater nodeUpdater, CommitVisitor commitVisitor); + + /** + * Retrieve up to {@code limit} storage entries beginning from the first entry with hash equal to + * or greater than {@code startKeyHash}. + * + * @param startKeyHash the first key hash to return. + * @param limit the maximum number of entries to return. + * @return the requested storage entries as a map of key hash to value. + */ + Map entriesFrom(Bytes32 startKeyHash, int limit); + + /** + * Retrieve entries using a custom collector + * + * @param handler a custom trie collector. + * @return the requested storage entries as a map of key hash to value. + */ + Map entriesFrom(final Function, Map> handler); + + void visitAll(Consumer> nodeConsumer); + + CompletableFuture visitAll(Consumer> nodeConsumer, ExecutorService executorService); + + void visitLeafs(final TrieIterator.LeafHandler handler); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java new file mode 100644 index 00000000000..594f2802f55 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java @@ -0,0 +1,52 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Optional; + +/** Storage for use in a {@link StoredMerklePatriciaTrie}. */ +public interface MerkleStorage { + + /** + * Returns an {@code Optional} of the content mapped to the hash if it exists; otherwise empty. + * + * @param location The location in the trie. + * @param hash The hash for the content. + * @return an {@code Optional} of the content mapped to the hash if it exists; otherwise empty + */ + Optional get(Bytes location, Bytes32 hash); + + /** + * Updates the content mapped to the specified hash, creating the mapping if one does not already + * exist. + * + *

Note: if the storage implementation already contains content for the given hash, it will + * replace the existing content. + * + * @param location The location in the trie. + * @param hash The hash for the content. + * @param content The content to store. + */ + void put(Bytes location, Bytes32 hash, Bytes content); + + /** Persist accumulated changes to underlying storage. */ + void commit(); + + /** Throws away any changes accumulated by this store. */ + void rollback(); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java new file mode 100644 index 00000000000..3ad6d766e2a --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrieException.java @@ -0,0 +1,50 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * This exception is thrown when there is an issue retrieving or decoding values from {@link + * MerkleStorage}. + */ +public class MerkleTrieException extends RuntimeException { + + private Bytes32 hash; + private Bytes location; + + public MerkleTrieException(final String message) { + super(message); + } + + public MerkleTrieException(final String message, final Bytes32 hash, final Bytes location) { + super(message); + this.hash = hash; + this.location = location; + } + + public MerkleTrieException(final String message, final Exception cause) { + super(message, cause); + } + + public Bytes32 getHash() { + return hash; + } + + public Bytes getLocation() { + return location; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java new file mode 100644 index 00000000000..14c3cf2ff52 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Optional; + +public class MissingNode extends NullNode { + + private final Bytes32 hash; + private final Bytes location; + private final Bytes path; + + public MissingNode(final Bytes32 hash, final Bytes location) { + this.hash = hash; + this.location = location; + this.path = location.isEmpty() ? Bytes.EMPTY : location.slice(0, location.size() - 1); + } + + @Override + public Bytes32 getHash() { + return hash; + } + + @Override + public Bytes getPath() { + return path; + } + + @Override + public boolean isHealNeeded() { + return true; + } + + @Override + public Optional getLocation() { + return Optional.ofNullable(location); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/Node.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/Node.java new file mode 100644 index 00000000000..eba5e67b2b5 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/Node.java @@ -0,0 +1,86 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.List; +import java.util.Optional; + +public interface Node { + + Node accept(PathNodeVisitor visitor, Bytes path); + + void accept(NodeVisitor visitor); + + void accept(Bytes location, LocationNodeVisitor visitor); + + Bytes getPath(); + + default Optional getLocation() { + return Optional.empty(); + } + + Optional getValue(); + + List> getChildren(); + + Bytes getRlp(); + + Bytes getRlpRef(); + + /** + * Whether a reference to this node should be represented as a hash of the rlp, or the node rlp + * itself should be inlined (the rlp stored directly in the parent node). If true, the node is + * referenced by hash. If false, the node is referenced by its rlp-encoded value. + * + * @return true if this node should be referenced by hash + */ + default boolean isReferencedByHash() { + return getRlp().size() >= 32; + } + + Bytes32 getHash(); + + Node replacePath(Bytes path); + + /** Marks the node as needing to be persisted */ + void markDirty(); + + /** + * Is this node not persisted and needs to be? + * + * @return True if the node needs to be persisted. + */ + boolean isDirty(); + + String print(); + + /** Unloads the node if it is, for example, a StoredNode. */ + default void unload() {} + + /** + * Return if a node needs heal. If one of its children missing in the storage + * + * @return true if the node need heal + */ + boolean isHealNeeded(); + + /** + * Marking a node as need heal means that one of its children is not yet present in the storage + */ + void markHealNeeded(); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java new file mode 100644 index 00000000000..dec2adc253d --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java @@ -0,0 +1,31 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +import java.util.ArrayList; +import java.util.Optional; + +public interface NodeFactory { + + Node createExtension(Bytes path, Node child); + + Node createBranch(byte leftIndex, Node left, byte rightIndex, Node right); + + Node createBranch(ArrayList> newChildren, Optional value); + + Node createLeaf(Bytes path, V value); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeLoader.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeLoader.java new file mode 100644 index 00000000000..57499dba74b --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeLoader.java @@ -0,0 +1,24 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Optional; + +public interface NodeLoader { + Optional getNode(Bytes location, Bytes32 hash); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeUpdater.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeUpdater.java new file mode 100644 index 00000000000..1668645fd87 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeUpdater.java @@ -0,0 +1,22 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public interface NodeUpdater { + void store(Bytes location, Bytes32 hash, Bytes value); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java new file mode 100644 index 00000000000..e8a771763c3 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java @@ -0,0 +1,26 @@ +/* + * 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.trie; + +interface NodeVisitor { + + void visit(ExtensionNode extensionNode); + + void visit(BranchNode branchNode); + + void visit(LeafNode leafNode); + + void visit(NullNode nullNode); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java new file mode 100644 index 00000000000..37933c67ac0 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java @@ -0,0 +1,109 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class NullNode implements Node { + @SuppressWarnings("rawtypes") + private static final NullNode instance = new NullNode(); + + protected NullNode() {} + + @SuppressWarnings("unchecked") + public static NullNode instance() { + return instance; + } + + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + return visitor.visit(this, path); + } + + @Override + public void accept(final NodeVisitor visitor) { + visitor.visit(this); + } + + @Override + public void accept(final Bytes location, final LocationNodeVisitor visitor) { + visitor.visit(location, this); + } + + @Override + public Bytes getPath() { + return Bytes.EMPTY; + } + + @Override + public Optional getValue() { + return Optional.empty(); + } + + @Override + public List> getChildren() { + return Collections.emptyList(); + } + + @Override + public Bytes getRlp() { + return MerklePatriciaTrie.EMPTY_TRIE_NODE; + } + + @Override + public Bytes getRlpRef() { + return MerklePatriciaTrie.EMPTY_TRIE_NODE; + } + + @Override + public Bytes32 getHash() { + return MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH; + } + + @Override + public Node replacePath(final Bytes path) { + return this; + } + + @Override + public String print() { + return "[NULL]"; + } + + @Override + public boolean isDirty() { + return false; + } + + @Override + public void markDirty() { + // do nothing + } + + @Override + public boolean isHealNeeded() { + return false; + } + + @Override + public void markHealNeeded() { + // do nothing + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java new file mode 100644 index 00000000000..424fca44799 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java @@ -0,0 +1,28 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +interface PathNodeVisitor { + + Node visit(ExtensionNode extensionNode, Bytes path); + + Node visit(BranchNode branchNode, Bytes path); + + Node visit(LeafNode leafNode, Bytes path); + + Node visit(NullNode nullNode, Bytes path); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java new file mode 100644 index 00000000000..2d2f9d9b9aa --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java @@ -0,0 +1,86 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.function.BiConsumer; + +public class PersistVisitor implements NodeVisitor { + + private int branchNodeCount = 0; + private int extensionNodeCount = 0; + private int leafNodeCount = 0; + + private final BiConsumer writer; + + public PersistVisitor(final BiConsumer writer) { + this.writer = writer; + } + + public Node initialRoot() { + return NullNode.instance(); + } + + public void persist(final Node root) { + if (root instanceof BranchNode) { + visit((BranchNode) root); + } else if (root instanceof ExtensionNode) { + visit((ExtensionNode) root); + } else if (root instanceof LeafNode) { + visit((LeafNode) root); + } else if (root instanceof NullNode) { + visit((NullNode) root); + } + } + + @Override + public void visit(final BranchNode branchNode) { + writer.accept(branchNode.getHash(), branchNode.getRlp()); + branchNodeCount++; + branchNode.getChildren().forEach(node -> node.accept(this)); + } + + @Override + public void visit(final ExtensionNode extensionNode) { + writer.accept(extensionNode.getHash(), extensionNode.getRlp()); + extensionNodeCount++; + extensionNode.getChild().accept(this); + } + + @Override + public void visit(final LeafNode leafNode) { + writer.accept(leafNode.getHash(), leafNode.getRlp()); + leafNodeCount++; + } + + @Override + public void visit(final NullNode nullNode) {} + + public int getBranchNodeCount() { + return branchNodeCount; + } + + public int getExtensionNodeCount() { + return extensionNodeCount; + } + + public int getLeafNodeCount() { + return leafNodeCount; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/Proof.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/Proof.java new file mode 100644 index 00000000000..a891d536cc3 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/Proof.java @@ -0,0 +1,40 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +import java.util.List; +import java.util.Optional; + +public class Proof { + + private final Optional value; + + private final List proofRelatedNodes; + + public Proof(final Optional value, final List proofRelatedNodes) { + this.value = value; + this.proofRelatedNodes = proofRelatedNodes; + } + + public Optional getValue() { + return value; + } + + public List getProofRelatedNodes() { + return proofRelatedNodes; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java new file mode 100644 index 00000000000..e0f24dcd0b9 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java @@ -0,0 +1,63 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +import java.util.ArrayList; +import java.util.List; + +class ProofVisitor extends GetVisitor implements PathNodeVisitor { + + private final Node rootNode; + private final List> proof = new ArrayList<>(); + + ProofVisitor(final Node rootNode) { + this.rootNode = rootNode; + } + + @Override + public Node visit(final ExtensionNode extensionNode, final Bytes path) { + maybeTrackNode(extensionNode); + return super.visit(extensionNode, path); + } + + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + maybeTrackNode(branchNode); + return super.visit(branchNode, path); + } + + @Override + public Node visit(final LeafNode leafNode, final Bytes path) { + maybeTrackNode(leafNode); + return super.visit(leafNode, path); + } + + @Override + public Node visit(final NullNode nullNode, final Bytes path) { + return super.visit(nullNode, path); + } + + public List> getProof() { + return proof; + } + + private void maybeTrackNode(final Node node) { + if (node.equals(rootNode) || node.isReferencedByHash()) { + proof.add(node); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PutVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PutVisitor.java new file mode 100644 index 00000000000..f157f5187a1 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/PutVisitor.java @@ -0,0 +1,107 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +public class PutVisitor implements PathNodeVisitor { + private final NodeFactory nodeFactory; + private final V value; + + public PutVisitor(final NodeFactory nodeFactory, final V value) { + this.nodeFactory = nodeFactory; + this.value = value; + } + + @Override + public Node visit(final ExtensionNode extensionNode, final Bytes path) { + final Bytes extensionPath = extensionNode.getPath(); + final int commonPathLength = extensionPath.commonPrefixLength(path); + assert commonPathLength < path.size() + : "Visiting path doesn't end with a non-matching terminator"; + + if (commonPathLength == extensionPath.size()) { + final Node newChild = extensionNode.getChild().accept(this, path.slice(commonPathLength)); + return extensionNode.replaceChild(newChild); + } + + // path diverges before the end of the extension - create a new branch + + final byte leafIndex = path.get(commonPathLength); + final Bytes leafPath = path.slice(commonPathLength + 1); + + final byte extensionIndex = extensionPath.get(commonPathLength); + final Node updatedExtension = + extensionNode.replacePath(extensionPath.slice(commonPathLength + 1)); + final Node leaf = nodeFactory.createLeaf(leafPath, value); + final Node branch = + nodeFactory.createBranch(leafIndex, leaf, extensionIndex, updatedExtension); + + if (commonPathLength > 0) { + return nodeFactory.createExtension(extensionPath.slice(0, commonPathLength), branch); + } else { + return branch; + } + } + + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + assert path.size() > 0 : "Visiting path doesn't end with a non-matching terminator"; + + final byte childIndex = path.get(0); + if (childIndex == CompactEncoding.LEAF_TERMINATOR) { + return branchNode.replaceValue(value); + } + + final Node updatedChild = branchNode.child(childIndex).accept(this, path.slice(1)); + return branchNode.replaceChild(childIndex, updatedChild); + } + + @Override + public Node visit(final LeafNode leafNode, final Bytes path) { + final Bytes leafPath = leafNode.getPath(); + final int commonPathLength = leafPath.commonPrefixLength(path); + + // Check if the current leaf node should be replaced + if (commonPathLength == leafPath.size() && commonPathLength == path.size()) { + return nodeFactory.createLeaf(leafPath, value); + } + + assert commonPathLength < leafPath.size() && commonPathLength < path.size() + : "Should not have consumed non-matching terminator"; + + // The current leaf path must be split to accommodate the new value. + + final byte newLeafIndex = path.get(commonPathLength); + final Bytes newLeafPath = path.slice(commonPathLength + 1); + + final byte updatedLeafIndex = leafPath.get(commonPathLength); + + final Node updatedLeaf = leafNode.replacePath(leafPath.slice(commonPathLength + 1)); + final Node leaf = nodeFactory.createLeaf(newLeafPath, value); + final Node branch = + nodeFactory.createBranch(updatedLeafIndex, updatedLeaf, newLeafIndex, leaf); + if (commonPathLength > 0) { + return nodeFactory.createExtension(leafPath.slice(0, commonPathLength), branch); + } else { + return branch; + } + } + + @Override + public Node visit(final NullNode nullNode, final Bytes path) { + return nodeFactory.createLeaf(path, value); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java new file mode 100644 index 00000000000..7879decd848 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java @@ -0,0 +1,87 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Map; +import java.util.Optional; + +public class RangeStorageEntriesCollector extends StorageEntriesCollector { + + private int currentSize = 0; + private final Optional endKeyHash; + private final Integer maxResponseBytes; + + public RangeStorageEntriesCollector( + final Bytes32 startKeyHash, + final Optional endKeyHash, + final int limit, + final int maxResponseBytes) { + super(startKeyHash, limit); + this.endKeyHash = endKeyHash; + this.maxResponseBytes = maxResponseBytes; + } + + public static RangeStorageEntriesCollector createCollector( + final Bytes32 startKeyHash, + final Bytes32 endKeyHash, + final int limit, + final int maxResponseBytes) { + return new RangeStorageEntriesCollector( + startKeyHash, Optional.ofNullable(endKeyHash), limit, maxResponseBytes); + } + + public static RangeStorageEntriesCollector createCollector( + final Bytes32 startKeyHash, final int limit, final int maxResponseBytes) { + return new RangeStorageEntriesCollector( + startKeyHash, Optional.empty(), limit, maxResponseBytes); + } + + public static TrieIterator createVisitor(final RangeStorageEntriesCollector collector) { + return new TrieIterator<>(collector, false); + } + + public static Map collectEntries( + final RangeStorageEntriesCollector collector, + final TrieIterator visitor, + final Node root, + final Bytes32 startKeyHash) { + root.accept(visitor, CompactEncoding.bytesToPath(startKeyHash)); + return collector.getValues(); + } + + @Override + public TrieIterator.State onLeaf(final Bytes32 keyHash, final Node node) { + if (keyHash.compareTo(startKeyHash) >= 0) { + if (node.getValue().isPresent()) { + final Bytes value = node.getValue().get(); + currentSize += Bytes32.SIZE + value.size(); + if (currentSize > maxResponseBytes) { + return TrieIterator.State.STOP; + } + if (endKeyHash.isPresent() + && !values.isEmpty() + && keyHash.compareTo(endKeyHash.get()) > 0) { + return TrieIterator.State.STOP; + } + + values.put(keyHash, value); + } + } + return limitReached() ? TrieIterator.State.STOP : TrieIterator.State.CONTINUE; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RemoveVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RemoveVisitor.java new file mode 100644 index 00000000000..1e55056767b --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RemoveVisitor.java @@ -0,0 +1,73 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +public class RemoveVisitor implements PathNodeVisitor { + private final Node NULL_NODE_RESULT = NullNode.instance(); + + private final boolean allowFlatten; + + public RemoveVisitor() { + allowFlatten = true; + } + + public RemoveVisitor(final boolean allowFlatten) { + this.allowFlatten = allowFlatten; + } + + @Override + public Node visit(final ExtensionNode extensionNode, final Bytes path) { + final Bytes extensionPath = extensionNode.getPath(); + final int commonPathLength = extensionPath.commonPrefixLength(path); + assert commonPathLength < path.size() + : "Visiting path doesn't end with a non-matching terminator"; + + if (commonPathLength == extensionPath.size()) { + final Node newChild = extensionNode.getChild().accept(this, path.slice(commonPathLength)); + return extensionNode.replaceChild(newChild); + } + + // path diverges before the end of the extension, so it cannot match + + return extensionNode; + } + + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + assert path.size() > 0 : "Visiting path doesn't end with a non-matching terminator"; + + final byte childIndex = path.get(0); + if (childIndex == CompactEncoding.LEAF_TERMINATOR) { + return branchNode.removeValue(); + } + + final Node updatedChild = branchNode.child(childIndex).accept(this, path.slice(1)); + return branchNode.replaceChild(childIndex, updatedChild, allowFlatten); + } + + @Override + public Node visit(final LeafNode leafNode, final Bytes path) { + final Bytes leafPath = leafNode.getPath(); + final int commonPathLength = leafPath.commonPrefixLength(path); + return (commonPathLength == leafPath.size()) ? NULL_NODE_RESULT : leafNode; + } + + @Override + public Node visit(final NullNode nullNode, final Bytes path) { + return NULL_NODE_RESULT; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java new file mode 100644 index 00000000000..7ffef43261a --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java @@ -0,0 +1,246 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +public class RestoreVisitor implements PathNodeVisitor { + + private final NodeFactory nodeFactory; + private final V value; + private final NodeVisitor persistVisitor; + + public RestoreVisitor( + final Function valueSerializer, + final V value, + final NodeVisitor persistVisitor) { + this.nodeFactory = new DefaultNodeFactory<>(valueSerializer); + this.value = value; + this.persistVisitor = persistVisitor; + } + + @Override + public Node visit(final ExtensionNode extensionNode, final Bytes path) { + final Bytes extensionPath = extensionNode.getPath(); + + final int commonPathLength = extensionPath.commonPrefixLength(path); + assert commonPathLength < path.size() + : "Visiting path doesn't end with a non-matching terminator"; + + if (commonPathLength == extensionPath.size()) { + final Node newChild = extensionNode.getChild().accept(this, path.slice(commonPathLength)); + return extensionNode.replaceChild(newChild); + } + + // path diverges before the end of the extension - create a new branch + + final byte leafIndex = path.get(commonPathLength); + final Bytes leafPath = path.slice(commonPathLength + 1); + + final byte extensionIndex = extensionPath.get(commonPathLength); + final Node updatedExtension = + extensionNode.replacePath(extensionPath.slice(commonPathLength + 1)); + final Node leaf = nodeFactory.createLeaf(leafPath, value); + final Node branch = + nodeFactory.createBranch(leafIndex, leaf, extensionIndex, updatedExtension); + + if (commonPathLength > 0) { + return nodeFactory.createExtension(extensionPath.slice(0, commonPathLength), branch); + } else { + return branch; + } + } + + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + assert path.size() > 0 : "Visiting path doesn't end with a non-matching terminator"; + BranchNode workingNode = branchNode; + + final byte childIndex = path.get(0); + if (childIndex == CompactEncoding.LEAF_TERMINATOR) { + return workingNode.replaceValue(value); + } + + for (byte i = 0; i < childIndex; i++) { + workingNode = persistNode(workingNode, i); + } + + final Node updatedChild = workingNode.child(childIndex).accept(this, path.slice(1)); + return workingNode.replaceChild(childIndex, updatedChild); + } + + private BranchNode persistNode(final BranchNode parent, final byte index) { + final Node child = parent.getChildren().get(index); + if (!(child instanceof StoredNode)) { + child.accept(persistVisitor); + final PersistedNode persistedNode = + new PersistedNode<>(null, child.getHash(), child.getRlpRef()); + return (BranchNode) parent.replaceChild(index, persistedNode); + } else { + return parent; + } + } + + @Override + public Node visit(final LeafNode leafNode, final Bytes path) { + final Bytes leafPath = leafNode.getPath(); + final int commonPathLength = leafPath.commonPrefixLength(path); + + // Check if the current leaf node should be replaced + if (commonPathLength == leafPath.size() && commonPathLength == path.size()) { + return nodeFactory.createLeaf(leafPath, value); + } + + assert commonPathLength < leafPath.size() && commonPathLength < path.size() + : "Should not have consumed non-matching terminator"; + + // The current leaf path must be split to accommodate the new value. + + final byte newLeafIndex = path.get(commonPathLength); + final Bytes newLeafPath = path.slice(commonPathLength + 1); + + final byte updatedLeafIndex = leafPath.get(commonPathLength); + + final Node updatedLeaf = leafNode.replacePath(leafPath.slice(commonPathLength + 1)); + final Node leaf = nodeFactory.createLeaf(newLeafPath, value); + final Node branch = + nodeFactory.createBranch(updatedLeafIndex, updatedLeaf, newLeafIndex, leaf); + if (commonPathLength > 0) { + return nodeFactory.createExtension(leafPath.slice(0, commonPathLength), branch); + } else { + return branch; + } + } + + @Override + public Node visit(final NullNode nullNode, final Bytes path) { + return nodeFactory.createLeaf(path, value); + } + + static class PersistedNode implements Node { + private final Bytes path; + private final Bytes32 hash; + private final Bytes refRlp; + + PersistedNode(final Bytes path, final Bytes32 hash, final Bytes refRlp) { + this.path = path; + this.hash = hash; + this.refRlp = refRlp; + } + + /** + * @return True if the node needs to be persisted. + */ + @Override + public boolean isDirty() { + return false; + } + + /** Marks the node as being modified (needs to be persisted); */ + @Override + public void markDirty() { + throw new UnsupportedOperationException( + "A persisted node cannot ever be dirty since it's loaded from storage"); + } + + @Override + public boolean isHealNeeded() { + return false; + } + + @Override + public void markHealNeeded() { + throw new UnsupportedOperationException( + "A persisted node cannot be healed since it's loaded from storage"); + } + + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + // do nothing + return this; + } + + @Override + public void accept(final NodeVisitor visitor) { + // do nothing + } + + @Override + public void accept(final Bytes location, final LocationNodeVisitor visitor) { + // do nothing + } + + @Override + public Bytes getPath() { + return path; + } + + @Override + public Optional getValue() { + throw new UnsupportedOperationException( + "A persisted node cannot have a value, as it's already been restored."); + } + + @Override + public List> getChildren() { + return Collections.emptyList(); + } + + @Override + public Bytes getRlp() { + throw new UnsupportedOperationException( + "A persisted node cannot have rlp, as it's already been restored."); + } + + @Override + public Bytes getRlpRef() { + return refRlp; + } + + @Override + public boolean isReferencedByHash() { + // Persisted nodes represent only nodes that are referenced by hash + return true; + } + + @Override + public Bytes32 getHash() { + return hash; + } + + @Override + public Node replacePath(final Bytes path) { + throw new UnsupportedOperationException( + "A persisted node cannot be replaced, as it's already been restored."); + } + + @Override + public void unload() { + throw new UnsupportedOperationException( + "A persisted node cannot be unloaded, as it's already been restored."); + } + + @Override + public String print() { + return "PersistedNode:" + "\n\tPath: " + getPath() + "\n\tHash: " + getHash(); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerklePatriciaTrie.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerklePatriciaTrie.java new file mode 100644 index 00000000000..75d6bb9bc80 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerklePatriciaTrie.java @@ -0,0 +1,162 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.hyperledger.besu.ethereum.trie.CompactEncoding.bytesToPath; + +/** + * An in-memory {@link MerklePatriciaTrie}. + * + * @param The type of values stored by this trie. + */ +public class SimpleMerklePatriciaTrie implements MerklePatriciaTrie { + private final PathNodeVisitor getVisitor = new GetVisitor<>(); + private final PathNodeVisitor removeVisitor = new RemoveVisitor<>(); + private final DefaultNodeFactory nodeFactory; + + private Node root; + + /** + * Create a trie. + * + * @param valueSerializer A function for serializing values to bytes. + */ + public SimpleMerklePatriciaTrie(final Function valueSerializer) { + this.nodeFactory = new DefaultNodeFactory<>(valueSerializer); + this.root = NullNode.instance(); + } + + @Override + public Optional get(final K key) { + checkNotNull(key); + return root.accept(getVisitor, bytesToPath(key)).getValue(); + } + + @Override + public Optional getPath(final K path) { + checkNotNull(path); + return root.accept(getVisitor, path).getValue(); + } + + @Override + public Proof getValueWithProof(final K key) { + checkNotNull(key); + final ProofVisitor proofVisitor = new ProofVisitor<>(root); + final Optional value = root.accept(proofVisitor, bytesToPath(key)).getValue(); + final List proof = + proofVisitor.getProof().stream().map(Node::getRlp).collect(Collectors.toList()); + return new Proof<>(value, proof); + } + + @Override + public void put(final K key, final V value) { + checkNotNull(key); + checkNotNull(value); + this.root = root.accept(new PutVisitor<>(nodeFactory, value), bytesToPath(key)); + } + + @Override + public void put(final K key, final PutVisitor putVisitor) { + checkNotNull(key); + this.root = root.accept(putVisitor, bytesToPath(key)); + } + + @Override + public void remove(final K key) { + checkNotNull(key); + this.root = root.accept(removeVisitor, bytesToPath(key)); + } + + @Override + public void removePath(final K path, final RemoveVisitor removeVisitor) { + checkNotNull(path); + this.root = root.accept(removeVisitor, path); + } + + @Override + public Bytes32 getRootHash() { + return root.getHash(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getRootHash() + "]"; + } + + @Override + public void commit(final NodeUpdater nodeUpdater) { + // Nothing to do here + } + + @Override + public void commit(final NodeUpdater nodeUpdater, final CommitVisitor commitVisitor) { + // Nothing to do here + } + + @Override + public Map entriesFrom(final Bytes32 startKeyHash, final int limit) { + return StorageEntriesCollector.collectEntries(root, startKeyHash, limit); + } + + @Override + public Map entriesFrom(final Function, Map> handler) { + return handler.apply(root); + } + + @Override + public void visitAll(final Consumer> nodeConsumer) { + root.accept(new AllNodesVisitor<>(nodeConsumer)); + } + + @Override + public CompletableFuture visitAll( + final Consumer> nodeConsumer, final ExecutorService executorService) { + return CompletableFuture.allOf( + Stream.concat( + Stream.of( + CompletableFuture.runAsync(() -> nodeConsumer.accept(root), executorService)), + root.getChildren().stream() + .map( + rootChild -> + CompletableFuture.runAsync( + () -> rootChild.accept(new AllNodesVisitor<>(nodeConsumer)), + executorService))) + .collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet + )).toArray(new CompletableFuture[0])); + } + + @Override + public void visitLeafs(final TrieIterator.LeafHandler handler) { + final TrieIterator visitor = new TrieIterator<>(handler, true); + root.accept(visitor, CompactEncoding.bytesToPath(Bytes32.ZERO)); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitor.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitor.java new file mode 100644 index 00000000000..72dc7430154 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitor.java @@ -0,0 +1,48 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +public class SnapPutVisitor extends PutVisitor { + + public SnapPutVisitor(final NodeFactory nodeFactory, final V value) { + super(nodeFactory, value); + } + + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + final Node visit = super.visit(branchNode, path); + for (Node child : visit.getChildren()) { + if (child.isHealNeeded() || (child instanceof StoredNode && !child.getValue().isPresent())) { + visit.markHealNeeded(); // not save an incomplete node + return visit; + } + } + return visit; + } + + @Override + public Node visit(final ExtensionNode extensionNode, final Bytes path) { + final Node visit = super.visit(extensionNode, path); + for (Node child : visit.getChildren()) { + if (child.isHealNeeded() || (child instanceof StoredNode && !child.getValue().isPresent())) { + visit.markHealNeeded(); // not save an incomplete node + return visit; + } + } + return visit; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StorageEntriesCollector.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StorageEntriesCollector.java new file mode 100644 index 00000000000..bb0e9aadfae --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StorageEntriesCollector.java @@ -0,0 +1,57 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Map; +import java.util.TreeMap; + +public class StorageEntriesCollector implements TrieIterator.LeafHandler { + + protected final Bytes32 startKeyHash; + protected final int limit; + protected final Map values = new TreeMap<>(); + + public StorageEntriesCollector(final Bytes32 startKeyHash, final int limit) { + this.startKeyHash = startKeyHash; + this.limit = limit; + } + + public static Map collectEntries( + final Node root, final Bytes32 startKeyHash, final int limit) { + final StorageEntriesCollector entriesCollector = + new StorageEntriesCollector<>(startKeyHash, limit); + final TrieIterator visitor = new TrieIterator<>(entriesCollector, false); + root.accept(visitor, CompactEncoding.bytesToPath(startKeyHash)); + return entriesCollector.getValues(); + } + + protected boolean limitReached() { + return limit <= values.size(); + } + + @Override + public TrieIterator.State onLeaf(final Bytes32 keyHash, final Node node) { + if (keyHash.compareTo(startKeyHash) >= 0) { + node.getValue().ifPresent(value -> values.put(keyHash, value)); + } + return limitReached() ? TrieIterator.State.STOP : TrieIterator.State.CONTINUE; + } + + public Map getValues() { + return values; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerklePatriciaTrie.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerklePatriciaTrie.java new file mode 100644 index 00000000000..52b6ddad591 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerklePatriciaTrie.java @@ -0,0 +1,241 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.hyperledger.besu.ethereum.trie.CompactEncoding.bytesToPath; + +/** + * A {@link MerklePatriciaTrie} that persists trie nodes to a {@link MerkleStorage} key/value store. + * + * @param The type of values stored by this trie. + */ +public class StoredMerklePatriciaTrie implements MerklePatriciaTrie { + + private final GetVisitor getVisitor = new GetVisitor<>(); + private final RemoveVisitor removeVisitor = new RemoveVisitor<>(); + private final StoredNodeFactory nodeFactory; + + private Node root; + + /** + * Create a trie. + * + * @param nodeLoader The {@link NodeLoader} to retrieve node data from. + * @param valueSerializer A function for serializing values to bytes. + * @param valueDeserializer A function for deserializing values from bytes. + */ + public StoredMerklePatriciaTrie( + final NodeLoader nodeLoader, + final Function valueSerializer, + final Function valueDeserializer) { + this(nodeLoader, EMPTY_TRIE_NODE_HASH, valueSerializer, valueDeserializer); + } + + /** + * Create a trie. + * + * @param nodeLoader The {@link NodeLoader} to retrieve node data from. + * @param rootHash The initial root has for the trie, which should be already present in {@code + * storage}. + * @param rootLocation The initial root location for the trie + * @param valueSerializer A function for serializing values to bytes. + * @param valueDeserializer A function for deserializing values from bytes. + */ + public StoredMerklePatriciaTrie( + final NodeLoader nodeLoader, + final Bytes32 rootHash, + final Bytes rootLocation, + final Function valueSerializer, + final Function valueDeserializer) { + this.nodeFactory = new StoredNodeFactory<>(nodeLoader, valueSerializer, valueDeserializer); + this.root = + rootHash.equals(EMPTY_TRIE_NODE_HASH) + ? NullNode.instance() + : new StoredNode<>(nodeFactory, rootLocation, rootHash); + } + + /** + * Create a trie. + * + * @param nodeLoader The {@link NodeLoader} to retrieve node data from. + * @param rootHash The initial root has for the trie, which should be already present in {@code + * storage}. + * @param valueSerializer A function for serializing values to bytes. + * @param valueDeserializer A function for deserializing values from bytes. + */ + public StoredMerklePatriciaTrie( + final NodeLoader nodeLoader, + final Bytes32 rootHash, + final Function valueSerializer, + final Function valueDeserializer) { + this(nodeLoader, rootHash, Bytes.EMPTY, valueSerializer, valueDeserializer); + } + + /** + * Create a trie. + * + * @param nodeFactory The {@link StoredNodeFactory} to retrieve node. + * @param rootHash The initial root hash for the trie, which should be already present in {@code + * storage}. + */ + public StoredMerklePatriciaTrie(final StoredNodeFactory nodeFactory, final Bytes32 rootHash) { + this.nodeFactory = nodeFactory; + this.root = + rootHash.equals(EMPTY_TRIE_NODE_HASH) + ? NullNode.instance() + : new StoredNode<>(nodeFactory, Bytes.EMPTY, rootHash); + } + + @Override + public Optional get(final K key) { + checkNotNull(key); + return root.accept(getVisitor, bytesToPath(key)).getValue(); + } + + @Override + public Optional getPath(final K path) { + checkNotNull(path); + return root.accept(getVisitor, path).getValue(); + } + + @Override + public Proof getValueWithProof(final K key) { + checkNotNull(key); + final ProofVisitor proofVisitor = new ProofVisitor<>(root); + final Optional value = root.accept(proofVisitor, bytesToPath(key)).getValue(); + final List proof = + proofVisitor.getProof().stream().map(Node::getRlp).collect(Collectors.toList()); + return new Proof<>(value, proof); + } + + @Override + public void put(final K key, final V value) { + checkNotNull(key); + checkNotNull(value); + this.root = root.accept(new PutVisitor<>(nodeFactory, value), bytesToPath(key)); + } + + @Override + public void put(final K key, final PutVisitor putVisitor) { + checkNotNull(key); + this.root = root.accept(putVisitor, bytesToPath(key)); + } + + @Override + public void remove(final K key) { + checkNotNull(key); + this.root = root.accept(removeVisitor, bytesToPath(key)); + } + + @Override + public void removePath(final K path, final RemoveVisitor removeVisitor) { + checkNotNull(path); + this.root = root.accept(removeVisitor, path); + } + + @Override + public void commit(final NodeUpdater nodeUpdater) { + commit(nodeUpdater, new CommitVisitor<>(nodeUpdater)); + } + + @Override + public void commit(final NodeUpdater nodeUpdater, final CommitVisitor commitVisitor) { + root.accept(Bytes.EMPTY, commitVisitor); + // Make sure root node was stored + if (root.isDirty() && root.getRlpRef().size() < 32) { + nodeUpdater.store(Bytes.EMPTY, root.getHash(), root.getRlpRef()); + } + // Reset root so dirty nodes can be garbage collected + final Bytes32 rootHash = root.getHash(); + this.root = + rootHash.equals(EMPTY_TRIE_NODE_HASH) + ? NullNode.instance() + : new StoredNode<>(nodeFactory, Bytes.EMPTY, rootHash); + } + + public void acceptAtRoot(final NodeVisitor visitor) { + root.accept(visitor); + } + + public void acceptAtRoot(final PathNodeVisitor visitor, final Bytes path) { + root.accept(visitor, path); + } + + @Override + public Map entriesFrom(final Bytes32 startKeyHash, final int limit) { + return StorageEntriesCollector.collectEntries(root, startKeyHash, limit); + } + + @Override + public Map entriesFrom(final Function, Map> handler) { + return handler.apply(root); + } + + @Override + public void visitAll(final Consumer> nodeConsumer) { + root.accept(new AllNodesVisitor<>(nodeConsumer)); + } + + @Override + public CompletableFuture visitAll( + final Consumer> nodeConsumer, final ExecutorService executorService) { + return CompletableFuture.allOf( + Stream.concat( + Stream.of( + CompletableFuture.runAsync(() -> nodeConsumer.accept(root), executorService)), + root.getChildren().stream() + .map( + rootChild -> + CompletableFuture.runAsync( + () -> rootChild.accept(new AllNodesVisitor<>(nodeConsumer, executorService)), + //() -> rootChild.accept(new AllNodesVisitor<>(nodeConsumer)), + executorService))) + .collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet + )).toArray(new CompletableFuture[0])); + } + + @Override + public void visitLeafs(final TrieIterator.LeafHandler handler) { + final TrieIterator visitor = new TrieIterator<>(handler, true); + root.accept(visitor, CompactEncoding.bytesToPath(Bytes32.ZERO)); + } + + @Override + public Bytes32 getRootHash() { + return root.getHash(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getRootHash() + "]"; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java new file mode 100644 index 00000000000..c80239ce1cc --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java @@ -0,0 +1,159 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import java.util.List; +import java.util.Optional; + +public class StoredNode implements Node { + private final StoredNodeFactory nodeFactory; + private final Bytes location; + private final Bytes32 hash; + private Node loaded; + + StoredNode(final StoredNodeFactory nodeFactory, final Bytes location, final Bytes32 hash) { + this.nodeFactory = nodeFactory; + this.location = location; + this.hash = hash; + } + + /** + * @return True if the node needs to be persisted. + */ + @Override + public boolean isDirty() { + return false; + } + + /** Marks the node as being modified (needs to be persisted); */ + @Override + public void markDirty() { + throw new IllegalStateException( + "A stored node cannot ever be dirty since it's loaded from storage"); + } + + @Override + public boolean isHealNeeded() { + return false; + } + + @Override + public void markHealNeeded() { + throw new IllegalStateException( + "A stored node cannot be healed since it's loaded from storage"); + } + + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + final Node node = load(); + return node.accept(visitor, path); + } + + @Override + public void accept(final NodeVisitor visitor) { + final Node node = load(); + node.accept(visitor); + } + + @Override + public void accept(final Bytes location, final LocationNodeVisitor visitor) { + final Node node = load(); + node.accept(location, visitor); + } + + @Override + public Bytes getPath() { + return load().getPath(); + } + + @Override + public Optional getLocation() { + return Optional.ofNullable(location); + } + + @Override + public Optional getValue() { + return load().getValue(); + } + + @Override + public List> getChildren() { + return load().getChildren(); + } + + @Override + public Bytes getRlp() { + return load().getRlp(); + } + + @Override + public Bytes getRlpRef() { + // If this node was stored, then it must have a rlp larger than a hash + return RLP.encodeOne(hash); + } + + @Override + public boolean isReferencedByHash() { + // Stored nodes represent only nodes that are referenced by hash + return true; + } + + @Override + public Bytes32 getHash() { + return hash; + } + + @Override + public Node replacePath(final Bytes path) { + return load().replacePath(path); + } + + private Node load() { + if (loaded == null) { + loaded = + nodeFactory + .retrieve(location, hash) + .orElseThrow( + () -> + new MerkleTrieException( + "Unable to load trie node value for hash " + + hash + + " location " + + location, + hash, + location)); + } + + return loaded; + } + + @Override + public void unload() { + loaded = null; + } + + @Override + public String print() { + if (loaded == null) { + return "StoredNode:" + "\n\tRef: " + getRlpRef(); + } else { + return load().print(); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNodeFactory.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNodeFactory.java new file mode 100644 index 00000000000..a440498fd7b --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNodeFactory.java @@ -0,0 +1,256 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.lang.String.format; + +public class StoredNodeFactory implements NodeFactory { + @SuppressWarnings("rawtypes") + private static final NullNode NULL_NODE = NullNode.instance(); + + private final NodeLoader nodeLoader; + private final Function valueSerializer; + private final Function valueDeserializer; + + public StoredNodeFactory( + final NodeLoader nodeLoader, + final Function valueSerializer, + final Function valueDeserializer) { + this.nodeLoader = nodeLoader; + this.valueSerializer = valueSerializer; + this.valueDeserializer = valueDeserializer; + } + + @Override + public Node createExtension(final Bytes path, final Node child) { + return handleNewNode(new ExtensionNode<>(path, child, this)); + } + + @SuppressWarnings("unchecked") + @Override + public Node createBranch( + final byte leftIndex, final Node left, final byte rightIndex, final Node right) { + assert (leftIndex <= BranchNode.RADIX); + assert (rightIndex <= BranchNode.RADIX); + assert (leftIndex != rightIndex); + + final ArrayList> children = + new ArrayList<>(Collections.nCopies(BranchNode.RADIX, (Node) NULL_NODE)); + + if (leftIndex == BranchNode.RADIX) { + children.set(rightIndex, right); + return createBranch(children, left.getValue()); + } else if (rightIndex == BranchNode.RADIX) { + children.set(leftIndex, left); + return createBranch(children, right.getValue()); + } else { + children.set(leftIndex, left); + children.set(rightIndex, right); + return createBranch(children, Optional.empty()); + } + } + + @Override + public Node createBranch(final ArrayList> children, final Optional value) { + return handleNewNode(new BranchNode<>(children, value, this, valueSerializer)); + } + + @Override + public Node createLeaf(final Bytes path, final V value) { + return handleNewNode(new LeafNode<>(path, value, this, valueSerializer)); + } + + private Node handleNewNode(final Node node) { + node.markDirty(); + return node; + } + + public Optional> retrieve(final Bytes location, final Bytes32 hash) + throws MerkleTrieException { + return nodeLoader + .getNode(location, hash) + .map( + rlp -> { + final Node node = + decode(location, rlp, () -> format("Invalid RLP value for hash %s", hash)); + // recalculating the node.hash() is expensive, so we only do this as an assertion + assert (hash.equals(node.getHash())) + : "Node hash " + node.getHash() + " not equal to expected " + hash; + return node; + }); + } + + public Node decode(final Bytes location, final Bytes rlp) { + return decode(location, rlp, () -> String.format("Failed to decode value %s", rlp.toString())); + } + + private Node decode(final Bytes location, final Bytes rlp, final Supplier errMessage) + throws MerkleTrieException { + try { + return decode(location, RLP.input(rlp), errMessage); + } catch (final RLPException ex) { + throw new MerkleTrieException(errMessage.get(), ex); + } + } + + private Node decode( + final Bytes location, final RLPInput nodeRLPs, final Supplier errMessage) { + final int nodesCount = nodeRLPs.enterList(); + switch (nodesCount) { + case 1: + final NullNode nullNode = decodeNull(nodeRLPs, errMessage); + nodeRLPs.leaveList(); + return nullNode; + + case 2: + final Bytes encodedPath = nodeRLPs.readBytes(); + final Bytes path; + try { + path = CompactEncoding.decode(encodedPath); + } catch (final IllegalArgumentException ex) { + throw new MerkleTrieException(errMessage.get() + ": invalid path " + encodedPath, ex); + } + + final int size = path.size(); + if (size > 0 && path.get(size - 1) == CompactEncoding.LEAF_TERMINATOR) { + final LeafNode leafNode = decodeLeaf(location, path, nodeRLPs, errMessage); + nodeRLPs.leaveList(); + return leafNode; + } else { + final Node extensionNode = decodeExtension(location, path, nodeRLPs, errMessage); + nodeRLPs.leaveList(); + return extensionNode; + } + + case (BranchNode.RADIX + 1): + final BranchNode branchNode = decodeBranch(location, nodeRLPs, errMessage); + nodeRLPs.leaveList(); + return branchNode; + + default: + throw new MerkleTrieException( + errMessage.get() + format(": invalid list size %s", nodesCount)); + } + } + + protected Node decodeExtension( + final Bytes location, + final Bytes path, + final RLPInput valueRlp, + final Supplier errMessage) { + final RLPInput childRlp = valueRlp.readAsRlp(); + if (childRlp.nextIsList()) { + final Node childNode = + decode(location == null ? null : Bytes.concatenate(location, path), childRlp, errMessage); + return new ExtensionNode<>(location, path, childNode, this); + } else { + final Bytes32 childHash = childRlp.readBytes32(); + final StoredNode childNode = + new StoredNode<>( + this, location == null ? null : Bytes.concatenate(location, path), childHash); + return new ExtensionNode<>(location, path, childNode, this); + } + } + + @SuppressWarnings("unchecked") + protected BranchNode decodeBranch( + final Bytes location, final RLPInput nodeRLPs, final Supplier errMessage) { + final ArrayList> children = new ArrayList<>(BranchNode.RADIX); + for (int i = 0; i < BranchNode.RADIX; ++i) { + if (nodeRLPs.nextIsNull()) { + nodeRLPs.skipNext(); + children.add(NULL_NODE); + } else if (nodeRLPs.nextIsList()) { + final Node child = + decode( + location == null ? null : Bytes.concatenate(location, Bytes.of((byte) i)), + nodeRLPs, + errMessage); + children.add(child); + } else { + final Bytes32 childHash = nodeRLPs.readBytes32(); + children.add( + new StoredNode<>( + this, + location == null ? null : Bytes.concatenate(location, Bytes.of((byte) i)), + childHash)); + } + } + + final Optional value; + if (nodeRLPs.nextIsNull()) { + nodeRLPs.skipNext(); + value = Optional.empty(); + } else { + value = Optional.of(decodeValue(nodeRLPs, errMessage)); + } + + return new BranchNode<>(location, children, value, this, valueSerializer); + } + + protected LeafNode decodeLeaf( + final Bytes location, + final Bytes path, + final RLPInput valueRlp, + final Supplier errMessage) { + if (valueRlp.nextIsNull()) { + throw new MerkleTrieException(errMessage.get() + ": leaf has null value"); + } + final V value = decodeValue(valueRlp, errMessage); + return new LeafNode<>(location, path, value, this, valueSerializer); + } + + @SuppressWarnings("unchecked") + private NullNode decodeNull(final RLPInput nodeRLPs, final Supplier errMessage) { + if (!nodeRLPs.nextIsNull()) { + throw new MerkleTrieException(errMessage.get() + ": list size 1 but not null"); + } + nodeRLPs.skipNext(); + return NULL_NODE; + } + + private V decodeValue(final RLPInput valueRlp, final Supplier errMessage) { + final Bytes bytes; + try { + bytes = valueRlp.readBytes(); + } catch (final RLPException ex) { + throw new MerkleTrieException( + errMessage.get() + ": failed decoding value rlp " + valueRlp, ex); + } + return deserializeValue(errMessage, bytes); + } + + private V deserializeValue(final Supplier errMessage, final Bytes bytes) { + final V value; + try { + value = valueDeserializer.apply(bytes); + } catch (final IllegalArgumentException ex) { + throw new MerkleTrieException(errMessage.get() + ": failed deserializing value " + bytes, ex); + } + return value; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java new file mode 100644 index 00000000000..6782075ad58 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java @@ -0,0 +1,125 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +public class TrieIterator implements PathNodeVisitor { + + private final Deque paths = new ArrayDeque<>(); + private final LeafHandler leafHandler; + private State state = State.SEARCHING; + private final boolean unload; + + public TrieIterator(final LeafHandler leafHandler, final boolean unload) { + this.leafHandler = leafHandler; + this.unload = unload; + } + + @Override + public Node visit(final ExtensionNode node, final Bytes searchPath) { + Bytes remainingPath = searchPath; + if (state == State.SEARCHING) { + final Bytes extensionPath = node.getPath(); + final int commonPathLength = extensionPath.commonPrefixLength(searchPath); + remainingPath = searchPath.slice(commonPathLength); + } + + paths.push(node.getPath()); + node.getChild().accept(this, remainingPath); + if (unload) { + node.getChild().unload(); + } + paths.pop(); + return node; + } + + @Override + public Node visit(final BranchNode node, final Bytes searchPath) { + byte iterateFrom = 0; + Bytes remainingPath = searchPath; + if (state == State.SEARCHING) { + iterateFrom = searchPath.get(0); + if (iterateFrom == CompactEncoding.LEAF_TERMINATOR) { + return node; + } + remainingPath = searchPath.slice(1); + } + paths.push(node.getPath()); + for (byte i = iterateFrom; i < BranchNode.RADIX && state.continueIterating(); i++) { + paths.push(Bytes.of(i)); + final Node child = node.child(i); + child.accept(this, remainingPath); + if (unload) { + child.unload(); + } + paths.pop(); + } + paths.pop(); + return node; + } + + @Override + public Node visit(final LeafNode node, final Bytes path) { + paths.push(node.getPath()); + state = State.CONTINUE; + try { + state = leafHandler.onLeaf(keyHash(), node); + } catch (IllegalArgumentException e) { + state = State.STOP; + return null; + } finally { + paths.pop(); + } + return node; + } + + @Override + public Node visit(final NullNode node, final Bytes path) { + state = State.CONTINUE; + return node; + } + + private Bytes32 keyHash() { + final Iterator iterator = paths.descendingIterator(); + Bytes fullPath = iterator.next(); + while (iterator.hasNext()) { + fullPath = Bytes.wrap(fullPath, iterator.next()); + } + return fullPath.isZero() + ? Bytes32.ZERO + : Bytes32.wrap(CompactEncoding.pathToBytes(fullPath), 0); + } + + public interface LeafHandler { + + State onLeaf(Bytes32 keyHash, Node node); + } + + public enum State { + SEARCHING, + CONTINUE, + STOP; + + public boolean continueIterating() { + return this != STOP; + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoder.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoder.java new file mode 100644 index 00000000000..4cdff3e2ea2 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoder.java @@ -0,0 +1,165 @@ +/* + * 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.trie; + +import com.google.common.collect.Streams; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkArgument; + +public class TrieNodeDecoder { + private static final StoredNodeFactory emptyNodeFactory = + new StoredNodeFactory<>((l, h) -> Optional.empty(), Function.identity(), Function.identity()); + + // Hide constructor for static utility class + private TrieNodeDecoder() {} + + /** + * Decode an rlp-encoded trie node + * + * @param location The location in the trie. + * @param rlp The rlp-encoded node + * @return A {@code Node} representation of the rlp data + */ + public static Node decode(final Bytes location, final Bytes rlp) { + return emptyNodeFactory.decode(location, rlp); + } + + /** + * Flattens this node and all of its inlined nodes and node references into a list. + * + * @param location The location in the trie. + * @param nodeRlp The bytes of the trie node to be decoded. + * @return A list of nodes and node references embedded in the given rlp. + */ + public static List> decodeNodes(final Bytes location, final Bytes nodeRlp) { + final Node node = decode(location, nodeRlp); + final List> nodes = new ArrayList<>(); + nodes.add(node); + + final List> toProcess = new ArrayList<>(node.getChildren()); + while (!toProcess.isEmpty()) { + final Node currentNode = toProcess.remove(0); + if (Objects.equals(NullNode.instance(), currentNode)) { + // Skip null nodes + continue; + } + nodes.add(currentNode); + + if (!currentNode.isReferencedByHash()) { + // If current node is inlined, that means we can process its children + toProcess.addAll(currentNode.getChildren()); + } + } + + return nodes; + } + + /** + * Walks the trie in a bread-first manner, returning the list of nodes encountered in order. If + * any nodes are missing from the nodeLoader, those nodes are just skipped. + * + * @param nodeLoader The NodeLoader for looking up nodes by hash + * @param rootHash The hash of the root node + * @param maxDepth The maximum depth to traverse to. A value of zero will traverse the root node + * only. + * @return A stream non-null nodes in the breadth-first traversal order. + */ + public static Stream> breadthFirstDecoder( + final NodeLoader nodeLoader, final Bytes32 rootHash, final int maxDepth) { + checkArgument(maxDepth >= 0); + return Streams.stream(new BreadthFirstIterator(nodeLoader, rootHash, maxDepth)); + } + + /** + * Walks the trie in a bread-first manner, returning the list of nodes encountered in order. If + * any nodes are missing from the nodeLoader, those nodes are just skipped. + * + * @param nodeLoader The NodeLoader for looking up nodes by hash + * @param rootHash The hash of the root node + * @return A stream non-null nodes in the breadth-first traversal order. + */ + public static Stream> breadthFirstDecoder( + final NodeLoader nodeLoader, final Bytes32 rootHash) { + return breadthFirstDecoder(nodeLoader, rootHash, Integer.MAX_VALUE); + } + + private static class BreadthFirstIterator implements Iterator> { + + private final int maxDepth; + private final StoredNodeFactory nodeFactory; + + private int currentDepth = 0; + private final List> currentNodes = new ArrayList<>(); + private final List> nextNodes = new ArrayList<>(); + + BreadthFirstIterator(final NodeLoader nodeLoader, final Bytes32 rootHash, final int maxDepth) { + this.maxDepth = maxDepth; + this.nodeFactory = + new StoredNodeFactory<>(nodeLoader, Function.identity(), Function.identity()); + + nodeLoader + .getNode(Bytes.EMPTY, rootHash) + .map(h -> TrieNodeDecoder.decode(Bytes.EMPTY, h)) + .ifPresent(currentNodes::add); + } + + @Override + public boolean hasNext() { + return !currentNodes.isEmpty() && currentDepth <= maxDepth; + } + + @Override + public Node next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final Node nextNode = currentNodes.remove(0); + + final List> children = new ArrayList<>(nextNode.getChildren()); + while (!children.isEmpty()) { + Node child = children.remove(0); + if (Objects.equals(child, NullNode.instance())) { + // Ignore null nodes + continue; + } + if (child.isReferencedByHash()) { + // Retrieve hash-referenced child + final Optional> maybeChildNode = nodeFactory.retrieve(null, child.getHash()); + if (!maybeChildNode.isPresent()) { + continue; + } + child = maybeChildNode.get(); + } + nextNodes.add(child); + } + + // Set up next level + if (currentNodes.isEmpty()) { + currentDepth += 1; + currentNodes.addAll(nextNodes); + nextNodes.clear(); + } + + return nextNode; + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/InMemoryKeyValueStorage.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/InMemoryKeyValueStorage.java new file mode 100644 index 00000000000..745d9d44123 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/InMemoryKeyValueStorage.java @@ -0,0 +1,190 @@ +/* + * 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.storage; + +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes; + +import java.io.PrintStream; +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class InMemoryKeyValueStorage implements KeyValueStorage { + + private final Map hashValueStore; + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + + public InMemoryKeyValueStorage() { + this(new HashMap<>()); + } + + protected InMemoryKeyValueStorage(final Map hashValueStore) { + this.hashValueStore = hashValueStore; + } + + @Override + public void clear() { + final Lock lock = rwLock.writeLock(); + lock.lock(); + try { + hashValueStore.clear(); + } finally { + lock.unlock(); + } + } + + @Override + public boolean containsKey(final byte[] key) throws StorageException { + return get(key).isPresent(); + } + + @Override + public Optional get(final byte[] key) throws StorageException { + final Lock lock = rwLock.readLock(); + lock.lock(); + try { + return Optional.ofNullable(hashValueStore.get(Bytes.wrap(key))); + } finally { + lock.unlock(); + } + } + + @Override + public Set getAllKeysThat(final Predicate returnCondition) { + return stream() + .filter(pair -> returnCondition.test(pair.getKey())) + .map(Pair::getKey) + .collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet + )); + } + + @Override + public Set getAllValuesFromKeysThat(final Predicate returnCondition) { + return stream() + .filter(pair -> returnCondition.test(pair.getKey())) + .map(Pair::getValue) + .collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet + )); + } + + @Override + public Stream> stream() { + final Lock lock = rwLock.readLock(); + lock.lock(); + try { + return ImmutableSet.copyOf(hashValueStore.entrySet()).stream() + .map(bytesEntry -> Pair.of(bytesEntry.getKey().toArrayUnsafe(), bytesEntry.getValue())); + } finally { + lock.unlock(); + } + } + + @Override + public Stream streamKeys() { + final Lock lock = rwLock.readLock(); + lock.lock(); + try { + return ImmutableSet.copyOf(hashValueStore.entrySet()).stream() + .map(bytesEntry -> bytesEntry.getKey().toArrayUnsafe()); + } finally { + lock.unlock(); + } + } + + @Override + public boolean tryDelete(final byte[] key) { + final Lock lock = rwLock.writeLock(); + if (lock.tryLock()) { + try { + hashValueStore.remove(Bytes.wrap(key)); + } finally { + lock.unlock(); + } + return true; + } + return false; + } + + @Override + public void close() {} + + @Override + public KeyValueStorageTransaction startTransaction() { + return new KeyValueStorageTransactionTransitionValidatorDecorator(new InMemoryTransaction()); + } + + public Set keySet() { + return ImmutableSet.copyOf((hashValueStore.keySet())); + } + + private class InMemoryTransaction implements KeyValueStorageTransaction { + + private Map updatedValues = new HashMap<>(); + private Set removedKeys = new HashSet<>(); + + @Override + public void put(final byte[] key, final byte[] value) { + updatedValues.put(Bytes.wrap(key), value); + removedKeys.remove(Bytes.wrap(key)); + } + + @Override + public void remove(final byte[] key) { + removedKeys.add(Bytes.wrap(key)); + updatedValues.remove(Bytes.wrap(key)); + } + + @Override + public void commit() throws StorageException { + final Lock lock = rwLock.writeLock(); + lock.lock(); + try { + hashValueStore.putAll(updatedValues); + removedKeys.forEach(hashValueStore::remove); + updatedValues = null; + removedKeys = null; + } finally { + lock.unlock(); + } + } + + @Override + public void rollback() { + updatedValues.clear(); + removedKeys.clear(); + } + } + + public void dump(final PrintStream ps) { + final Lock lock = rwLock.readLock(); + lock.lock(); + try { + hashValueStore.forEach( + (k, v) -> ps.printf(" %s : %s%n", k.toHexString(), Bytes.wrap(v).toHexString())); + } finally { + lock.unlock(); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/InvalidConfigurationException.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/InvalidConfigurationException.java new file mode 100644 index 00000000000..612199d9eaa --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/InvalidConfigurationException.java @@ -0,0 +1,21 @@ +/* + * 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.storage; + +public class InvalidConfigurationException extends IllegalArgumentException { + public InvalidConfigurationException(final String message) { + super(message); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorage.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorage.java new file mode 100644 index 00000000000..e28a83fdf8e --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorage.java @@ -0,0 +1,108 @@ +/* + * 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.storage; + +import org.apache.commons.lang3.tuple.Pair; + +import java.io.Closeable; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Responsible for storing values against keys. + * + *

Behaviour expected with regard to key to value mapping is that of a map, one key maps to one + * value, when a new value is added with an existing key, that key now points at the new value. + * + *

All keys and values must be non-null. + */ +@Unstable +public interface KeyValueStorage extends Closeable { + + /** + * Deletes all keys and values from the storage. + * + * @throws StorageException problem encountered when attempting to clear storage. + */ + void clear() throws StorageException; + + /** + * Whether the key-value storage contains the given key. + * + * @param key a key that might be contained in the key-value storage. + * @return true when the given key is present in keyset, false + * otherwise. + * @throws StorageException problem encountered when interacting with the key set. + */ + boolean containsKey(byte[] key) throws StorageException; + + /** + * Retrieves the value associated with a given key. + * + * @param key whose associated value is being retrieved. + * @return an {@link Optional} containing the value associated with the specified key, otherwise + * empty. + * @throws StorageException problem encountered during the retrieval attempt. + */ + Optional get(byte[] key) throws StorageException; + + /** + * Returns a stream of all keys and values. + * + * @return A stream of all keys and values in storage. + * @throws StorageException problem encountered during the retrieval attempt. + */ + Stream> stream() throws StorageException; + + /** + * Returns a stream of all keys. + * + * @return A stream of all keys in storage. + * @throws StorageException problem encountered during the retrieval attempt. + */ + Stream streamKeys() throws StorageException; + + /** + * Delete the value corresponding to the given key if a write lock can be instantly acquired on + * the underlying storage. Do nothing otherwise. + * + * @param key The key to delete. + * @throws StorageException any problem encountered during the deletion attempt. + * @return false if the lock on the underlying storage could not be instantly acquired, true + * otherwise + */ + boolean tryDelete(byte[] key) throws StorageException; + + /** + * Performs an evaluation against each key in the store, returning the set of entries that pass. + * + * @param returnCondition predicate to evaluate each key against, unless the result is {@code + * null}, the key is added to the returned list of keys. + * @return the set of keys that pass the condition. + */ + Set getAllKeysThat(Predicate returnCondition); + + Set getAllValuesFromKeysThat(final Predicate returnCondition); + + /** + * Begins a fresh transaction, for sequencing operations for later atomic execution. + * + * @return transaciton to sequence key-value operations. + * @throws StorageException problem encountered when starting a new transaction. + */ + KeyValueStorageTransaction startTransaction() throws StorageException; +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorageTransaction.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorageTransaction.java new file mode 100644 index 00000000000..26a0f29f6fe --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorageTransaction.java @@ -0,0 +1,48 @@ +/* + * 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.storage; + +/** A transaction that can atomically commit a sequence of operations to a key-value store. */ +@Unstable +public interface KeyValueStorageTransaction { + + /** + * Associates the specified value with the specified key. + * + *

If a previously value had been store against the given key, the old value is replaced by the + * given value. + * + * @param key the given value is to be associated with. + * @param value associated with the specified key. + */ + void put(byte[] key, byte[] value); + + /** + * When the given key is present, the key and mapped value will be removed from storage. + * + * @param key the key and mapped value that will be removed. + */ + void remove(byte[] key); + + /** + * Performs an atomic commit of all the operations queued in the transaction. + * + * @throws StorageException problem was encountered preventing the commit + */ + void commit() throws StorageException; + + /** Reset the transaction to a state prior to any operations being queued. */ + void rollback(); +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorageTransactionTransitionValidatorDecorator.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorageTransactionTransitionValidatorDecorator.java new file mode 100644 index 00000000000..e618b634fd2 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/KeyValueStorageTransactionTransitionValidatorDecorator.java @@ -0,0 +1,55 @@ +/* + * 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.storage; + +import static com.google.common.base.Preconditions.checkState; + +public class KeyValueStorageTransactionTransitionValidatorDecorator + implements KeyValueStorageTransaction { + + private final KeyValueStorageTransaction transaction; + private boolean active = true; + + public KeyValueStorageTransactionTransitionValidatorDecorator( + final KeyValueStorageTransaction toDecorate) { + this.transaction = toDecorate; + } + + @Override + public void put(final byte[] key, final byte[] value) { + checkState(active, "Cannot invoke put() on a completed transaction."); + transaction.put(key, value); + } + + @Override + public void remove(final byte[] key) { + checkState(active, "Cannot invoke remove() on a completed transaction."); + transaction.remove(key); + } + + @Override + public final void commit() throws StorageException { + checkState(active, "Cannot commit a completed transaction."); + active = false; + transaction.commit(); + } + + @Override + public final void rollback() { + checkState(active, "Cannot rollback a completed transaction."); + active = false; + transaction.rollback(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBConfiguration.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBConfiguration.java new file mode 100644 index 00000000000..0588cc185a5 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBConfiguration.java @@ -0,0 +1,87 @@ +/* + * 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.storage; + +import java.nio.file.Path; + +public class RocksDBConfiguration { + + private final Path databaseDir; + private final int maxOpenFiles; + private final String label; + private final int maxBackgroundCompactions; + private final int backgroundThreadCount; + private final long cacheCapacity; + private final long writeBufferSize; + private final boolean isHighSpec; + private final boolean cacheIndexAndFilter; + + public RocksDBConfiguration( + final Path databaseDir, + final int maxOpenFiles, + final int maxBackgroundCompactions, + final int backgroundThreadCount, + final long cacheCapacity, + final long writeBufferSize, + final String label, + final boolean isHighSpec, + final boolean cacheIndexAndFilter) { + this.maxBackgroundCompactions = maxBackgroundCompactions; + this.backgroundThreadCount = backgroundThreadCount; + this.databaseDir = databaseDir; + this.maxOpenFiles = maxOpenFiles; + this.cacheCapacity = cacheCapacity; + this.writeBufferSize = writeBufferSize; + this.label = label; + this.isHighSpec = isHighSpec; + this.cacheIndexAndFilter = cacheIndexAndFilter; + } + + public Path getDatabaseDir() { + return databaseDir; + } + + public int getMaxOpenFiles() { + return maxOpenFiles; + } + + public int getMaxBackgroundCompactions() { + return maxBackgroundCompactions; + } + + public int getBackgroundThreadCount() { + return backgroundThreadCount; + } + + public long getCacheCapacity() { + return cacheCapacity; + } + + public long getWriteBufferSize() { + return writeBufferSize; + } + + public String getLabel() { + return label; + } + + public boolean isHighSpec() { + return isHighSpec; + } + + public boolean isCacheIndexAndFilter() { + return cacheIndexAndFilter; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBConfigurationBuilder.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBConfigurationBuilder.java new file mode 100644 index 00000000000..13010d9f7d6 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBConfigurationBuilder.java @@ -0,0 +1,105 @@ +/* + * 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.storage; + +import java.nio.file.Path; + +public class RocksDBConfigurationBuilder { + public static final int DEFAULT_MAX_OPEN_FILES = -1; + public static final long DEFAULT_CACHE_CAPACITY = 1 * 1024 * 1024 * 1024L; + public static final long DEFAULT_WRITE_BUFFER_SIZE = 256 * 1024 * 1024L; + public static final int DEFAULT_MAX_BACKGROUND_COMPACTIONS = Runtime.getRuntime().availableProcessors(); + public static final int DEFAULT_BACKGROUND_THREAD_COUNT = 4; + public static final boolean DEFAULT_IS_HIGH_SPEC = false; + public static final boolean DEFAULT_IS_CACHE_INDEX_AND_FILTER = false; + private Path databaseDir; + private String label = "blockchain"; + private int maxOpenFiles = DEFAULT_MAX_OPEN_FILES; + private long cacheCapacity = DEFAULT_CACHE_CAPACITY; + private long writeBufferSize = DEFAULT_WRITE_BUFFER_SIZE; + private int maxBackgroundCompactions = DEFAULT_MAX_BACKGROUND_COMPACTIONS; + private int backgroundThreadCount = DEFAULT_BACKGROUND_THREAD_COUNT; + private boolean isHighSpec = DEFAULT_IS_HIGH_SPEC; + private boolean cacheIndexAndFilter = DEFAULT_IS_CACHE_INDEX_AND_FILTER; + + public RocksDBConfigurationBuilder databaseDir(final Path databaseDir) { + this.databaseDir = databaseDir; + return this; + } + + public RocksDBConfigurationBuilder maxOpenFiles(final int maxOpenFiles) { + this.maxOpenFiles = maxOpenFiles; + return this; + } + + public RocksDBConfigurationBuilder label(final String label) { + this.label = label; + return this; + } + + public RocksDBConfigurationBuilder cacheCapacity(final long cacheCapacity) { + this.cacheCapacity = cacheCapacity; + return this; + } + + public RocksDBConfigurationBuilder writeBufferSize(final long writeBufferSize) { + this.writeBufferSize = writeBufferSize; + return this; + } + + public RocksDBConfigurationBuilder maxBackgroundCompactions(final int maxBackgroundCompactions) { + this.maxBackgroundCompactions = maxBackgroundCompactions; + return this; + } + + public RocksDBConfigurationBuilder backgroundThreadCount(final int backgroundThreadCount) { + this.backgroundThreadCount = backgroundThreadCount; + return this; + } + + public RocksDBConfigurationBuilder isHighSpec(final boolean isHighSpec) { + this.isHighSpec = isHighSpec; + return this; + } + + public RocksDBConfigurationBuilder isCacheIndexAndFilter(final boolean cacheIndexAndFilter) { + this.cacheIndexAndFilter = cacheIndexAndFilter; + return this; + } + + public static RocksDBConfigurationBuilder from(final RocksDBFactoryConfiguration configuration) { + return new RocksDBConfigurationBuilder() + .backgroundThreadCount(configuration.getBackgroundThreadCount()) + .cacheCapacity(configuration.getCacheCapacity()) + .writeBufferSize(configuration.getWriteBufferSize()) + .maxBackgroundCompactions(configuration.getMaxBackgroundCompactions()) + .maxOpenFiles(configuration.getMaxOpenFiles()) + .isHighSpec(configuration.isHighSpec()) + .isCacheIndexAndFilter(configuration.isCacheIndexAndFilter()); + } + + public RocksDBConfiguration build() { + return new RocksDBConfiguration( + databaseDir, + maxOpenFiles, + maxBackgroundCompactions, + backgroundThreadCount, + cacheCapacity, + writeBufferSize, + label, + isHighSpec, + cacheIndexAndFilter); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBFactoryConfiguration.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBFactoryConfiguration.java new file mode 100644 index 00000000000..2929ecc4a64 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBFactoryConfiguration.java @@ -0,0 +1,71 @@ +/* + * 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.storage; + +public class RocksDBFactoryConfiguration { + + private final int maxOpenFiles; + private final int maxBackgroundCompactions; + private final int backgroundThreadCount; + private final long cacheCapacity; + private final long writeBufferSize; + private final boolean isHighSpec; + private final boolean isCacheIndexAndFilter; + + public RocksDBFactoryConfiguration( + final int maxOpenFiles, + final int maxBackgroundCompactions, + final int backgroundThreadCount, + final long cacheCapacity, + final long writeBufferSize, + final boolean isHighSpec, + final boolean isCacheIndexAndFilter) { + this.maxBackgroundCompactions = maxBackgroundCompactions; + this.backgroundThreadCount = backgroundThreadCount; + this.maxOpenFiles = maxOpenFiles; + this.cacheCapacity = cacheCapacity; + this.writeBufferSize = writeBufferSize; + this.isHighSpec = isHighSpec; + this.isCacheIndexAndFilter = isCacheIndexAndFilter; + } + + public int getMaxOpenFiles() { + return maxOpenFiles; + } + + public int getMaxBackgroundCompactions() { + return maxBackgroundCompactions; + } + + public int getBackgroundThreadCount() { + return backgroundThreadCount; + } + + public long getCacheCapacity() { + return cacheCapacity; + } + + public long getWriteBufferSize() { + return writeBufferSize; + } + + public boolean isHighSpec() { + return isHighSpec; + } + + public boolean isCacheIndexAndFilter() { + return isCacheIndexAndFilter; + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBKeyValueStorage.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBKeyValueStorage.java new file mode 100644 index 00000000000..2684d0c5a72 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBKeyValueStorage.java @@ -0,0 +1,195 @@ +/* + * 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.storage; + +import org.apache.commons.lang3.tuple.Pair; +import org.rocksdb.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class RocksDBKeyValueStorage implements KeyValueStorage { + + static { + loadNativeLibrary(); + } + + private static final Logger LOG = LoggerFactory.getLogger(RocksDBKeyValueStorage.class); + + private final Options options; + protected final OptimisticTransactionDB db; + protected final AtomicBoolean closed = new AtomicBoolean(false); + private final WriteOptions tryDeleteOptions = + new WriteOptions().setNoSlowdown(true).setIgnoreMissingColumnFamilies(true); + + public RocksDBKeyValueStorage(final RocksDBConfiguration configuration) { + + try { + final Statistics stats = new Statistics(); + options = + new Options() + .setCreateIfMissing(true) + .setMaxOpenFiles(configuration.getMaxOpenFiles()) + .setTableFormatConfig(createBlockBasedTableConfig(configuration)) + .setMaxBackgroundCompactions(configuration.getMaxBackgroundCompactions()) + .setStatistics(stats); + options.getEnv().setBackgroundThreads(configuration.getBackgroundThreadCount()); + + db = OptimisticTransactionDB.open(options, configuration.getDatabaseDir().toString()); + } catch (final RocksDBException e) { + throw new StorageException(e); + } + } + + @Override + public void clear() throws StorageException { + try (final RocksIterator rocksIterator = db.newIterator()) { + rocksIterator.seekToFirst(); + if (rocksIterator.isValid()) { + final byte[] firstKey = rocksIterator.key(); + rocksIterator.seekToLast(); + if (rocksIterator.isValid()) { + final byte[] lastKey = rocksIterator.key(); + db.deleteRange(firstKey, lastKey); + db.delete(lastKey); + } + } + } catch (final RocksDBException e) { + throw new StorageException(e); + } + } + + @Override + public boolean containsKey(final byte[] key) throws StorageException { + return get(key).isPresent(); + } + + @Override + public Optional get(final byte[] key) throws StorageException { + throwIfClosed(); + + try { + return Optional.ofNullable(db.get(key)); + } catch (final RocksDBException e) { + throw new StorageException(e); + } + } + + @Override + public Set getAllKeysThat(final Predicate returnCondition) { + return stream() + .filter(pair -> returnCondition.test(pair.getKey())) + .map(Pair::getKey) + .collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet + )); + } + + @Override + public Stream> stream() { + final RocksIterator rocksIterator = db.newIterator(); + rocksIterator.seekToFirst(); + return RocksDbIterator.create(rocksIterator).toStream(); + } + + @Override + public Stream streamKeys() { + final RocksIterator rocksIterator = db.newIterator(); + rocksIterator.seekToFirst(); + return RocksDbIterator.create(rocksIterator).toStreamKeys(); + } + + @Override + public Set getAllValuesFromKeysThat(final Predicate returnCondition) { + return stream() + .filter(pair -> returnCondition.test(pair.getKey())) + .map(Pair::getValue) + .collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet + )); + } + + @Override + public boolean tryDelete(final byte[] key) { + try { + db.delete(tryDeleteOptions, key); + return true; + } catch (RocksDBException e) { + if (e.getStatus().getCode() == Status.Code.Incomplete) { + return false; + } else { + throw new StorageException(e); + } + } + } + + @Override + public KeyValueStorageTransaction startTransaction() throws StorageException { + throwIfClosed(); + final WriteOptions options = new WriteOptions(); + options.setIgnoreMissingColumnFamilies(true); + return new KeyValueStorageTransactionTransitionValidatorDecorator( + new RocksDBTransaction(db.beginTransaction(options), options)); + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + tryDeleteOptions.close(); + options.close(); + db.close(); + } + } + + private BlockBasedTableConfig createBlockBasedTableConfig(final RocksDBConfiguration config) { + final LRUCache cache = new LRUCache(config.getCacheCapacity()); + final BlockBasedTableConfig tableCfg = new BlockBasedTableConfig(); + tableCfg.setBlockCache(cache); + tableCfg.setCacheIndexAndFilterBlocks(config.isCacheIndexAndFilter()); + tableCfg.setPinL0FilterAndIndexBlocksInCache(config.isCacheIndexAndFilter()); + tableCfg.setFilter(new BloomFilter(10, false)); + return tableCfg; + } + + private void throwIfClosed() { + if (closed.get()) { + LOG.error("Attempting to use a closed RocksDBKeyValueStorage"); + throw new IllegalStateException("Storage has been closed"); + } + } + + private static void loadNativeLibrary() { + try { + RocksDB.loadLibrary(); + } catch (final ExceptionInInitializerError e) { + if (e.getCause() instanceof UnsupportedOperationException) { + LOG.info("Unable to load RocksDB library", e); + throw new InvalidConfigurationException( + "Unsupported platform detected. On Windows, ensure you have 64bit Java installed."); + } else { + throw e; + } + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBTransaction.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBTransaction.java new file mode 100644 index 00000000000..3c252100ef2 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDBTransaction.java @@ -0,0 +1,96 @@ +/* + * 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.storage; + +import org.rocksdb.RocksDBException; +import org.rocksdb.Transaction; +import org.rocksdb.WriteOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RocksDBTransaction implements KeyValueStorageTransaction { + private static final Logger logger = LoggerFactory.getLogger(RocksDBTransaction.class); + private static final String NO_SPACE_LEFT_ON_DEVICE = "No space left on device"; + + private final Transaction innerTx; + private final WriteOptions options; + + RocksDBTransaction( + final Transaction innerTx, final WriteOptions options) { + this.innerTx = innerTx; + this.options = options; + } + + @Override + public void put(final byte[] key, final byte[] value) { + try { + innerTx.put(key, value); + } catch (final RocksDBException e) { + if (e.getMessage().contains(NO_SPACE_LEFT_ON_DEVICE)) { + logger.error(e.getMessage()); + System.exit(0); + } + throw new StorageException(e); + } + } + + @Override + public void remove(final byte[] key) { + try { + innerTx.delete(key); + } catch (final RocksDBException e) { + if (e.getMessage().contains(NO_SPACE_LEFT_ON_DEVICE)) { + logger.error(e.getMessage()); + System.exit(0); + } + throw new StorageException(e); + } + } + + @Override + public void commit() throws StorageException { + try { + innerTx.commit(); + } catch (final RocksDBException e) { + if (e.getMessage().contains(NO_SPACE_LEFT_ON_DEVICE)) { + logger.error(e.getMessage()); + System.exit(0); + } + throw new StorageException(e); + } finally { + close(); + } + } + + @Override + public void rollback() { + try { + innerTx.rollback(); + } catch (final RocksDBException e) { + if (e.getMessage().contains(NO_SPACE_LEFT_ON_DEVICE)) { + logger.error(e.getMessage()); + System.exit(0); + } + throw new StorageException(e); + } finally { + close(); + } + } + + private void close() { + innerTx.close(); + options.close(); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDbIterator.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDbIterator.java new file mode 100644 index 00000000000..94e109ecad4 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/RocksDbIterator.java @@ -0,0 +1,140 @@ +/* + * 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.storage; + +import org.apache.commons.lang3.tuple.Pair; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static com.google.common.base.Preconditions.checkState; + +public class RocksDbIterator implements Iterator>, AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(RocksDbIterator.class); + + private final RocksIterator rocksIterator; + private final AtomicBoolean closed = new AtomicBoolean(false); + + private RocksDbIterator(final RocksIterator rocksIterator) { + this.rocksIterator = rocksIterator; + } + + public static RocksDbIterator create(final RocksIterator rocksIterator) { + return new RocksDbIterator(rocksIterator); + } + + @Override + public boolean hasNext() { + assertOpen(); + return rocksIterator.isValid(); + } + + @Override + public Pair next() { + assertOpen(); + try { + rocksIterator.status(); + } catch (final RocksDBException e) { + LOG.error( + String.format("%s encountered a problem while iterating.", getClass().getSimpleName()), + e); + } + if (!hasNext()) { + throw new NoSuchElementException(); + } + final byte[] key = rocksIterator.key(); + final byte[] value = rocksIterator.value(); + rocksIterator.next(); + return Pair.of(key, value); + } + + public byte[] nextKey() { + assertOpen(); + try { + rocksIterator.status(); + } catch (final RocksDBException e) { + LOG.error( + String.format("%s encountered a problem while iterating.", getClass().getSimpleName()), + e); + } + if (!hasNext()) { + throw new NoSuchElementException(); + } + final byte[] key = rocksIterator.key(); + rocksIterator.next(); + return key; + } + + public Stream> toStream() { + assertOpen(); + final Spliterator> spliterator = + Spliterators.spliteratorUnknownSize( + this, + Spliterator.IMMUTABLE + | Spliterator.DISTINCT + | Spliterator.NONNULL + | Spliterator.ORDERED + | Spliterator.SORTED); + + return StreamSupport.stream(spliterator, false).onClose(this::close); + } + + public Stream toStreamKeys() { + assertOpen(); + final Spliterator spliterator = + Spliterators.spliteratorUnknownSize( + new Iterator() { + @Override + public boolean hasNext() { + return RocksDbIterator.this.hasNext(); + } + + @Override + public byte[] next() { + return RocksDbIterator.this.nextKey(); + } + }, + Spliterator.IMMUTABLE + | Spliterator.DISTINCT + | Spliterator.NONNULL + | Spliterator.ORDERED + | Spliterator.SORTED); + + return StreamSupport.stream(spliterator, false).onClose(this::close); + } + + private void assertOpen() { + checkState( + !closed.get(), + String.format("Attempt to read from a closed %s", getClass().getSimpleName())); + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + rocksIterator.close(); + } + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/StorageException.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/StorageException.java new file mode 100644 index 00000000000..130eb5f6b7d --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/StorageException.java @@ -0,0 +1,49 @@ +/* + * 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.storage; + +/** Base exception class for problems encountered in the domain for storage. */ +public class StorageException extends RuntimeException { + + /** + * Constructs a new storage exception with the specified cause. + * + * @param cause saved for later retrieval by the {@link #getCause()} method). (A {@code null} + * value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public StorageException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a new storage exception with the specified detail message and cause. + * + * @param message the detail that may be retrieved later by Throwable.getMessage(). + * @param cause saved for later retrieval by the {@link #getCause()} method). (A {@code null} + * value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public StorageException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new storage exception with the specified detail message. + * + * @param message the detail that may be retrieved later by Throwable.getMessage(). + */ + public StorageException(final String message) { + super(message); + } +} diff --git a/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/Unstable.java b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/Unstable.java new file mode 100644 index 00000000000..9a35a8ef1a1 --- /dev/null +++ b/state-trie-jdk8/src/main/java/org/hyperledger/besu/storage/Unstable.java @@ -0,0 +1,30 @@ +/* + * 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.storage; + +import java.lang.annotation.Retention; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * This annotation is an indicator that the interface or method may evolve in a way that it not + * backwards compatible. Such as deleting methods, changing signatures, and adding checked + * exceptions. Authors are advised to exercise caution when using these APIs. + */ +@Retention(CLASS) +@java.lang.annotation.Target({METHOD, TYPE}) +public @interface Unstable {} diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/BufferBytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/BufferBytesTest.java new file mode 100644 index 00000000000..b48c82eb6fe --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/BufferBytesTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.vertx.core.buffer.Buffer; + +class BufferBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.wrapBuffer(Buffer.buffer(Bytes.fromHexString(hex).toArrayUnsafe())); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.wrapBuffer(Buffer.buffer(new byte[size])); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrapBuffer(Buffer.buffer(Bytes.of(bytes).toArray())); + } + + @Override + Bytes of(int... bytes) { + return Bytes.wrapBuffer(Buffer.buffer(Bytes.of(bytes).toArray())); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ByteBufBytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ByteBufBytesTest.java new file mode 100644 index 00000000000..c006e863137 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ByteBufBytesTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.Unpooled; + +class ByteBufBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.fromHexString(hex).toArrayUnsafe())); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.wrapByteBuf(Unpooled.copiedBuffer(new byte[size])); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.of(bytes).toArray())); + } + + @Override + Bytes of(int... bytes) { + return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.of(bytes).toArray())); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ByteBufferBytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ByteBufferBytesTest.java new file mode 100644 index 00000000000..9904f89d76c --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ByteBufferBytesTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import java.nio.ByteBuffer; + +class ByteBufferBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.fromHexString(hex).toArrayUnsafe())); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.wrapByteBuffer(ByteBuffer.allocate(size)); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.of(bytes).toArray())); + } + + @Override + Bytes of(int... bytes) { + return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.of(bytes).toArray())); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/Bytes32Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/Bytes32Test.java new file mode 100644 index 00000000000..b1936f5a83b --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/Bytes32Test.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class Bytes32Test { + + @Test + void testConcatenation() { + Bytes wrapped = Bytes.wrap(Bytes.wrap(new byte[32]), Bytes.wrap(new byte[6])); + assertEquals(37, wrapped.slice(0, 37).size()); + Bytes wrappedCopy = wrapped.slice(0, 37).copy(); + assertEquals(wrapped.slice(0, 37), wrappedCopy); + } + + @Test + void constantBytes32slice() { + assertEquals(Bytes32.ZERO.slice(12, 20).size(), 20); + } + + @Test + void constantBytesslice() { + assertEquals(Bytes.repeat((byte) 1, 63).slice(12, 20).size(), 20); + } + + @Test + void testMutableBytes32WrapWithOffset() { + Bytes bytes = Bytes + .fromHexString( + "0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + MutableBytes mutableBytes = MutableBytes.create(48); + bytes.copyTo(mutableBytes); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00", + Bytes32.wrap(mutableBytes, 1).toHexString()); + } + + @Test + void testMutableBytes32SliceWithOffset() { + Bytes bytes = Bytes + .fromHexString( + "0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + MutableBytes mutableBytes = MutableBytes.create(48); + bytes.copyTo(mutableBytes); + assertEquals("0x11", mutableBytes.slice(1, 1).toHexString()); + assertEquals("0x1122", mutableBytes.slice(1, 2).toHexString()); + assertEquals("0x112233445566778899aa", mutableBytes.slice(1, 10).toHexString()); + assertEquals("0x112233445566778899aabbccddeeff", mutableBytes.slice(1, 15).toHexString()); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddee", + mutableBytes.slice(1, 30).toHexString()); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00", + mutableBytes.slice(1, 32).toHexString()); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddee", + mutableBytes.slice(1, 46).toHexString()); + } + + @Test + void testBytes32SliceWithOffset() { + Bytes bytes = Bytes + .fromHexString( + "0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + assertEquals("0x11", bytes.slice(1, 1).toHexString()); + assertEquals("0x1122", bytes.slice(1, 2).toHexString()); + assertEquals("0x112233445566778899aa", bytes.slice(1, 10).toHexString()); + assertEquals("0x112233445566778899aabbccddeeff", bytes.slice(1, 15).toHexString()); + assertEquals("0x112233445566778899aabbccddeeff00112233445566778899aabbccddee", bytes.slice(1, 30).toHexString()); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00", + bytes.slice(1, 32).toHexString()); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddee", + bytes.slice(1, 46).toHexString()); + } + + @Test + void failsWhenWrappingArraySmallerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(new byte[31])); + assertEquals("Expected 32 bytes but got 31", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(new byte[33])); + assertEquals("Expected 32 bytes but got 33", exception.getMessage()); + } + + @Test + void leftPadAValueToBytes32() { + Bytes32 b32 = Bytes32.leftPad(Bytes.of(1, 2, 3)); + assertEquals(32, b32.size()); + for (int i = 0; i < 28; ++i) { + assertEquals((byte) 0, b32.get(i)); + } + assertEquals((byte) 1, b32.get(29)); + assertEquals((byte) 2, b32.get(30)); + assertEquals((byte) 3, b32.get(31)); + } + + @Test + void rightPadAValueToBytes32() { + Bytes32 b32 = Bytes32.rightPad(Bytes.of(1, 2, 3)); + assertEquals(32, b32.size()); + for (int i = 3; i < 32; ++i) { + assertEquals((byte) 0, b32.get(i)); + } + assertEquals((byte) 1, b32.get(0)); + assertEquals((byte) 2, b32.get(1)); + assertEquals((byte) 3, b32.get(2)); + } + + @Test + void failsWhenLeftPaddingValueLargerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.leftPad(MutableBytes.create(33))); + assertEquals("Expected at most 32 bytes but got 33", exception.getMessage()); + } + + @Test + void failsWhenRightPaddingValueLargerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.rightPad(MutableBytes.create(33))); + assertEquals("Expected at most 32 bytes but got 33", exception.getMessage()); + } + + @Test + void testWrapSlicesCorrectly() { + Bytes input = Bytes + .fromHexString( + "0xA99A76ED7796F7BE22D5B7E85DEEB7C5677E88E511E0B337618F8C4EB61349B4BF2D153F649F7B53359FE8B94A38E44C00000000000000000000000000000000"); + Bytes32 value = Bytes32.wrap(input, 0); + assertEquals(Bytes.fromHexString("0xA99A76ED7796F7BE22D5B7E85DEEB7C5677E88E511E0B337618F8C4EB61349B4"), value); + + Bytes32 secondValue = Bytes32.wrap(input, 32); + assertEquals( + Bytes.fromHexString("0xBF2D153F649F7B53359FE8B94A38E44C00000000000000000000000000000000"), + secondValue); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/Bytes48Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/Bytes48Test.java new file mode 100644 index 00000000000..67aab201f8f --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/Bytes48Test.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class Bytes48Test { + + @Test + void failsWhenWrappingArraySmallerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.wrap(new byte[47])); + assertEquals("Expected 48 bytes but got 47", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.wrap(new byte[49])); + assertEquals("Expected 48 bytes but got 49", exception.getMessage()); + } + + @Test + void rightPadAValueToBytes48() { + Bytes48 b48 = Bytes48.rightPad(Bytes.of(1, 2, 3)); + assertEquals(48, b48.size()); + for (int i = 3; i < 48; ++i) { + assertEquals((byte) 0, b48.get(i)); + } + assertEquals((byte) 1, b48.get(0)); + assertEquals((byte) 2, b48.get(1)); + assertEquals((byte) 3, b48.get(2)); + } + + @Test + void leftPadAValueToBytes48() { + Bytes48 b48 = Bytes48.leftPad(Bytes.of(1, 2, 3)); + assertEquals(48, b48.size()); + for (int i = 0; i < 28; ++i) { + assertEquals((byte) 0, b48.get(i)); + } + assertEquals((byte) 1, b48.get(45)); + assertEquals((byte) 2, b48.get(46)); + assertEquals((byte) 3, b48.get(47)); + } + + @Test + void failsWhenLeftPaddingValueLargerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.leftPad(MutableBytes.create(49))); + assertEquals("Expected at most 48 bytes but got 49", exception.getMessage()); + } + + @Test + void failsWhenRightPaddingValueLargerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.rightPad(MutableBytes.create(49))); + assertEquals("Expected at most 48 bytes but got 49", exception.getMessage()); + } + + @Test + void hexString() { + Bytes initial = Bytes48.random(); + assertEquals(initial, Bytes48.fromHexStringLenient(initial.toHexString())); + assertEquals(initial, Bytes48.fromHexString(initial.toHexString())); + assertEquals(initial, Bytes48.fromHexStringStrict(initial.toHexString())); + } + + @Test + void size() { + assertEquals(48, Bytes48.random().size()); + } + + @Test + void not() { + assertEquals( + Bytes48 + .fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + Bytes48.leftPad(Bytes.EMPTY).not()); + } + + @Test + void wrap() { + Bytes source = Bytes.random(96); + Bytes48 value = Bytes48.wrap(source, 2); + assertEquals(source.slice(2, 48), value); + } + + @Test + void leftPad() { + Bytes48 source = Bytes48.random(); + assertSame(source, Bytes48.leftPad(source)); + assertSame(source, Bytes48.rightPad(source)); + } + + @Test + void or() { + Bytes48 one = Bytes48 + .fromHexString( + "0x0000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes48 two = Bytes48 + .fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000"); + assertEquals( + Bytes48 + .fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + one.or(two)); + } + + @Test + void and() { + Bytes48 one = Bytes48 + .fromHexString( + "0x0000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes48 two = Bytes48 + .fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000"); + assertEquals( + Bytes48 + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + one.and(two)); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/BytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/BytesTest.java new file mode 100644 index 00000000000..81288d980e5 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/BytesTest.java @@ -0,0 +1,700 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.stream.Stream; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static org.junit.jupiter.api.Assertions.*; + +class BytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.fromHexString(hex); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.create(size); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrap(bytes); + } + + @Override + Bytes of(int... bytes) { + return Bytes.of(bytes); + } + + @Test + void wrapEmpty() { + Bytes wrap = Bytes.wrap(new byte[0]); + assertEquals(Bytes.EMPTY, wrap); + } + + @ParameterizedTest + @MethodSource("wrapProvider") + void wrap(Object arr) { + byte[] bytes = (byte[]) arr; + Bytes value = Bytes.wrap(bytes); + assertEquals(bytes.length, value.size()); + assertArrayEquals(value.toArray(), bytes); + } + + @SuppressWarnings("UnusedMethod") + private static Stream wrapProvider() { + return Stream + .of( + Arguments.of(new Object[] {new byte[10]}), + Arguments.of(new Object[] {new byte[] {1}}), + Arguments.of(new Object[] {new byte[] {1, 2, 3, 4}}), + Arguments.of(new Object[] {new byte[] {-1, 127, -128}})); + } + + @Test + void wrapNull() { + assertThrows(NullPointerException.class, () -> Bytes.wrap((byte[]) null)); + } + + /** + * Checks that modifying a wrapped array modifies the value itself. + */ + @Test + void wrapReflectsUpdates() { + byte[] bytes = new byte[] {1, 2, 3, 4, 5}; + Bytes value = Bytes.wrap(bytes); + + assertEquals(bytes.length, value.size()); + assertArrayEquals(value.toArray(), bytes); + + bytes[1] = 127; + bytes[3] = 127; + + assertEquals(bytes.length, value.size()); + assertArrayEquals(value.toArray(), bytes); + } + + @Test + void wrapSliceEmpty() { + assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[0], 0, 0)); + assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[] {1, 2, 3}, 0, 0)); + assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[] {1, 2, 3}, 2, 0)); + } + + @ParameterizedTest + @MethodSource("wrapSliceProvider") + void wrapSlice(Object arr, int offset, int length) { + assertWrapSlice((byte[]) arr, offset, length); + } + + @SuppressWarnings("UnusedMethod") + private static Stream wrapSliceProvider() { + return Stream + .of( + Arguments.of(new byte[] {1, 2, 3, 4}, 0, 4), + Arguments.of(new byte[] {1, 2, 3, 4}, 0, 2), + Arguments.of(new byte[] {1, 2, 3, 4}, 2, 1), + Arguments.of(new byte[] {1, 2, 3, 4}, 2, 2)); + } + + private void assertWrapSlice(byte[] bytes, int offset, int length) { + Bytes value = Bytes.wrap(bytes, offset, length); + assertEquals(length, value.size()); + assertArrayEquals(value.toArray(), Arrays.copyOfRange(bytes, offset, offset + length)); + } + + @Test + void wrapSliceNull() { + assertThrows(NullPointerException.class, () -> Bytes.wrap(null, 0, 2)); + } + + @Test + void wrapSliceNegativeOffset() { + assertThrows(IndexOutOfBoundsException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, -1, 4)); + } + + @Test + void wrapSliceOutOfBoundOffset() { + assertThrows(IndexOutOfBoundsException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 5, 1)); + } + + @Test + void wrapSliceNegativeLength() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 0, -2)); + assertEquals("Invalid negative length", exception.getMessage()); + } + + @Test + void wrapSliceTooBig() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 2, 3)); + assertEquals("Provided length 3 is too big: the value has only 2 bytes from offset 2", exception.getMessage()); + } + + /** + * Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped slice. + */ + @Test + void wrapSliceReflectsUpdates() { + byte[] bytes = new byte[] {1, 2, 3, 4, 5}; + assertWrapSlice(bytes, 2, 2); + bytes[2] = 127; + bytes[3] = 127; + assertWrapSlice(bytes, 2, 2); + + Bytes wrapped = Bytes.wrap(bytes, 2, 2); + Bytes copy = wrapped.copy(); + + // Modify the bytes outside of the wrapped slice and check this doesn't affect the value (that + // it is still equal to the copy from before the updates) + bytes[0] = 127; + assertEquals(copy, wrapped); + + // Sanity check for copy(): modify within the wrapped slice and check the copy differs now. + bytes[2] = 42; + assertEquals("0x2a7f", wrapped.toHexString()); + assertEquals(Bytes.fromHexString("0x7f7f"), copy); + } + + @Test + void ofBytes() { + assertArrayEquals(Bytes.of().toArray(), new byte[] {}); + assertArrayEquals(Bytes.of((byte) 1, (byte) 2).toArray(), new byte[] {1, 2}); + assertArrayEquals(Bytes.of((byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5).toArray(), new byte[] {1, 2, 3, 4, 5}); + assertArrayEquals(Bytes.of((byte) -1, (byte) 2, (byte) -3).toArray(), new byte[] {-1, 2, -3}); + } + + @Test + void ofInts() { + assertArrayEquals(Bytes.of(1, 2).toArray(), new byte[] {1, 2}); + assertArrayEquals(Bytes.of(1, 2, 3, 4, 5).toArray(), new byte[] {1, 2, 3, 4, 5}); + assertArrayEquals(Bytes.of(0xff, 0x7f, 0x80).toArray(), new byte[] {-1, 127, -128}); + } + + @Test + void ofIntsTooBig() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.of(2, 3, 256)); + assertEquals("3th value 256 does not fit a byte", exception.getMessage()); + } + + @Test + void ofIntsTooLow() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.of(2, -1, 3)); + assertEquals("2th value -1 does not fit a byte", exception.getMessage()); + } + + @Test + void minimalBytes() { + assertEquals(h("0x"), Bytes.minimalBytes(0)); + assertEquals(h("0x01"), Bytes.minimalBytes(1)); + assertEquals(h("0x04"), Bytes.minimalBytes(4)); + assertEquals(h("0x10"), Bytes.minimalBytes(16)); + assertEquals(h("0xFF"), Bytes.minimalBytes(255)); + assertEquals(h("0x0100"), Bytes.minimalBytes(256)); + assertEquals(h("0x0200"), Bytes.minimalBytes(512)); + assertEquals(h("0x010000"), Bytes.minimalBytes(1L << 16)); + assertEquals(h("0x01000000"), Bytes.minimalBytes(1L << 24)); + assertEquals(h("0x0100000000"), Bytes.minimalBytes(1L << 32)); + assertEquals(h("0x010000000000"), Bytes.minimalBytes(1L << 40)); + assertEquals(h("0x01000000000000"), Bytes.minimalBytes(1L << 48)); + assertEquals(h("0x0100000000000000"), Bytes.minimalBytes(1L << 56)); + assertEquals(h("0xFFFFFFFFFFFFFFFF"), Bytes.minimalBytes(-1L)); + } + + @Test + void ofUnsignedShort() { + assertEquals(h("0x0000"), Bytes.ofUnsignedShort(0)); + assertEquals(h("0x0001"), Bytes.ofUnsignedShort(1)); + assertEquals(h("0x0100"), Bytes.ofUnsignedShort(256)); + assertEquals(h("0xFFFF"), Bytes.ofUnsignedShort(65535)); + } + + @Test + void ofUnsignedShortLittleEndian() { + assertEquals(h("0x0000"), Bytes.ofUnsignedShort(0, LITTLE_ENDIAN)); + assertEquals(h("0x0100"), Bytes.ofUnsignedShort(1, LITTLE_ENDIAN)); + assertEquals(h("0x0001"), Bytes.ofUnsignedShort(256, LITTLE_ENDIAN)); + assertEquals(h("0xFFFF"), Bytes.ofUnsignedShort(65535, LITTLE_ENDIAN)); + } + + @Test + void ofUnsignedShortNegative() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.ofUnsignedShort(-1)); + assertEquals( + "Value -1 cannot be represented as an unsigned short (it is negative or too big)", + exception.getMessage()); + } + + @Test + void ofUnsignedShortTooBig() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.ofUnsignedShort(65536)); + assertEquals( + "Value 65536 cannot be represented as an unsigned short (it is negative or too big)", + exception.getMessage()); + } + + @Test + void asUnsignedBigIntegerConstants() { + assertEquals(bi("0"), Bytes.EMPTY.toUnsignedBigInteger()); + assertEquals(bi("1"), Bytes.of(1).toUnsignedBigInteger()); + } + + @Test + void asSignedBigIntegerConstants() { + assertEquals(bi("0"), Bytes.EMPTY.toBigInteger()); + assertEquals(bi("1"), Bytes.of(1).toBigInteger()); + } + + @Test + void fromHexStringLenient() { + assertEquals(Bytes.of(), Bytes.fromHexStringLenient("")); + assertEquals(Bytes.of(), Bytes.fromHexStringLenient("0x")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0x0")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("00")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0x00")); + assertEquals(Bytes.of(1), Bytes.fromHexStringLenient("0x1")); + assertEquals(Bytes.of(1), Bytes.fromHexStringLenient("0x01")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("1FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1ff2a")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1fF2a")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("01FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01ff2A")); + } + + @Test + void compareTo() { + assertEquals(1, Bytes.of(0x05).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0x05).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0xef).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0xef).compareTo(Bytes.of(0x00, 0x01))); + assertEquals(1, Bytes.of(0x00, 0x00, 0xef).compareTo(Bytes.of(0x00, 0x01))); + assertEquals(1, Bytes.of(0x00, 0xef).compareTo(Bytes.of(0x00, 0x00, 0x01))); + assertEquals(1, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0xff))); + assertEquals(1, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0xef, 0xf1).compareTo(Bytes.of(0xef, 0xf0))); + assertEquals(1, Bytes.of(0x00, 0x00, 0x01).compareTo(Bytes.of(0x00, 0x00))); + assertEquals(0, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0xef, 0xf0))); + assertEquals(-1, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0xef, 0xf5))); + assertEquals(-1, Bytes.of(0xef).compareTo(Bytes.of(0xff))); + assertEquals(-1, Bytes.of(0x01).compareTo(Bytes.of(0xff))); + assertEquals(-1, Bytes.of(0x01).compareTo(Bytes.of(0x01, 0xff))); + assertEquals(-1, Bytes.of(0x00, 0x00, 0x01).compareTo(Bytes.of(0x00, 0x02))); + assertEquals(-1, Bytes.of(0x00, 0x01).compareTo(Bytes.of(0x00, 0x00, 0x05))); + assertEquals(0, Bytes.fromHexString("0x0000").compareTo(Bytes.fromHexString("0x00"))); + assertEquals(0, Bytes.fromHexString("0x00").compareTo(Bytes.fromHexString("0x0000"))); + assertEquals(0, Bytes.fromHexString("0x000000").compareTo(Bytes.fromHexString("0x000000"))); + assertEquals(-1, Bytes.fromHexString("0x000001").compareTo(Bytes.fromHexString("0x0001"))); + } + + @Test + void fromHexStringLenientInvalidInput() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("foo")); + assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLenientLeftPadding() { + assertEquals(Bytes.of(), Bytes.fromHexStringLenient("", 0)); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("", 1)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexStringLenient("", 2)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexStringLenient("0x", 2)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0", 3)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0x0", 3)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("00", 3)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0x00", 3)); + assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexStringLenient("0x1", 3)); + assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexStringLenient("0x01", 3)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("1FF2A", 3)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1FF2A", 4)); + assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1ff2a", 5)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1fF2a", 4)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("01FF2A", 4)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01FF2A", 3)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01ff2A", 3)); + } + + @Test + void fromHexStringLenientLeftPaddingInvalidInput() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("foo", 10)); + assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLenientLeftPaddingInvalidSize() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("0x001F34", 2)); + assertEquals("Hex value is too large: expected at most 2 bytes but got 3", exception.getMessage()); + } + + @Test + void fromHexString() { + assertEquals(Bytes.of(), Bytes.fromHexString("0x")); + assertEquals(Bytes.of(0), Bytes.fromHexString("00")); + assertEquals(Bytes.of(0), Bytes.fromHexString("0x00")); + assertEquals(Bytes.of(1), Bytes.fromHexString("0x01")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("01FF2A")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01FF2A")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01ff2a")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01fF2a")); + } + + @Test + void fromHexStringInvalidInput() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("fooo")); + assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringNotLenient() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("0x100")); + assertEquals("Invalid odd-length hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLeftPadding() { + assertEquals(Bytes.of(), Bytes.fromHexString("0x", 0)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexString("0x", 2)); + assertEquals(Bytes.of(0, 0, 0, 0), Bytes.fromHexString("0x", 4)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexString("00", 2)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexString("0x00", 2)); + assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexString("0x01", 3)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("01FF2A", 4)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexString("0x01FF2A", 3)); + assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("0x01ff2a", 5)); + assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("0x01fF2a", 5)); + } + + @Test + void fromHexStringLeftPaddingInvalidInput() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("fooo", 4)); + assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLeftPaddingNotLenient() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("0x100", 4)); + assertEquals("Invalid odd-length hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLeftPaddingInvalidSize() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("0x001F34", 2)); + assertEquals("Hex value is too large: expected at most 2 bytes but got 3", exception.getMessage()); + } + + @Test + void fromBase64Roundtrip() { + Bytes value = Bytes.fromBase64String("deadbeefISDAbest"); + assertEquals("deadbeefISDAbest", value.toBase64String()); + } + + @Test + void littleEndianRoundtrip() { + int val = Integer.MAX_VALUE - 5; + Bytes littleEndianEncoded = Bytes.ofUnsignedInt(val, LITTLE_ENDIAN); + assertEquals(4, littleEndianEncoded.size()); + Bytes bigEndianEncoded = Bytes.ofUnsignedInt(val); + assertEquals(bigEndianEncoded.get(0), littleEndianEncoded.get(3)); + assertEquals(bigEndianEncoded.get(1), littleEndianEncoded.get(2)); + assertEquals(bigEndianEncoded.get(2), littleEndianEncoded.get(1)); + assertEquals(bigEndianEncoded.get(3), littleEndianEncoded.get(0)); + + int read = littleEndianEncoded.toInt(LITTLE_ENDIAN); + assertEquals(val, read); + } + + @Test + void littleEndianLongRoundtrip() { + long val = 1L << 46; + Bytes littleEndianEncoded = Bytes.ofUnsignedLong(val, LITTLE_ENDIAN); + assertEquals(8, littleEndianEncoded.size()); + Bytes bigEndianEncoded = Bytes.ofUnsignedLong(val); + assertEquals(bigEndianEncoded.get(0), littleEndianEncoded.get(7)); + assertEquals(bigEndianEncoded.get(1), littleEndianEncoded.get(6)); + assertEquals(bigEndianEncoded.get(2), littleEndianEncoded.get(5)); + assertEquals(bigEndianEncoded.get(3), littleEndianEncoded.get(4)); + assertEquals(bigEndianEncoded.get(4), littleEndianEncoded.get(3)); + assertEquals(bigEndianEncoded.get(5), littleEndianEncoded.get(2)); + assertEquals(bigEndianEncoded.get(6), littleEndianEncoded.get(1)); + assertEquals(bigEndianEncoded.get(7), littleEndianEncoded.get(0)); + + long read = littleEndianEncoded.toLong(LITTLE_ENDIAN); + assertEquals(val, read); + } + + @Test + void reverseBytes() { + Bytes bytes = Bytes.fromHexString("0x000102030405"); + assertEquals(Bytes.fromHexString("0x050403020100"), bytes.reverse()); + } + + @Test + void reverseBytesEmptyArray() { + Bytes bytes = Bytes.fromHexString("0x"); + assertEquals(Bytes.fromHexString("0x"), bytes.reverse()); + } + + @Test + void mutableBytesIncrement() { + MutableBytes one = MutableBytes.of(1); + one.increment(); + assertEquals(Bytes.of(2), one); + } + + @Test + void mutableBytesIncrementMax() { + MutableBytes maxed = MutableBytes.of(1, 0xFF); + maxed.increment(); + assertEquals(Bytes.of(2, 0), maxed); + } + + @Test + void mutableBytesIncrementOverflow() { + MutableBytes maxed = MutableBytes.of(0xFF, 0xFF, 0xFF); + maxed.increment(); + assertEquals(Bytes.of(0, 0, 0), maxed); + } + + @Test + void mutableBytesDecrement() { + MutableBytes one = MutableBytes.of(2); + one.decrement(); + assertEquals(Bytes.of(1), one); + } + + @Test + void mutableBytesDecrementMax() { + MutableBytes maxed = MutableBytes.of(1, 0); + maxed.decrement(); + assertEquals(Bytes.of(0, 0xFF), maxed); + } + + @Test + void mutableBytesDecrementOverflow() { + MutableBytes maxed = MutableBytes.of(0x00, 0x00, 0x00); + maxed.decrement(); + assertEquals(Bytes.of(0xFF, 0xFF, 0xFF), maxed); + } + + @Test + void concatenation() { + MutableBytes value1 = MutableBytes.wrap(Bytes.fromHexString("deadbeef").toArrayUnsafe()); + Bytes result = Bytes.concatenate(value1, value1); + assertEquals(Bytes.fromHexString("deadbeefdeadbeef"), result); + value1.set(0, (byte) 0); + assertEquals(Bytes.fromHexString("deadbeefdeadbeef"), result); + } + + @Test + void wrap() { + MutableBytes value1 = MutableBytes.wrap(Bytes.fromHexString("deadbeef").toArrayUnsafe()); + Bytes result = Bytes.wrap(value1, value1); + assertEquals(Bytes.fromHexString("deadbeefdeadbeef"), result); + value1.set(0, (byte) 0); + assertEquals(Bytes.fromHexString("0x00adbeef00adbeef"), result); + } + + @Test + void random() { + Bytes value = Bytes.random(20); + assertNotEquals(value, Bytes.random(20)); + assertEquals(20, value.size()); + } + + @Test + void getInt() { + Bytes value = Bytes.fromHexString("0x00000001"); + assertEquals(1, value.getInt(0)); + assertEquals(16777216, value.getInt(0, LITTLE_ENDIAN)); + assertEquals(1, value.toInt()); + assertEquals(16777216, value.toInt(LITTLE_ENDIAN)); + } + + @Test + void getLong() { + Bytes value = Bytes.fromHexString("0x0000000000000001"); + assertEquals(1, value.getLong(0)); + assertEquals(72057594037927936L, value.getLong(0, LITTLE_ENDIAN)); + assertEquals(1, value.toLong()); + assertEquals(72057594037927936L, value.toLong(LITTLE_ENDIAN)); + } + + @Test + void numberOfLeadingZeros() { + Bytes value = Bytes.fromHexString("0x00000001"); + assertEquals(31, value.numberOfLeadingZeros()); + } + + @Test + void and() { + Bytes value = Bytes.fromHexString("0x01000001").and(Bytes.fromHexString("0x01000000")); + assertEquals(Bytes.fromHexString("0x01000000"), value); + } + + @Test + void andResult() { + MutableBytes result = MutableBytes.create(4); + Bytes.fromHexString("0x01000001").and(Bytes.fromHexString("0x01000000"), result); + assertEquals(Bytes.fromHexString("0x01000000"), result); + } + + @Test + void or() { + Bytes value = Bytes.fromHexString("0x01000001").or(Bytes.fromHexString("0x01000000")); + assertEquals(Bytes.fromHexString("0x01000001"), value); + } + + @Test + void orResult() { + MutableBytes result = MutableBytes.create(4); + Bytes.fromHexString("0x01000001").or(Bytes.fromHexString("0x01000000"), result); + assertEquals(Bytes.fromHexString("0x01000001"), result); + } + + @Test + void xor() { + Bytes value = Bytes.fromHexString("0x01000001").xor(Bytes.fromHexString("0x01000000")); + assertEquals(Bytes.fromHexString("0x00000001"), value); + } + + @Test + void xorResult() { + MutableBytes result = MutableBytes.create(4); + Bytes.fromHexString("0x01000001").xor(Bytes.fromHexString("0x01000000"), result); + assertEquals(Bytes.fromHexString("0x00000001"), result); + } + + @Test + void not() { + Bytes value = Bytes.fromHexString("0x01000001").not(); + assertEquals(Bytes.fromHexString("0xfefffffe"), value); + } + + @Test + void notResult() { + MutableBytes result = MutableBytes.create(4); + Bytes.fromHexString("0x01000001").not(result); + assertEquals(Bytes.fromHexString("0xfefffffe"), result); + } + + @Test + void shiftRight() { + Bytes value = Bytes.fromHexString("0x01000001").shiftRight(2); + assertEquals(Bytes.fromHexString("0x00400000"), value); + } + + @Test + void shiftRightResult() { + MutableBytes result = MutableBytes.create(4); + Bytes.fromHexString("0x01000001").shiftRight(2, result); + assertEquals(Bytes.fromHexString("0x00400000"), result); + } + + @Test + void shiftLeft() { + Bytes value = Bytes.fromHexString("0x01000001").shiftLeft(2); + assertEquals(Bytes.fromHexString("0x04000004"), value); + } + + @Test + void shiftLeftResult() { + MutableBytes result = MutableBytes.create(4); + Bytes.fromHexString("0x01000001").shiftLeft(2, result); + assertEquals(Bytes.fromHexString("0x04000004"), result); + } + + @Test + void commonPrefix() { + Bytes value = Bytes.fromHexString("0x01234567"); + Bytes value2 = Bytes.fromHexString("0x01236789"); + assertEquals(2, value.commonPrefixLength(value2)); + assertEquals(Bytes.fromHexString("0x0123"), value.commonPrefix(value2)); + } + + @Test + void testWrapByteBufEmpty() { + ByteBuf buffer = Unpooled.buffer(0); + assertSame(Bytes.EMPTY, Bytes.wrapByteBuf(buffer)); + } + + @Test + void testWrapByteBufWithIndexEmpty() { + ByteBuf buffer = Unpooled.buffer(3); + assertSame(Bytes.EMPTY, Bytes.wrapByteBuf(buffer, 3, 0)); + } + + @Test + void testWrapByteBufSizeWithOffset() { + ByteBuf buffer = Unpooled.buffer(10); + assertEquals(1, Bytes.wrapByteBuf(buffer, 1, 1).size()); + } + + @Test + void testWrapByteBufSize() { + ByteBuf buffer = Unpooled.buffer(20); + assertEquals(20, Bytes.wrapByteBuf(buffer).size()); + } + + @Test + void testWrapByteBufReadableBytes() { + ByteBuf buffer = Unpooled.buffer(20).writeByte(3); + assertEquals(1, Bytes.wrapByteBuf(buffer, 0, buffer.readableBytes()).size()); + } + + @Test + void segmentBytes() { + Bytes b = Bytes + .wrap( + Bytes32.ZERO, + Bytes32.random(), + Bytes32.rightPad(Bytes.fromHexStringLenient("0x1")), + Bytes.fromHexString("0xf000")); + Bytes32[] result = Bytes.segment(b); + assertEquals(4, result.length); + assertEquals(Bytes32.rightPad(Bytes.fromHexString("0xf000")), result[3]); + } + + @Test + void segments() { + Bytes value = Bytes.fromHexString("0x7b600035f660115760006000526001601ff35b60016000526001601ff3600052601c6000f3"); + Bytes32[] result = Bytes.segment(value); + assertEquals(Bytes.fromHexString("0x7b600035f660115760006000526001601ff35b60016000526001601ff3600052"), result[0]); + assertEquals(Bytes.fromHexString("0x601c6000f3000000000000000000000000000000000000000000000000000000"), result[1]); + } + + @Test + void testTrimLeadingZeros() { + Bytes b = Bytes.fromHexString("0x000000f300567800"); + assertEquals(Bytes.fromHexString("0xf300567800"), b.trimLeadingZeros()); + } + + @Test + void testTrimTrailingZeros() { + Bytes b = Bytes.fromHexString("0x000000f300567800"); + assertEquals(Bytes.fromHexString("0x000000f3005678"), b.trimTrailingZeros()); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/CommonBytesTests.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/CommonBytesTests.java new file mode 100644 index 00000000000..7e717dfe01d --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/CommonBytesTests.java @@ -0,0 +1,687 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.Unpooled; +import io.vertx.core.buffer.Buffer; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.*; + +abstract class CommonBytesTests { + + abstract Bytes h(String hex); + + abstract MutableBytes m(int size); + + abstract Bytes w(byte[] bytes); + + abstract Bytes of(int... bytes); + + BigInteger bi(String decimal) { + return new BigInteger(decimal); + } + + @Test + void asUnsignedBigInteger() { + // Make sure things are interpreted unsigned. + assertEquals(bi("255"), h("0xFF").toUnsignedBigInteger()); + + // Try 2^100 + Long.MAX_VALUE, as an easy to define a big not too special big integer. + BigInteger expected = BigInteger.valueOf(2).pow(100).add(BigInteger.valueOf(Long.MAX_VALUE)); + + // 2^100 is a one followed by 100 zeros, that's 12 bytes of zeros (=96) plus 4 more zeros (so + // 0x10 == 16). + MutableBytes v = m(13); + v.set(0, (byte) 16); + v.setLong(v.size() - 8, Long.MAX_VALUE); + assertEquals(expected, v.toUnsignedBigInteger()); + } + + @Test + void testAsSignedBigInteger() { + // Make sure things are interpreted signed. + assertEquals(bi("-1"), h("0xFF").toBigInteger()); + + // Try 2^100 + Long.MAX_VALUE, as an easy to define a big but not too special big integer. + BigInteger expected = BigInteger.valueOf(2).pow(100).add(BigInteger.valueOf(Long.MAX_VALUE)); + + // 2^100 is a one followed by 100 zeros, that's 12 bytes of zeros (=96) plus 4 more zeros (so + // 0x10 == 16). + MutableBytes v = m(13); + v.set(0, (byte) 16); + v.setLong(v.size() - 8, Long.MAX_VALUE); + assertEquals(expected, v.toBigInteger()); + + // And for a large negative one, we use -(2^100 + Long.MAX_VALUE), which is: + // 2^100 + Long.MAX_VALUE = 0x10(4 bytes of 0)7F( 7 bytes of 1) + // inverse = 0xEF(4 bytes of 1)80( 7 bytes of 0) + // +1 = 0xEF(4 bytes of 1)80(6 bytes of 0)01 + expected = expected.negate(); + v = m(13); + v.set(0, (byte) 0xEF); + for (int i = 1; i < 5; i++) { + v.set(i, (byte) 0xFF); + } + v.set(5, (byte) 0x80); + // 6 bytes of 0 + v.set(12, (byte) 1); + assertEquals(expected, v.toBigInteger()); + } + + @Test + void testSize() { + assertEquals(0, w(new byte[0]).size()); + assertEquals(1, w(new byte[1]).size()); + assertEquals(10, w(new byte[10]).size()); + } + + @Test + void testGet() { + Bytes v = w(new byte[] {1, 2, 3, 4}); + assertEquals((int) (byte) 1, (int) v.get(0)); + assertEquals((int) (byte) 2, (int) v.get(1)); + assertEquals((int) (byte) 3, (int) v.get(2)); + assertEquals((int) (byte) 4, (int) v.get(3)); + } + + @Test + void testGetNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).get(-1)); + } + + @Test + void testGetOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).get(4)); + } + + @Test + void testGetInt() { + Bytes value = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1}); + + // 0x00000100 = 256 + assertEquals(256, value.getInt(0)); + // 0x000100FF = 65536 + 255 = 65791 + assertEquals(65791, value.getInt(1)); + // 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751 + assertEquals(16842751, value.getInt(2)); + // 0xFFFFFFFF = -1 + assertEquals(-1, value.getInt(4)); + } + + @Test + void testGetIntNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(-1)); + } + + @Test + void testGetIntOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(4)); + } + + @Test + void testGetIntNotEnoughBytes() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(1)); + } + + @Test + void testAsInt() { + assertEquals(0, Bytes.EMPTY.toInt()); + Bytes value1 = w(new byte[] {0, 0, 1, 0}); + // 0x00000100 = 256 + assertEquals(256, value1.toInt()); + assertEquals(256, value1.slice(2).toInt()); + + Bytes value2 = w(new byte[] {0, 1, 0, -1}); + // 0x000100FF = 65536 + 255 = 65791 + assertEquals(65791, value2.toInt()); + assertEquals(65791, value2.slice(1).toInt()); + + Bytes value3 = w(new byte[] {1, 0, -1, -1}); + // 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751 + assertEquals(16842751, value3.toInt()); + + Bytes value4 = w(new byte[] {-1, -1, -1, -1}); + // 0xFFFFFFFF = -1 + assertEquals(-1, value4.toInt()); + } + + @Test + void testAsIntTooManyBytes() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> w(new byte[] {1, 2, 3, 4, 5}).toInt()); + assertEquals("Value of size 5 has more than 4 bytes", exception.getMessage()); + } + + @Test + void testGetLong() { + Bytes value1 = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1, 0, 0}); + // 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071 + assertEquals(1103806595071L, value1.getLong(0)); + // 0x 000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176 + assertEquals(282574488338176L, value1.getLong(1)); + + Bytes value2 = w(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1}); + assertEquals(-1L, value2.getLong(0)); + } + + @Test + void testGetLongNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(-1)); + } + + @Test + void testGetLongOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(8)); + } + + @Test + void testGetLongNotEnoughBytes() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getLong(0)); + } + + @Test + void testAsLong() { + assertEquals(0, Bytes.EMPTY.toLong()); + Bytes value1 = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1}); + // 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071 + assertEquals(1103806595071L, value1.toLong()); + assertEquals(1103806595071L, value1.slice(2).toLong()); + Bytes value2 = w(new byte[] {0, 1, 0, -1, -1, -1, -1, 0}); + // 0x000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176 + assertEquals(282574488338176L, value2.toLong()); + assertEquals(282574488338176L, value2.slice(1).toLong()); + + Bytes value3 = w(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1}); + assertEquals(-1L, value3.toLong()); + } + + @Test + void testAsLongTooManyBytes() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}).toLong()); + assertEquals("Value of size 9 has more than 8 bytes", exception.getMessage()); + } + + @Test + void testSlice() { + assertEquals(h("0x"), h("0x0123456789").slice(0, 0)); + assertEquals(h("0x"), h("0x0123456789").slice(2, 0)); + assertEquals(h("0x01"), h("0x0123456789").slice(0, 1)); + assertEquals(h("0x0123"), h("0x0123456789").slice(0, 2)); + + assertEquals(h("0x4567"), h("0x0123456789").slice(2, 2)); + assertEquals(h("0x23456789"), h("0x0123456789").slice(1, 4)); + } + + @Test + void testSliceNegativeOffset() { + assertThrows(IndexOutOfBoundsException.class, () -> h("0x012345").slice(-1, 2)); + } + + @Test + void testSliceOffsetOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> h("0x012345").slice(3, 2)); + } + + @Test + void testSliceTooLong() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> h("0x012345").slice(1, 3)); + assertEquals( + "Provided length 3 is too big: the value has size 3 and has only 2 bytes from 1", + exception.getMessage()); + } + + @Test + void testMutableCopy() { + Bytes v = h("0x012345"); + MutableBytes mutableCopy = v.mutableCopy(); + + // Initially, copy must be equal. + assertEquals(mutableCopy, v); + + // Upon modification, original should not have been modified. + mutableCopy.set(0, (byte) -1); + assertNotEquals(mutableCopy, v); + assertEquals(h("0x012345"), v); + assertEquals(h("0xFF2345"), mutableCopy); + } + + @Test + void testCopyTo() { + MutableBytes dest; + + // The follow does nothing, but simply making sure it doesn't throw. + dest = MutableBytes.EMPTY; + Bytes.EMPTY.copyTo(dest); + assertEquals(Bytes.EMPTY, dest); + + dest = MutableBytes.create(1); + of(1).copyTo(dest); + assertEquals(h("0x01"), dest); + + dest = MutableBytes.create(1); + of(10).copyTo(dest); + assertEquals(h("0x0A"), dest); + + dest = MutableBytes.create(2); + of(0xff, 0x03).copyTo(dest); + assertEquals(h("0xFF03"), dest); + + dest = MutableBytes.create(4); + of(0xff, 0x03).copyTo(dest.mutableSlice(1, 2)); + assertEquals(h("0x00FF0300"), dest); + } + + @Test + void testCopyToTooSmall() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(2))); + assertEquals("Cannot copy 3 bytes to destination of non-equal size 2", exception.getMessage()); + } + + @Test + void testCopyToTooBig() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(4))); + assertEquals("Cannot copy 3 bytes to destination of non-equal size 4", exception.getMessage()); + } + + @Test + void testCopyToWithOffset() { + MutableBytes dest; + + dest = MutableBytes.wrap(new byte[] {1, 2, 3}); + Bytes.EMPTY.copyTo(dest, 0); + assertEquals(h("0x010203"), dest); + + dest = MutableBytes.wrap(new byte[] {1, 2, 3}); + of(1).copyTo(dest, 1); + assertEquals(h("0x010103"), dest); + + dest = MutableBytes.wrap(new byte[] {1, 2, 3}); + of(2).copyTo(dest, 0); + assertEquals(h("0x020203"), dest); + + dest = MutableBytes.wrap(new byte[] {1, 2, 3}); + of(1, 1).copyTo(dest, 1); + assertEquals(h("0x010101"), dest); + + dest = MutableBytes.create(4); + of(0xff, 0x03).copyTo(dest, 1); + assertEquals(h("0x00FF0300"), dest); + } + + @Test + void testCopyToWithOffsetTooSmall() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(4), 2)); + assertEquals("Cannot copy 3 bytes, destination has only 2 bytes from index 2", exception.getMessage()); + } + + @Test + void testCopyToWithNegativeOffset() { + assertThrows(IndexOutOfBoundsException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(10), -1)); + } + + @Test + void testCopyToWithOutOfBoundIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(10), 10)); + } + + @Test + void testAppendTo() { + testAppendTo(Bytes.EMPTY, Buffer.buffer(), Bytes.EMPTY); + testAppendTo(Bytes.EMPTY, Buffer.buffer(h("0x1234").toArrayUnsafe()), h("0x1234")); + testAppendTo(h("0x1234"), Buffer.buffer(), h("0x1234")); + testAppendTo(h("0x5678"), Buffer.buffer(h("0x1234").toArrayUnsafe()), h("0x12345678")); + } + + private void testAppendTo(Bytes toAppend, Buffer buffer, Bytes expected) { + toAppend.appendTo(buffer); + assertEquals(expected, Bytes.wrap(buffer.getBytes())); + } + + @Test + void testIsZero() { + assertTrue(Bytes.EMPTY.isZero()); + assertTrue(Bytes.of(0).isZero()); + assertTrue(Bytes.of(0, 0, 0).isZero()); + + assertFalse(Bytes.of(1).isZero()); + assertFalse(Bytes.of(1, 0, 0).isZero()); + assertFalse(Bytes.of(0, 0, 1).isZero()); + assertFalse(Bytes.of(0, 0, 1, 0, 0).isZero()); + } + + @Test + void testIsEmpty() { + assertTrue(Bytes.EMPTY.isEmpty()); + + assertFalse(Bytes.of(0).isEmpty()); + assertFalse(Bytes.of(0, 0, 0).isEmpty()); + assertFalse(Bytes.of(1).isEmpty()); + } + + @Test + void findsCommonPrefix() { + Bytes v = Bytes.of(1, 2, 3, 4, 5, 6, 7); + Bytes o = Bytes.of(1, 2, 3, 4, 4, 3, 2); + assertEquals(4, v.commonPrefixLength(o)); + assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); + } + + @Test + void findsCommonPrefixOfShorter() { + Bytes v = Bytes.of(1, 2, 3, 4, 5, 6, 7); + Bytes o = Bytes.of(1, 2, 3, 4); + assertEquals(4, v.commonPrefixLength(o)); + assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); + } + + @Test + void findsCommonPrefixOfLonger() { + Bytes v = Bytes.of(1, 2, 3, 4); + Bytes o = Bytes.of(1, 2, 3, 4, 4, 3, 2); + assertEquals(4, v.commonPrefixLength(o)); + assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); + } + + @Test + void findsCommonPrefixOfSliced() { + Bytes v = Bytes.of(1, 2, 3, 4).slice(2, 2); + Bytes o = Bytes.of(3, 4, 3, 3, 2).slice(3, 2); + assertEquals(1, v.commonPrefixLength(o)); + assertEquals(Bytes.of(3), v.commonPrefix(o)); + } + + @Test + void testTrimLeadingZeroes() { + assertEquals(h("0x"), h("0x").trimLeadingZeros()); + assertEquals(h("0x"), h("0x00").trimLeadingZeros()); + assertEquals(h("0x"), h("0x00000000").trimLeadingZeros()); + + assertEquals(h("0x01"), h("0x01").trimLeadingZeros()); + assertEquals(h("0x01"), h("0x00000001").trimLeadingZeros()); + + assertEquals(h("0x3010"), h("0x3010").trimLeadingZeros()); + assertEquals(h("0x3010"), h("0x00003010").trimLeadingZeros()); + + assertEquals(h("0xFFFFFFFF"), h("0xFFFFFFFF").trimLeadingZeros()); + assertEquals(h("0xFFFFFFFF"), h("0x000000000000FFFFFFFF").trimLeadingZeros()); + } + + @Test + void testQuantityHexString() { + assertEquals("0x0", h("0x").toQuantityHexString()); + assertEquals("0x0", h("0x0000").toQuantityHexString()); + assertEquals("0x1000001", h("0x01000001").toQuantityHexString()); + } + + @Test + void testHexString() { + assertEquals("0x", h("0x").toShortHexString()); + assertEquals("0x", h("0x0000").toShortHexString()); + assertEquals("0x1000001", h("0x01000001").toShortHexString()); + + assertEquals("0000", h("0x0000").toUnprefixedHexString()); + assertEquals("1234", h("0x1234").toUnprefixedHexString()); + assertEquals("0022", h("0x0022").toUnprefixedHexString()); + } + + @Test + void testEllipsisHexString() { + assertEquals("0x", h("0x").toEllipsisHexString()); + assertEquals("0x0000", h("0x0000").toEllipsisHexString()); + assertEquals("0x01000001", h("0x01000001").toEllipsisHexString()); + assertEquals("0x0100000001", h("0x0100000001").toEllipsisHexString()); + assertEquals("0x0100..0001", h("0x010000000001").toEllipsisHexString()); + assertEquals("0x1234..5678", h("0x123456789abcdef012345678").toEllipsisHexString()); + assertEquals("0x1234..789a", h("0x123456789abcdef0123456789a").toEllipsisHexString()); + assertEquals("0x1234..9abc", h("0x123456789abcdef0123456789abc").toEllipsisHexString()); + assertEquals("0x1234..bcde", h("0x123456789abcdef0123456789abcde").toEllipsisHexString()); + assertEquals("0x1234..def0", h("0x123456789abcdef0123456789abcdef0").toEllipsisHexString()); + assertEquals("0x1234..def0", h("0x123456789abcdef0123456789abcdef0").toEllipsisHexString()); + } + + @Test + void slideToEnd() { + assertEquals(Bytes.of(1, 2, 3, 4), Bytes.of(1, 2, 3, 4).slice(0)); + assertEquals(Bytes.of(2, 3, 4), Bytes.of(1, 2, 3, 4).slice(1)); + assertEquals(Bytes.of(3, 4), Bytes.of(1, 2, 3, 4).slice(2)); + assertEquals(Bytes.of(4), Bytes.of(1, 2, 3, 4).slice(3)); + } + + @Test + void slicePastEndReturnsEmpty() { + assertEquals(Bytes.EMPTY, Bytes.of(1, 2, 3, 4).slice(4)); + assertEquals(Bytes.EMPTY, Bytes.of(1, 2, 3, 4).slice(5)); + } + + @Test + void testUpdate() throws NoSuchAlgorithmException { + // Digest the same byte array in 4 ways: + // 1) directly from the array + // 2) after wrapped using the update() method + // 3) after wrapped and copied using the update() method + // 4) after wrapped but getting the byte manually + // and check all compute the same digest. + MessageDigest md1 = MessageDigest.getInstance("SHA-1"); + MessageDigest md2 = MessageDigest.getInstance("SHA-1"); + MessageDigest md3 = MessageDigest.getInstance("SHA-1"); + MessageDigest md4 = MessageDigest.getInstance("SHA-1"); + + byte[] toDigest = new BigInteger("12324029423415041783577517238472017314").toByteArray(); + Bytes wrapped = w(toDigest); + + byte[] digest1 = md1.digest(toDigest); + + wrapped.update(md2); + byte[] digest2 = md2.digest(); + + wrapped.copy().update(md3); + byte[] digest3 = md3.digest(); + + for (int i = 0; i < wrapped.size(); i++) + md4.update(wrapped.get(i)); + byte[] digest4 = md4.digest(); + + assertArrayEquals(digest2, digest1); + assertArrayEquals(digest3, digest1); + assertArrayEquals(digest4, digest1); + } + + @Test + void testArrayExtraction() { + // extractArray() and getArrayUnsafe() have essentially the same contract... + testArrayExtraction(Bytes::toArray); + testArrayExtraction(Bytes::toArrayUnsafe); + + // But on top of the basic, extractArray() guarantees modifying the returned array is safe from + // impacting the original value (not that getArrayUnsafe makes no guarantees here one way or + // another, so there is nothing to test). + byte[] orig = new byte[] {1, 2, 3, 4}; + Bytes value = w(orig); + byte[] extracted = value.toArray(); + assertArrayEquals(orig, extracted); + Arrays.fill(extracted, (byte) -1); + assertArrayEquals(extracted, new byte[] {-1, -1, -1, -1}); + assertArrayEquals(orig, new byte[] {1, 2, 3, 4}); + assertEquals(of(1, 2, 3, 4), value); + } + + private void testArrayExtraction(Function extractor) { + byte[] bytes = new byte[0]; + assertArrayEquals(extractor.apply(Bytes.EMPTY), bytes); + + byte[][] toTest = new byte[][] {new byte[] {1}, new byte[] {1, 2, 3, 4, 5, 6}, new byte[] {-1, -1, 0, -1}}; + for (byte[] array : toTest) { + assertArrayEquals(extractor.apply(w(array)), array); + } + + // Test slightly more complex interactions + assertArrayEquals(extractor.apply(w(new byte[] {1, 2, 3, 4, 5}).slice(2, 2)), new byte[] {3, 4}); + assertArrayEquals(extractor.apply(w(new byte[] {1, 2, 3, 4, 5}).slice(2, 0)), new byte[] {}); + } + + @Test + void testToString() { + assertEquals("0x", Bytes.EMPTY.toString()); + + assertEquals("0x01", of(1).toString()); + assertEquals("0x0aff03", of(0x0a, 0xff, 0x03).toString()); + } + + @Test + void testHasLeadingZeroByte() { + assertFalse(Bytes.fromHexString("0x").hasLeadingZeroByte()); + assertTrue(Bytes.fromHexString("0x0012").hasLeadingZeroByte()); + assertFalse(Bytes.fromHexString("0x120012").hasLeadingZeroByte()); + } + + @Test + void testNumberOfLeadingZeroBytes() { + assertEquals(0, Bytes.fromHexString("0x12").numberOfLeadingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x0012").numberOfLeadingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x000012").numberOfLeadingZeroBytes()); + assertEquals(0, Bytes.fromHexString("0x").numberOfLeadingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x00").numberOfLeadingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x0000").numberOfLeadingZeroBytes()); + assertEquals(3, Bytes.fromHexString("0x000000").numberOfLeadingZeroBytes()); + } + + @Test + void testNumberOfTrailingZeroBytes() { + assertEquals(0, Bytes.fromHexString("0x12").numberOfTrailingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x1200").numberOfTrailingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x120000").numberOfTrailingZeroBytes()); + assertEquals(0, Bytes.fromHexString("0x").numberOfTrailingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x00").numberOfTrailingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x0000").numberOfTrailingZeroBytes()); + assertEquals(3, Bytes.fromHexString("0x000000").numberOfTrailingZeroBytes()); + } + + @Test + void testHasLeadingZeroBit() { + assertFalse(Bytes.fromHexString("0x").hasLeadingZero()); + assertTrue(Bytes.fromHexString("0x01").hasLeadingZero()); + assertFalse(Bytes.fromHexString("0xFF0012").hasLeadingZero()); + } + + @Test + void testEquals() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes b2 = w(key); + assertEquals(b.hashCode(), b2.hashCode()); + } + + @Test + void testEqualsWithOffset() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key).slice(16, 4); + Bytes b2 = w(key).slice(16, 8).slice(0, 4); + assertEquals(b, b2); + } + + @Test + void testHashCode() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes b2 = w(key); + assertEquals(b.hashCode(), b2.hashCode()); + } + + @Test + void testHashCodeWithOffset() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key).slice(16, 16); + Bytes b2 = w(key).slice(16, 16); + assertEquals(b.hashCode(), b2.hashCode()); + } + + @Test + void testHashCodeWithByteBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuffer(ByteBuffer.wrap(key)); + assertEquals(b.hashCode(), other.hashCode()); + } + + @Test + void testEqualsWithByteBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuffer(ByteBuffer.wrap(key)); + assertEquals(b, other); + } + + @Test + void testHashCodeWithBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapBuffer(Buffer.buffer(key)); + assertEquals(b.hashCode(), other.hashCode()); + } + + @Test + void testEqualsWithBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapBuffer(Buffer.buffer(key)); + assertEquals(b, other); + } + + @Test + void testHashCodeWithByteBufWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuf(Unpooled.copiedBuffer(key)); + assertEquals(b.hashCode(), other.hashCode()); + } + + @Test + void testEqualsWithByteBufWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuf(Unpooled.copiedBuffer(key)); + assertEquals(b, other); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java new file mode 100644 index 00000000000..195b84d18b2 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InOrder; + +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.apache.tuweni.bytes.Bytes.fromHexString; +import static org.apache.tuweni.bytes.Bytes.wrap; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +class ConcatenatedBytesTest { + + @ParameterizedTest + @MethodSource("concatenatedWrapProvider") + void concatenatedWrap(Object arr1, Object arr2) { + byte[] first = (byte[]) arr1; + byte[] second = (byte[]) arr2; + byte[] res = wrap(wrap(first), wrap(second)).toArray(); + assertArrayEquals(Arrays.copyOfRange(res, 0, first.length), first); + assertArrayEquals(Arrays.copyOfRange(res, first.length, res.length), second); + } + + @SuppressWarnings("UnusedMethod") + private static Stream concatenatedWrapProvider() { + return Stream + .of( + Arguments.of(new byte[] {}, new byte[] {}), + Arguments.of(new byte[] {}, new byte[] {1, 2, 3}), + Arguments.of(new byte[] {1, 2, 3}, new byte[] {}), + Arguments.of(new byte[] {1, 2, 3}, new byte[] {4, 5})); + } + + @Test + void testConcatenatedWrapReflectsUpdates() { + byte[] first = new byte[] {1, 2, 3}; + byte[] second = new byte[] {4, 5}; + byte[] expected1 = new byte[] {1, 2, 3, 4, 5}; + Bytes res = wrap(wrap(first), wrap(second)); + assertArrayEquals(res.toArray(), expected1); + + first[1] = 42; + second[0] = 42; + byte[] expected2 = new byte[] {1, 42, 3, 42, 5}; + assertArrayEquals(res.toArray(), expected2); + } + + @Test + void shouldReadConcatenatedValue() { + Bytes bytes = wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")); + assertEquals(8, bytes.size()); + assertEquals("0x0123456789abcdef", bytes.toHexString()); + } + + @Test + void shouldSliceConcatenatedValue() { + Bytes bytes = wrap( + fromHexString("0x01234567"), + fromHexString("0x89ABCDEF"), + fromHexString("0x01234567"), + fromHexString("0x89ABCDEF")); + assertEquals("0x", bytes.slice(4, 0).toHexString()); + assertEquals("0x0123456789abcdef0123456789abcdef", bytes.slice(0, 16).toHexString()); + assertEquals("0x01234567", bytes.slice(0, 4).toHexString()); + assertEquals("0x0123", bytes.slice(0, 2).toHexString()); + assertEquals("0x6789", bytes.slice(3, 2).toHexString()); + assertEquals("0x89abcdef", bytes.slice(4, 4).toHexString()); + assertEquals("0xabcd", bytes.slice(5, 2).toHexString()); + assertEquals("0xef012345", bytes.slice(7, 4).toHexString()); + assertEquals("0x01234567", bytes.slice(8, 4).toHexString()); + assertEquals("0x456789abcdef", bytes.slice(10, 6).toHexString()); + assertEquals("0x89abcdef", bytes.slice(12, 4).toHexString()); + } + + @Test + void shouldReadDeepConcatenatedValue() { + Bytes bytes = wrap( + wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")), + wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")), + fromHexString("0x01234567"), + fromHexString("0x89ABCDEF")); + assertEquals(24, bytes.size()); + assertEquals("0x0123456789abcdef0123456789abcdef0123456789abcdef", bytes.toHexString()); + } + + @Test + void testCopy() { + Bytes bytes = wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")); + assertEquals(bytes, bytes.copy()); + assertEquals(bytes, bytes.mutableCopy()); + } + + @Test + void testCopyTo() { + Bytes bytes = wrap(fromHexString("0x0123"), fromHexString("0x4567")); + MutableBytes dest = MutableBytes.create(32); + bytes.copyTo(dest, 10); + assertEquals(Bytes.fromHexString("0x0000000000000000000001234567000000000000000000000000000000000000"), dest); + } + + @Test + void testHashcodeUpdates() { + MutableBytes dest = MutableBytes.create(32); + Bytes bytes = wrap(dest, fromHexString("0x4567")); + int hashCode = bytes.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, bytes.hashCode()); + } + + @Test + void shouldUpdateMessageDigest() { + Bytes value1 = fromHexString("0x01234567"); + Bytes value2 = fromHexString("0x89ABCDEF"); + Bytes value3 = fromHexString("0x01234567"); + Bytes bytes = wrap(value1, value2, value3); + MessageDigest digest = mock(MessageDigest.class); + bytes.update(digest); + + final InOrder inOrder = inOrder(digest); + inOrder.verify(digest).update(value1.toArrayUnsafe(), 0, 4); + inOrder.verify(digest).update(value2.toArrayUnsafe(), 0, 4); + inOrder.verify(digest).update(value3.toArrayUnsafe(), 0, 4); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytes32Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytes32Test.java new file mode 100644 index 00000000000..073718d8974 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytes32Test.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class DelegateBytes32Test { + + @Test + void failsWhenWrappingArraySmallerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(Bytes.wrap(new byte[31]))); + assertEquals("Expected 32 bytes but got 31", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(Bytes.wrap(new byte[33]))); + assertEquals("Expected 32 bytes but got 33", exception.getMessage()); + } + + @Test + void failsWhenLeftPaddingValueLargerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.leftPad(MutableBytes.create(33))); + assertEquals("Expected at most 32 bytes but got 33", exception.getMessage()); + } + + @Test + void failsWhenRightPaddingValueLargerThan32() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.rightPad(MutableBytes.create(33))); + assertEquals("Expected at most 32 bytes but got 33", exception.getMessage()); + } + + @Test + void testSize() { + assertEquals(32, new DelegatingBytes32(Bytes32.random()).size()); + } + + @Test + void testCopy() { + Bytes bytes = new DelegatingBytes32(Bytes32.random()).copy(); + assertEquals(bytes, bytes.copy()); + assertEquals(bytes, bytes.mutableCopy()); + } + + @Test + void testSlice() { + Bytes bytes = new DelegatingBytes32(Bytes32.random()).copy(); + assertEquals(Bytes.wrap(new byte[] {bytes.get(2), bytes.get(3), bytes.get(4)}), bytes.slice(2, 3)); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytes48Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytes48Test.java new file mode 100644 index 00000000000..7332d826335 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytes48Test.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class DelegateBytes48Test { + + @Test + void failsWhenWrappingArraySmallerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.wrap(Bytes.wrap(new byte[47]))); + assertEquals("Expected 48 bytes but got 47", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.wrap(Bytes.wrap(new byte[49]))); + assertEquals("Expected 48 bytes but got 49", exception.getMessage()); + } + + @Test + void failsWhenLeftPaddingValueLargerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.leftPad(MutableBytes.create(49))); + assertEquals("Expected at most 48 bytes but got 49", exception.getMessage()); + } + + @Test + void failsWhenRightPaddingValueLargerThan48() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes48.rightPad(MutableBytes.create(49))); + assertEquals("Expected at most 48 bytes but got 49", exception.getMessage()); + } + + @Test + void testSize() { + assertEquals(48, new DelegatingBytes48(Bytes48.random()).size()); + } + + @Test + void testCopy() { + Bytes bytes = new DelegatingBytes48(Bytes48.random()).copy(); + assertEquals(bytes, bytes.copy()); + assertEquals(bytes, bytes.mutableCopy()); + } + + @Test + void testSlice() { + Bytes bytes = new DelegatingBytes48(Bytes48.random()).copy(); + assertEquals(Bytes.wrap(new byte[] {bytes.get(2), bytes.get(3), bytes.get(4)}), bytes.slice(2, 3)); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytesTest.java new file mode 100644 index 00000000000..66b1be4abb3 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateBytesTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +class DelegateBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return new DelegatingBytes(Bytes.fromHexString(hex)); + } + + @Override + MutableBytes m(int size) { + // no-op + return MutableBytes.create(size); + } + + @Override + Bytes w(byte[] bytes) { + return new DelegatingBytes(Bytes.wrap(bytes)); + } + + @Override + Bytes of(int... bytes) { + return new DelegatingBytes(Bytes.of(bytes)); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateMutableBytes32Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateMutableBytes32Test.java new file mode 100644 index 00000000000..44efd2a0ca9 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateMutableBytes32Test.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class DelegateMutableBytes32Test { + + @Test + void failsWhenWrappingArraySmallerThan32() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> MutableBytes32.wrap(MutableBytes.wrap(new byte[31]))); + assertEquals("Expected 32 bytes but got 31", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan32() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> MutableBytes32.wrap(MutableBytes.wrap(new byte[33]))); + assertEquals("Expected 32 bytes but got 33", exception.getMessage()); + } + + @Test + void testSize() { + assertEquals(32, DelegatingMutableBytes32.delegateTo(MutableBytes32.create()).size()); + } + + @Test + void testCopy() { + Bytes bytes = DelegatingMutableBytes32.delegateTo(MutableBytes32.create()).copy(); + assertEquals(bytes, bytes.copy()); + assertEquals(bytes, bytes.mutableCopy()); + } + + @Test + void testSlice() { + Bytes bytes = DelegatingMutableBytes32.delegateTo(MutableBytes32.create()).copy(); + assertEquals(Bytes.wrap(new byte[] {bytes.get(2), bytes.get(3), bytes.get(4)}), bytes.slice(2, 3)); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateMutableBytes48Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateMutableBytes48Test.java new file mode 100644 index 00000000000..95e4fd2c5cf --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/DelegateMutableBytes48Test.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class DelegateMutableBytes48Test { + + @Test + void failsWhenWrappingArraySmallerThan48() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> MutableBytes48.wrap(MutableBytes.wrap(new byte[47]))); + assertEquals("Expected 48 bytes but got 47", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan48() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> MutableBytes48.wrap(MutableBytes.wrap(new byte[49]))); + assertEquals("Expected 48 bytes but got 49", exception.getMessage()); + } + + @Test + void testSize() { + assertEquals(48, DelegatingMutableBytes48.delegateTo(MutableBytes48.create()).size()); + } + + @Test + void testCopy() { + Bytes bytes = DelegatingMutableBytes48.delegateTo(MutableBytes48.create()).copy(); + assertEquals(bytes, bytes.copy()); + assertEquals(bytes, bytes.mutableCopy()); + } + + @Test + void testSlice() { + Bytes bytes = DelegatingMutableBytes48.delegateTo(MutableBytes48.create()).copy(); + assertEquals(Bytes.wrap(new byte[] {bytes.get(2), bytes.get(3), bytes.get(4)}), bytes.slice(2, 3)); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/MutableBytesTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/MutableBytesTest.java new file mode 100644 index 00000000000..494c657d73e --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/bytes/MutableBytesTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.bytes; + +import io.netty.buffer.Unpooled; +import io.vertx.core.buffer.Buffer; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + +import static org.junit.jupiter.api.Assertions.*; + +class MutableBytesTest { + + @Test + void testMutableBytesWrap() { + MutableBytes b = MutableBytes.wrap(Bytes.fromHexString("deadbeef").toArrayUnsafe(), 1, 3); + assertEquals(Bytes.fromHexString("adbeef"), b); + } + + @Test + void testClear() { + MutableBytes b = MutableBytes.wrap(Bytes.fromHexString("deadbeef").toArrayUnsafe()); + b.clear(); + assertEquals(Bytes.fromHexString("00000000"), b); + } + + @Test + void testFill() { + MutableBytes b = MutableBytes.create(2); + b.fill((byte) 34); + assertEquals(Bytes.fromHexString("0x2222"), b); + } + + @Test + void testDecrementAndIncrement() { + MutableBytes b = MutableBytes.create(2); + b.increment(); + assertEquals(Bytes.fromHexString("0x0001"), b); + b.decrement(); + assertEquals(Bytes.fromHexString("0x0000"), b); + + b.fill((byte) 0xFF); + b.decrement(); + assertEquals(Bytes.fromHexString("0xFFFE"), b); + + b = MutableBytes.wrap(Bytes.fromHexString("0x00FF").toArrayUnsafe()); + b.increment(); + assertEquals(Bytes.fromHexString("0x0100"), b); + } + + @Test + void setLong() { + MutableBytes b = MutableBytes.create(8); + b.setLong(0, 256); + assertEquals(Bytes.fromHexString("0x0000000000000100"), b); + } + + @Test + void setInt() { + MutableBytes b = MutableBytes.create(4); + b.setInt(0, 256); + assertEquals(Bytes.fromHexString("0x00000100"), b); + } + + @Test + void setIntOverflow() { + MutableBytes b = MutableBytes.create(2); + assertThrows(IndexOutOfBoundsException.class, () -> { + b.setInt(0, 18096); + }); + } + + @Test + void setLongOverflow() { + MutableBytes b = MutableBytes.create(6); + assertThrows(IndexOutOfBoundsException.class, () -> { + b.setLong(0, Long.MAX_VALUE); + }); + } + + @Test + void wrap32() { + MutableBytes b = MutableBytes.create(32); + assertTrue(b instanceof MutableBytes32); + b = MutableBytes.wrap(Bytes.random(36).toArrayUnsafe(), 4, 32); + assertTrue(b instanceof MutableBytes32); + } + + @Test + void wrapEmpty() { + MutableBytes b = MutableBytes.wrapBuffer(Buffer.buffer()); + assertSame(MutableBytes.EMPTY, b); + b = MutableBytes.wrapByteBuf(Unpooled.buffer(0)); + assertSame(MutableBytes.EMPTY, b); + b = MutableBytes.wrapBuffer(Buffer.buffer(), 3, 0); + assertSame(MutableBytes.EMPTY, b); + b = MutableBytes.wrapByteBuf(Unpooled.buffer(), 4, 0); + assertSame(MutableBytes.EMPTY, b); + } + + @Test + void testHashcodeUpdates() { + MutableBytes dest = MutableBytes.create(32); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @Test + void testHashcodeUpdatesBuffer() { + MutableBytes dest = MutableBytes.wrapBuffer(Buffer.buffer(new byte[4])); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @Test + void testHashcodeUpdatesByteBuffer() { + MutableBytes dest = MutableBytes.wrapByteBuffer(ByteBuffer.wrap(new byte[4])); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @Test + void testHashcodeUpdatesByteBuf() { + MutableBytes dest = MutableBytes.wrapByteBuf(Unpooled.buffer(4)); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt256ValueTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt256ValueTest.java new file mode 100644 index 00000000000..b196c266870 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt256ValueTest.java @@ -0,0 +1,1035 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// This test is in a `test` sub-package to ensure that it does not have access to package-private +// methods within the bigints package, as it should be testing the usage of the public API. +class BaseUInt256ValueTest { + + private static class Value extends BaseUInt256Value { + static final Value MAX_VALUE = new Value(UInt256.MAX_VALUE); + + Value(UInt256 v) { + super(v, Value::new); + } + + Value(long v) { + super(v, Value::new); + } + + Value(BigInteger s) { + super(s, Value::new); + } + } + + private static Value v(long v) { + return new Value(v); + } + + private static Value biv(String s) { + return new Value(new BigInteger(s)); + } + + private static Value hv(String s) { + return new Value(UInt256.fromHexString(s)); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428444256376332556")), + Arguments.of(Value.MAX_VALUE, v(1), v(0)), + Arguments.of(Value.MAX_VALUE, v(2), v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + v(1), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), v(1), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addUInt256Provider") + void addUInt256(Value v1, UInt256 v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addUInt256Provider() { + return Stream + .of( + Arguments.of(v(1), UInt256.ZERO, v(1)), + Arguments.of(v(5), UInt256.ZERO, v(5)), + Arguments.of(v(0), UInt256.ONE, v(1)), + Arguments.of(v(0), UInt256.valueOf(100), v(100)), + Arguments.of(v(2), UInt256.valueOf(2), v(4)), + Arguments.of(v(100), UInt256.valueOf(90), v(190)), + Arguments + .of(biv("13492324908428420834234908342"), UInt256.valueOf(10), biv("13492324908428420834234908352")), + Arguments + .of( + biv("13492324908428420834234908342"), + UInt256.valueOf(23422141424214L), + biv("13492324908428444256376332556")), + Arguments.of(Value.MAX_VALUE, UInt256.valueOf(1), v(0)), + Arguments.of(Value.MAX_VALUE, UInt256.valueOf(2), v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + UInt256.valueOf(1), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + UInt256.valueOf(1), + Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428444256376332556")), + Arguments.of(Value.MAX_VALUE, 1L, v(0)), + Arguments.of(Value.MAX_VALUE, 2L, v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 1L, Value.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(Value v1, Value v2, UInt256 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt256.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), v(1), UInt256.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), v(1), UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt256.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt256.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt256UInt256Provider") + void addModUInt256UInt256(Value v1, UInt256 v2, UInt256 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt256UInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.ONE, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), UInt256.ONE, UInt256.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), UInt256.ONE, UInt256.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), UInt256.ONE, UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt256.ONE, UInt256.valueOf(2), v(1)), + Arguments.of(v(3), UInt256.valueOf(2), UInt256.valueOf(6), v(5)), + Arguments.of(v(3), UInt256.valueOf(4), UInt256.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt256OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt256Provider") + void addModLongUInt256(Value v1, long v2, UInt256 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), 1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt256.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), 1L, UInt256.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), 1L, UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt256.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt256UInt256OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt256.ONE, UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(Value v1, long v2, long m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream + .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428397412093484128")), + Arguments.of(v(0), v(1), Value.MAX_VALUE), + Arguments.of(v(1), v(2), Value.MAX_VALUE), + Arguments + .of(Value.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractUInt256Provider") + void subtractUInt256(Value v1, UInt256 v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractUInt256Provider() { + return Stream + .of( + Arguments.of(v(1), UInt256.ZERO, v(1)), + Arguments.of(v(5), UInt256.ZERO, v(5)), + Arguments.of(v(2), UInt256.ONE, v(1)), + Arguments.of(v(100), UInt256.valueOf(100), v(0)), + Arguments + .of(biv("13492324908428420834234908342"), UInt256.valueOf(10), biv("13492324908428420834234908332")), + Arguments + .of( + biv("13492324908428420834234908342"), + UInt256.valueOf(23422141424214L), + biv("13492324908428397412093484128")), + Arguments.of(v(0), UInt256.ONE, Value.MAX_VALUE), + Arguments.of(v(1), UInt256.valueOf(2), Value.MAX_VALUE), + Arguments + .of( + Value.MAX_VALUE, + UInt256.ONE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428397412093484128")), + Arguments.of(v(0), 1L, Value.MAX_VALUE), + Arguments.of(v(1), 2L, Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), v(0)), + Arguments.of(v(1), v(0), v(0)), + Arguments.of(v(1), v(20), v(20)), + Arguments.of(v(20), v(1), v(20)), + Arguments.of(hv("0x0a0000000000"), v(1), hv("0x0a0000000000")), + Arguments.of(v(1), hv("0x0a0000000000"), hv("0x0a0000000000")), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("1768466010397529975584837906202624")), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyUInt256Provider") + void multiplyUInt256(Value v1, UInt256 v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.valueOf(1), v(0)), + Arguments.of(v(1), UInt256.valueOf(0), v(0)), + Arguments.of(v(1), UInt256.valueOf(20), v(20)), + Arguments.of(v(20), UInt256.valueOf(1), v(20)), + + Arguments.of(hv("0x0a0000000000"), UInt256.valueOf(1), hv("0x0a0000000000")), + Arguments.of(v(1), UInt256.fromHexString("0x0a0000000000"), hv("0x0a0000000000")), + Arguments.of(v(0), UInt256.valueOf(2), v(0)), + Arguments.of(v(1), UInt256.valueOf(2), v(2)), + Arguments.of(v(2), UInt256.valueOf(2), v(4)), + Arguments.of(v(3), UInt256.valueOf(2), v(6)), + Arguments.of(v(4), UInt256.valueOf(2), v(8)), + Arguments.of(v(10), UInt256.valueOf(18), v(180)), + Arguments + .of(biv("13492324908428420834234908341"), UInt256.valueOf(2), biv("26984649816856841668469816682")), + Arguments + .of(biv("13492324908428420834234908342"), UInt256.valueOf(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), UInt256.valueOf(8), v(16)), + Arguments.of(v(7), UInt256.valueOf(8), v(56)), + Arguments.of(v(8), UInt256.valueOf(8), v(64)), + Arguments.of(v(17), UInt256.valueOf(8), v(136)), + Arguments + .of(biv("13492324908428420834234908342"), UInt256.valueOf(8), biv("107938599267427366673879266736")), + Arguments + .of( + biv("13492324908428420834234908342"), + UInt256.valueOf(2048), + biv("27632281412461405868513092284416")), + Arguments + .of( + biv("13492324908428420834234908342"), + UInt256.valueOf(131072), + biv("1768466010397529975584837906202624")), + Arguments.of(v(22), UInt256.ZERO, v(0)), + Arguments + .of( + hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + UInt256.valueOf(2), + hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + UInt256.valueOf(2), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 1L, v(0)), + Arguments.of(v(1), 0L, v(0)), + Arguments.of(v(1), 20L, v(20)), + Arguments.of(v(20), 1L, v(20)), + Arguments.of(hv("0x0a0000000000"), 1L, hv("0x0a0000000000")), + Arguments.of(v(1), 10995116277760L, hv("0x0a0000000000")), + Arguments.of(v(7), 1152921504606846976L, hv("0x07000000000000000")), + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("1768466010397529975584837906202624")), + Arguments.of(v(22), 0L, v(0)), + Arguments + .of( + hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(Value v1, Value v2, UInt256 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt256.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt256.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt256.valueOf(6), v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(2), + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModUInt256UInt256Provider") + void multiplyModUInt256UInt256(Value v1, UInt256 v2, UInt256 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModUInt256UInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.valueOf(5), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), UInt256.valueOf(3), UInt256.valueOf(7), v(6)), + Arguments.of(v(2), UInt256.valueOf(3), UInt256.valueOf(6), v(0)), + Arguments.of(v(2), UInt256.ZERO, UInt256.valueOf(6), v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + UInt256.valueOf(2), + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModUInt256UInt256OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(UInt256.valueOf(5), UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt256Provider") + void multiplyModLongUInt256(Value v1, long v2, UInt256 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), 5L, UInt256.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt256.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt256.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt256.valueOf(6), v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt256OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(1L, UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt256OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(-1, UInt256.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(Value v1, long v2, long m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + Long.MAX_VALUE, + hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideUInt256Provider") + void divideUInt256(Value v1, UInt256 v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.valueOf(2), v(0)), + Arguments.of(v(1), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), UInt256.valueOf(2), v(1)), + Arguments.of(v(3), UInt256.valueOf(2), v(1)), + Arguments.of(v(4), UInt256.valueOf(2), v(2)), + Arguments.of(biv("13492324908428420834234908341"), UInt256.valueOf(2), biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), UInt256.valueOf(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), UInt256.valueOf(8), v(0)), + Arguments.of(v(7), UInt256.valueOf(8), v(0)), + Arguments.of(v(8), UInt256.valueOf(8), v(1)), + Arguments.of(v(9), UInt256.valueOf(8), v(1)), + Arguments.of(v(17), UInt256.valueOf(8), v(2)), + Arguments.of(v(1024), UInt256.valueOf(8), v(128)), + Arguments.of(v(1026), UInt256.valueOf(8), v(128)), + Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(8), biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2048), biv("6588049271693564860466263")), + Arguments + .of(biv("13492324908428420834234908342"), UInt256.valueOf(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideUInt256ByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(UInt256.ZERO)); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt256Provider") + void powUInt256(Value v1, UInt256 v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), UInt256.valueOf(2), v(4)), + Arguments.of(v(2), UInt256.valueOf(8), v(256)), + Arguments.of(v(3), UInt256.valueOf(3), v(27)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + UInt256.valueOf(3), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + 3L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), + Arguments.of(v(3), -3L, hv("0x2F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); + } + + @ParameterizedTest + @MethodSource("modUInt256Provider") + void modUInt256(Value v1, UInt256 v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.valueOf(2), v(0)), + Arguments.of(v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(2), UInt256.valueOf(2), v(0)), + Arguments.of(v(3), UInt256.valueOf(2), v(1)), + Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2), v(0)), + Arguments.of(biv("13492324908428420834234908343"), UInt256.valueOf(2), v(1)), + Arguments.of(v(0), UInt256.valueOf(8), v(0)), + Arguments.of(v(1), UInt256.valueOf(8), v(1)), + Arguments.of(v(2), UInt256.valueOf(8), v(2)), + Arguments.of(v(3), UInt256.valueOf(8), v(3)), + Arguments.of(v(7), UInt256.valueOf(8), v(7)), + Arguments.of(v(8), UInt256.valueOf(8), v(0)), + Arguments.of(v(9), UInt256.valueOf(8), v(1)), + Arguments.of(v(1024), UInt256.valueOf(8), v(0)), + Arguments.of(v(1026), UInt256.valueOf(8), v(2)), + Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(8), v(6)), + Arguments.of(biv("13492324908428420834234908343"), UInt256.valueOf(8), v(7)), + Arguments.of(biv("13492324908428420834234908344"), UInt256.valueOf(8), v(0))); + } + + @Test + void shouldThrowForModUInt256ByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(UInt256.ZERO)); + assertEquals("mod by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), + Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2)), + Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), + Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), + Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldReturnZeroForMod0LongByZero() { + assertEquals(UInt256.ZERO, v(5).mod0(0).toUInt256()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(Value v1, Value v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1), + Arguments + .of( + hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 1), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x01000000"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000001000000")), + Arguments + .of( + hv("0x0100000000"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000100000000")), + Arguments + .of( + hv("0xf100000000ab"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000f100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(Value value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 256), + Arguments.of(hv("0x01"), 255), + Arguments.of(hv("0x02"), 254), + Arguments.of(hv("0x03"), 254), + Arguments.of(hv("0x0F"), 252), + Arguments.of(hv("0x8F"), 248), + Arguments.of(hv("0x100000000"), 223)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(Value value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(Value.MAX_VALUE, v(1)), Arguments.of(Value.MAX_VALUE, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(Value value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of(Arguments.of(Value.MAX_VALUE, 3), Arguments.of(Value.MAX_VALUE, Long.MAX_VALUE), Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(Value value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Long.MAX_VALUE), Arguments.of(Value.MAX_VALUE, -1)); + } + + @SuppressWarnings("UnusedMethod") + private void assertValueEquals(Value expected, Value actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt32ValueTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt32ValueTest.java new file mode 100644 index 00000000000..0eca3f9561f --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt32ValueTest.java @@ -0,0 +1,795 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// This test is in a `test` sub-package to ensure that it does not have access to package-private +// methods within the bigints package, as it should be testing the usage of the public API. +class BaseUInt32ValueTest { + + private static class Value extends BaseUInt32Value { + static final Value MAX_VALUE = new Value(UInt32.MAX_VALUE); + + Value(UInt32 v) { + super(v, Value::new); + } + + Value(int v) { + super(v, Value::new); + } + } + + private static Value v(int v) { + return new Value(v); + } + + private static Value hv(String s) { + return new Value(UInt32.fromHexString(s)); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(Value.MAX_VALUE, v(1), v(0)), + Arguments.of(Value.MAX_VALUE, v(2), v(1)), + Arguments.of(hv("0xFFFFFFF0"), v(1), hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), v(1), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addUInt32Provider") + void addUInt32(Value v1, UInt32 v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addUInt32Provider() { + return Stream + .of( + Arguments.of(v(1), UInt32.ZERO, v(1)), + Arguments.of(v(5), UInt32.ZERO, v(5)), + Arguments.of(v(0), UInt32.ONE, v(1)), + Arguments.of(v(0), UInt32.valueOf(100), v(100)), + Arguments.of(v(2), UInt32.valueOf(2), v(4)), + Arguments.of(v(100), UInt32.valueOf(90), v(190)), + Arguments.of(Value.MAX_VALUE, UInt32.valueOf(1), v(0)), + Arguments.of(Value.MAX_VALUE, UInt32.valueOf(2), v(1)), + Arguments.of(hv("0xFFFFFFF0"), UInt32.valueOf(1), hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), UInt32.valueOf(1), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(Value v1, int v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0, v(1)), + Arguments.of(v(5), 0, v(5)), + Arguments.of(v(0), 1, v(1)), + Arguments.of(v(0), 100, v(100)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(100), 90, v(190)), + Arguments.of(Value.MAX_VALUE, 1, v(0)), + Arguments.of(Value.MAX_VALUE, 2, v(1)), + Arguments.of(hv("0xFFFFFFF0"), 1, hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), 1, Value.MAX_VALUE), + Arguments.of(v(10), -5, v(5)), + Arguments.of(v(0), -1, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(Value v1, Value v2, UInt32 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt32.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), v(1), UInt32.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), v(1), UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt32.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt32.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt32UInt32Provider") + void addModUInt32UInt32(Value v1, UInt32 v2, UInt32 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt32UInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.ONE, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), UInt32.ONE, UInt32.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), UInt32.ONE, UInt32.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), UInt32.ONE, UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt32.ONE, UInt32.valueOf(2), v(1)), + Arguments.of(v(3), UInt32.valueOf(2), UInt32.valueOf(6), v(5)), + Arguments.of(v(3), UInt32.valueOf(4), UInt32.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt32OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt32Provider") + void addModLongUInt32(Value v1, int v2, UInt32 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), 1, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), 1, UInt32.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), 1, UInt32.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), 1, UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), 1, UInt32.valueOf(2), v(1)), + Arguments.of(v(2), -1, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), -7, UInt32.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt32UInt32OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt32.ONE, UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(Value v1, int v2, int m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream.of(Arguments.of(v(0), 1, 2, v(1)), Arguments.of(v(1), 1, 2, v(0)), Arguments.of(v(2), 1, 2, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(v(0), v(1), Value.MAX_VALUE), + Arguments.of(v(1), v(2), Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, v(1), hv("0xFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractUInt32Provider") + void subtractUInt32(Value v1, UInt32 v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractUInt32Provider() { + return Stream + .of( + Arguments.of(v(1), UInt32.ZERO, v(1)), + Arguments.of(v(5), UInt32.ZERO, v(5)), + Arguments.of(v(2), UInt32.ONE, v(1)), + Arguments.of(v(100), UInt32.valueOf(100), v(0)), + Arguments.of(v(0), UInt32.ONE, Value.MAX_VALUE), + Arguments.of(v(1), UInt32.valueOf(2), Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, UInt32.ONE, hv("0xFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(Value v1, int v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0, v(1)), + Arguments.of(v(5), 0, v(5)), + Arguments.of(v(2), 1, v(1)), + Arguments.of(v(100), 100, v(0)), + Arguments.of(v(0), 1, Value.MAX_VALUE), + Arguments.of(v(1), 2, Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, 1, hv("0xFFFFFFFE")), + Arguments.of(v(0), -1, v(1)), + Arguments.of(v(0), -100, v(100)), + Arguments.of(v(2), -2, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyUInt32Provider") + void multiplyUInt32(Value v1, UInt32 v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.valueOf(2), v(0)), + Arguments.of(v(1), UInt32.valueOf(2), v(2)), + Arguments.of(v(2), UInt32.valueOf(2), v(4)), + Arguments.of(v(3), UInt32.valueOf(2), v(6)), + Arguments.of(v(4), UInt32.valueOf(2), v(8)), + Arguments.of(v(10), UInt32.valueOf(18), v(180)), + Arguments.of(v(2), UInt32.valueOf(8), v(16)), + Arguments.of(v(7), UInt32.valueOf(8), v(56)), + Arguments.of(v(8), UInt32.valueOf(8), v(64)), + Arguments.of(v(17), UInt32.valueOf(8), v(136)), + Arguments.of(v(22), UInt32.ZERO, v(0)), + Arguments.of(hv("0xFFFFFFFF"), UInt32.valueOf(2), hv("0xFFFFFFFE")), + Arguments.of(hv("0xFFFFFFFF"), UInt32.valueOf(2), hv("0xFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(Value v1, int v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(2)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(3), 2, v(6)), + Arguments.of(v(4), 2, v(8)), + Arguments.of(v(10), 18, v(180)), + Arguments.of(v(2), 8, v(16)), + Arguments.of(v(7), 8, v(56)), + Arguments.of(v(8), 8, v(64)), + Arguments.of(v(17), 8, v(136)), + Arguments.of(v(22), 0, v(0)), + Arguments.of(hv("0xFFFFFFFF"), 2, hv("0xFFFFFFFE")), + Arguments.of(hv("0xFFFFFFFF"), 2, hv("0xFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(Value v1, Value v2, UInt32 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt32.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt32.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt32.valueOf(6), v(0)), + Arguments.of(hv("0xFFFFFFFE"), v(2), UInt32.MAX_VALUE, hv("0xFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModUInt32UInt32Provider") + void multiplyModUInt32UInt32(Value v1, UInt32 v2, UInt32 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModUInt32UInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.valueOf(5), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), UInt32.valueOf(3), UInt32.valueOf(7), v(6)), + Arguments.of(v(2), UInt32.valueOf(3), UInt32.valueOf(6), v(0)), + Arguments.of(v(2), UInt32.ZERO, UInt32.valueOf(6), v(0)), + Arguments.of(hv("0xFFFFFFFE"), UInt32.valueOf(2), UInt32.MAX_VALUE, hv("0xFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModUInt32UInt32OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(UInt32.valueOf(5), UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt32Provider") + void multiplyModLongUInt32(Value v1, int v2, UInt32 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), 5, UInt32.valueOf(2), v(0)), + Arguments.of(v(2), 3, UInt32.valueOf(7), v(6)), + Arguments.of(v(2), 3, UInt32.valueOf(6), v(0)), + Arguments.of(v(2), 0, UInt32.valueOf(6), v(0)), + Arguments.of(hv("0xFFFFFFFE"), 2, UInt32.MAX_VALUE, hv("0xFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt32OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(1, UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt32OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(-1, UInt32.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(Value v1, int v2, int m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5, 2, v(0)), + Arguments.of(v(2), 3, 7, v(6)), + Arguments.of(v(2), 3, 6, v(0)), + Arguments.of(v(2), 0, 6, v(0)), + Arguments.of(hv("0x0FFFFFFE"), 2, Integer.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideUInt32Provider") + void divideUInt32(Value v1, UInt32 v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.valueOf(2), v(0)), + Arguments.of(v(1), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), UInt32.valueOf(2), v(1)), + Arguments.of(v(3), UInt32.valueOf(2), v(1)), + Arguments.of(v(4), UInt32.valueOf(2), v(2)), + Arguments.of(v(2), UInt32.valueOf(8), v(0)), + Arguments.of(v(7), UInt32.valueOf(8), v(0)), + Arguments.of(v(8), UInt32.valueOf(8), v(1)), + Arguments.of(v(9), UInt32.valueOf(8), v(1)), + Arguments.of(v(17), UInt32.valueOf(8), v(2)), + Arguments.of(v(1024), UInt32.valueOf(8), v(128)), + Arguments.of(v(1026), UInt32.valueOf(8), v(128))); + } + + @Test + void shouldThrowForDivideUInt32ByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(UInt32.ZERO)); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(Value v1, int v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(0)), + Arguments.of(v(2), 2, v(1)), + Arguments.of(v(3), 2, v(1)), + Arguments.of(v(4), 2, v(2)), + Arguments.of(v(2), 8, v(0)), + Arguments.of(v(7), 8, v(0)), + Arguments.of(v(8), 8, v(1)), + Arguments.of(v(9), 8, v(1)), + Arguments.of(v(17), 8, v(2)), + Arguments.of(v(1024), 8, v(128)), + Arguments.of(v(1026), 8, v(128))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt32Provider") + void powUInt32(Value v1, UInt32 v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), UInt32.valueOf(2), v(4)), + Arguments.of(v(2), UInt32.valueOf(8), v(256)), + Arguments.of(v(3), UInt32.valueOf(3), v(27))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(Value v1, int v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(2), 8, v(256)), + Arguments.of(v(3), 3, v(27))); + } + + @ParameterizedTest + @MethodSource("modUInt32Provider") + void modUInt32(Value v1, UInt32 v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.valueOf(2), v(0)), + Arguments.of(v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(2), UInt32.valueOf(2), v(0)), + Arguments.of(v(3), UInt32.valueOf(2), v(1)), + Arguments.of(v(0), UInt32.valueOf(8), v(0)), + Arguments.of(v(1), UInt32.valueOf(8), v(1)), + Arguments.of(v(2), UInt32.valueOf(8), v(2)), + Arguments.of(v(3), UInt32.valueOf(8), v(3)), + Arguments.of(v(7), UInt32.valueOf(8), v(7)), + Arguments.of(v(8), UInt32.valueOf(8), v(0)), + Arguments.of(v(9), UInt32.valueOf(8), v(1)), + Arguments.of(v(1024), UInt32.valueOf(8), v(0)), + Arguments.of(v(1026), UInt32.valueOf(8), v(2))); + } + + @Test + void shouldThrowForModUInt32ByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(UInt32.ZERO)); + assertEquals("mod by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(Value v1, int v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(1)), + Arguments.of(v(2), 2, v(0)), + Arguments.of(v(3), 2, v(1)), + Arguments.of(v(0), 8, v(0)), + Arguments.of(v(1), 8, v(1)), + Arguments.of(v(2), 8, v(2)), + Arguments.of(v(3), 8, v(3)), + Arguments.of(v(7), 8, v(7)), + Arguments.of(v(8), 8, v(0)), + Arguments.of(v(9), 8, v(1)), + Arguments.of(v(1024), 8, v(0)), + Arguments.of(v(1026), 8, v(2))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(Value v1, Value v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of(hv("0x00000000"), hv("0x00000000"), 0), + Arguments.of(hv("0xFFFFFFFF"), hv("0xFFFFFFFF"), 0), + Arguments.of(hv("0x0000FFFF"), hv("0x0000FFFF"), 0), + Arguments.of(hv("0xFFFFFFFF"), hv("0x00000000"), 1), + Arguments.of(hv("0x00000000"), hv("0xFFFFFFFF"), -1), + Arguments.of(hv("0x0001FFFF"), hv("0x0000FFFF"), 1), + Arguments.of(hv("0x0000FFFE"), hv("0x0000FFFF"), -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.fromHexString("0x00000000")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of(Arguments.of(hv("0x00"), Bytes.EMPTY), Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(Value value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 32), + Arguments.of(hv("0x01"), 31), + Arguments.of(hv("0x02"), 30), + Arguments.of(hv("0x03"), 30), + Arguments.of(hv("0x0F"), 28), + Arguments.of(hv("0x8F"), 24)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(Value value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(Value.MAX_VALUE, v(1)), Arguments.of(Value.MAX_VALUE, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(Value value, int operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of(Arguments.of(Value.MAX_VALUE, 3), Arguments.of(Value.MAX_VALUE, Integer.MAX_VALUE), Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(Value value, int operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Integer.MAX_VALUE), Arguments.of(Value.MAX_VALUE, -1)); + } + + private void assertValueEquals(Value expected, Value actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt384ValueTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt384ValueTest.java new file mode 100644 index 00000000000..f8c9e1fd7fb --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt384ValueTest.java @@ -0,0 +1,921 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// This test is in a `test` sub-package to ensure that it does not have access to package-private +// methods within the bigints package, as it should be testing the usage of the public API. +class BaseUInt384ValueTest { + + private static class Value extends BaseUInt384Value { + static final Value MAX_VALUE = new Value(UInt384.MAX_VALUE); + + Value(UInt384 v) { + super(v, Value::new); + } + + Value(long v) { + super(v, Value::new); + } + + Value(BigInteger s) { + super(s, Value::new); + } + } + + private static Value v(long v) { + return new Value(v); + } + + private static Value biv(String s) { + return new Value(new BigInteger(s)); + } + + private static Value hv(String s) { + return new Value(UInt384.fromHexString(s)); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(biv("9223372036854775807"), v(1), biv("9223372036854775808")), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428444256376332556")), + Arguments.of(new Value(UInt384.MAX_VALUE), v(1), v(0)), + Arguments.of(new Value(UInt384.MAX_VALUE), v(2), v(1)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + v(1), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(1), + new Value(UInt384.MAX_VALUE))); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428444256376332556")), + Arguments.of(new Value(UInt384.MAX_VALUE), 1L, v(0)), + Arguments.of(new Value(UInt384.MAX_VALUE), 2L, v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 1L, + new Value(UInt384.MAX_VALUE)), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, new Value(UInt384.MAX_VALUE))); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(Value v1, Value v2, UInt384 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt384.valueOf(2), v(0)), + Arguments + .of( + new Value(UInt384.MAX_VALUE.subtract(2)), + v(1), + UInt384.MAX_VALUE, + new Value(UInt384.MAX_VALUE.subtract(1))), + Arguments.of(new Value(UInt384.MAX_VALUE.subtract(1)), v(1), UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt384.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt384.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt384UInt384Provider") + void addModUInt384UInt384(Value v1, Value v2, UInt384 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt384UInt384Provider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt384.valueOf(2), v(0)), + Arguments + .of( + new Value(UInt384.MAX_VALUE.subtract(2)), + v(1), + UInt384.MAX_VALUE, + new Value(UInt384.MAX_VALUE.subtract(1))), + Arguments.of(new Value(UInt384.MAX_VALUE.subtract(1)), new Value(UInt384.ONE), UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), new Value(UInt384.ONE), UInt384.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt384.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt384.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt384OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt384Provider") + void addModLongUInt384(Value v1, long v2, UInt384 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt384Provider() { + return Stream + .of( + Arguments.of(v(0), 1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt384.valueOf(2), v(0)), + Arguments + .of( + new Value(UInt384.MAX_VALUE.subtract(2)), + 1L, + UInt384.MAX_VALUE, + new Value(UInt384.MAX_VALUE.subtract(1))), + Arguments.of(new Value(UInt384.MAX_VALUE.subtract(1)), 1L, UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt384.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt384UInt384OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt384.ONE, UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(Value v1, long v2, long m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream + .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428397412093484128")), + Arguments.of(v(0), v(1), new Value(UInt384.MAX_VALUE)), + Arguments + .of( + v(1), + v(2), + new Value(UInt384.MAX_VALUE), + Arguments + .of( + UInt384.MAX_VALUE, + v(1), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428397412093484128")), + Arguments.of(v(0), 1L, new Value(UInt384.MAX_VALUE)), + Arguments.of(v(1), 2L, new Value(UInt384.MAX_VALUE)), + Arguments + .of( + new Value(UInt384.MAX_VALUE), + 1L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("1768466010397529975584837906202624")), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("1768466010397529975584837906202624")), + Arguments.of(v(22), 0L, v(0)), + Arguments + .of( + hv( + "0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv( + "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(Value v1, Value v2, UInt384 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt384.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt384.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt384.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt384.valueOf(6), v(0)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(2), + UInt384.MAX_VALUE, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt384.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt384Provider") + void multiplyModLongUInt384(Value v1, long v2, UInt384 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt384Provider() { + return Stream + .of( + Arguments.of(v(0), 5L, UInt384.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt384.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt384.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt384.valueOf(6), v(0)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + UInt384.MAX_VALUE, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt384OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt384.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt384OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt384.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(Value v1, long v2, long m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + Long.MAX_VALUE, + hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt384Provider") + void powUInt384(Value v1, UInt384 v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt384Provider() { + return Stream + .of( + Arguments.of(v(0), UInt384.valueOf(2), v(0)), + Arguments.of(v(2), UInt384.valueOf(2), v(4)), + Arguments.of(v(2), UInt384.valueOf(8), v(256)), + Arguments.of(v(3), UInt384.valueOf(3), v(27)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + UInt384.valueOf(3), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + 3L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), + Arguments + .of( + v(3), + -3L, + hv( + "0x4BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), + Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2)), + Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), + Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), + Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(Value value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(Value value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x0000000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @Test + void shouldThrowForLongValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x010000000000000000").toLong()); + assertEquals("Value does not fit a 8 byte long", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(Value v1, Value v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1), + Arguments + .of( + hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 1), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00"), + Bytes + .fromHexString( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x01000000"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000")), + Arguments + .of( + hv("0x0100000000"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000")), + Arguments + .of( + hv("0xf100000000ab"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000f100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(Value value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 384), + Arguments.of(hv("0x01"), 383), + Arguments.of(hv("0x02"), 382), + Arguments.of(hv("0x03"), 382), + Arguments.of(hv("0x0F"), 380), + Arguments.of(hv("0x8F"), 376), + Arguments.of(hv("0x100000000"), 351)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(Value value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(Value.MAX_VALUE, v(1)), Arguments.of(Value.MAX_VALUE, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(Value value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of(Arguments.of(Value.MAX_VALUE, 3), Arguments.of(Value.MAX_VALUE, Long.MAX_VALUE), Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(Value value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Long.MAX_VALUE), Arguments.of(Value.MAX_VALUE, -1)); + } + + private void assertValueEquals(Value expected, Value actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt64ValueTest.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt64ValueTest.java new file mode 100644 index 00000000000..3c96949d9ac --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/BaseUInt64ValueTest.java @@ -0,0 +1,805 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// This test is in a `test` sub-package to ensure that it does not have access to package-private +// methods within the bigints package, as it should be testing the usage of the public API. +class BaseUInt64ValueTest { + + private static class Value extends BaseUInt64Value { + static final Value MAX_VALUE = new Value(UInt64.MAX_VALUE); + + Value(UInt64 v) { + super(v, Value::new); + } + + Value(long v) { + super(v, Value::new); + } + } + + private static Value v(long v) { + return new Value(v); + } + + private static Value hv(String s) { + return new Value(UInt64.fromHexString(s)); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(Value.MAX_VALUE, v(1), v(0)), + Arguments.of(Value.MAX_VALUE, v(2), v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), v(1), hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), v(1), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addUInt64Provider") + void addUInt64(Value v1, UInt64 v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addUInt64Provider() { + return Stream + .of( + Arguments.of(v(1), UInt64.ZERO, v(1)), + Arguments.of(v(5), UInt64.ZERO, v(5)), + Arguments.of(v(0), UInt64.ONE, v(1)), + Arguments.of(v(0), UInt64.valueOf(100), v(100)), + Arguments.of(v(2), UInt64.valueOf(2), v(4)), + Arguments.of(v(100), UInt64.valueOf(90), v(190)), + Arguments.of(Value.MAX_VALUE, UInt64.valueOf(1), v(0)), + Arguments.of(Value.MAX_VALUE, UInt64.valueOf(2), v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), UInt64.valueOf(1), hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), UInt64.valueOf(1), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(Value.MAX_VALUE, 1L, v(0)), + Arguments.of(Value.MAX_VALUE, 2L, v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), 1L, hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), 1L, Value.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(Value v1, Value v2, UInt64 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt64.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), v(1), UInt64.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), v(1), UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt64.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt64.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt64UInt64Provider") + void addModUInt64UInt64(Value v1, UInt64 v2, UInt64 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt64UInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.ONE, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), UInt64.ONE, UInt64.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), UInt64.ONE, UInt64.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), UInt64.ONE, UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt64.ONE, UInt64.valueOf(2), v(1)), + Arguments.of(v(3), UInt64.valueOf(2), UInt64.valueOf(6), v(5)), + Arguments.of(v(3), UInt64.valueOf(4), UInt64.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt64OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt64Provider") + void addModLongUInt64(Value v1, long v2, UInt64 m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), 1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt64.valueOf(2), v(0)), + Arguments.of(Value.MAX_VALUE.subtract(2), 1L, UInt64.MAX_VALUE, Value.MAX_VALUE.subtract(1)), + Arguments.of(Value.MAX_VALUE.subtract(1), 1L, UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt64.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt64UInt64OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt64.ONE, UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(Value v1, long v2, long m, Value expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream + .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(v(0), v(1), Value.MAX_VALUE), + Arguments.of(v(1), v(2), Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractUInt64Provider") + void subtractUInt64(Value v1, UInt64 v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractUInt64Provider() { + return Stream + .of( + Arguments.of(v(1), UInt64.ZERO, v(1)), + Arguments.of(v(5), UInt64.ZERO, v(5)), + Arguments.of(v(2), UInt64.ONE, v(1)), + Arguments.of(v(100), UInt64.valueOf(100), v(0)), + Arguments.of(v(0), UInt64.ONE, Value.MAX_VALUE), + Arguments.of(v(1), UInt64.valueOf(2), Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, UInt64.ONE, hv("0xFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(v(0), 1L, Value.MAX_VALUE), + Arguments.of(v(1), 2L, Value.MAX_VALUE), + Arguments.of(Value.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyUInt64Provider") + void multiplyUInt64(Value v1, UInt64 v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.valueOf(2), v(0)), + Arguments.of(v(1), UInt64.valueOf(2), v(2)), + Arguments.of(v(2), UInt64.valueOf(2), v(4)), + Arguments.of(v(3), UInt64.valueOf(2), v(6)), + Arguments.of(v(4), UInt64.valueOf(2), v(8)), + Arguments.of(v(10), UInt64.valueOf(18), v(180)), + Arguments.of(v(2), UInt64.valueOf(8), v(16)), + Arguments.of(v(7), UInt64.valueOf(8), v(56)), + Arguments.of(v(8), UInt64.valueOf(8), v(64)), + Arguments.of(v(17), UInt64.valueOf(8), v(136)), + Arguments.of(v(22), UInt64.ZERO, v(0)), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), UInt64.valueOf(2), hv("0xFFFFFFFFFFFFFFFE")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), UInt64.valueOf(2), hv("0xFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(v(22), 0L, v(0)), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 2L, hv("0xFFFFFFFFFFFFFFFE")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 2L, hv("0xFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(Value v1, Value v2, UInt64 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt64.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt64.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt64.valueOf(6), v(0)), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), v(2), UInt64.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModUInt64UInt64Provider") + void multiplyModUInt64UInt64(Value v1, UInt64 v2, UInt64 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModUInt64UInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.valueOf(5), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), UInt64.valueOf(3), UInt64.valueOf(7), v(6)), + Arguments.of(v(2), UInt64.valueOf(3), UInt64.valueOf(6), v(0)), + Arguments.of(v(2), UInt64.ZERO, UInt64.valueOf(6), v(0)), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), UInt64.valueOf(2), UInt64.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModUInt64UInt64OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(UInt64.valueOf(5), UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt64Provider") + void multiplyModLongUInt64(Value v1, long v2, UInt64 m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), 5L, UInt64.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt64.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt64.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt64.valueOf(6), v(0)), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), 2L, UInt64.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt64OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(1L, UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt64OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(-1, UInt64.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(Value v1, long v2, long m, Value expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), 2L, Long.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(Value v1, Value v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideUInt64Provider") + void divideUInt64(Value v1, UInt64 v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.valueOf(2), v(0)), + Arguments.of(v(1), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), UInt64.valueOf(2), v(1)), + Arguments.of(v(3), UInt64.valueOf(2), v(1)), + Arguments.of(v(4), UInt64.valueOf(2), v(2)), + Arguments.of(v(2), UInt64.valueOf(8), v(0)), + Arguments.of(v(7), UInt64.valueOf(8), v(0)), + Arguments.of(v(8), UInt64.valueOf(8), v(1)), + Arguments.of(v(9), UInt64.valueOf(8), v(1)), + Arguments.of(v(17), UInt64.valueOf(8), v(2)), + Arguments.of(v(1024), UInt64.valueOf(8), v(128)), + Arguments.of(v(1026), UInt64.valueOf(8), v(128))); + } + + @Test + void shouldThrowForDivideUInt64ByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(UInt64.ZERO)); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt64Provider") + void powUInt64(Value v1, UInt64 v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), UInt64.valueOf(2), v(4)), + Arguments.of(v(2), UInt64.valueOf(8), v(256)), + Arguments.of(v(3), UInt64.valueOf(3), v(27)), + Arguments.of(hv("0xFFFFFFFFFFF0F0F0"), UInt64.valueOf(3), hv("0xF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27))); + } + + @ParameterizedTest + @MethodSource("modUInt64Provider") + void modUInt64(Value v1, UInt64 v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.valueOf(2), v(0)), + Arguments.of(v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(2), UInt64.valueOf(2), v(0)), + Arguments.of(v(3), UInt64.valueOf(2), v(1)), + Arguments.of(v(0), UInt64.valueOf(8), v(0)), + Arguments.of(v(1), UInt64.valueOf(8), v(1)), + Arguments.of(v(2), UInt64.valueOf(8), v(2)), + Arguments.of(v(3), UInt64.valueOf(8), v(3)), + Arguments.of(v(7), UInt64.valueOf(8), v(7)), + Arguments.of(v(8), UInt64.valueOf(8), v(0)), + Arguments.of(v(9), UInt64.valueOf(8), v(1)), + Arguments.of(v(1024), UInt64.valueOf(8), v(0)), + Arguments.of(v(1026), UInt64.valueOf(8), v(2))); + } + + @Test + void shouldThrowForModUInt64ByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(UInt64.ZERO)); + assertEquals("mod by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(Value v1, long v2, Value expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(Value v1, Value v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of(hv("0x0000000000000000"), hv("0x0000000000000000"), 0), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFF"), 0), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0x00000000FFFFFFFF"), 0), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000"), 1), + Arguments.of(hv("0x0000000000000000"), hv("0xFFFFFFFFFFFFFFFF"), -1), + Arguments.of(hv("0x00000001FFFFFFFF"), hv("0x00000000FFFFFFFF"), 1), + Arguments.of(hv("0x00000000FFFFFFFE"), hv("0x00000000FFFFFFFF"), -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.fromHexString("0x0000000000000000")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x0000000001000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0000000100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0x0000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(Value value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(Value value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 64), + Arguments.of(hv("0x01"), 63), + Arguments.of(hv("0x02"), 62), + Arguments.of(hv("0x03"), 62), + Arguments.of(hv("0x0F"), 60), + Arguments.of(hv("0x8F"), 56), + Arguments.of(hv("0x100000000"), 31)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(Value value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(Value.MAX_VALUE, v(1)), Arguments.of(Value.MAX_VALUE, Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(Value value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of(Arguments.of(Value.MAX_VALUE, 3), Arguments.of(Value.MAX_VALUE, Long.MAX_VALUE), Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(Value value, Value operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), Value.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(Value value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Long.MAX_VALUE), Arguments.of(Value.MAX_VALUE, -1)); + } + + private void assertValueEquals(Value expected, Value actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt256Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt256Test.java new file mode 100644 index 00000000000..a899161e7e5 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt256Test.java @@ -0,0 +1,1175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class UInt256Test { + + private static UInt256 v(long v) { + return UInt256.valueOf(v); + } + + private static UInt256 biv(String s) { + return UInt256.valueOf(new BigInteger(s)); + } + + private static UInt256 hv(String s) { + return UInt256.fromHexString(s); + } + + @Test + void valueOfLong() { + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(Long.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(~0L)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(BigInteger.valueOf(-1))); + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(BigInteger.valueOf(2).pow(256))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(biv("9223372036854775807"), v(1), biv("9223372036854775808")), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428444256376332556")), + Arguments.of(UInt256.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt256.MAX_VALUE, v(2), v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + v(1), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), v(1), UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428444256376332556")), + Arguments.of(UInt256.MAX_VALUE, 1L, v(0)), + Arguments.of(UInt256.MAX_VALUE, 2L, v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 1L, UInt256.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt256.valueOf(2), v(0)), + Arguments.of(UInt256.MAX_VALUE.subtract(2), v(1), UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), + Arguments.of(UInt256.MAX_VALUE.subtract(1), v(1), UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt256.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt256.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt256UInt256Provider") + void addModUInt256UInt256(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt256UInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.ONE, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), UInt256.ONE, UInt256.valueOf(2), v(0)), + Arguments.of(UInt256.MAX_VALUE.subtract(2), UInt256.ONE, UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), + Arguments.of(UInt256.MAX_VALUE.subtract(1), UInt256.ONE, UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt256.ONE, UInt256.valueOf(2), v(1)), + Arguments.of(v(3), UInt256.valueOf(2), UInt256.valueOf(6), v(5)), + Arguments.of(v(3), UInt256.valueOf(4), UInt256.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt256OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt256Provider") + void addModLongUInt256(UInt256 v1, long v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), 1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt256.valueOf(2), v(0)), + Arguments.of(UInt256.MAX_VALUE.subtract(2), 1L, UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), + Arguments.of(UInt256.MAX_VALUE.subtract(1), 1L, UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt256.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt256UInt256OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt256.ONE, UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt256 v1, long v2, long m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream + .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428397412093484128")), + Arguments.of(v(0), v(1), UInt256.MAX_VALUE), + Arguments.of(v(1), v(2), UInt256.MAX_VALUE), + Arguments + .of(UInt256.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428397412093484128")), + Arguments.of(v(0), 1L, UInt256.MAX_VALUE), + Arguments.of(v(1), 2L, UInt256.MAX_VALUE), + Arguments + .of(UInt256.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), v(0)), + Arguments.of(v(1), v(0), v(0)), + Arguments.of(v(1), v(20), v(20)), + Arguments.of(hv("0x0a0000000000"), v(1), hv("0x0a0000000000")), + Arguments.of(v(1), hv("0x0a0000000000"), hv("0x0a0000000000")), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("1768466010397529975584837906202624")), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 1L, v(0)), + Arguments.of(v(1), 0L, v(0)), + Arguments.of(v(1), 20L, v(20)), + Arguments.of(v(20), 1L, v(20)), + Arguments.of(hv("0x0a0000000000"), 1L, hv("0x0a0000000000")), + Arguments.of(v(1), 10995116277760L, hv("0x0a0000000000")), + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("1768466010397529975584837906202624")), + Arguments.of(v(22), 0L, v(0)), + Arguments + .of( + hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt256.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt256.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt256.valueOf(6), v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(2), + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt256Provider") + void multiplyModLongUInt256(UInt256 v1, long v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), 5L, UInt256.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt256.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt256.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt256.valueOf(6), v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt256OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt256OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt256.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt256 v1, long v2, long m, UInt256 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + Long.MAX_VALUE, + hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideCeilProvider") + void divideCeil(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.divideCeil(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideCeilProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(1)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(2)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454172")), + Arguments.of(v(2), v(8), v(1)), + Arguments.of(v(7), v(8), v(1)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(2)), + Arguments.of(v(17), v(8), v(3)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(129)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363543")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466264")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944786"))); + } + + @Test + void shouldThrowForDivideCeilByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divideCeil(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt256Provider") + void powUInt256(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt256Provider() { + return Stream + .of( + Arguments.of(v(0), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), UInt256.valueOf(2), v(4)), + Arguments.of(v(2), UInt256.valueOf(8), v(256)), + Arguments.of(v(3), UInt256.valueOf(3), v(27)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + UInt256.valueOf(3), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + 3L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), + Arguments.of(v(3), -3L, hv("0x2F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), + Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2)), + Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), + Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), + Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @Test + void shouldReturnZeroForMod0LongByZero() { + assertEquals(UInt256.ZERO, v(5).mod0(0)); + } + + @Test + void shouldThrowForMod0LongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod0(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.and(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.or(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.xor(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream + .of( + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt256 value, UInt256 expected) { + assertValueEquals(expected, value.not()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream + .of( + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt256 value, int distance, UInt256 expected) { + assertValueEquals(expected, value.shiftLeft(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000001"), + 16, + hv("0x0000000000000000000000000000000000000000000000000000000000010000")), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000001"), + 15, + hv("0x0000000000000000000000000000000000000000000000000000000000008000")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 202, + hv("0xFFFFFFFFFFFFFC00000000000000000000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt256 value, int distance, UInt256 expected) { + assertValueEquals(expected, value.shiftRight(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments + .of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 16, + hv("0x0000100000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 15, + hv("0x0000200000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv("0x00000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000"), + 202, + hv("0x000000000000000000000000000000000000000000000000003FFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt256 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt256 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x0000000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @Test + void shouldThrowForLongValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x010000000000000000").toLong()); + assertEquals("Value does not fit a 8 byte long", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt256 v1, UInt256 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1), + Arguments + .of( + hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 1), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt256 value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x01000000"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000001000000")), + Arguments + .of( + hv("0x0100000000"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000100000000")), + Arguments + .of( + hv("0xf100000000ab"), + Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000f100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("fromBytesProvider") + void fromBytesTest(Bytes value, UInt256 expected, boolean isBytes32) { + assertEquals(expected.toBytes(), UInt256.fromBytes(value).toBytes()); + assertEquals(expected, UInt256.fromBytes(value)); + assertEquals(isBytes32, value instanceof Bytes32); + } + + @SuppressWarnings("UnusedMethod") + private static Stream fromBytesProvider() { + String onesString = "11111111111111111111111111111111"; + String twosString = "22222222222222222222222222222222"; + String eString = "e000000000e000000000e000000000e0"; + Bytes onesBytes = Bytes.fromHexString(onesString); + Bytes twosBytes = Bytes.fromHexString(twosString); + Bytes eBytes = Bytes.fromHexString(eString); + Bytes onetwoBytes = Bytes.fromHexString(onesString + twosString); + Bytes oneeBytes = Bytes.fromHexString(onesString + eString); + return Stream + .of( + // Mutable Bytes + Arguments.of(Bytes.concatenate(onesBytes), hv(onesString), false), + Arguments.of(Bytes.concatenate(eBytes), hv(eString), false), + Arguments.of(Bytes.concatenate(onesBytes, twosBytes), hv(onesString + twosString), true), + Arguments.of(Bytes.concatenate(onesBytes, eBytes), hv(onesString + eString), true), + // Array Wrapping Bytes + Arguments.of(Bytes.fromHexString(onesString), hv(onesString), false), + Arguments.of(Bytes.fromHexString(eString), hv(eString), false), + Arguments.of(Bytes.fromHexString(onesString + twosString), hv(onesString + twosString), true), + Arguments.of(Bytes.fromHexString(onesString + eString), hv(onesString + eString), true), + // Delegating Bytes32 + Arguments.of(Bytes32.wrap(onetwoBytes), hv(onesString + twosString), true), + Arguments.of(Bytes32.wrap(oneeBytes), hv(onesString + eString), true)); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt256 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt256 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 256), + Arguments.of(hv("0x01"), 255), + Arguments.of(hv("0x02"), 254), + Arguments.of(hv("0x03"), 254), + Arguments.of(hv("0x0F"), 252), + Arguments.of(hv("0x8F"), 248), + Arguments.of(hv("0x100000000"), 223)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt256 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt256 value, UInt256 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(UInt256.MAX_VALUE, v(1)), Arguments.of(UInt256.MAX_VALUE, UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt256 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of( + Arguments.of(UInt256.MAX_VALUE, 3), + Arguments.of(UInt256.MAX_VALUE, Long.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt256 value, UInt256 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt256 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @Test + void testGet() { + UInt256 value = UInt256.ONE; + assertEquals(1, value.get(31)); + UInt256 value5 = UInt256.valueOf(5); + assertEquals(5, value5.get(31)); + UInt256 value255 = UInt256.valueOf(255); + assertEquals((byte) 0xff, value255.get(31)); + UInt256 value256 = UInt256.valueOf(256); + assertEquals(1, value256.get(30)); + + for (int i = 0; i < 32; i++) { + assertEquals(0, UInt256.ZERO.get(i)); + } + } + + @Test + void testHashcode() { + UInt256 value = UInt256.ZERO; + assertEquals(2111290369, value.hashCode()); + UInt256 valueOne = UInt256.ONE; + assertEquals(2111290370, valueOne.hashCode()); + } + + @Test + void testOverflowSubtraction() { + UInt256 value = UInt256.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); + UInt256 result = UInt256.ZERO.subtract(value); + assertEquals(value, result); + } + + @Test + void testEquals() { + UInt256 value = UInt256.ZERO; + assertEquals(Bytes32.leftPad(Bytes.of(0)), value); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Long.MAX_VALUE), Arguments.of(UInt256.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt256 expected, UInt256 actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt256.valueOf(3456).toDecimalString()); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt32Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt32Test.java new file mode 100644 index 00000000000..eba2e992164 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt32Test.java @@ -0,0 +1,867 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class UInt32Test { + + private static UInt32 v(int v) { + return UInt32.valueOf(v); + } + + private static UInt32 hv(String s) { + return UInt32.fromHexString(s); + } + + private static Bytes b(String s) { + return Bytes.fromHexString(s); + } + + @Test + void valueOfInt() { + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(~0)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(BigInteger.valueOf(-1))); + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(BigInteger.valueOf(2).pow(32))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(UInt32.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt32.MAX_VALUE, v(2), v(1)), + Arguments.of(hv("0xFFFFFFF0"), v(1), hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), v(1), UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0, v(1)), + Arguments.of(v(5), 0, v(5)), + Arguments.of(v(0), 1, v(1)), + Arguments.of(v(0), 100, v(100)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(100), 90, v(190)), + Arguments.of(UInt32.MAX_VALUE, 1, v(0)), + Arguments.of(UInt32.MAX_VALUE, 2, v(1)), + Arguments.of(hv("0xFFFFFFF0"), 1, hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), 1, UInt32.MAX_VALUE), + Arguments.of(v(10), -5, v(5)), + Arguments.of(v(0), -1, UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt32 v1, UInt32 v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt32.valueOf(2), v(0)), + Arguments.of(UInt32.MAX_VALUE.subtract(2), v(1), UInt32.MAX_VALUE, UInt32.MAX_VALUE.subtract(1)), + Arguments.of(UInt32.MAX_VALUE.subtract(1), v(1), UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt32.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt32.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt32UInt32Provider") + void addModUInt32UInt32(UInt32 v1, UInt32 v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt32UInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.ONE, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), UInt32.ONE, UInt32.valueOf(2), v(0)), + Arguments.of(UInt32.MAX_VALUE.subtract(2), UInt32.ONE, UInt32.MAX_VALUE, UInt32.MAX_VALUE.subtract(1)), + Arguments.of(UInt32.MAX_VALUE.subtract(1), UInt32.ONE, UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt32.ONE, UInt32.valueOf(2), v(1)), + Arguments.of(v(3), UInt32.valueOf(2), UInt32.valueOf(6), v(5)), + Arguments.of(v(3), UInt32.valueOf(4), UInt32.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt32OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt32Provider") + void addModLongUInt32(UInt32 v1, long v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), 1, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), 1, UInt32.valueOf(2), v(0)), + Arguments.of(UInt32.MAX_VALUE.subtract(2), 1, UInt32.MAX_VALUE, UInt32.MAX_VALUE.subtract(1)), + Arguments.of(UInt32.MAX_VALUE.subtract(1), 1, UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), 1, UInt32.valueOf(2), v(1)), + Arguments.of(v(2), -1, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), -7, UInt32.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt32UInt32OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt32.ONE, UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt32 v1, long v2, long m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream.of(Arguments.of(v(0), 1, 2, v(1)), Arguments.of(v(1), 1, 2, v(0)), Arguments.of(v(2), 1, 2, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(v(0), v(1), UInt32.MAX_VALUE), + Arguments.of(v(1), v(2), UInt32.MAX_VALUE), + Arguments.of(UInt32.MAX_VALUE, v(1), hv("0xFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0, v(1)), + Arguments.of(v(5), 0, v(5)), + Arguments.of(v(2), 1, v(1)), + Arguments.of(v(100), 100, v(0)), + Arguments.of(v(0), 1, UInt32.MAX_VALUE), + Arguments.of(v(1), 2, UInt32.MAX_VALUE), + Arguments.of(UInt32.MAX_VALUE, 1, hv("0xFFFFFFFE")), + Arguments.of(v(0), -1, v(1)), + Arguments.of(v(0), -100, v(100)), + Arguments.of(v(2), -2, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(1), v(1), v(1)), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(1), 1, v(1)), + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(2)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(3), 2, v(6)), + Arguments.of(v(4), 2, v(8)), + Arguments.of(v(10), 18, v(180)), + Arguments.of(v(2), 8, v(16)), + Arguments.of(v(7), 8, v(56)), + Arguments.of(v(8), 8, v(64)), + Arguments.of(v(17), 8, v(136)), + Arguments.of(v(22), 0, v(0)), + Arguments.of(hv("0x0FFFFFFF"), 2, hv("0x1FFFFFFE")), + Arguments.of(hv("0xFFFFFFFF"), 2, hv("0xFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt32 v1, UInt32 v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt32.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt32.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt32.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFE"), v(2), UInt32.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt32Provider") + void multiplyModLongUInt32(UInt32 v1, int v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), 5, UInt32.valueOf(2), v(0)), + Arguments.of(v(2), 3, UInt32.valueOf(7), v(6)), + Arguments.of(v(2), 3, UInt32.valueOf(6), v(0)), + Arguments.of(v(2), 0, UInt32.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFE"), 2, UInt32.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt32OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt32OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt32.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt32 v1, int v2, int m, UInt32 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5, 2, v(0)), + Arguments.of(v(2), 3, 7, v(6)), + Arguments.of(v(2), 3, 6, v(0)), + Arguments.of(v(2), 0, 6, v(0)), + Arguments.of(hv("0x0FFFFFFE"), 2, Integer.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(1), v(1), v(1)), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(1), 1, v(1)), + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(0)), + Arguments.of(v(2), 2, v(1)), + Arguments.of(v(3), 2, v(1)), + Arguments.of(v(4), 2, v(2)), + Arguments.of(v(2), 8, v(0)), + Arguments.of(v(7), 8, v(0)), + Arguments.of(v(8), 8, v(1)), + Arguments.of(v(9), 8, v(1)), + Arguments.of(v(17), 8, v(2)), + Arguments.of(v(1024), 8, v(128)), + Arguments.of(v(1026), 8, v(128))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt32Provider") + void powUInt32(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt32Provider() { + return Stream + .of( + Arguments.of(v(0), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), UInt32.valueOf(2), v(4)), + Arguments.of(v(2), UInt32.valueOf(8), v(256)), + Arguments.of(v(3), UInt32.valueOf(3), v(27)), + Arguments.of(hv("0xFFF0F0F0"), UInt32.valueOf(3), hv("0x19A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt32 v1, long v2, UInt32 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(2), 8, v(256)), + Arguments.of(v(3), 3, v(27)), + Arguments.of(hv("0xFFF0F0F0"), 3, hv("0x19A2F000"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(1)), + Arguments.of(v(2), 2, v(0)), + Arguments.of(v(3), 2, v(1)), + Arguments.of(v(0), 8, v(0)), + Arguments.of(v(1), 8, v(1)), + Arguments.of(v(2), 8, v(2)), + Arguments.of(v(3), 8, v(3)), + Arguments.of(v(7), 8, v(7)), + Arguments.of(v(8), 8, v(0)), + Arguments.of(v(9), 8, v(1)), + Arguments.of(v(1024), 8, v(0)), + Arguments.of(v(1026), 8, v(2))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt32 v1, Object v2, UInt32 expected) { + if (v2 instanceof UInt32) { + assertValueEquals(expected, v1.and((UInt32) v2)); + } else if (v2 instanceof Bytes) { + assertValueEquals(expected, v1.and((Bytes) v2)); + } else { + throw new IllegalArgumentException(); + } + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream + .of( + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFFFF00"), hv("0x0000FF00")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFFFF00"), hv("0x0000FF00"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt32 v1, Object v2, UInt32 expected) { + if (v2 instanceof UInt32) { + assertValueEquals(expected, v1.or((UInt32) v2)); + } else if (v2 instanceof Bytes) { + assertValueEquals(expected, v1.or((Bytes) v2)); + } else { + throw new IllegalArgumentException(); + } + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream + .of( + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x000000FF"), b("0xFFFF0000"), hv("0xFFFF00FF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x000000FF"), hv("0xFFFF0000"), hv("0xFFFF00FF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt32 v1, Object v2, UInt32 expected) { + if (v2 instanceof UInt32) { + assertValueEquals(expected, v1.xor((UInt32) v2)); + } else if (v2 instanceof Bytes) { + assertValueEquals(expected, v1.xor((Bytes) v2)); + } else { + throw new IllegalArgumentException(); + } + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream + .of( + Arguments.of(hv("0xFFFFFFFF"), b("0xFFFFFFFF"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFFFF00"), hv("0xFFFF00FF")), + Arguments.of(hv("0xFFFFFFFF"), hv("0xFFFFFFFF"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFFFF00"), hv("0xFFFF00FF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt32 value, UInt32 expected) { + assertValueEquals(expected, value.not()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream + .of( + Arguments.of(hv("0xFFFFFFFF"), hv("0x00000000")), + Arguments.of(hv("0x00000000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt32 value, int distance, UInt32 expected) { + assertValueEquals(expected, value.shiftLeft(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 0, hv("0x01")), + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments.of(hv("0x00000001"), 16, hv("0x00010000")), + Arguments.of(hv("0x00000001"), 15, hv("0x00008000")), + Arguments.of(hv("0xFFFFFFFF"), 23, hv("0xFF800000")), + Arguments.of(hv("0x0000FFFF"), 18, hv("0xFFFC0000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt32 value, int distance, UInt32 expected) { + assertValueEquals(expected, value.shiftRight(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 0, hv("0x01")), + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments.of(hv("0x100000"), 16, hv("0x000010")), + Arguments.of(hv("0x100000"), 15, hv("0x000020")), + Arguments.of(hv("0xFFFFFFFF"), 23, hv("0x000001FF")), + Arguments.of(hv("0xFFFFFFFF"), 202, hv("0x00000000"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt32 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt32 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt32 v1, UInt32 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of(hv("0x00000000"), hv("0x00000000"), 0), + Arguments.of(hv("0xFFFFFFFF"), hv("0xFFFFFFFF"), 0), + Arguments.of(hv("0x0000FFFF"), hv("0x0000FFFF"), 0), + Arguments.of(hv("0xFFFFFFFF"), hv("0x00000000"), 1), + Arguments.of(hv("0x00000000"), hv("0xFFFFFFFF"), -1), + Arguments.of(hv("0x0001FFFF"), hv("0x0000FFFF"), 1), + Arguments.of(hv("0x0000FFFE"), hv("0x0000FFFF"), -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt32 value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.fromHexString("0x00000000")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0xf10000ab"), Bytes.fromHexString("0xF10000AB"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt32 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0xf10000ab"), Bytes.fromHexString("0xf10000ab")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt32 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 32), + Arguments.of(hv("0x01"), 31), + Arguments.of(hv("0x02"), 30), + Arguments.of(hv("0x03"), 30), + Arguments.of(hv("0x0F"), 28), + Arguments.of(hv("0x8F"), 24), + Arguments.of(hv("0x1000000"), 7)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt32 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x10000000"), 29)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt32 value, UInt32 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(UInt32.MAX_VALUE, v(1)), Arguments.of(UInt32.MAX_VALUE, UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt32 value, int operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of( + Arguments.of(UInt32.MAX_VALUE, 3), + Arguments.of(UInt32.MAX_VALUE, Integer.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt32 value, UInt32 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt32 value, int operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Integer.MAX_VALUE), Arguments.of(UInt32.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt32 expected, UInt32 actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToUInt32() { + UInt32 value = UInt32.valueOf(42); + assertSame(value, value.toUInt32()); + } + + @Test + void toIntTooLarge() { + assertThrows(ArithmeticException.class, UInt32.MAX_VALUE::intValue); + } + + @Test + void toLongTooLarge() { + assertEquals(4294967295L, UInt32.MAX_VALUE.toLong()); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt32.valueOf(3456).toDecimalString()); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt384Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt384Test.java new file mode 100644 index 00000000000..eee43a4d81f --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt384Test.java @@ -0,0 +1,1103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class UInt384Test { + + private static UInt384 v(long v) { + return UInt384.valueOf(v); + } + + private static UInt384 biv(String s) { + return UInt384.valueOf(new BigInteger(s)); + } + + private static UInt384 hv(String s) { + return UInt384.fromHexString(s); + } + + @Test + void valueOfLong() { + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(Long.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(~0L)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(BigInteger.valueOf(-1))); + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(BigInteger.valueOf(2).pow(384))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(biv("9223372036854775807"), v(1), biv("9223372036854775808")), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428444256376332556")), + Arguments.of(UInt384.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt384.MAX_VALUE, v(2), v(1)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + v(1), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(1), + UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428444256376332556")), + Arguments.of(UInt384.MAX_VALUE, 1L, v(0)), + Arguments.of(UInt384.MAX_VALUE, 2L, v(1)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 1L, + UInt384.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt384 v1, UInt384 v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt384.valueOf(2), v(0)), + Arguments.of(UInt384.MAX_VALUE.subtract(2), v(1), UInt384.MAX_VALUE, UInt384.MAX_VALUE.subtract(1)), + Arguments.of(UInt384.MAX_VALUE.subtract(1), v(1), UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt384.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt384.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt384UInt384Provider") + void addModUInt384UInt384(UInt384 v1, UInt384 v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt384UInt384Provider() { + return Stream + .of( + Arguments.of(v(0), UInt384.ONE, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), UInt384.ONE, UInt384.valueOf(2), v(0)), + Arguments.of(UInt384.MAX_VALUE.subtract(2), UInt384.ONE, UInt384.MAX_VALUE, UInt384.MAX_VALUE.subtract(1)), + Arguments.of(UInt384.MAX_VALUE.subtract(1), UInt384.ONE, UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt384.ONE, UInt384.valueOf(2), v(1)), + Arguments.of(v(3), UInt384.valueOf(2), UInt384.valueOf(6), v(5)), + Arguments.of(v(3), UInt384.valueOf(4), UInt384.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt384OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt384Provider") + void addModLongUInt384(UInt384 v1, long v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt384Provider() { + return Stream + .of( + Arguments.of(v(0), 1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt384.valueOf(2), v(0)), + Arguments.of(UInt384.MAX_VALUE.subtract(2), 1L, UInt384.MAX_VALUE, UInt384.MAX_VALUE.subtract(1)), + Arguments.of(UInt384.MAX_VALUE.subtract(1), 1L, UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt384.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt384UInt384OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt384.ONE, UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt384 v1, long v2, long m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream + .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), + Arguments + .of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428397412093484128")), + Arguments.of(v(0), v(1), UInt384.MAX_VALUE), + Arguments.of(v(1), v(2), UInt384.MAX_VALUE), + Arguments + .of( + UInt384.MAX_VALUE, + v(1), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), + Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428397412093484128")), + Arguments.of(v(0), 1L, UInt384.MAX_VALUE), + Arguments.of(v(1), 2L, UInt384.MAX_VALUE), + Arguments + .of( + UInt384.MAX_VALUE, + 1L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("1768466010397529975584837906202624")), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("1768466010397529975584837906202624")), + Arguments.of(v(22), 0L, v(0)), + Arguments + .of( + hv( + "0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv( + "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt384 v1, UInt384 v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt384.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt384.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt384.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt384.valueOf(6), v(0)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(2), + UInt384.MAX_VALUE, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt384.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt384Provider") + void multiplyModLongUInt384(UInt384 v1, long v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt384Provider() { + return Stream + .of( + Arguments.of(v(0), 5L, UInt384.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt384.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt384.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt384.valueOf(6), v(0)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + UInt384.MAX_VALUE, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt384OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt384.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt384OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt384.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt384 v1, long v2, long m, UInt384 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + Long.MAX_VALUE, + hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128)), + Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), + Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt384Provider") + void powUInt384(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt384Provider() { + return Stream + .of( + Arguments.of(v(0), UInt384.valueOf(2), v(0)), + Arguments.of(v(2), UInt384.valueOf(2), v(4)), + Arguments.of(v(2), UInt384.valueOf(8), v(256)), + Arguments.of(v(3), UInt384.valueOf(3), v(27)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + UInt384.valueOf(3), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + 3L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), + Arguments + .of( + v(3), + -3L, + hv( + "0x4BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), + Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2)), + Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), + Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), + Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.and(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.or(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.xor(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream + .of( + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt384 value, UInt384 expected) { + assertValueEquals(expected, value.not()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream + .of( + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv( + "0x00000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt384 value, int distance, UInt384 expected) { + assertValueEquals(expected, value.shiftLeft(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments + .of( + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), + 16, + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000")), + Arguments + .of( + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), + 15, + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000")), + Arguments + .of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000")), + Arguments + .of( + hv( + "0x00000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 330, + hv( + "0xFFFFFFFFFFFFFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt384 value, int distance, UInt384 expected) { + assertValueEquals(expected, value.shiftRight(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments + .of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 16, + hv("0x0000100000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 15, + hv("0x0000200000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv("0x00000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000"), + 202, + hv("0x000000000000000000000000000000000000000000000000003FFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt384 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt384 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x0000000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @Test + void shouldThrowForLongValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x010000000000000000").toLong()); + assertEquals("Value does not fit a 8 byte long", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt384 v1, UInt384 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments + .of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 1), + Arguments + .of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1), + Arguments + .of( + hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 1), + Arguments + .of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt384 value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments + .of( + hv("0x00"), + Bytes + .fromHexString( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Arguments + .of( + hv("0x01000000"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000")), + Arguments + .of( + hv("0x0100000000"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000")), + Arguments + .of( + hv("0xf100000000ab"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000f100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes + .fromHexString( + "0x000000000000000000000000000000000400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt384 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments + .of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt384 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 384), + Arguments.of(hv("0x01"), 383), + Arguments.of(hv("0x02"), 382), + Arguments.of(hv("0x03"), 382), + Arguments.of(hv("0x0F"), 380), + Arguments.of(hv("0x8F"), 376), + Arguments.of(hv("0x100000000"), 351)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt384 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt384 value, UInt384 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(UInt384.MAX_VALUE, v(1)), Arguments.of(UInt384.MAX_VALUE, UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt384 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of( + Arguments.of(UInt384.MAX_VALUE, 3), + Arguments.of(UInt384.MAX_VALUE, Long.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt384 value, UInt384 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt384 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Long.MAX_VALUE), Arguments.of(UInt384.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt384 expected, UInt384 actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt384.valueOf(3456).toDecimalString()); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt64Test.java b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt64Test.java new file mode 100644 index 00000000000..3ab00c5436b --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/apache/tuweni/units/bigints/UInt64Test.java @@ -0,0 +1,855 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +package org.apache.tuweni.units.bigints; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class UInt64Test { + + private static UInt64 v(long v) { + return UInt64.valueOf(v); + } + + private static UInt64 hv(String s) { + return UInt64.fromHexString(s); + } + + + private static Bytes b(String s) { + return Bytes.fromHexString(s); + } + + @Test + void valueOfLong() { + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(Long.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(~0L)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(BigInteger.valueOf(-1))); + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(BigInteger.valueOf(2).pow(64))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(UInt64.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt64.MAX_VALUE, v(2), v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), v(1), hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), v(1), UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(UInt64.MAX_VALUE, 1L, v(0)), + Arguments.of(UInt64.MAX_VALUE, 2L, v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), 1L, hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), 1L, UInt64.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt64 v1, UInt64 v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream + .of( + Arguments.of(v(0), v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt64.valueOf(2), v(0)), + Arguments.of(UInt64.MAX_VALUE.subtract(2), v(1), UInt64.MAX_VALUE, UInt64.MAX_VALUE.subtract(1)), + Arguments.of(UInt64.MAX_VALUE.subtract(1), v(1), UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt64.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt64.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt64UInt64Provider") + void addModUInt64UInt64(UInt64 v1, UInt64 v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt64UInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.ONE, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), UInt64.ONE, UInt64.valueOf(2), v(0)), + Arguments.of(UInt64.MAX_VALUE.subtract(2), UInt64.ONE, UInt64.MAX_VALUE, UInt64.MAX_VALUE.subtract(1)), + Arguments.of(UInt64.MAX_VALUE.subtract(1), UInt64.ONE, UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt64.ONE, UInt64.valueOf(2), v(1)), + Arguments.of(v(3), UInt64.valueOf(2), UInt64.valueOf(6), v(5)), + Arguments.of(v(3), UInt64.valueOf(4), UInt64.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt64OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt64Provider") + void addModLongUInt64(UInt64 v1, long v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), 1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt64.valueOf(2), v(0)), + Arguments.of(UInt64.MAX_VALUE.subtract(2), 1L, UInt64.MAX_VALUE, UInt64.MAX_VALUE.subtract(1)), + Arguments.of(UInt64.MAX_VALUE.subtract(1), 1L, UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt64.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt64UInt64OfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt64.ONE, UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt64 v1, long v2, long m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream + .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream + .of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(v(0), v(1), UInt64.MAX_VALUE), + Arguments.of(v(1), v(2), UInt64.MAX_VALUE), + Arguments.of(UInt64.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream + .of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(v(0), 1L, UInt64.MAX_VALUE), + Arguments.of(v(1), 2L, UInt64.MAX_VALUE), + Arguments.of(UInt64.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(v(22), 0L, v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFF"), 2L, hv("0x1FFFFFFFFFFFFFFE")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 2L, hv("0xFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt64 v1, UInt64 v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream + .of( + Arguments.of(v(0), v(5), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt64.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt64.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt64.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), v(2), UInt64.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt64Provider") + void multiplyModLongUInt64(UInt64 v1, long v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), 5L, UInt64.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt64.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt64.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt64.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), 2L, UInt64.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt64OfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt64OfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt64.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt64 v1, long v2, long m, UInt64 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream + .of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), 2L, Long.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream + .of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt64Provider") + void powUInt64(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt64Provider() { + return Stream + .of( + Arguments.of(v(0), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), UInt64.valueOf(2), v(4)), + Arguments.of(v(2), UInt64.valueOf(8), v(256)), + Arguments.of(v(3), UInt64.valueOf(3), v(27)), + Arguments.of(hv("0xFFFFFFFFFFF0F0F0"), UInt64.valueOf(3), hv("0xF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments.of(hv("0xFFFFFFFFFFF0F0F0"), 3L, hv("0xF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream + .of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt64 v1, Object v2, UInt64 expected) { + if (v2 instanceof UInt64) { + assertValueEquals(expected, v1.and((UInt64) v2)); + } else if (v2 instanceof Bytes) { + assertValueEquals(expected, v1.and((Bytes) v2)); + } else { + throw new IllegalArgumentException(v2.getClass().getName()); + } + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream + .of( + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0x0000000000000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFFFF000000"), hv("0x00000000FF000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), b("0xFFFFFFFFFF000000"), hv("0x00000000FF000000"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt64 v1, Object v2, UInt64 expected) { + if (v2 instanceof UInt64) { + assertValueEquals(expected, v1.or((UInt64) v2)); + } else if (v2 instanceof Bytes) { + assertValueEquals(expected, v1.or((Bytes) v2)); + } else { + throw new IllegalArgumentException(v2.getClass().getName()); + } + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream + .of( + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000000000FF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFF000000FF")), + Arguments.of(hv("0x00000000000000FF"), b("0xFFFFFFFF00000000"), hv("0xFFFFFFFF000000FF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt64 v1, Object v2, UInt64 expected) { + if (v2 instanceof UInt64) { + assertValueEquals(expected, v1.xor((UInt64) v2)); + } else if (v2 instanceof Bytes) { + assertValueEquals(expected, v1.xor((Bytes) v2)); + } else { + throw new IllegalArgumentException(v2.getClass().getName()); + } + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream + .of( + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFFFF000000"), hv("0xFFFFFFFF00FFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), b("0xFFFFFFFFFF000000"), hv("0xFFFFFFFF00FFFFFF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt64 value, UInt64 expected) { + assertValueEquals(expected, value.not()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream + .of( + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000")), + Arguments.of(hv("0x0000000000000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt64 value, int distance, UInt64 expected) { + assertValueEquals(expected, value.shiftLeft(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments.of(hv("0x0000000000000001"), 16, hv("0x0000000000010000")), + Arguments.of(hv("0x0000000000000001"), 15, hv("0x0000000000008000")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 55, hv("0xFF80000000000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), 50, hv("0xFFFC000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt64 value, int distance, UInt64 expected) { + assertValueEquals(expected, value.shiftRight(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream + .of( + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments.of(hv("0x1000000000000000"), 16, hv("0x0000100000000000")), + Arguments.of(hv("0x1000000000000000"), 15, hv("0x0000200000000000")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 55, hv("0x00000000000001FF")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 202, hv("0x0000000000000000"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt64 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt64 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream + .of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt64 v1, UInt64 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream + .of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of(hv("0x0000000000000000"), hv("0x0000000000000000"), 0), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFF"), 0), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0x00000000FFFFFFFF"), 0), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000"), 1), + Arguments.of(hv("0x0000000000000000"), hv("0xFFFFFFFFFFFFFFFF"), -1), + Arguments.of(hv("0x00000001FFFFFFFF"), hv("0x00000000FFFFFFFF"), 1), + Arguments.of(hv("0x00000000FFFFFFFE"), hv("0x00000000FFFFFFFF"), -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt64 value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.fromHexString("0x0000000000000000")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x0000000001000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0000000100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0x0000F100000000AB"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt64 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments.of(hv("0100000000000000"), Bytes.fromHexString("0x0100000000000000"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt64 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 64), + Arguments.of(hv("0x01"), 63), + Arguments.of(hv("0x02"), 62), + Arguments.of(hv("0x03"), 62), + Arguments.of(hv("0x0F"), 60), + Arguments.of(hv("0x8F"), 56), + Arguments.of(hv("0x100000000"), 31)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt64 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream + .of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt64 value, UInt64 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of(Arguments.of(UInt64.MAX_VALUE, v(1)), Arguments.of(UInt64.MAX_VALUE, UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt64 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream + .of(Arguments.of(UInt64.MAX_VALUE, 3), Arguments.of(UInt64.MAX_VALUE, Long.MAX_VALUE), Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt64 value, UInt64 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt64 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of(Arguments.of(v(0), 1), Arguments.of(v(0), Long.MAX_VALUE), Arguments.of(UInt64.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt64 expected, UInt64 actual) { + String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt64.valueOf(3456).toDecimalString()); + } +} \ No newline at end of file diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AbstractKeyValueStorageTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AbstractKeyValueStorageTest.java new file mode 100644 index 00000000000..1235e039bcf --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AbstractKeyValueStorageTest.java @@ -0,0 +1,417 @@ +/* + * 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.trie; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorageTransaction; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +@Ignore +public abstract class AbstractKeyValueStorageTest { + + protected abstract KeyValueStorage createStore() throws Exception; + + @Test + public void twoStoresAreIndependent() throws Exception { + final KeyValueStorage store1 = createStore(); + final KeyValueStorage store2 = createStore(); + + final KeyValueStorageTransaction tx = store1.startTransaction(); + final byte[] key = bytesFromHexString("0001"); + final byte[] value = bytesFromHexString("0FFF"); + + tx.put(key, value); + tx.commit(); + + final Optional result = store2.get(key); + assertThat(result).isEmpty(); + } + + @Test + public void put() throws Exception { + final KeyValueStorage store = createStore(); + final byte[] key = bytesFromHexString("0F"); + final byte[] firstValue = bytesFromHexString("0ABC"); + final byte[] secondValue = bytesFromHexString("0DEF"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(key, firstValue); + tx.commit(); + assertThat(store.get(key)).contains(firstValue); + + tx = store.startTransaction(); + tx.put(key, secondValue); + tx.commit(); + assertThat(store.get(key)).contains(secondValue); + } + + @Test + public void streamKeys() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + final List keys = + Stream.of("0F", "10", "11", "12") + .map(this::bytesFromHexString) + .collect(Collectors.collectingAndThen( + Collectors.toList(), + Collections::unmodifiableList + )); + keys.forEach(key -> tx.put(key, bytesFromHexString("0ABC"))); + tx.commit(); + Set set = store.stream().map(Pair::getKey).collect(Collectors.collectingAndThen( + Collectors.toSet(), + Collections::unmodifiableSet)); + + assertThat(set) + .containsExactlyInAnyOrder(keys.toArray(new byte[][] {})); + } + + @Test + public void getAllKeysThat() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(bytesFromHexString("0F"), bytesFromHexString("0ABC")); + tx.put(bytesFromHexString("10"), bytesFromHexString("0ABC")); + tx.put(bytesFromHexString("11"), bytesFromHexString("0ABC")); + tx.put(bytesFromHexString("12"), bytesFromHexString("0ABC")); + tx.commit(); + Set keys = store.getAllKeysThat(bv -> Bytes.wrap(bv).toString().contains("1")); + assertThat(keys.size()).isEqualTo(3); + assertThat(keys) + .containsExactlyInAnyOrder( + bytesFromHexString("10"), bytesFromHexString("11"), bytesFromHexString("12")); + } + + @Test + public void containsKey() throws Exception { + final KeyValueStorage store = createStore(); + final byte[] key = bytesFromHexString("ABCD"); + final byte[] value = bytesFromHexString("DEFF"); + + assertThat(store.containsKey(key)).isFalse(); + + final KeyValueStorageTransaction transaction = store.startTransaction(); + transaction.put(key, value); + transaction.commit(); + + assertThat(store.containsKey(key)).isTrue(); + } + + @Test + public void removeExisting() throws Exception { + final KeyValueStorage store = createStore(); + final byte[] key = bytesFromHexString("0F"); + final byte[] value = bytesFromHexString("0ABC"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(key, value); + tx.commit(); + + tx = store.startTransaction(); + tx.remove(key); + tx.commit(); + assertThat(store.get(key)).isEmpty(); + } + + @Test + public void removeExistingSameTransaction() throws Exception { + final KeyValueStorage store = createStore(); + final byte[] key = bytesFromHexString("0F"); + final byte[] value = bytesFromHexString("0ABC"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(key, value); + tx.remove(key); + tx.commit(); + assertThat(store.get(key)).isEmpty(); + } + + @Test + public void removeNonExistent() throws Exception { + final KeyValueStorage store = createStore(); + final byte[] key = bytesFromHexString("0F"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.remove(key); + tx.commit(); + assertThat(store.get(key)).isEmpty(); + } + + @Test + public void concurrentUpdate() throws Exception { + final int keyCount = 1000; + final KeyValueStorage store = createStore(); + + final CountDownLatch finishedLatch = new CountDownLatch(2); + final Function updater = + (value) -> + new Thread( + () -> { + try { + for (int i = 0; i < keyCount; i++) { + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(Bytes.minimalBytes(i).toArrayUnsafe(), value); + tx.commit(); + } + } finally { + finishedLatch.countDown(); + } + }); + + // Run 2 concurrent transactions that write a bunch of values to the same keys + final byte[] a = Bytes.of(10).toArrayUnsafe(); + final byte[] b = Bytes.of(20).toArrayUnsafe(); + updater.apply(a).start(); + updater.apply(b).start(); + + finishedLatch.await(); + + for (int i = 0; i < keyCount; i++) { + final byte[] key = Bytes.minimalBytes(i).toArrayUnsafe(); + final byte[] actual = store.get(key).get(); + assertThat(Arrays.equals(actual, a) || Arrays.equals(actual, b)).isTrue(); + } + + store.close(); + } + + @Test + public void transactionCommit() throws Exception { + final KeyValueStorage store = createStore(); + // Add some values + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(bytesOf(1), bytesOf(1)); + tx.put(bytesOf(2), bytesOf(2)); + tx.put(bytesOf(3), bytesOf(3)); + tx.commit(); + + // Start transaction that adds, modifies, and removes some values + tx = store.startTransaction(); + tx.put(bytesOf(2), bytesOf(3)); + tx.put(bytesOf(2), bytesOf(4)); + tx.remove(bytesOf(3)); + tx.put(bytesOf(4), bytesOf(8)); + + // Check values before committing have not changed + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); + assertThat(store.get(bytesOf(4))).isEmpty(); + + tx.commit(); + + // Check that values have been updated after commit + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(4)); + assertThat(store.get(bytesOf(3))).isEmpty(); + assertThat(store.get(bytesOf(4))).contains(bytesOf(8)); + } + + @Test + public void transactionRollback() throws Exception { + final KeyValueStorage store = createStore(); + // Add some values + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(bytesOf(1), bytesOf(1)); + tx.put(bytesOf(2), bytesOf(2)); + tx.put(bytesOf(3), bytesOf(3)); + tx.commit(); + + // Start transaction that adds, modifies, and removes some values + tx = store.startTransaction(); + tx.put(bytesOf(2), bytesOf(3)); + tx.put(bytesOf(2), bytesOf(4)); + tx.remove(bytesOf(3)); + tx.put(bytesOf(4), bytesOf(8)); + + // Check values before committing have not changed + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); + assertThat(store.get(bytesOf(4))).isEmpty(); + + tx.rollback(); + + // Check that values have not changed after rollback + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); + assertThat(store.get(bytesOf(4))).isEmpty(); + } + + @Test + public void transactionCommitEmpty() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + } + + @Test + public void transactionRollbackEmpty() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + } + + @Test(expected = IllegalStateException.class) + public void transactionPutAfterCommit() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.put(bytesOf(1), bytesOf(1)); + } + + @Test(expected = IllegalStateException.class) + public void transactionRemoveAfterCommit() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.remove(bytesOf(1)); + } + + @Test(expected = IllegalStateException.class) + public void transactionPutAfterRollback() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.put(bytesOf(1), bytesOf(1)); + } + + @Test(expected = IllegalStateException.class) + public void transactionRemoveAfterRollback() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.remove(bytesOf(1)); + } + + @Test(expected = IllegalStateException.class) + public void transactionCommitAfterRollback() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.commit(); + } + + @Test(expected = IllegalStateException.class) + public void transactionCommitTwice() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.commit(); + } + + @Test(expected = IllegalStateException.class) + public void transactionRollbackAfterCommit() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.rollback(); + } + + @Test(expected = IllegalStateException.class) + public void transactionRollbackTwice() throws Exception { + final KeyValueStorage store = createStore(); + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.rollback(); + } + + @Test + public void twoTransactions() throws Exception { + final KeyValueStorage store = createStore(); + + final KeyValueStorageTransaction tx1 = store.startTransaction(); + final KeyValueStorageTransaction tx2 = store.startTransaction(); + + tx1.put(bytesOf(1), bytesOf(1)); + tx2.put(bytesOf(2), bytesOf(2)); + + tx1.commit(); + tx2.commit(); + + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + } + + @Test + public void transactionIsolation() throws Exception { + final int keyCount = 1000; + final KeyValueStorage store = createStore(); + + final CountDownLatch finishedLatch = new CountDownLatch(2); + final Function txRunner = + (value) -> + new Thread( + () -> { + final KeyValueStorageTransaction tx = store.startTransaction(); + for (int i = 0; i < keyCount; i++) { + tx.put(Bytes.minimalBytes(i).toArrayUnsafe(), value); + } + try { + tx.commit(); + } finally { + finishedLatch.countDown(); + } + }); + + // Run 2 concurrent transactions that write a bunch of values to the same keys + final byte[] a = bytesOf(10); + final byte[] b = bytesOf(20); + txRunner.apply(a).start(); + txRunner.apply(b).start(); + + finishedLatch.await(); + + // Check that transaction results are isolated (not interleaved) + final List finalValues = new ArrayList<>(keyCount); + for (int i = 0; i < keyCount; i++) { + final byte[] key = Bytes.minimalBytes(i).toArrayUnsafe(); + finalValues.add(store.get(key).get()); + } + + // Expecting the same value for all entries + final byte[] expected = finalValues.get(0); + for (final byte[] actual : finalValues) { + assertThat(actual).containsExactly(expected); + } + + assertThat(Arrays.equals(expected, a) || Arrays.equals(expected, b)).isTrue(); + + store.close(); + } + + /* + * Used to mimic the wrapping with Bytes performed in Besu + */ + protected byte[] bytesFromHexString(final String hex) { + return Bytes.fromHexString(hex).toArrayUnsafe(); + } + + protected byte[] bytesOf(final int... bytes) { + return Bytes.of(bytes).toArrayUnsafe(); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AbstractMerklePatriciaTrieTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AbstractMerklePatriciaTrieTest.java new file mode 100644 index 00000000000..b32391312da --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AbstractMerklePatriciaTrieTest.java @@ -0,0 +1,412 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.storage.InMemoryKeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.Optional; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertFalse; + +public abstract class AbstractMerklePatriciaTrieTest { + protected MerklePatriciaTrie trie; + + @Before + public void setup() { + trie = createTrie(); + } + + protected abstract MerklePatriciaTrie createTrie(); + + @Test + public void emptyTreeReturnsEmpty() { + assertFalse(trie.get(Bytes.EMPTY).isPresent()); + } + + @Test + public void emptyTreeHasKnownRootHash() { + assertThat(trie.getRootHash().toString()) + .isEqualTo("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); + } + + @Test + public void throwsOnUpdateWithNull() { + assertThatThrownBy(() -> trie.put(Bytes.EMPTY, (String) null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void replaceSingleValue() { + final Bytes key = Bytes.of(1); + final String value1 = "value1"; + trie.put(key, value1); + assertThat(trie.get(key)).isEqualTo(Optional.of(value1)); + + final String value2 = "value2"; + trie.put(key, value2); + assertThat(trie.get(key)).isEqualTo(Optional.of(value2)); + } + + @Test + public void hashChangesWhenSingleValueReplaced() { + final Bytes key = Bytes.of(1); + final String value1 = "value1"; + trie.put(key, value1); + final Bytes32 hash1 = trie.getRootHash(); + + final String value2 = "value2"; + trie.put(key, value2); + final Bytes32 hash2 = trie.getRootHash(); + + assertThat(hash1).isNotEqualTo(hash2); + + trie.put(key, value1); + assertThat(trie.getRootHash()).isEqualTo(hash1); + } + + @Test + public void readPastLeaf() { + final Bytes key1 = Bytes.of(1); + trie.put(key1, "value"); + final Bytes key2 = Bytes.of(1, 3); + assertFalse(trie.get(key2).isPresent()); + } + + @Test + public void branchValue() { + final Bytes key1 = Bytes.of(1); + final Bytes key2 = Bytes.of(16); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + } + + @Test + public void readPastBranch() { + final Bytes key1 = Bytes.of(12); + final Bytes key2 = Bytes.of(12, 54); + + final String value1 = "value1"; + trie.put(key1, value1); + final String value2 = "value2"; + trie.put(key2, value2); + + final Bytes key3 = Bytes.of(3); + assertFalse(trie.get(key3).isPresent()); + } + + @Test + public void branchWithValue() { + final Bytes key1 = Bytes.of(5); + final Bytes key2 = Bytes.EMPTY; + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + } + + @Test + public void extendAndBranch() { + final Bytes key1 = Bytes.of(1, 5, 9); + final Bytes key2 = Bytes.of(1, 5, 2); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + assertFalse(trie.get(Bytes.of(1, 4)).isPresent()); + } + + @Test + public void branchFromTopOfExtend() { + final Bytes key1 = Bytes.of(0xfe, 1); + final Bytes key2 = Bytes.of(0xfe, 2); + final Bytes key3 = Bytes.of(0xe1, 1); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + final String value3 = "value3"; + trie.put(key3, value3); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + assertThat(trie.get(key3)).isEqualTo(Optional.of(value3)); + assertFalse(trie.get(Bytes.of(1, 4)).isPresent()); + assertFalse(trie.get(Bytes.of(2, 4)).isPresent()); + assertFalse(trie.get(Bytes.of(3)).isPresent()); + } + + @Test + public void splitBranchExtension() { + final Bytes key1 = Bytes.of(1, 5, 9); + final Bytes key2 = Bytes.of(1, 5, 2); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + final Bytes key3 = Bytes.of(1, 9, 1); + + final String value3 = "value3"; + trie.put(key3, value3); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + assertThat(trie.get(key3)).isEqualTo(Optional.of(value3)); + } + + @Test + public void replaceBranchChild() { + final Bytes key1 = Bytes.of(0); + final Bytes key2 = Bytes.of(1); + + final String value1 = "value1"; + trie.put(key1, value1); + final String value2 = "value2"; + trie.put(key2, value2); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + + final String value3 = "value3"; + trie.put(key1, value3); + + assertThat(trie.get(key1)).isEqualTo(Optional.of(value3)); + assertThat(trie.get(key2)).isEqualTo(Optional.of(value2)); + } + + @Test + public void inlineBranchInBranch() { + final Bytes key1 = Bytes.of(0); + final Bytes key2 = Bytes.of(1); + final Bytes key3 = Bytes.of(2); + final Bytes key4 = Bytes.of(0, 0); + final Bytes key5 = Bytes.of(0, 1); + + trie.put(key1, "value1"); + trie.put(key2, "value2"); + trie.put(key3, "value3"); + trie.put(key4, "value4"); + trie.put(key5, "value5"); + + trie.remove(key2); + trie.remove(key3); + + assertThat(trie.get(key1)).isEqualTo(Optional.of("value1")); + assertFalse(trie.get(key2).isPresent()); + assertFalse(trie.get(key3).isPresent()); + assertThat(trie.get(key4)).isEqualTo(Optional.of("value4")); + assertThat(trie.get(key5)).isEqualTo(Optional.of("value5")); + } + + @Test + public void removeNodeInBranchExtensionHasNoEffect() { + final Bytes key1 = Bytes.of(1, 5, 9); + final Bytes key2 = Bytes.of(1, 5, 2); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + final Bytes hash = trie.getRootHash(); + + trie.remove(Bytes.of(1, 4)); + assertThat(trie.getRootHash()).isEqualTo(hash); + } + + @Test + public void hashChangesWhenValueChanged() { + final Bytes key1 = Bytes.of(1, 5, 8, 9); + final Bytes key2 = Bytes.of(1, 6, 1, 2); + final Bytes key3 = Bytes.of(1, 6, 1, 3); + + final String value1 = "value1"; + trie.put(key1, value1); + final Bytes32 hash1 = trie.getRootHash(); + + final String value2 = "value2"; + trie.put(key2, value2); + final String value3 = "value3"; + trie.put(key3, value3); + final Bytes32 hash2 = trie.getRootHash(); + + assertThat(hash1).isNotEqualTo(hash2); + + final String value4 = "value4"; + trie.put(key1, value4); + final Bytes32 hash3 = trie.getRootHash(); + + assertThat(hash1).isNotEqualTo(hash3); + assertThat(hash2).isNotEqualTo(hash3); + + trie.put(key1, value1); + assertThat(trie.getRootHash()).isEqualTo(hash2); + + trie.remove(key2); + trie.remove(key3); + assertThat(trie.getRootHash()).isEqualTo(hash1); + } + + @Test + public void shouldRetrieveStoredExtensionWithInlinedChild() { + final KeyValueStorage keyValueStorage = new InMemoryKeyValueStorage(); + final MerkleStorage merkleStorage = new KeyValueMerkleStorage(keyValueStorage); + final StoredMerklePatriciaTrie trie = + new StoredMerklePatriciaTrie<>(merkleStorage::get, b -> b, b -> b); + + // Both of these can be inlined in its parent branch and the branch + // itself can be inlined into its parent extension. + trie.put(Bytes.fromHexString("0x0400"), Bytes.of(1)); + trie.put(Bytes.fromHexString("0x0800"), Bytes.of(2)); + trie.commit(merkleStorage::put); + + // Ensure the extension branch can be loaded correct with its inlined child. + final Bytes32 rootHash = trie.getRootHash(); + final StoredMerklePatriciaTrie newTrie = + new StoredMerklePatriciaTrie<>(merkleStorage::get, rootHash, b -> b, b -> b); + newTrie.get(Bytes.fromHexString("0x0401")); + } + + @Test + public void shouldInlineNodesInParentAcrossModifications() { + // Misuse of StorageNode allowed inlineable trie nodes to end + // up being stored as a hash in its parent, which this would fail for. + final KeyValueStorage keyValueStorage = new InMemoryKeyValueStorage(); + final MerkleStorage merkleStorage = new KeyValueMerkleStorage(keyValueStorage); + final StoredMerklePatriciaTrie trie = + new StoredMerklePatriciaTrie<>(merkleStorage::get, b -> b, b -> b); + + // Both of these can be inlined in its parent branch. + trie.put(Bytes.fromHexString("0x0400"), Bytes.of(1)); + trie.put(Bytes.fromHexString("0x0800"), Bytes.of(2)); + trie.commit(merkleStorage::put); + + final Bytes32 rootHash = trie.getRootHash(); + final StoredMerklePatriciaTrie newTrie = + new StoredMerklePatriciaTrie<>(merkleStorage::get, rootHash, b -> b, b -> b); + + newTrie.put(Bytes.fromHexString("0x0800"), Bytes.of(3)); + newTrie.get(Bytes.fromHexString("0x0401")); + trie.commit(merkleStorage::put); + + newTrie.get(Bytes.fromHexString("0x0401")); + } + + @Test + public void getValueWithProof_emptyTrie() { + final Bytes key1 = Bytes.of(0xfe, 1); + + Proof valueWithProof = trie.getValueWithProof(key1); + assertThat(valueWithProof.getValue()).isEmpty(); + assertThat(valueWithProof.getProofRelatedNodes()).hasSize(0); + } + + @Test + public void getValueWithProof_forExistingValues() { + final Bytes key1 = Bytes.of(0xfe, 1); + final Bytes key2 = Bytes.of(0xfe, 2); + final Bytes key3 = Bytes.of(0xfe, 3); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + final String value3 = "value3"; + trie.put(key3, value3); + + final Proof valueWithProof = trie.getValueWithProof(key1); + assertThat(valueWithProof.getProofRelatedNodes()).hasSize(2); + assertThat(valueWithProof.getValue()).contains(value1); + + final List> nodes = + TrieNodeDecoder.decodeNodes(null, valueWithProof.getProofRelatedNodes().get(1)); + + assertThat(new String(nodes.get(1).getValue().get().toArray(), UTF_8)).isEqualTo(value1); + assertThat(new String(nodes.get(2).getValue().get().toArray(), UTF_8)).isEqualTo(value2); + } + + @Test + public void getValueWithProof_forNonExistentValue() { + final Bytes key1 = Bytes.of(0xfe, 1); + final Bytes key2 = Bytes.of(0xfe, 2); + final Bytes key3 = Bytes.of(0xfe, 3); + final Bytes key4 = Bytes.of(0xfe, 4); + + final String value1 = "value1"; + trie.put(key1, value1); + + final String value2 = "value2"; + trie.put(key2, value2); + + final String value3 = "value3"; + trie.put(key3, value3); + + final Proof valueWithProof = trie.getValueWithProof(key4); + assertThat(valueWithProof.getValue()).isEmpty(); + assertThat(valueWithProof.getProofRelatedNodes()).hasSize(2); + } + + @Test + public void getValueWithProof_singleNodeTrie() { + final Bytes key1 = Bytes.of(0xfe, 1); + final String value1 = "1"; + trie.put(key1, value1); + + final Proof valueWithProof = trie.getValueWithProof(key1); + assertThat(valueWithProof.getValue()).contains(value1); + assertThat(valueWithProof.getProofRelatedNodes()).hasSize(1); + + final List> nodes = + TrieNodeDecoder.decodeNodes(null, valueWithProof.getProofRelatedNodes().get(0)); + + assertThat(nodes.size()).isEqualTo(1); + final String nodeValue = new String(nodes.get(0).getValue().get().toArray(), UTF_8); + assertThat(nodeValue).isEqualTo(value1); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java new file mode 100644 index 00000000000..f8dfeac06fd --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java @@ -0,0 +1,47 @@ +/* + * 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.trie; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class AllNodesVisitorTest { + + @Mock private StoredNode storedNode; + @Mock private ExtensionNode extensionNode; + @Mock private BranchNode branchNode; + + @Test + public void visitExtension() { + when(extensionNode.getChild()).thenReturn(storedNode); + new AllNodesVisitor(x -> {}).visit(extensionNode); + verify(storedNode).unload(); + } + + @Test + public void visitBranch() { + when(branchNode.getChildren()).thenReturn(Collections.singletonList(storedNode)); + new AllNodesVisitor(x -> {}).visit(branchNode); + verify(storedNode).unload(); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/CompactEncodingTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/CompactEncodingTest.java new file mode 100644 index 00000000000..a23973c77cd --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/CompactEncodingTest.java @@ -0,0 +1,68 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.crypto.Hash; +import org.junit.Test; + +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompactEncodingTest { + + @Test + public void bytesToPath() { + final Bytes path = CompactEncoding.bytesToPath(Bytes.of(0xab, 0xcd, 0xff)); + assertThat(path).isEqualTo(Bytes.of(0xa, 0xb, 0xc, 0xd, 0xf, 0xf, 0x10)); + } + + @Test + public void shouldRoundTripFromBytesToPathAndBack() { + final Random random = new Random(282943948928429484L); + for (int i = 0; i < 1000; i++) { + final Bytes32 bytes = Hash.keccak256(UInt256.valueOf(random.nextInt(Integer.MAX_VALUE))); + final Bytes path = CompactEncoding.bytesToPath(bytes); + assertThat(CompactEncoding.pathToBytes(path)).isEqualTo(bytes); + } + } + + @Test + public void encodePath() { + assertThat(CompactEncoding.encode(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05))) + .isEqualTo(Bytes.of(0x11, 0x23, 0x45)); + assertThat(CompactEncoding.encode(Bytes.of(0x00, 0x01, 0x02, 0x03, 0x04, 0x05))) + .isEqualTo(Bytes.of(0x00, 0x01, 0x23, 0x45)); + assertThat(CompactEncoding.encode(Bytes.of(0x00, 0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10))) + .isEqualTo(Bytes.of(0x20, 0x0f, 0x1c, 0xb8)); + assertThat(CompactEncoding.encode(Bytes.of(0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10))) + .isEqualTo(Bytes.of(0x3f, 0x1c, 0xb8)); + } + + @Test + public void decode() { + assertThat(CompactEncoding.decode(Bytes.of(0x11, 0x23, 0x45))) + .isEqualTo(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05)); + assertThat(CompactEncoding.decode(Bytes.of(0x00, 0x01, 0x23, 0x45))) + .isEqualTo(Bytes.of(0x00, 0x01, 0x02, 0x03, 0x04, 0x05)); + assertThat(CompactEncoding.decode(Bytes.of(0x20, 0x0f, 0x1c, 0xb8))) + .isEqualTo(Bytes.of(0x00, 0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10)); + assertThat(CompactEncoding.decode(Bytes.of(0x3f, 0x1c, 0xb8))) + .isEqualTo(Bytes.of(0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10)); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/RocksDBKeyValueStorageTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/RocksDBKeyValueStorageTest.java new file mode 100644 index 00000000000..111d6157745 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/RocksDBKeyValueStorageTest.java @@ -0,0 +1,39 @@ +/* + * 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.trie; + +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.RocksDBConfiguration; +import org.hyperledger.besu.storage.RocksDBConfigurationBuilder; +import org.hyperledger.besu.storage.RocksDBKeyValueStorage; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class RocksDBKeyValueStorageTest extends AbstractKeyValueStorageTest { + + @Rule public final TemporaryFolder folder = new TemporaryFolder(); + + @Override + protected KeyValueStorage createStore() throws Exception { + return new RocksDBKeyValueStorage(config()); + } + + private RocksDBConfiguration config() throws Exception { + return new RocksDBConfigurationBuilder().databaseDir(folder.newFolder().toPath()).build(); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/SimpleMerklePatriciaTrieTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/SimpleMerklePatriciaTrieTest.java new file mode 100644 index 00000000000..bcf4ad715b8 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/SimpleMerklePatriciaTrieTest.java @@ -0,0 +1,27 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; + +import java.nio.charset.Charset; + +public class SimpleMerklePatriciaTrieTest extends AbstractMerklePatriciaTrieTest { + @Override + protected MerklePatriciaTrie createTrie() { + return new SimpleMerklePatriciaTrie<>( + value -> (value != null) ? Bytes.wrap(value.getBytes(Charset.forName("UTF-8"))) : null); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/StoredMerklePatriciaTrieTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/StoredMerklePatriciaTrieTest.java new file mode 100644 index 00000000000..b4a029a1d20 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/StoredMerklePatriciaTrieTest.java @@ -0,0 +1,215 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.RocksDBConfiguration; +import org.hyperledger.besu.storage.RocksDBConfigurationBuilder; +import org.hyperledger.besu.storage.RocksDBKeyValueStorage; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; + +public class StoredMerklePatriciaTrieTest extends AbstractMerklePatriciaTrieTest { + private KeyValueStorage keyValueStore; + private MerkleStorage merkleStorage; + private Function valueSerializer; + private Function valueDeserializer; + @Rule + public final TemporaryFolder folder = new TemporaryFolder(); + + protected KeyValueStorage createStore() { + try { + return new RocksDBKeyValueStorage(config()); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private RocksDBConfiguration config() throws IOException { + return new RocksDBConfigurationBuilder().databaseDir(folder.newFolder().toPath()).build(); + } + + @Override + protected MerklePatriciaTrie createTrie() { + keyValueStore = createStore(); + merkleStorage = new KeyValueMerkleStorage(keyValueStore); + valueSerializer = + value -> (value != null) ? Bytes.wrap(value.getBytes(StandardCharsets.UTF_8)) : null; + valueDeserializer = bytes -> new String(bytes.toArrayUnsafe(), StandardCharsets.UTF_8); + return new StoredMerklePatriciaTrie<>(merkleStorage::get, valueSerializer, valueDeserializer); + } + + @Test + public void putEmpty() { + final Bytes key0 = Bytes.of(1, 9, 8, 9); + // Push some values into the trie and commit changes so nodes are persisted + final String value0 = ""; + trie.put(key0, value0); + // put data into pendingUpdates + trie.commit(merkleStorage::put); + assertThatRuntimeException().isThrownBy(() -> trie.get(key0)) + .withMessageContaining("leaf has null value"); + } + + @Test + public void canReloadTrieFromHash() { + final Bytes key1 = Bytes.of(1, 5, 8, 9); + final Bytes key2 = Bytes.of(1, 6, 1, 2); + final Bytes key3 = Bytes.of(1, 6, 1, 3); + + // Push some values into the trie and commit changes so nodes are persisted + final String value1 = "value1"; + trie.put(key1, value1); + final Bytes32 hash1 = trie.getRootHash(); + // put data into pendingUpdates + trie.commit(merkleStorage::put); + + final String value2 = "value2"; + trie.put(key2, value2); + final String value3 = "value3"; + trie.put(key3, value3); + final Bytes32 hash2 = trie.getRootHash(); + // put data into pendingUpdates + trie.commit(merkleStorage::put); + + final String value4 = "value4"; + trie.put(key1, value4); + final Bytes32 hash3 = trie.getRootHash(); + // put data into pendingUpdates + trie.commit(merkleStorage::put); + + // Check the root hashes for 3 tries are all distinct + assertThat(hash1).isNotEqualTo(hash2); + assertThat(hash1).isNotEqualTo(hash3); + assertThat(hash2).isNotEqualTo(hash3); + // And that we can retrieve the last value we set for key1 + assertThat(trie.get(key1)).isEqualTo(Optional.of("value4")); + + // Create new tries from root hashes and check that we find expected values + trie = + new StoredMerklePatriciaTrie<>( + merkleStorage::get, hash1, valueSerializer, valueDeserializer); + assertThat(trie.get(key1)).isEqualTo(Optional.of("value1")); + assertThat(trie.get(key2)).isEqualTo(Optional.empty()); + assertThat(trie.get(key3)).isEqualTo(Optional.empty()); + + trie = + new StoredMerklePatriciaTrie<>( + merkleStorage::get, hash2, valueSerializer, valueDeserializer); + assertThat(trie.get(key1)).isEqualTo(Optional.of("value1")); + assertThat(trie.get(key2)).isEqualTo(Optional.of("value2")); + assertThat(trie.get(key3)).isEqualTo(Optional.of("value3")); + + trie = + new StoredMerklePatriciaTrie<>( + merkleStorage::get, hash3, valueSerializer, valueDeserializer); + assertThat(trie.get(key1)).isEqualTo(Optional.of("value4")); + assertThat(trie.get(key2)).isEqualTo(Optional.of("value2")); + assertThat(trie.get(key3)).isEqualTo(Optional.of("value3")); + + // Commit changes to storage, and create new tries from roothash and new storage instance + merkleStorage.commit(); + final MerkleStorage newMerkleStorage = new KeyValueMerkleStorage(keyValueStore); + trie = + new StoredMerklePatriciaTrie<>( + newMerkleStorage::get, hash1, valueSerializer, valueDeserializer); + assertThat(trie.get(key1)).isEqualTo(Optional.of("value1")); + assertThat(trie.get(key2)).isEqualTo(Optional.empty()); + assertThat(trie.get(key3)).isEqualTo(Optional.empty()); + + trie = + new StoredMerklePatriciaTrie<>( + newMerkleStorage::get, hash2, valueSerializer, valueDeserializer); + assertThat(trie.get(key1)).isEqualTo(Optional.of("value1")); + assertThat(trie.get(key2)).isEqualTo(Optional.of("value2")); + assertThat(trie.get(key3)).isEqualTo(Optional.of("value3")); + + trie = + new StoredMerklePatriciaTrie<>( + newMerkleStorage::get, hash3, valueSerializer, valueDeserializer); + assertThat(trie.get(key1)).isEqualTo(Optional.of("value4")); + assertThat(trie.get(key2)).isEqualTo(Optional.of("value2")); + assertThat(trie.get(key3)).isEqualTo(Optional.of("value3")); + final Bytes key4 = Bytes.of(1,3,4,6,7,9); + final Bytes key5 = Bytes.of(1,3,4,6,3,9); + final Bytes key6 = Bytes.of(1,3,4,6,8,9); + final Bytes key7= Bytes.of(2); + final Bytes key8= Bytes32.random(); + final Bytes key9= Bytes.wrap(key8, key7); + trie.put(key4, "value5"); + trie.put(key5, "value6"); + trie.put(key6, "value7"); + trie.put(key5, "value8"); + trie.put(key7, "value9"); + trie.put(key8, "value10"); + trie.put(key9, "value11"); + Random r = new SecureRandom(); + List rl = new ArrayList<>(); + for (int i =1 ; i<= 1000; i++) { + byte[] array = new byte[i%256 + 8]; + r.nextBytes(array); + Bytes bytes = Bytes.wrap(array); + rl.add(bytes); + trie.put(bytes, UUID.randomUUID().toString()); + } + rl.addAll(Arrays.asList(key1,key2,key3,key4,key5,key6,key7, key8, key9)); + trie.commit(merkleStorage::put); + merkleStorage.commit(); + + List keys = new ArrayList<>(); + trie.visitAll((N) -> { + if (N instanceof BranchNode && N.getValue().isPresent()) { + Bytes k = CompactEncoding.pathToBytes( + Bytes.concatenate(N.getLocation().orElse(Bytes.EMPTY), + Bytes.of(CompactEncoding.LEAF_TERMINATOR))); + keys.add(k); + } + + if (N instanceof LeafNode) { + Bytes k = CompactEncoding.pathToBytes( + Bytes.concatenate(N.getLocation().orElse(Bytes.EMPTY), + N.getPath())); + keys.add(k); + } + }); + + Collections.sort(keys); + Collections.sort(rl); + rl = rl.stream().distinct().collect(Collectors.toList()); + assertThat(trie.get(key7)).isEqualTo(Optional.of("value9")); + assertThat(keys.size()).isEqualTo(rl.size()); + assertThat(keys).isEqualTo(rl); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java new file mode 100644 index 00000000000..95bf8829bdd --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java @@ -0,0 +1,143 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.crypto.Hash; +import org.hyperledger.besu.ethereum.trie.TrieIterator.LeafHandler; +import org.hyperledger.besu.ethereum.trie.TrieIterator.State; +import org.junit.Test; +import org.mockito.InOrder; + +import java.nio.charset.StandardCharsets; +import java.util.NavigableSet; +import java.util.Random; +import java.util.TreeSet; + +import static org.hyperledger.besu.ethereum.trie.CompactEncoding.bytesToPath; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +public class TrieIteratorTest { + + private static final Bytes32 KEY_HASH1 = + Bytes32.fromHexString("0x5555555555555555555555555555555555555555555555555555555555555555"); + private static final Bytes32 KEY_HASH2 = + Bytes32.fromHexString("0x5555555555555555555555555555555555555555555555555555555555555556"); + private static final Bytes PATH1 = bytesToPath(KEY_HASH1); + private static final Bytes PATH2 = bytesToPath(KEY_HASH2); + + @SuppressWarnings("unchecked") + private final LeafHandler leafHandler = mock(LeafHandler.class); + + private final DefaultNodeFactory nodeFactory = + new DefaultNodeFactory<>(this::valueSerializer); + private final TrieIterator iterator = new TrieIterator<>(leafHandler, false); + + private Bytes valueSerializer(final String value) { + return Bytes.wrap(value.getBytes(StandardCharsets.UTF_8)); + } + + @Test + public void shouldCallLeafHandlerWhenRootNodeIsALeaf() { + final Node leaf = nodeFactory.createLeaf(bytesToPath(KEY_HASH1), "Leaf"); + leaf.accept(iterator, PATH1); + + verify(leafHandler).onLeaf(KEY_HASH1, leaf); + } + + @Test + public void shouldNotNotifyLeafHandlerOfNullNodes() { + NullNode.instance().accept(iterator, PATH1); + + verifyNoInteractions(leafHandler); + } + + @Test + public void shouldConcatenatePathAndVisitChildOfExtensionNode() { + final Node leaf = nodeFactory.createLeaf(PATH1.slice(10), "Leaf"); + final Node extension = nodeFactory.createExtension(PATH1.slice(0, 10), leaf); + extension.accept(iterator, PATH1); + verify(leafHandler).onLeaf(KEY_HASH1, leaf); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldVisitEachChildOfABranchNode() { + when(leafHandler.onLeaf(any(Bytes32.class), any(Node.class))).thenReturn(State.CONTINUE); + final Node root = + NullNode.instance() + .accept(new PutVisitor<>(nodeFactory, "Leaf 1"), PATH1) + .accept(new PutVisitor<>(nodeFactory, "Leaf 2"), PATH2); + root.accept(iterator, PATH1); + + final InOrder inOrder = inOrder(leafHandler); + inOrder.verify(leafHandler).onLeaf(eq(KEY_HASH1), any(Node.class)); + inOrder.verify(leafHandler).onLeaf(eq(KEY_HASH2), any(Node.class)); + verifyNoMoreInteractions(leafHandler); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldStopIteratingChildrenOfBranchWhenLeafHandlerReturnsStop() { + when(leafHandler.onLeaf(any(Bytes32.class), any(Node.class))).thenReturn(State.STOP); + final Node root = + NullNode.instance() + .accept(new PutVisitor<>(nodeFactory, "Leaf 1"), PATH1) + .accept(new PutVisitor<>(nodeFactory, "Leaf 2"), PATH2); + root.accept(iterator, PATH1); + + verify(leafHandler).onLeaf(eq(KEY_HASH1), any(Node.class)); + verifyNoMoreInteractions(leafHandler); + } + + @Test + @SuppressWarnings({"unchecked", "MathAbsoluteRandom"}) + public void shouldIterateArbitraryStructureAccurately() { + Node root = NullNode.instance(); + final NavigableSet expectedKeyHashes = new TreeSet<>(); + final Random random = new Random(-5407159858935967790L); + Bytes32 startAtHash = Bytes32.ZERO; + Bytes32 stopAtHash = Bytes32.ZERO; + final int totalNodes = Math.abs(random.nextInt(1000)); + final int startNodeNumber = random.nextInt(Math.max(1, totalNodes - 1)); + final int stopNodeNumber = random.nextInt(Math.max(1, totalNodes - 1)); + for (int i = 0; i < totalNodes; i++) { + final Bytes32 keyHash = + Hash.keccak256(UInt256.valueOf(Math.abs(random.nextInt(Integer.MAX_VALUE)))); + root = root.accept(new PutVisitor<>(nodeFactory, "Value"), bytesToPath(keyHash)); + expectedKeyHashes.add(keyHash); + if (i == startNodeNumber) { + startAtHash = keyHash; + } else if (i == stopNodeNumber) { + stopAtHash = keyHash; + } + } + + final Bytes32 actualStopAtHash = + stopAtHash.compareTo(startAtHash) >= 0 ? stopAtHash : startAtHash; + when(leafHandler.onLeaf(any(Bytes32.class), any(Node.class))).thenReturn(State.CONTINUE); + when(leafHandler.onLeaf(eq(actualStopAtHash), any(Node.class))).thenReturn(State.STOP); + root.accept(iterator, bytesToPath(startAtHash)); + final InOrder inOrder = inOrder(leafHandler); + expectedKeyHashes + .subSet(startAtHash, true, actualStopAtHash, true) + .forEach(keyHash -> inOrder.verify(leafHandler).onLeaf(eq(keyHash), any(Node.class))); + verifyNoMoreInteractions(leafHandler); + } +} diff --git a/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java new file mode 100644 index 00000000000..4f5521d3f36 --- /dev/null +++ b/state-trie-jdk8/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java @@ -0,0 +1,245 @@ +/* + * 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.trie; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.storage.InMemoryKeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorage; +import org.hyperledger.besu.storage.KeyValueStorageTransaction; +import org.junit.Test; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TrieNodeDecoderTest { + + @Test + public void decodeNodes() { + final InMemoryKeyValueStorage storage = new InMemoryKeyValueStorage(); + + // Build a small trie + final MerklePatriciaTrie trie = + new StoredMerklePatriciaTrie<>( + new BytesToByteNodeLoader(storage), Function.identity(), Function.identity()); + trie.put(Bytes.fromHexString("0x100000"), Bytes.of(1)); + trie.put(Bytes.fromHexString("0x200000"), Bytes.of(2)); + trie.put(Bytes.fromHexString("0x300000"), Bytes.of(3)); + + trie.put(Bytes.fromHexString("0x110000"), Bytes.of(10)); + trie.put(Bytes.fromHexString("0x210000"), Bytes.of(20)); + // Create large leaf node that will not be inlined + trie.put( + Bytes.fromHexString("0x310000"), + Bytes.fromHexString("0x11223344556677889900112233445566778899")); + + // Save nodes to storage + final KeyValueStorageTransaction tx = storage.startTransaction(); + trie.commit((location, key, value) -> tx.put(key.toArrayUnsafe(), value.toArrayUnsafe())); + tx.commit(); + + // Get and flatten root node + final Bytes rootNodeRlp = Bytes.wrap(storage.get(trie.getRootHash().toArrayUnsafe()).get()); + final List> nodes = TrieNodeDecoder.decodeNodes(null, rootNodeRlp); + // The full trie hold 10 nodes, the branch node starting with 0x3... holding 2 values will be a + // hash + // referenced node and so its 2 child nodes will be missing + assertThat(nodes.size()).isEqualTo(8); + + // Collect and check values + final List actualValues = + nodes.stream() + .filter(n -> !n.isReferencedByHash()) + .map(Node::getValue) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + assertThat(actualValues) + .containsExactlyInAnyOrder(Bytes.of(1), Bytes.of(10), Bytes.of(2), Bytes.of(20)); + } + + @Test + public void breadthFirstDecode_smallTrie() { + final InMemoryKeyValueStorage storage = new InMemoryKeyValueStorage(); + + // Build a small trie + final MerklePatriciaTrie trie = + new StoredMerklePatriciaTrie<>( + new BytesToByteNodeLoader(storage), Function.identity(), Function.identity()); + trie.put(Bytes.fromHexString("0x100000"), Bytes.of(1)); + trie.put(Bytes.fromHexString("0x200000"), Bytes.of(2)); + trie.put(Bytes.fromHexString("0x300000"), Bytes.of(3)); + + trie.put(Bytes.fromHexString("0x110000"), Bytes.of(10)); + trie.put(Bytes.fromHexString("0x210000"), Bytes.of(20)); + trie.put(Bytes.fromHexString("0x310000"), Bytes.of(30)); + + // Save nodes to storage + final KeyValueStorageTransaction tx = storage.startTransaction(); + trie.commit((location, key, value) -> tx.put(key.toArrayUnsafe(), value.toArrayUnsafe())); + tx.commit(); + + // First layer should just be the root node + final List> depth0Nodes = + TrieNodeDecoder.breadthFirstDecoder( + new BytesToByteNodeLoader(storage), trie.getRootHash(), 0) + .collect(Collectors.toList()); + + assertThat(depth0Nodes.size()).isEqualTo(1); + final Node rootNode = depth0Nodes.get(0); + assertThat(rootNode.getHash()).isEqualTo(trie.getRootHash()); + + // Decode first 2 levels + final List> depth0And1Nodes = + (TrieNodeDecoder.breadthFirstDecoder( + new BytesToByteNodeLoader(storage), trie.getRootHash(), 1) + .collect(Collectors.toList())); + final int secondLevelNodeCount = 3; + final int expectedNodeCount = secondLevelNodeCount + 1; + assertThat(depth0And1Nodes.size()).isEqualTo(expectedNodeCount); + // First node should be root node + assertThat(depth0And1Nodes.get(0).getHash()).isEqualTo(rootNode.getHash()); + // Subsequent nodes should be children of root node + final List expectedNodesHashes = + rootNode.getChildren().stream() + .filter(n -> !Objects.equals(n, NullNode.instance())) + .map(Node::getHash) + .collect(Collectors.toList()); + final List actualNodeHashes = + depth0And1Nodes.subList(1, expectedNodeCount).stream() + .map(Node::getHash) + .collect(Collectors.toList()); + assertThat(actualNodeHashes).isEqualTo(expectedNodesHashes); + + // Decode full trie + final List> allNodes = + TrieNodeDecoder.breadthFirstDecoder(new BytesToByteNodeLoader(storage), trie.getRootHash()) + .collect(Collectors.toList()); + assertThat(allNodes.size()).isEqualTo(10); + // Collect and check values + final List actualValues = + allNodes.stream() + .map(Node::getValue) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + assertThat(actualValues) + .containsExactly( + Bytes.of(1), Bytes.of(10), Bytes.of(2), Bytes.of(20), Bytes.of(3), Bytes.of(30)); + } + + @Test + public void breadthFirstDecode_partialTrie() { + final InMemoryKeyValueStorage fullStorage = new InMemoryKeyValueStorage(); + final InMemoryKeyValueStorage partialStorage = new InMemoryKeyValueStorage(); + + // Build a small trie + final MerklePatriciaTrie trie = + new StoredMerklePatriciaTrie<>( + new BytesToByteNodeLoader(fullStorage), Function.identity(), Function.identity()); + final Random random = new Random(1); + for (int i = 0; i < 30; i++) { + final byte[] key = new byte[4]; + final byte[] val = new byte[4]; + random.nextBytes(key); + random.nextBytes(val); + trie.put(Bytes.wrap(key), Bytes.wrap(val)); + } + final KeyValueStorageTransaction tx = fullStorage.startTransaction(); + trie.commit((location, key, value) -> tx.put(key.toArrayUnsafe(), value.toArrayUnsafe())); + tx.commit(); + + // Get root node + final Node rootNode = + TrieNodeDecoder.breadthFirstDecoder( + new BytesToByteNodeLoader(fullStorage), trie.getRootHash()) + .findFirst() + .get(); + + // Decode partially available trie + final KeyValueStorageTransaction partialTx = partialStorage.startTransaction(); + partialTx.put(trie.getRootHash().toArrayUnsafe(), rootNode.getRlp().toArrayUnsafe()); + partialTx.commit(); + final List> allDecodableNodes = + TrieNodeDecoder.breadthFirstDecoder( + new BytesToByteNodeLoader(partialStorage), trie.getRootHash()) + .collect(Collectors.toList()); + assertThat(allDecodableNodes.size()).isGreaterThanOrEqualTo(1); + assertThat(allDecodableNodes.get(0).getHash()).isEqualTo(rootNode.getHash()); + } + + @Test + public void breadthFirstDecode_emptyTrie() { + final List> result = + TrieNodeDecoder.breadthFirstDecoder( + (l, h) -> Optional.empty(), MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH) + .collect(Collectors.toList()); + assertThat(result.size()).isEqualTo(0); + } + + @Test + public void breadthFirstDecode_singleNodeTrie() { + final InMemoryKeyValueStorage storage = new InMemoryKeyValueStorage(); + + final MerklePatriciaTrie trie = + new StoredMerklePatriciaTrie<>( + new BytesToByteNodeLoader(storage), Function.identity(), Function.identity()); + trie.put(Bytes.fromHexString("0x100000"), Bytes.of(1)); + + // Save nodes to storage + final KeyValueStorageTransaction tx = storage.startTransaction(); + trie.commit((location, key, value) -> tx.put(key.toArrayUnsafe(), value.toArrayUnsafe())); + tx.commit(); + + final List> result = + TrieNodeDecoder.breadthFirstDecoder(new BytesToByteNodeLoader(storage), trie.getRootHash()) + .collect(Collectors.toList()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getValue()).contains(Bytes.of(1)); + final Bytes actualPath = CompactEncoding.pathToBytes(result.get(0).getPath()); + assertThat(actualPath).isEqualTo(Bytes.fromHexString("0x100000")); + } + + @Test + public void breadthFirstDecode_unknownTrie() { + + final Bytes32 randomRootHash = Bytes32.fromHexStringLenient("0x12"); + final List> result = + TrieNodeDecoder.breadthFirstDecoder((l, h) -> Optional.empty(), randomRootHash) + .collect(Collectors.toList()); + assertThat(result.size()).isEqualTo(0); + } + + private static class BytesToByteNodeLoader implements NodeLoader { + + private final KeyValueStorage storage; + + private BytesToByteNodeLoader(final KeyValueStorage storage) { + this.storage = storage; + } + + @Override + public Optional getNode(final Bytes location, final Bytes32 hash) { + final byte[] value = storage.get(hash.toArrayUnsafe()).orElse(null); + return value == null ? Optional.empty() : Optional.of(Bytes.wrap(value)); + } + } +}