diff --git a/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java new file mode 100644 index 00000000..94adc3a7 --- /dev/null +++ b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java @@ -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 extends AbstractContractCode { + + private T instance; + + public InstantiatedContractCode(Bytes address, long version, Class 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; + } + + } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java index 792ca704..93286676 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java @@ -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.
+ * + * The value of the key will be updated only if it's latest version equals the + * specified version argument.
+ * If the key doesn't exist, the version checking will be ignored, and key will + * be created with a new sequence number as id.
+ * It also could specify the version argument to -1 to ignore the version + * checking. + *

+ * If updating is performed, the version of the key increase by 1.
+ * If creating is performed, the version of the key initialize by 0.
+ * + * @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.
+ * If the key is new created success, then return 0;
+ * If the key is updated success, then return the new version;
+ * 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.
+ * + * The value of the key will be updated only if it's latest version equals the + * specified version argument.
+ * If the key doesn't exist, the version checking will be ignored, and key will + * be created with a new sequence number as id.
+ * It also could specify the version argument to -1 to ignore the version + * checking. + *

+ * If updating is performed, the version of the key increase by 1.
+ * If creating is performed, the version of the key initialize by 0.
+ * + * @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.
+ * If the key is new created success, then return 0;
+ * If the key is updated success, then return the new version;
+ * 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.
+ * + * The value of the key will be updated only if it's latest version equals the + * specified version argument.
+ * If the key doesn't exist, the version checking will be ignored, and key will + * be created with a new sequence number as id.
+ * It also could specify the version argument to -1 to ignore the version + * checking. + *

+ * If updating is performed, the version of the key increase by 1.
+ * If creating is performed, the version of the key initialize by 0.
+ * + * @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.
+ * If the key is new created success, then return 0;
+ * If the key is updated success, then return the new version;
+ * 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; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java index b8dd170b..59ebd13f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java @@ -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(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java index 5696ded7..226be047 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java @@ -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); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java index 2745a377..75607b51 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java @@ -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; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java index c0a31321..ef354223 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java @@ -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 ContractCode setup(Bytes address, Class contractIntf, T instance) { - ContractCodeInstance contract = new ContractCodeInstance(address, 0, contractIntf, instance); + InstantiatedContractCode contract = new InstantiatedContractCode(address, 0, contractIntf, instance); contractInstances.put(address, contract); return contract; } - private static class ContractCodeInstance extends AbstractContractCode { - - private T instance; - - public ContractCodeInstance(Bytes address, long version, Class 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; - } - - } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index fd9df2d4..73287b04 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -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()); + } + + /** + * 验证在合约方法中写入数据账户时,如果版本校验失败是否会引发异常而导致回滚;
+ * 期待正确的表现是引发异常而回滚当前交易; + */ + @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); + + } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java new file mode 100644 index 00000000..c4b40d59 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java @@ -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(); + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java new file mode 100644 index 00000000..24215ea7 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java @@ -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; + } + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java index ce571d71..7bbe7682 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java @@ -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 项; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java index f857a6ad..f51f211d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java @@ -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()); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java new file mode 100644 index 00000000..80ee477f --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java @@ -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); + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java new file mode 100644 index 00000000..60ee6864 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java @@ -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; + } + + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java index af67719e..baee5868 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java @@ -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, BytesValueResolver> CLASS_RESOLVER_MAP = new ConcurrentHashMap<>(); private static final Map 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 bytesValues = new ArrayList<>(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java new file mode 100644 index 00000000..8af67d01 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java @@ -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; + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java index 6955eb94..55390655 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java @@ -38,6 +38,11 @@ public enum TransactionState { * 合约不存在; */ CONTRACT_DOES_NOT_EXIST((byte) 0x04), + + /** + * 数据写入时版本冲突; + */ + DATA_VERSION_CONFLICT((byte) 0x05), /** * 由于在错误的账本上执行交易而被丢弃; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java new file mode 100644 index 00000000..0664fdb9 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java @@ -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> 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())); + } + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java index 08e48658..4c659567 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java @@ -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> initIntConvertSet() { - return new HashSet<>(Arrays.asList(supportIntConvertClasses)); - } + /** + * 字节数组(字符串)相关可转换的Class集合 + */ + Class[] supportByteConvertClasses = { String.class, Bytes.class, byte[].class }; + + default Set> initBooleanConvertSet() { + return new HashSet<>(Arrays.asList(supportBooleanConvertClasses)); + } - default Set> initByteConvertSet() { - return new HashSet<>(Arrays.asList(supportByteConvertClasses)); - } + default Set> initIntConvertSet() { + return new HashSet<>(Arrays.asList(supportIntConvertClasses)); + } - /** - * 将对象转换为BytesValue - * - * @param value - * @return - */ - BytesValue encode(Object value); + default Set> 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); } diff --git a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java index 569a95a5..04bc55cd 100644 --- a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java +++ b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java @@ -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); diff --git a/source/test/test-contract/pom.xml b/source/test/test-contract/pom.xml new file mode 100644 index 00000000..a9c59ddf --- /dev/null +++ b/source/test/test-contract/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.jd.blockchain + test + 1.0.1.RELEASE + + test-contract + + + + com.jd.blockchain + contract-jvm + ${project.version} + + + com.jd.blockchain + ledger-core + ${project.version} + + + com.jd.blockchain + storage-rocksdb + ${project.version} + + + com.jd.blockchain + crypto-classic + ${project.version} + + + \ No newline at end of file diff --git a/source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java b/source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java new file mode 100644 index 00000000..bda44aed --- /dev/null +++ b/source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java @@ -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() { + + } + +}