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/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/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java
index 15bf2671..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,5 +1,6 @@
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;
@@ -25,6 +26,7 @@ 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;
@@ -40,16 +42,17 @@ 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 static com.jd.blockchain.transaction.ContractReturnValue.*;
import com.jd.blockchain.transaction.TxBuilder;
import com.jd.blockchain.utils.Bytes;
@@ -152,7 +155,7 @@ public class ContractInvokingTest {
}
- @Test
+// @Test
public void testReadNewWritting() {
// 初始化账本到指定的存储库;
HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3);
@@ -219,7 +222,6 @@ public class ContractInvokingTest {
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);
@@ -230,6 +232,164 @@ public class ContractInvokingTest {
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) {
// 创建新区块的交易处理器;
@@ -300,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 39665bde..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();
// 初始化账本到指定的存储库;
@@ -337,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();
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
index 80df9803..80ee477f 100644
--- 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
@@ -5,14 +5,11 @@ import com.jd.blockchain.contract.ContractEvent;
@Contract
public interface TxTestContract {
-
+
@ContractEvent(name = "testReadable")
boolean testReadable();
-// @ContractEvent(name = "prepareData")
-// String[] prepareData(String address);
-//
-// @ContractEvent(name = "doVersionConflictedWritting")
-// void doVersionConflictedWritting(String key, String value, long version);
+ @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
index bd9137d4..60ee6864 100644
--- 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
@@ -33,18 +33,12 @@ public class TxTestContractImpl implements TxTestContract, ContractLifecycleAwar
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 String[] prepareData(String address) {
-// // TODO Auto-generated method stub
-// return null;
-// }
-//
-// @Override
-// public void doVersionConflictedWritting(String key, String value, long version) {
-// // TODO Auto-generated method stub
-//
-// }
@Override
public void postConstruct() {
@@ -76,4 +70,5 @@ public class TxTestContractImpl implements TxTestContract, ContractLifecycleAwar
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 66a9eee2..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
@@ -183,10 +183,23 @@ public class BytesValueEncoding {
// 接口序列化必须实现DataContract注解
if (!currParamType.isAnnotationPresent(DataContract.class)) {
throw new IllegalStateException(
- String.format("Interface[%s] can not be serialize !!!", currParamType.getName()));
+ 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);
}
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/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);