# Conflicts: # source/base/pom.xml # source/binary-proto/pom.xml # source/consensus/consensus-bftsmart/pom.xml # source/consensus/consensus-framework/pom.xml # source/consensus/consensus-mq/pom.xml # source/consensus/pom.xml # source/contract/contract-framework/pom.xml # source/contract/contract-jvm/pom.xml # source/contract/contract-maven-plugin/pom.xml # source/contract/contract-samples/pom.xml # source/contract/pom.xml # source/crypto/crypto-adv/pom.xml # source/crypto/crypto-classic/pom.xml # source/crypto/crypto-framework/pom.xml # source/crypto/crypto-pki/pom.xml # source/crypto/crypto-sm/pom.xml # source/crypto/pom.xml # source/deployment/deployment-gateway/pom.xml # source/deployment/deployment-peer/pom.xml # source/deployment/pom.xml # source/gateway/pom.xml # source/ledger/ledger-core/pom.xml # source/ledger/ledger-model/pom.xml # source/ledger/ledger-rpc/pom.xml # source/ledger/pom.xml # source/peer/pom.xml # source/pom.xml # source/runtime/pom.xml # source/runtime/runtime-context/pom.xml # source/runtime/runtime-modular-booter/pom.xml # source/runtime/runtime-modular/pom.xml # source/sdk/pom.xml # source/sdk/sdk-base/pom.xml # source/sdk/sdk-client/pom.xml # source/sdk/sdk-samples/pom.xml # source/storage/pom.xml # source/storage/storage-composite/pom.xml # source/storage/storage-redis/pom.xml # source/storage/storage-rocksdb/pom.xml # source/storage/storage-service/pom.xml # source/test/pom.xml # source/test/test-consensus-client/pom.xml # source/test/test-consensus-node/pom.xml # source/test/test-integration/pom.xml # source/test/test-ledger-core/pom.xml # source/tools/pom.xml # source/tools/tools-capability/pom.xml # source/tools/tools-initializer-booter/pom.xml # source/tools/tools-initializer/pom.xml # source/tools/tools-keygen-booter/pom.xml # source/tools/tools-keygen/pom.xml # source/tools/tools-mocker/pom.xml # source/utils/pom.xml # source/utils/utils-common/pom.xml # source/utils/utils-http/pom.xml # source/utils/utils-serialize/pom.xml # source/utils/utils-test/pom.xml # source/utils/utils-web-server/pom.xml # source/utils/utils-web/pom.xmltags/1.1.0
| @@ -0,0 +1,25 @@ | |||
| package com.jd.blockchain.contract.jvm; | |||
| import com.jd.blockchain.contract.ContractType; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| public class InstantiatedContractCode<T> extends AbstractContractCode { | |||
| private T instance; | |||
| public InstantiatedContractCode(Bytes address, long version, Class<T> delaredInterface, T instance) { | |||
| super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); | |||
| this.instance = instance; | |||
| } | |||
| private static ContractDefinition resolveContractDefinition(Class<?> declaredIntf, Class<?> implementedClass) { | |||
| ContractType contractType = ContractType.resolve(declaredIntf); | |||
| return new ContractDefinition(contractType, implementedClass); | |||
| } | |||
| @Override | |||
| protected T getContractInstance() { | |||
| return instance; | |||
| } | |||
| } | |||
| @@ -1,12 +1,11 @@ | |||
| package com.jd.blockchain.ledger.core; | |||
| import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
| import com.jd.blockchain.binaryproto.PrimitiveType; | |||
| import com.jd.blockchain.crypto.HashDigest; | |||
| import com.jd.blockchain.crypto.PubKey; | |||
| import com.jd.blockchain.ledger.AccountHeader; | |||
| import com.jd.blockchain.ledger.BytesValue; | |||
| import com.jd.blockchain.ledger.BytesData; | |||
| import com.jd.blockchain.ledger.BytesValue; | |||
| import com.jd.blockchain.ledger.KVDataEntry; | |||
| import com.jd.blockchain.ledger.KVDataObject; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| @@ -43,16 +42,85 @@ public class DataAccount implements AccountHeader, MerkleProvable { | |||
| } | |||
| /** | |||
| * Create or update the value associated the specified key if the version | |||
| * checking is passed.<br> | |||
| * | |||
| * The value of the key will be updated only if it's latest version equals the | |||
| * specified version argument. <br> | |||
| * If the key doesn't exist, the version checking will be ignored, and key will | |||
| * be created with a new sequence number as id. <br> | |||
| * It also could specify the version argument to -1 to ignore the version | |||
| * checking. | |||
| * <p> | |||
| * If updating is performed, the version of the key increase by 1. <br> | |||
| * If creating is performed, the version of the key initialize by 0. <br> | |||
| * | |||
| * @param key The key of data; | |||
| * @param value The value of data; | |||
| * @param version The expected version of the key. | |||
| * @return The new version of the key. <br> | |||
| * If the key is new created success, then return 0; <br> | |||
| * If the key is updated success, then return the new version;<br> | |||
| * If this operation fail by version checking or other reason, then | |||
| * return -1; | |||
| */ | |||
| public long setBytes(Bytes key, BytesValue value, long version) { | |||
| return baseAccount.setBytes(key, value, version); | |||
| } | |||
| /** | |||
| * Create or update the value associated the specified key if the version | |||
| * checking is passed.<br> | |||
| * | |||
| * The value of the key will be updated only if it's latest version equals the | |||
| * specified version argument. <br> | |||
| * If the key doesn't exist, the version checking will be ignored, and key will | |||
| * be created with a new sequence number as id. <br> | |||
| * It also could specify the version argument to -1 to ignore the version | |||
| * checking. | |||
| * <p> | |||
| * If updating is performed, the version of the key increase by 1. <br> | |||
| * If creating is performed, the version of the key initialize by 0. <br> | |||
| * | |||
| * @param key The key of data; | |||
| * @param value The value of data; | |||
| * @param version The expected version of the key. | |||
| * @return The new version of the key. <br> | |||
| * If the key is new created success, then return 0; <br> | |||
| * If the key is updated success, then return the new version;<br> | |||
| * If this operation fail by version checking or other reason, then | |||
| * return -1; | |||
| */ | |||
| public long setBytes(Bytes key, String value, long version) { | |||
| BytesValue bytesValue = BytesData.fromText(value); | |||
| return baseAccount.setBytes(key, bytesValue, version); | |||
| } | |||
| /** | |||
| * Create or update the value associated the specified key if the version | |||
| * checking is passed.<br> | |||
| * | |||
| * The value of the key will be updated only if it's latest version equals the | |||
| * specified version argument. <br> | |||
| * If the key doesn't exist, the version checking will be ignored, and key will | |||
| * be created with a new sequence number as id. <br> | |||
| * It also could specify the version argument to -1 to ignore the version | |||
| * checking. | |||
| * <p> | |||
| * If updating is performed, the version of the key increase by 1. <br> | |||
| * If creating is performed, the version of the key initialize by 0. <br> | |||
| * | |||
| * @param key The key of data; | |||
| * @param value The value of data; | |||
| * @param version The expected version of the key. | |||
| * @return The new version of the key. <br> | |||
| * If the key is new created success, then return 0; <br> | |||
| * If the key is updated success, then return the new version;<br> | |||
| * If this operation fail by version checking or other reason, then | |||
| * return -1; | |||
| */ | |||
| public long setBytes(Bytes key, byte[] value, long version) { | |||
| BytesValue bytesValue = BytesData.fromBytes(value); | |||
| return baseAccount.setBytes(key, bytesValue, version); | |||
| @@ -121,6 +189,29 @@ public class DataAccount implements AccountHeader, MerkleProvable { | |||
| public BytesValue getBytes(Bytes key, long version) { | |||
| return baseAccount.getBytes(key, version); | |||
| } | |||
| /** | |||
| * @param key | |||
| * @param version | |||
| * @return | |||
| */ | |||
| public KVDataEntry getDataEntry(String key, long version) { | |||
| return getDataEntry(Bytes.fromString(key), version); | |||
| } | |||
| /** | |||
| * @param key | |||
| * @param version | |||
| * @return | |||
| */ | |||
| public KVDataEntry getDataEntry(Bytes key, long version) { | |||
| BytesValue value = baseAccount.getBytes(key, version); | |||
| if (value == null) { | |||
| return new KVDataObject(key.toUTF8String(), -1, null); | |||
| }else { | |||
| return new KVDataObject(key.toUTF8String(), version, value); | |||
| } | |||
| } | |||
| /** | |||
| * return the specified index's KVDataEntry; | |||
| @@ -131,7 +222,7 @@ public class DataAccount implements AccountHeader, MerkleProvable { | |||
| */ | |||
| public KVDataEntry[] getDataEntries(int fromIndex, int count) { | |||
| if (getDataEntriesTotalCount() == 0 || count == 0) { | |||
| if (count == 0 || getDataEntriesTotalCount() == 0) { | |||
| return null; | |||
| } | |||
| @@ -168,7 +168,7 @@ public class MerkleDataSet implements Transactional, MerkleProvable { | |||
| */ | |||
| public String getKeyAtIndex(int fromIndex) { | |||
| MerkleDataNode dataNode = merkleTree.getData(fromIndex); | |||
| return new String(dataNode.getKey().toBytes()); | |||
| return dataNode.getKey().toUTF8String(); | |||
| } | |||
| @@ -20,6 +20,8 @@ import com.jd.blockchain.utils.QueryUtil; | |||
| public class LedgerQueryService implements BlockchainQueryService { | |||
| private static final KVDataEntry[] EMPTY_ENTRIES = new KVDataEntry[0]; | |||
| private LedgerService ledgerService; | |||
| public LedgerQueryService(LedgerService ledgerService) { | |||
| @@ -254,7 +256,7 @@ public class LedgerQueryService implements BlockchainQueryService { | |||
| @Override | |||
| public KVDataEntry[] getDataEntries(HashDigest ledgerHash, String address, String... keys) { | |||
| if (keys == null || keys.length == 0) { | |||
| return null; | |||
| return EMPTY_ENTRIES; | |||
| } | |||
| LedgerRepository ledger = ledgerService.getLedger(ledgerHash); | |||
| LedgerBlock block = ledger.getLatestBlock(); | |||
| @@ -266,7 +268,7 @@ public class LedgerQueryService implements BlockchainQueryService { | |||
| for (int i = 0; i < entries.length; i++) { | |||
| final String currKey = keys[i]; | |||
| ver = dataAccount.getDataVersion(Bytes.fromString(currKey)); | |||
| ver = dataAccount == null ? -1 : dataAccount.getDataVersion(Bytes.fromString(currKey)); | |||
| if (ver < 0) { | |||
| entries[i] = new KVDataObject(currKey, -1, null); | |||
| @@ -7,6 +7,7 @@ import com.jd.blockchain.ledger.BytesValue; | |||
| import com.jd.blockchain.ledger.DataAccountDoesNotExistException; | |||
| import com.jd.blockchain.ledger.DataAccountKVSetOperation; | |||
| import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; | |||
| import com.jd.blockchain.ledger.DataVersionConflictException; | |||
| import com.jd.blockchain.ledger.Operation; | |||
| import com.jd.blockchain.ledger.core.DataAccount; | |||
| import com.jd.blockchain.ledger.core.LedgerDataSet; | |||
| @@ -31,8 +32,12 @@ public class DataAccountKVSetOperationHandle implements OperationHandle { | |||
| throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); | |||
| } | |||
| KVWriteEntry[] writeSet = kvWriteOp.getWriteSet(); | |||
| long v = -1; | |||
| for (KVWriteEntry kvw : writeSet) { | |||
| account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); | |||
| v = account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); | |||
| if (v < 0) { | |||
| throw new DataVersionConflictException(); | |||
| } | |||
| } | |||
| return null; | |||
| } | |||
| @@ -3,10 +3,8 @@ package test.com.jd.blockchain.ledger; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| import com.jd.blockchain.contract.ContractType; | |||
| import com.jd.blockchain.contract.engine.ContractCode; | |||
| import com.jd.blockchain.contract.jvm.AbstractContractCode; | |||
| import com.jd.blockchain.contract.jvm.ContractDefinition; | |||
| import com.jd.blockchain.contract.jvm.InstantiatedContractCode; | |||
| import com.jd.blockchain.ledger.core.ContractAccount; | |||
| import com.jd.blockchain.ledger.core.impl.handles.AbtractContractEventHandle; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| @@ -21,30 +19,10 @@ public class ContractInvokingHandle extends AbtractContractEventHandle { | |||
| } | |||
| public <T> ContractCode setup(Bytes address, Class<T> contractIntf, T instance) { | |||
| ContractCodeInstance<T> contract = new ContractCodeInstance<T>(address, 0, contractIntf, instance); | |||
| InstantiatedContractCode<T> contract = new InstantiatedContractCode<T>(address, 0, contractIntf, instance); | |||
| contractInstances.put(address, contract); | |||
| return contract; | |||
| } | |||
| private static class ContractCodeInstance<T> extends AbstractContractCode { | |||
| private T instance; | |||
| public ContractCodeInstance(Bytes address, long version, Class<T> delaredInterface, T instance) { | |||
| super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); | |||
| this.instance = instance; | |||
| } | |||
| private static ContractDefinition resolveContractDefinition(Class<?> declaredIntf, Class<?> implementedClass) { | |||
| ContractType contractType = ContractType.resolve(declaredIntf); | |||
| return new ContractDefinition(contractType, implementedClass); | |||
| } | |||
| @Override | |||
| protected T getContractInstance() { | |||
| return instance; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,27 +1,60 @@ | |||
| package test.com.jd.blockchain.ledger; | |||
| import static com.jd.blockchain.transaction.ContractReturnValue.decode; | |||
| import static org.junit.Assert.assertArrayEquals; | |||
| import static org.junit.Assert.assertEquals; | |||
| import static org.junit.Assert.assertNotNull; | |||
| import static org.junit.Assert.assertNull; | |||
| import static org.junit.Assert.assertTrue; | |||
| import static org.mockito.Matchers.anyLong; | |||
| import static org.mockito.Matchers.anyString; | |||
| import static org.mockito.Mockito.times; | |||
| import static org.mockito.Mockito.verify; | |||
| import static org.mockito.Mockito.when; | |||
| import java.util.Random; | |||
| import org.junit.Test; | |||
| import org.mockito.Mockito; | |||
| import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
| import com.jd.blockchain.binaryproto.DataContractRegistry; | |||
| import com.jd.blockchain.crypto.HashDigest; | |||
| import com.jd.blockchain.ledger.*; | |||
| import com.jd.blockchain.ledger.core.*; | |||
| import com.jd.blockchain.ledger.BlockchainKeyGenerator; | |||
| import com.jd.blockchain.ledger.BlockchainKeypair; | |||
| import com.jd.blockchain.ledger.BytesData; | |||
| import com.jd.blockchain.ledger.BytesValue; | |||
| import com.jd.blockchain.ledger.DataAccountRegisterOperation; | |||
| import com.jd.blockchain.ledger.EndpointRequest; | |||
| import com.jd.blockchain.ledger.KVDataEntry; | |||
| import com.jd.blockchain.ledger.LedgerBlock; | |||
| import com.jd.blockchain.ledger.LedgerInitSetting; | |||
| import com.jd.blockchain.ledger.LedgerTransaction; | |||
| import com.jd.blockchain.ledger.NodeRequest; | |||
| import com.jd.blockchain.ledger.OperationResult; | |||
| import com.jd.blockchain.ledger.TransactionContent; | |||
| import com.jd.blockchain.ledger.TransactionContentBody; | |||
| import com.jd.blockchain.ledger.TransactionRequest; | |||
| import com.jd.blockchain.ledger.TransactionRequestBuilder; | |||
| import com.jd.blockchain.ledger.TransactionResponse; | |||
| import com.jd.blockchain.ledger.TransactionState; | |||
| import com.jd.blockchain.ledger.UserRegisterOperation; | |||
| import com.jd.blockchain.ledger.core.LedgerDataSet; | |||
| import com.jd.blockchain.ledger.core.LedgerEditor; | |||
| import com.jd.blockchain.ledger.core.LedgerRepository; | |||
| import com.jd.blockchain.ledger.core.LedgerService; | |||
| import com.jd.blockchain.ledger.core.LedgerTransactionContext; | |||
| import com.jd.blockchain.ledger.core.UserAccount; | |||
| import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; | |||
| import com.jd.blockchain.ledger.core.impl.LedgerManager; | |||
| import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; | |||
| import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; | |||
| import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; | |||
| import com.jd.blockchain.service.TransactionBatchResultHandle; | |||
| import com.jd.blockchain.storage.service.utils.MemoryKVStorage; | |||
| import com.jd.blockchain.transaction.BooleanValueHolder; | |||
| import com.jd.blockchain.transaction.TxBuilder; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| import org.junit.Test; | |||
| import org.mockito.Mockito; | |||
| import java.util.Random; | |||
| import static org.junit.Assert.*; | |||
| import static org.mockito.Matchers.anyLong; | |||
| import static org.mockito.Matchers.anyString; | |||
| import static org.mockito.Mockito.*; | |||
| public class ContractInvokingTest { | |||
| static { | |||
| @@ -32,6 +65,7 @@ public class ContractInvokingTest { | |||
| DataContractRegistry.register(EndpointRequest.class); | |||
| DataContractRegistry.register(TransactionResponse.class); | |||
| DataContractRegistry.register(UserRegisterOperation.class); | |||
| DataContractRegistry.register(DataAccountRegisterOperation.class); | |||
| } | |||
| private static final String LEDGER_KEY_PREFIX = "LDG://"; | |||
| @@ -45,7 +79,7 @@ public class ContractInvokingTest { | |||
| private MemoryKVStorage storage = new MemoryKVStorage(); | |||
| @Test | |||
| public void test() { | |||
| public void testNormal() { | |||
| // 初始化账本到指定的存储库; | |||
| HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); | |||
| @@ -69,7 +103,6 @@ public class ContractInvokingTest { | |||
| // 发布指定地址合约 | |||
| deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); | |||
| // 创建新区块的交易处理器; | |||
| LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
| LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
| @@ -122,9 +155,243 @@ public class ContractInvokingTest { | |||
| } | |||
| // @Test | |||
| public void testReadNewWritting() { | |||
| // 初始化账本到指定的存储库; | |||
| HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); | |||
| // 重新加载账本; | |||
| LedgerManager ledgerManager = new LedgerManager(); | |||
| LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); | |||
| // 创建合约处理器; | |||
| ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); | |||
| // 创建和加载合约实例; | |||
| BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); | |||
| Bytes contractAddress = contractKey.getAddress(); | |||
| TxTestContractImpl contractInstance = new TxTestContractImpl(); | |||
| contractInvokingHandle.setup(contractAddress, TxTestContract.class, contractInstance); | |||
| // 注册合约处理器; | |||
| DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); | |||
| opReg.insertAsTopPriority(contractInvokingHandle); | |||
| // 发布指定地址合约 | |||
| deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); | |||
| // 创建新区块的交易处理器; | |||
| LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
| LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
| // 加载合约 | |||
| LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||
| TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||
| opReg, ledgerManager); | |||
| String key = TxTestContractImpl.KEY; | |||
| String value = "VAL"; | |||
| TxBuilder txBuilder = new TxBuilder(ledgerHash); | |||
| BlockchainKeypair kpDataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| contractInstance.setDataAddress(kpDataAccount.getAddress()); | |||
| txBuilder.dataAccounts().register(kpDataAccount.getIdentity()); | |||
| TransactionRequestBuilder txReqBuilder1 = txBuilder.prepareRequest(); | |||
| txReqBuilder1.signAsEndpoint(parti0); | |||
| txReqBuilder1.signAsNode(parti0); | |||
| TransactionRequest txReq1 = txReqBuilder1.buildRequest(); | |||
| // 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
| txBuilder = new TxBuilder(ledgerHash); | |||
| TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
| BooleanValueHolder readableHolder = decode(contractProxy.testReadable()); | |||
| TransactionRequestBuilder txReqBuilder2 = txBuilder.prepareRequest(); | |||
| txReqBuilder2.signAsEndpoint(parti0); | |||
| txReqBuilder2.signAsNode(parti0); | |||
| TransactionRequest txReq2 = txReqBuilder2.buildRequest(); | |||
| TransactionResponse resp1 = txbatchProcessor.schedule(txReq1); | |||
| TransactionResponse resp2 = txbatchProcessor.schedule(txReq2); | |||
| // 提交区块; | |||
| TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); | |||
| txResultHandle.commit(); | |||
| BytesValue latestValue = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getBytes(key, | |||
| -1); | |||
| System.out.printf("latest value=[%s] %s \r\n", latestValue.getType(), latestValue.getValue().toUTF8String()); | |||
| boolean readable = readableHolder.get(); | |||
| assertTrue(readable); | |||
| LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); | |||
| assertEquals(preBlock.getHeight() + 1, latestBlock.getHeight()); | |||
| assertEquals(resp1.getBlockHeight(), latestBlock.getHeight()); | |||
| assertEquals(resp1.getBlockHash(), latestBlock.getHash()); | |||
| } | |||
| /** | |||
| * 验证在合约方法中写入数据账户时,如果版本校验失败是否会引发异常而导致回滚;<br> | |||
| * 期待正确的表现是引发异常而回滚当前交易; | |||
| */ | |||
| @Test | |||
| public void testRollbackWhileVersionConfliction() { | |||
| // 初始化账本到指定的存储库; | |||
| HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); | |||
| // 重新加载账本; | |||
| LedgerManager ledgerManager = new LedgerManager(); | |||
| LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); | |||
| // 创建合约处理器; | |||
| ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); | |||
| // 创建和加载合约实例; | |||
| BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); | |||
| Bytes contractAddress = contractKey.getAddress(); | |||
| TxTestContractImpl contractInstance = new TxTestContractImpl(); | |||
| contractInvokingHandle.setup(contractAddress, TxTestContract.class, contractInstance); | |||
| // 注册合约处理器; | |||
| DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); | |||
| opReg.insertAsTopPriority(contractInvokingHandle); | |||
| // 发布指定地址合约 | |||
| deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); | |||
| // 注册数据账户; | |||
| BlockchainKeypair kpDataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| contractInstance.setDataAddress(kpDataAccount.getAddress()); | |||
| registerDataAccount(ledgerRepo, ledgerManager, opReg, ledgerHash, kpDataAccount); | |||
| // 调用合约 | |||
| // 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
| buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { | |||
| @Override | |||
| public void buildTx(TxBuilder txBuilder) { | |||
| TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
| contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-0", | |||
| -1); | |||
| contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-0", | |||
| -1); | |||
| } | |||
| }); | |||
| // 预期数据都能够正常写入; | |||
| KVDataEntry kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", | |||
| 0); | |||
| KVDataEntry kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", | |||
| 0); | |||
| assertEquals(0, kv1.getVersion()); | |||
| assertEquals(0, kv2.getVersion()); | |||
| assertEquals("V1-0", kv1.getValue()); | |||
| assertEquals("V2-0", kv2.getValue()); | |||
| // 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
| buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { | |||
| @Override | |||
| public void buildTx(TxBuilder txBuilder) { | |||
| TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
| contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-1", | |||
| 0); | |||
| contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-1", | |||
| 0); | |||
| } | |||
| }); | |||
| // 预期数据都能够正常写入; | |||
| kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); | |||
| kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", 1); | |||
| assertEquals(1, kv1.getVersion()); | |||
| assertEquals(1, kv2.getVersion()); | |||
| assertEquals("V1-1", kv1.getValue()); | |||
| assertEquals("V2-1", kv2.getValue()); | |||
| // 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
| buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { | |||
| @Override | |||
| public void buildTx(TxBuilder txBuilder) { | |||
| TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
| contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-2", | |||
| 1); | |||
| contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-2", | |||
| 0); | |||
| } | |||
| }); | |||
| // 预期数据都能够正常写入; | |||
| kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); | |||
| assertEquals(1, kv1.getVersion()); | |||
| assertEquals("V1-1", kv1.getValue()); | |||
| kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 2); | |||
| assertEquals(-1, kv1.getVersion()); | |||
| assertEquals(null, kv1.getValue()); | |||
| } | |||
| private LedgerBlock buildBlock(LedgerRepository ledgerRepo, LedgerService ledgerService, | |||
| OperationHandleRegisteration opReg, TxDefinitor txDefinitor) { | |||
| LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
| LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
| LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||
| TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||
| opReg, ledgerService); | |||
| TxBuilder txBuilder = new TxBuilder(ledgerRepo.getHash()); | |||
| txDefinitor.buildTx(txBuilder); | |||
| TransactionRequest txReq = buildAndSignRequest(txBuilder, parti0, parti0); | |||
| TransactionResponse resp = txbatchProcessor.schedule(txReq); | |||
| // 提交区块; | |||
| TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); | |||
| txResultHandle.commit(); | |||
| LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); | |||
| assertNotNull(resp.getBlockHash()); | |||
| assertEquals(preBlock.getHeight() + 1, resp.getBlockHeight()); | |||
| return latestBlock; | |||
| } | |||
| private TransactionRequest buildAndSignRequest(TxBuilder txBuilder, BlockchainKeypair endpointKey, | |||
| BlockchainKeypair nodeKey) { | |||
| TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); | |||
| txReqBuilder.signAsEndpoint(endpointKey); | |||
| txReqBuilder.signAsNode(nodeKey); | |||
| TransactionRequest txReq = txReqBuilder.buildRequest(); | |||
| return txReq; | |||
| } | |||
| private void registerDataAccount(LedgerRepository ledgerRepo, LedgerManager ledgerManager, | |||
| DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair kpDataAccount) { | |||
| LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
| LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
| // 加载合约 | |||
| LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||
| TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||
| opReg, ledgerManager); | |||
| // 注册数据账户; | |||
| TxBuilder txBuilder = new TxBuilder(ledgerHash); | |||
| txBuilder.dataAccounts().register(kpDataAccount.getIdentity()); | |||
| TransactionRequestBuilder txReqBuilder1 = txBuilder.prepareRequest(); | |||
| txReqBuilder1.signAsEndpoint(parti0); | |||
| txReqBuilder1.signAsNode(parti0); | |||
| TransactionRequest txReq = txReqBuilder1.buildRequest(); | |||
| TransactionResponse resp = txbatchProcessor.schedule(txReq); | |||
| TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); | |||
| txResultHandle.commit(); | |||
| assertNotNull(resp.getBlockHash()); | |||
| assertEquals(TransactionState.SUCCESS, resp.getExecutionState()); | |||
| assertEquals(preBlock.getHeight() + 1, resp.getBlockHeight()); | |||
| } | |||
| private void deploy(LedgerRepository ledgerRepo, LedgerManager ledgerManager, | |||
| DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, | |||
| BlockchainKeypair contractKey) { | |||
| DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair contractKey) { | |||
| // 创建新区块的交易处理器; | |||
| LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
| LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
| @@ -193,4 +460,10 @@ public class ContractInvokingTest { | |||
| new Random().nextBytes(chainCode); | |||
| return chainCode; | |||
| } | |||
| public static interface TxDefinitor { | |||
| void buildTx(TxBuilder txBuilder); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| package test.com.jd.blockchain.ledger; | |||
| import com.jd.blockchain.binaryproto.DataContract; | |||
| import com.jd.blockchain.binaryproto.DataField; | |||
| import com.jd.blockchain.binaryproto.PrimitiveType; | |||
| @DataContract(code = 0x4010) | |||
| public interface KeyValueEntry { | |||
| @DataField(order = 1, primitiveType = PrimitiveType.TEXT) | |||
| String getKey(); | |||
| @DataField(order = 2, primitiveType = PrimitiveType.TEXT) | |||
| String getValue(); | |||
| @DataField(order = 3, primitiveType = PrimitiveType.INT64) | |||
| long getVersion(); | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| package test.com.jd.blockchain.ledger; | |||
| public class KeyValueObject implements KeyValueEntry { | |||
| private String key; | |||
| private String value; | |||
| private long version; | |||
| public KeyValueObject() { | |||
| } | |||
| public KeyValueObject(String key, String value, long version) { | |||
| this.key = key; | |||
| this.value = value; | |||
| this.version = version; | |||
| } | |||
| @Override | |||
| public String getKey() { | |||
| return key; | |||
| } | |||
| @Override | |||
| public String getValue() { | |||
| return value; | |||
| } | |||
| @Override | |||
| public long getVersion() { | |||
| return version; | |||
| } | |||
| public void setKey(String key) { | |||
| this.key = key; | |||
| } | |||
| public void setValue(String value) { | |||
| this.value = value; | |||
| } | |||
| public void setVersion(long version) { | |||
| this.version = version; | |||
| } | |||
| } | |||
| @@ -32,6 +32,38 @@ public class MerkleDataSetTest { | |||
| private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), | |||
| SMCryptoService.class.getName() }; | |||
| /** | |||
| * 测试存储的增长; | |||
| */ | |||
| @Test | |||
| public void testKeyIndex() { | |||
| CryptoProvider[] supportedProviders = new CryptoProvider[SUPPORTED_PROVIDERS.length]; | |||
| for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { | |||
| supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); | |||
| } | |||
| String keyPrefix = ""; | |||
| CryptoConfig cryptoConfig = new CryptoConfig(); | |||
| cryptoConfig.setSupportedProviders(supportedProviders); | |||
| cryptoConfig.setHashAlgorithm(ClassicAlgorithm.SHA256); | |||
| cryptoConfig.setAutoVerifyHash(true); | |||
| MemoryKVStorage storage = new MemoryKVStorage(); | |||
| MerkleDataSet mds = new MerkleDataSet(cryptoConfig, keyPrefix, storage, storage); | |||
| mds.setValue("A", "A".getBytes(), -1); | |||
| mds.setValue("B", "B".getBytes(), -1); | |||
| mds.setValue("C", "C".getBytes(), -1); | |||
| mds.commit(); | |||
| //校验 Key 的正确性; | |||
| assertEquals("A", mds.getKeyAtIndex(0)); | |||
| assertEquals("B", mds.getKeyAtIndex(1)); | |||
| assertEquals("C", mds.getKeyAtIndex(2)); | |||
| } | |||
| /** | |||
| * 测试存储的增长; | |||
| @@ -59,6 +91,7 @@ public class MerkleDataSetTest { | |||
| mds.commit(); | |||
| HashDigest root1 = mds.getRootHash(); | |||
| // 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量; | |||
| // 所以:3 项; | |||
| @@ -14,6 +14,7 @@ import com.jd.blockchain.ledger.BlockchainKeyGenerator; | |||
| import com.jd.blockchain.ledger.BlockchainKeypair; | |||
| import com.jd.blockchain.ledger.BytesValue; | |||
| import com.jd.blockchain.ledger.DataAccountRegisterOperation; | |||
| import com.jd.blockchain.ledger.DataVersionConflictException; | |||
| import com.jd.blockchain.ledger.EndpointRequest; | |||
| import com.jd.blockchain.ledger.LedgerBlock; | |||
| import com.jd.blockchain.ledger.LedgerInitSetting; | |||
| @@ -245,7 +246,7 @@ public class TransactionBatchProcessorTest { | |||
| } | |||
| @Test | |||
| public void testTxRollbackByVersionsConfliction() { | |||
| public void testTxRollbackByVersionsConflict() { | |||
| final MemoryKVStorage STORAGE = new MemoryKVStorage(); | |||
| // 初始化账本到指定的存储库; | |||
| @@ -288,6 +289,8 @@ public class TransactionBatchProcessorTest { | |||
| "K2", "V-2-1", -1, ledgerHash, parti0, parti0); | |||
| TransactionRequest txreq3 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||
| "K3", "V-3-1", -1, ledgerHash, parti0, parti0); | |||
| // 连续写 K1,K1的版本将变为1; | |||
| TransactionRequest txreq4 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||
| "K1", "V-1-2", 0, ledgerHash, parti0, parti0); | |||
| @@ -316,14 +319,14 @@ public class TransactionBatchProcessorTest { | |||
| assertNotNull(v1_1); | |||
| assertNotNull(v2); | |||
| assertNotNull(v3); | |||
| assertEquals("V-1-1", v1_0.getValue().toUTF8String()); | |||
| assertEquals("V-1-2", v1_1.getValue().toUTF8String()); | |||
| assertEquals("V-2-1", v2.getValue().toUTF8String()); | |||
| assertEquals("V-3-1", v3.getValue().toUTF8String()); | |||
| // 提交多笔数据写入的交易,包含存在数据版本冲突的交易,验证交易是否正确回滚; | |||
| // 先写一笔正确的交易; k3 的版本将变为 1 ; | |||
| TransactionRequest txreq5 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||
| "K3", "V-3-2", 0, ledgerHash, parti0, parti0); | |||
| // 指定冲突的版本号,正确的应该是版本1; | |||
| @@ -335,7 +338,14 @@ public class TransactionBatchProcessorTest { | |||
| txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); | |||
| txbatchProcessor.schedule(txreq5); | |||
| txbatchProcessor.schedule(txreq6); | |||
| // 预期会产生版本冲突异常; DataVersionConflictionException; | |||
| DataVersionConflictException versionConflictionException = null; | |||
| try { | |||
| txbatchProcessor.schedule(txreq6); | |||
| } catch (DataVersionConflictException e) { | |||
| versionConflictionException = e; | |||
| } | |||
| assertNotNull(versionConflictionException); | |||
| newBlock = newBlockEditor.prepare(); | |||
| newBlockEditor.commit(); | |||
| @@ -343,11 +353,15 @@ public class TransactionBatchProcessorTest { | |||
| BytesValue v1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1"); | |||
| v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3"); | |||
| long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K1"); | |||
| // k1 的版本仍然为1,没有更新; | |||
| long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) | |||
| .getDataVersion("K1"); | |||
| assertEquals(1, k1_version); | |||
| long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K3"); | |||
| long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) | |||
| .getDataVersion("K3"); | |||
| assertEquals(1, k3_version); | |||
| assertNotNull(v1); | |||
| assertNotNull(v3); | |||
| assertEquals("V-1-2", v1.getValue().toUTF8String()); | |||
| @@ -0,0 +1,15 @@ | |||
| package test.com.jd.blockchain.ledger; | |||
| import com.jd.blockchain.contract.Contract; | |||
| import com.jd.blockchain.contract.ContractEvent; | |||
| @Contract | |||
| public interface TxTestContract { | |||
| @ContractEvent(name = "testReadable") | |||
| boolean testReadable(); | |||
| @ContractEvent(name = "testRollbackWhileVersionConfliction") | |||
| void testRollbackWhileVersionConfliction(String address, String key, String value, long version); | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| package test.com.jd.blockchain.ledger; | |||
| import com.jd.blockchain.contract.ContractEventContext; | |||
| import com.jd.blockchain.contract.ContractLifecycleAware; | |||
| import com.jd.blockchain.contract.EventProcessingAware; | |||
| import com.jd.blockchain.ledger.KVDataEntry; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| public class TxTestContractImpl implements TxTestContract, ContractLifecycleAware, EventProcessingAware { | |||
| private ContractEventContext eventContext; | |||
| private Bytes dataAddress; | |||
| public static String KEY = "k1"; | |||
| @Override | |||
| public boolean testReadable() { | |||
| KVDataEntry v1 = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), | |||
| dataAddress.toBase58(), KEY)[0]; | |||
| String text1 = (String) v1.getValue(); | |||
| System.out.printf("k1=%s, version=%s \r\n", text1, v1.getVersion()); | |||
| text1 = null == text1 ? "v" : text1; | |||
| String newValue = text1 + "-" + (v1.getVersion() + 1); | |||
| System.out.printf("new value = %s\r\n", newValue); | |||
| eventContext.getLedger().dataAccount(dataAddress).setText(KEY, newValue, v1.getVersion()); | |||
| KVDataEntry v2 = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), | |||
| dataAddress.toBase58(), KEY)[0]; | |||
| System.out.printf("---- read new value ----\r\nk1=%s, version=%s \r\n", v2.getValue(), v2.getVersion()); | |||
| String text2 = (String) v2.getValue(); | |||
| return text1.equals(text2); | |||
| } | |||
| @Override | |||
| public void testRollbackWhileVersionConfliction(String address, String key, String value, long version) { | |||
| eventContext.getLedger().dataAccount(address).setText(key, value, version); | |||
| } | |||
| @Override | |||
| public void postConstruct() { | |||
| // TODO Auto-generated method stub | |||
| } | |||
| @Override | |||
| public void beforeDestroy() { | |||
| // TODO Auto-generated method stub | |||
| } | |||
| @Override | |||
| public void beforeEvent(ContractEventContext eventContext) { | |||
| this.eventContext = eventContext; | |||
| } | |||
| @Override | |||
| public void postEvent(ContractEventContext eventContext, Exception error) { | |||
| this.eventContext = null; | |||
| } | |||
| public Bytes getDataAddress() { | |||
| return dataAddress; | |||
| } | |||
| public void setDataAddress(Bytes dataAddress) { | |||
| this.dataAddress = dataAddress; | |||
| } | |||
| } | |||
| @@ -1,32 +1,36 @@ | |||
| package com.jd.blockchain.ledger; | |||
| import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
| import com.jd.blockchain.binaryproto.DataContract; | |||
| import com.jd.blockchain.ledger.resolver.*; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
| import com.jd.blockchain.binaryproto.DataContract; | |||
| import com.jd.blockchain.ledger.resolver.BooleanToBytesValueResolver; | |||
| import com.jd.blockchain.ledger.resolver.BytesToBytesValueResolver; | |||
| import com.jd.blockchain.ledger.resolver.BytesValueResolver; | |||
| import com.jd.blockchain.ledger.resolver.IntegerToBytesValueResolver; | |||
| import com.jd.blockchain.ledger.resolver.LongToBytesValueResolver; | |||
| import com.jd.blockchain.ledger.resolver.ShortToBytesValueResolver; | |||
| import com.jd.blockchain.ledger.resolver.StringToBytesValueResolver; | |||
| public class BytesValueEncoding { | |||
| private static final Map<Class<?>, BytesValueResolver> CLASS_RESOLVER_MAP = new ConcurrentHashMap<>(); | |||
| private static final Map<DataType, BytesValueResolver> DATA_TYPE_RESOLVER_MAP = new ConcurrentHashMap<>(); | |||
| private static final Object[] EMPTY_OBJECTS = {}; | |||
| static { | |||
| init(); | |||
| } | |||
| private static void init() { | |||
| BytesValueResolver[] resolvers = new BytesValueResolver[]{ | |||
| new BytesToBytesValueResolver(), | |||
| new IntegerToBytesValueResolver(), | |||
| new LongToBytesValueResolver(), | |||
| new ShortToBytesValueResolver(), | |||
| new StringToBytesValueResolver() | |||
| }; | |||
| BytesValueResolver[] resolvers = new BytesValueResolver[] { new BooleanToBytesValueResolver(), | |||
| new BytesToBytesValueResolver(), new IntegerToBytesValueResolver(), new LongToBytesValueResolver(), | |||
| new ShortToBytesValueResolver(), new StringToBytesValueResolver() }; | |||
| for (BytesValueResolver currResolver : resolvers) { | |||
| // 填充classMAP | |||
| @@ -47,7 +51,6 @@ public class BytesValueEncoding { | |||
| } | |||
| } | |||
| public static BytesValue encodeSingle(Object value, Class<?> type) { | |||
| if (value == null) { | |||
| return null; | |||
| @@ -60,7 +63,8 @@ public class BytesValueEncoding { | |||
| if (type.isInterface()) { | |||
| // 判断是否含有DataContract注解 | |||
| if (!type.isAnnotationPresent(DataContract.class)) { | |||
| throw new IllegalStateException(String.format("Interface[%s] can not be serialize !!!", type.getName())); | |||
| throw new IllegalStateException( | |||
| String.format("Interface[%s] can not be serialize !!!", type.getName())); | |||
| } | |||
| // 将对象序列化 | |||
| byte[] serialBytes = BinaryProtocol.encode(value, type); | |||
| @@ -72,7 +76,7 @@ public class BytesValueEncoding { | |||
| } | |||
| return bytesValueResolver.encode(value, type); | |||
| } | |||
| public static BytesValueList encodeArray(Object[] values, Class<?>[] types) { | |||
| if (values == null || values.length == 0) { | |||
| return null; | |||
| @@ -101,11 +105,14 @@ public class BytesValueEncoding { | |||
| } | |||
| return type == null ? valueResolver.decode(value) : valueResolver.decode(value, type); | |||
| } | |||
| public static Object[] decode(BytesValueList values, Class<?>[] types) { | |||
| if (values == null) { | |||
| return EMPTY_OBJECTS; | |||
| } | |||
| BytesValue[] bytesValues = values.getValues(); | |||
| if (bytesValues == null || bytesValues.length == 0) { | |||
| return null; | |||
| return EMPTY_OBJECTS; | |||
| } | |||
| // 允许types为null,此时每个BytesValue按照当前的对象来处理 | |||
| // 若types不为null,则types's长度必须和bytesValues一致 | |||
| @@ -120,7 +127,8 @@ public class BytesValueEncoding { | |||
| DataType dataType = bytesValue.getType(); | |||
| BytesValueResolver valueResolver = DATA_TYPE_RESOLVER_MAP.get(dataType); | |||
| if (valueResolver == null) { | |||
| throw new IllegalStateException(String.format("DataType[%s] can not find encoder !!!", dataType.name())); | |||
| throw new IllegalStateException( | |||
| String.format("DataType[%s] can not find encoder !!!", dataType.name())); | |||
| } | |||
| resolveObjs[i] = valueResolver.decode(bytesValue); | |||
| } | |||
| @@ -132,7 +140,7 @@ public class BytesValueEncoding { | |||
| } | |||
| return resolveObjs; | |||
| } | |||
| public static Object getDefaultValue(Class<?> type) { | |||
| if (type == void.class || type == Void.class) { | |||
| return null; | |||
| @@ -174,14 +182,27 @@ public class BytesValueEncoding { | |||
| if (currParamType.isInterface()) { | |||
| // 接口序列化必须实现DataContract注解 | |||
| if (!currParamType.isAnnotationPresent(DataContract.class)) { | |||
| throw new IllegalStateException(String.format("Interface[%s] can not be serialize !!!", currParamType.getName())); | |||
| throw new IllegalStateException( | |||
| String.format("Interface[%s] can not be annotated as a DataContract!!!", currParamType.getName())); | |||
| } | |||
| return true; | |||
| } | |||
| if (currParamType.isArray() ) { | |||
| Class<?> componentType = currParamType.getComponentType(); | |||
| if (componentType.isInterface()) { | |||
| // 接口序列化必须实现DataContract注解 | |||
| if (!componentType.isAnnotationPresent(DataContract.class)) { | |||
| throw new IllegalStateException( | |||
| String.format("Interface[%s] can not be annotated as a DataContract!!!", currParamType.getName())); | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| return CLASS_RESOLVER_MAP.containsKey(currParamType); | |||
| } | |||
| public static class BytesValueListData implements BytesValueList { | |||
| private List<BytesValue> bytesValues = new ArrayList<>(); | |||
| @@ -0,0 +1,27 @@ | |||
| package com.jd.blockchain.ledger; | |||
| public class DataVersionConflictException extends BlockRollbackException { | |||
| private static final long serialVersionUID = 3583192000738807503L; | |||
| private TransactionState state; | |||
| public DataVersionConflictException() { | |||
| this(TransactionState.DATA_VERSION_CONFLICT, null); | |||
| } | |||
| public DataVersionConflictException(String message) { | |||
| this(TransactionState.DATA_VERSION_CONFLICT, message); | |||
| } | |||
| private DataVersionConflictException(TransactionState state, String message) { | |||
| super(message); | |||
| assert TransactionState.SUCCESS != state; | |||
| this.state = state; | |||
| } | |||
| public TransactionState getState() { | |||
| return state; | |||
| } | |||
| } | |||
| @@ -38,6 +38,11 @@ public enum TransactionState { | |||
| * 合约不存在; | |||
| */ | |||
| CONTRACT_DOES_NOT_EXIST((byte) 0x04), | |||
| /** | |||
| * 数据写入时版本冲突; | |||
| */ | |||
| DATA_VERSION_CONFLICT((byte) 0x05), | |||
| /** | |||
| * 由于在错误的账本上执行交易而被丢弃; | |||
| @@ -0,0 +1,58 @@ | |||
| package com.jd.blockchain.ledger.resolver; | |||
| import com.jd.blockchain.ledger.BytesData; | |||
| import com.jd.blockchain.ledger.BytesValue; | |||
| import com.jd.blockchain.ledger.DataType; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| import com.jd.blockchain.utils.io.BytesUtils; | |||
| import java.util.Set; | |||
| public class BooleanToBytesValueResolver extends AbstractBytesValueResolver { | |||
| private final Class<?>[] supportClasses = { Boolean.class, boolean.class }; | |||
| private final DataType[] supportDataTypes = { DataType.BOOLEAN }; | |||
| private final Set<Class<?>> convertClasses = initBooleanConvertSet(); | |||
| @Override | |||
| public BytesValue encode(Object value, Class<?> type) { | |||
| if (!isSupport(type)) { | |||
| throw new IllegalStateException(String.format("Un-support encode Class[%s] Object !!!", type.getName())); | |||
| } | |||
| return BytesData.fromBoolean((boolean) value); | |||
| } | |||
| @Override | |||
| public Class<?>[] supportClasses() { | |||
| return supportClasses; | |||
| } | |||
| @Override | |||
| public DataType[] supportDataTypes() { | |||
| return supportDataTypes; | |||
| } | |||
| @Override | |||
| protected Object decode(Bytes value) { | |||
| return BytesUtils.toInt(value.toBytes()); | |||
| } | |||
| @Override | |||
| public Object decode(BytesValue value, Class<?> clazz) { | |||
| // 支持转换为short、int、long | |||
| int intVal = (int) decode(value); | |||
| if (convertClasses.contains(clazz)) { | |||
| // 对于short和Short需要强制类型转换 | |||
| if (clazz.equals(short.class) || clazz.equals(Short.class)) { | |||
| return (short) intVal; | |||
| } else if (clazz.equals(long.class) || clazz.equals(Long.class)) { | |||
| return (long) intVal; | |||
| } | |||
| return intVal; | |||
| } else { | |||
| throw new IllegalStateException(String.format("Un-Support decode value to class[%s] !!!", clazz.getName())); | |||
| } | |||
| } | |||
| } | |||
| @@ -10,71 +10,79 @@ import java.util.Set; | |||
| public interface BytesValueResolver { | |||
| /** | |||
| * Int相关的可转换Class集合 | |||
| */ | |||
| Class<?>[] supportIntConvertClasses = { | |||
| short.class, Short.class, int.class, Integer.class, long.class, Long.class}; | |||
| /** | |||
| * Boolean相关的可转换Class集合 | |||
| */ | |||
| Class<?>[] supportBooleanConvertClasses = { boolean.class, Boolean.class }; | |||
| /** | |||
| * 字节数组(字符串)相关可转换的Class集合 | |||
| */ | |||
| Class<?>[] supportByteConvertClasses = { | |||
| String.class, Bytes.class, byte[].class}; | |||
| /** | |||
| * Int相关的可转换Class集合 | |||
| */ | |||
| Class<?>[] supportIntConvertClasses = { short.class, Short.class, int.class, Integer.class, long.class, | |||
| Long.class }; | |||
| default Set<Class<?>> initIntConvertSet() { | |||
| return new HashSet<>(Arrays.asList(supportIntConvertClasses)); | |||
| } | |||
| /** | |||
| * 字节数组(字符串)相关可转换的Class集合 | |||
| */ | |||
| Class<?>[] supportByteConvertClasses = { String.class, Bytes.class, byte[].class }; | |||
| default Set<Class<?>> initBooleanConvertSet() { | |||
| return new HashSet<>(Arrays.asList(supportBooleanConvertClasses)); | |||
| } | |||
| default Set<Class<?>> initByteConvertSet() { | |||
| return new HashSet<>(Arrays.asList(supportByteConvertClasses)); | |||
| } | |||
| default Set<Class<?>> initIntConvertSet() { | |||
| return new HashSet<>(Arrays.asList(supportIntConvertClasses)); | |||
| } | |||
| /** | |||
| * 将对象转换为BytesValue | |||
| * | |||
| * @param value | |||
| * @return | |||
| */ | |||
| BytesValue encode(Object value); | |||
| default Set<Class<?>> initByteConvertSet() { | |||
| return new HashSet<>(Arrays.asList(supportByteConvertClasses)); | |||
| } | |||
| /** | |||
| * 将对象转换为BytesValue | |||
| * | |||
| * @param value | |||
| * @param type | |||
| * @return | |||
| */ | |||
| BytesValue encode(Object value, Class<?> type); | |||
| /** | |||
| * 将对象转换为BytesValue | |||
| * | |||
| * @param value | |||
| * @return | |||
| */ | |||
| BytesValue encode(Object value); | |||
| /** | |||
| * 当前解析器支持的Class列表 | |||
| * | |||
| * @return | |||
| */ | |||
| Class<?>[] supportClasses(); | |||
| /** | |||
| * 将对象转换为BytesValue | |||
| * | |||
| * @param value | |||
| * @param type | |||
| * @return | |||
| */ | |||
| BytesValue encode(Object value, Class<?> type); | |||
| /** | |||
| * 当前解析器支持的DataType列表 | |||
| * | |||
| * @return | |||
| */ | |||
| DataType[] supportDataTypes(); | |||
| /** | |||
| * 当前解析器支持的Class列表 | |||
| * | |||
| * @return | |||
| */ | |||
| Class<?>[] supportClasses(); | |||
| /** | |||
| * 将BytesValue解析为对应的Object | |||
| * | |||
| * @param value | |||
| * @return | |||
| */ | |||
| Object decode(BytesValue value); | |||
| /** | |||
| * 当前解析器支持的DataType列表 | |||
| * | |||
| * @return | |||
| */ | |||
| DataType[] supportDataTypes(); | |||
| /** | |||
| * 将BytesValue转换为指定Class的Object | |||
| * | |||
| * @param value | |||
| * @param clazz | |||
| * @return | |||
| */ | |||
| Object decode(BytesValue value, Class<?> clazz); | |||
| /** | |||
| * 将BytesValue解析为对应的Object | |||
| * | |||
| * @param value | |||
| * @return | |||
| */ | |||
| Object decode(BytesValue value); | |||
| /** | |||
| * 将BytesValue转换为指定Class的Object | |||
| * | |||
| * @param value | |||
| * @param clazz | |||
| * @return | |||
| */ | |||
| Object decode(BytesValue value, Class<?> clazz); | |||
| } | |||
| @@ -81,7 +81,7 @@ public abstract class RuntimeContext { | |||
| if (jarFile.isFile()) { | |||
| FileUtils.deleteFile(jarFile); | |||
| } else { | |||
| throw new IllegalStateException("Code storage confliction! --" + jarFile.getAbsolutePath()); | |||
| throw new IllegalStateException("Code storage conflict! --" + jarFile.getAbsolutePath()); | |||
| } | |||
| } | |||
| FileUtils.writeBytes(jarBytes, jarFile); | |||
| @@ -0,0 +1,34 @@ | |||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <parent> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>test</artifactId> | |||
| <version>1.0.1.RELEASE</version> | |||
| </parent> | |||
| <artifactId>test-contract</artifactId> | |||
| <dependencies> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-jvm</artifactId> | |||
| <version>${project.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>ledger-core</artifactId> | |||
| <version>${project.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>storage-rocksdb</artifactId> | |||
| <version>${project.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>crypto-classic</artifactId> | |||
| <version>${project.version}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| </project> | |||
| @@ -0,0 +1,14 @@ | |||
| package test.com.jd.blockchain.contract; | |||
| import static org.junit.Assert.*; | |||
| import org.junit.Test; | |||
| public class ContractTransactionRollbackTest { | |||
| @Test | |||
| public void test() { | |||
| } | |||
| } | |||