@@ -243,6 +243,7 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer | |||||
messageHandle.commitBatch(realmName, batchId); | messageHandle.commitBatch(realmName, batchId); | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
// todo 需要处理应答码 404 | // todo 需要处理应答码 404 | ||||
LOGGER.error("Error occurred while processing ordered messages! --" + e.getMessage(), e); | |||||
messageHandle.rollbackBatch(realmName, batchId, TransactionState.CONSENSUS_ERROR.CODE); | messageHandle.rollbackBatch(realmName, batchId, TransactionState.CONSENSUS_ERROR.CODE); | ||||
} | } | ||||
@@ -333,9 +333,9 @@ public class AccountSet implements Transactional, MerkleProvable { | |||||
if (!updated) { | if (!updated) { | ||||
return; | return; | ||||
} | } | ||||
String[] addresses = new String[latestAccountsCache.size()]; | |||||
Bytes[] addresses = new Bytes[latestAccountsCache.size()]; | |||||
latestAccountsCache.keySet().toArray(addresses); | latestAccountsCache.keySet().toArray(addresses); | ||||
for (String address : addresses) { | |||||
for (Bytes address : addresses) { | |||||
VersioningAccount acc = latestAccountsCache.remove(address); | VersioningAccount acc = latestAccountsCache.remove(address); | ||||
// cancel; | // cancel; | ||||
if (acc.isUpdated()) { | if (acc.isUpdated()) { | ||||
@@ -50,9 +50,11 @@ public interface LedgerEditor { | |||||
* 每一次事务性的账本写入操作在提交后,都会记录该事务相关的系统全局快照,以交易对象 {@link LedgerTransaction} 进行保存; | * 每一次事务性的账本写入操作在提交后,都会记录该事务相关的系统全局快照,以交易对象 {@link LedgerTransaction} 进行保存; | ||||
* <p> | * <p> | ||||
* | * | ||||
* 注:方法不解析、不执行交易中的操作; | |||||
* | * | ||||
* @param txRequest | |||||
* | |||||
* 注:方法不解析、不执行交易中的操作;<p> | |||||
* | |||||
* @param txRequest 交易请求; | |||||
* @return | * @return | ||||
*/ | */ | ||||
LedgerTransactionContext newTransaction(TransactionRequest txRequest); | LedgerTransactionContext newTransaction(TransactionRequest txRequest); | ||||
@@ -24,7 +24,7 @@ public interface LedgerTransactionContext { | |||||
* | * | ||||
* @return | * @return | ||||
*/ | */ | ||||
TransactionRequest getRequestTX(); | |||||
TransactionRequest getTransactionRequest(); | |||||
/** | /** | ||||
* 提交对账本数据的修改,以指定的交易状态提交交易; | * 提交对账本数据的修改,以指定的交易状态提交交易; | ||||
@@ -0,0 +1,51 @@ | |||||
package com.jd.blockchain.ledger.core; | |||||
public class SettingContext { | |||||
private static final TxSettingContext txSettings = new TxSettingContext(); | |||||
private static final QueryingSettingContext queryingSettings = new QueryingSettingContext(); | |||||
public static TxSettingContext txSettings() { | |||||
return txSettings; | |||||
} | |||||
public static QueryingSettingContext queryingSettings() { | |||||
return queryingSettings; | |||||
} | |||||
/** | |||||
* 与交易处理相关的设置; | |||||
* @author huanghaiquan | |||||
* | |||||
*/ | |||||
public static class TxSettingContext { | |||||
public boolean verifyLedger() { | |||||
return true; | |||||
} | |||||
public boolean verifySignature() { | |||||
return true; | |||||
} | |||||
} | |||||
/** | |||||
* 与账本查询相关的设置; | |||||
* @author huanghaiquan | |||||
* | |||||
*/ | |||||
public static class QueryingSettingContext { | |||||
/** | |||||
* 查询区块等具有 hash 标识符的对象时是否重新校验哈希; | |||||
* @return | |||||
*/ | |||||
public boolean verifyHash() { | |||||
return false; | |||||
} | |||||
} | |||||
} |
@@ -33,7 +33,9 @@ public class LedgerBlockData implements LedgerBlock { | |||||
// private HashDigest contractPrivilegeHash; | // private HashDigest contractPrivilegeHash; | ||||
private HashDigest transactionSetHash; | private HashDigest transactionSetHash; | ||||
private long timestamp; | |||||
public LedgerBlockData() { | public LedgerBlockData() { | ||||
} | } | ||||
@@ -155,4 +157,14 @@ public class LedgerBlockData implements LedgerBlock { | |||||
this.ledgerHash = ledgerHash; | this.ledgerHash = ledgerHash; | ||||
} | } | ||||
public long getTimestamp() { | |||||
return timestamp; | |||||
} | |||||
public void setTimestamp(long timestamp) { | |||||
this.timestamp = timestamp; | |||||
} | |||||
} | } |
@@ -15,6 +15,7 @@ import com.jd.blockchain.ledger.core.LedgerDataSet; | |||||
import com.jd.blockchain.ledger.core.LedgerEditor; | import com.jd.blockchain.ledger.core.LedgerEditor; | ||||
import com.jd.blockchain.ledger.core.LedgerRepository; | import com.jd.blockchain.ledger.core.LedgerRepository; | ||||
import com.jd.blockchain.ledger.core.LedgerTransactionContext; | import com.jd.blockchain.ledger.core.LedgerTransactionContext; | ||||
import com.jd.blockchain.ledger.core.SettingContext; | |||||
import com.jd.blockchain.ledger.core.TransactionSet; | import com.jd.blockchain.ledger.core.TransactionSet; | ||||
import com.jd.blockchain.ledger.core.UserAccountSet; | import com.jd.blockchain.ledger.core.UserAccountSet; | ||||
import com.jd.blockchain.storage.service.ExPolicyKVStorage; | import com.jd.blockchain.storage.service.ExPolicyKVStorage; | ||||
@@ -77,7 +78,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { | |||||
this.ledgerIndexKey = encodeLedgerIndexKey(ledgerHash); | this.ledgerIndexKey = encodeLedgerIndexKey(ledgerHash); | ||||
if (getLatestBlockHeight() < 0) { | if (getLatestBlockHeight() < 0) { | ||||
throw new LedgerException("Ledger doesn't exist!"); | |||||
throw new RuntimeException("Ledger doesn't exist!"); | |||||
} | } | ||||
} | } | ||||
@@ -205,15 +206,14 @@ public class LedgerRepositoryImpl implements LedgerRepository { | |||||
LedgerBlockData block = new LedgerBlockData(deserialize(blockBytes)); | LedgerBlockData block = new LedgerBlockData(deserialize(blockBytes)); | ||||
if (!blockHash.equals(block.getHash())) { | if (!blockHash.equals(block.getHash())) { | ||||
throw new LedgerException("Block hash not equals to it's storage key!"); | |||||
throw new RuntimeException("Block hash not equals to it's storage key!"); | |||||
} | } | ||||
// verify hash; | // verify hash; | ||||
// boolean requiredVerifyHash = | // boolean requiredVerifyHash = | ||||
// adminAccount.getMetadata().getSetting().getCryptoSetting().getAutoVerifyHash(); | // adminAccount.getMetadata().getSetting().getCryptoSetting().getAutoVerifyHash(); | ||||
// TODO: 未实现从配置中加载是否校验 Hash 的设置; | // TODO: 未实现从配置中加载是否校验 Hash 的设置; | ||||
boolean requiredVerifyHash = false; | |||||
if (requiredVerifyHash) { | |||||
if (SettingContext.queryingSettings().verifyHash()) { | |||||
byte[] blockBodyBytes = null; | byte[] blockBodyBytes = null; | ||||
if (block.getHeight() == 0) { | if (block.getHeight() == 0) { | ||||
// 计算创世区块的 hash 时,不包括 ledgerHash 字段; | // 计算创世区块的 hash 时,不包括 ledgerHash 字段; | ||||
@@ -227,14 +227,14 @@ public class LedgerRepositoryImpl implements LedgerRepository { | |||||
HashFunction hashFunc = Crypto.getHashFunction(blockHash.getAlgorithm()); | HashFunction hashFunc = Crypto.getHashFunction(blockHash.getAlgorithm()); | ||||
boolean pass = hashFunc.verify(blockHash, blockBodyBytes); | boolean pass = hashFunc.verify(blockHash, blockBodyBytes); | ||||
if (!pass) { | if (!pass) { | ||||
throw new LedgerException("Block hash verification fail!"); | |||||
throw new RuntimeException("Block hash verification fail!"); | |||||
} | } | ||||
} | } | ||||
// verify height; | // verify height; | ||||
HashDigest indexedHash = getBlockHash(block.getHeight()); | HashDigest indexedHash = getBlockHash(block.getHeight()); | ||||
if (indexedHash == null || !indexedHash.equals(blockHash)) { | if (indexedHash == null || !indexedHash.equals(blockHash)) { | ||||
throw new LedgerException( | |||||
throw new RuntimeException( | |||||
"Illegal ledger state in storage that ledger height index doesn't match it's block data in height[" | "Illegal ledger state in storage that ledger height index doesn't match it's block data in height[" | ||||
+ block.getHeight() + "] and block hash[" + Base58Utils.encode(blockHash.toBytes()) | + block.getHeight() + "] and block hash[" + Base58Utils.encode(blockHash.toBytes()) | ||||
+ "] !"); | + "] !"); | ||||
@@ -394,15 +394,15 @@ public class LedgerRepositoryImpl implements LedgerRepository { | |||||
@Override | @Override | ||||
public synchronized LedgerEditor createNextBlock() { | public synchronized LedgerEditor createNextBlock() { | ||||
if (closed) { | if (closed) { | ||||
throw new LedgerException("Ledger repository has been closed!"); | |||||
throw new RuntimeException("Ledger repository has been closed!"); | |||||
} | } | ||||
if (this.nextBlockEditor != null) { | if (this.nextBlockEditor != null) { | ||||
throw new LedgerException( | |||||
throw new RuntimeException( | |||||
"A new block is in process, cann't create another one until it finish by committing or canceling."); | "A new block is in process, cann't create another one until it finish by committing or canceling."); | ||||
} | } | ||||
LedgerBlock previousBlock = getLatestBlock(); | LedgerBlock previousBlock = getLatestBlock(); | ||||
LedgerTransactionalEditor editor = LedgerTransactionalEditor.createEditor(ledgerHash, | |||||
getAdminInfo().getMetadata().getSetting(), previousBlock, keyPrefix, exPolicyStorage, | |||||
LedgerTransactionalEditor editor = LedgerTransactionalEditor.createEditor(previousBlock, | |||||
getAdminInfo().getMetadata().getSetting(), keyPrefix, exPolicyStorage, | |||||
versioningStorage); | versioningStorage); | ||||
NewBlockCommittingMonitor committingMonitor = new NewBlockCommittingMonitor(editor, this); | NewBlockCommittingMonitor committingMonitor = new NewBlockCommittingMonitor(editor, this); | ||||
this.nextBlockEditor = committingMonitor; | this.nextBlockEditor = committingMonitor; | ||||
@@ -420,7 +420,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { | |||||
return; | return; | ||||
} | } | ||||
if (this.nextBlockEditor != null) { | if (this.nextBlockEditor != null) { | ||||
throw new LedgerException("A new block is in process, cann't close the ledger repository!"); | |||||
throw new RuntimeException("A new block is in process, cann't close the ledger repository!"); | |||||
} | } | ||||
closed = true; | closed = true; | ||||
} | } | ||||
@@ -600,7 +600,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { | |||||
public void commit() { | public void commit() { | ||||
try { | try { | ||||
editor.commit(); | editor.commit(); | ||||
LedgerBlock latestBlock = editor.getNewlyBlock(); | |||||
LedgerBlock latestBlock = editor.getCurrentBlock(); | |||||
ledgerRepo.latestState = new LedgerState(latestBlock); | ledgerRepo.latestState = new LedgerState(latestBlock); | ||||
} finally { | } finally { | ||||
ledgerRepo.nextBlockEditor = null; | ledgerRepo.nextBlockEditor = null; | ||||
@@ -1,8 +1,5 @@ | |||||
package com.jd.blockchain.ledger.core.impl; | package com.jd.blockchain.ledger.core.impl; | ||||
import java.util.Arrays; | |||||
import java.util.Comparator; | |||||
import com.jd.blockchain.crypto.HashDigest; | import com.jd.blockchain.crypto.HashDigest; | ||||
import com.jd.blockchain.ledger.DigitalSignature; | import com.jd.blockchain.ledger.DigitalSignature; | ||||
import com.jd.blockchain.ledger.LedgerTransaction; | import com.jd.blockchain.ledger.LedgerTransaction; | ||||
@@ -29,48 +26,27 @@ public class LedgerTransactionData implements LedgerTransaction { | |||||
private OperationResult[] operationResults; | private OperationResult[] operationResults; | ||||
// private HashDigest adminAccountHash; | |||||
// | |||||
// private HashDigest userAccountSetHash; | |||||
// | |||||
// private HashDigest dataAccountSetHash; | |||||
// | |||||
// private HashDigest contractAccountSetHash; | |||||
/** | /** | ||||
* Declare a private no-arguments constructor for deserializing purpose; | * Declare a private no-arguments constructor for deserializing purpose; | ||||
*/ | */ | ||||
@SuppressWarnings("unused") | |||||
private LedgerTransactionData() { | private LedgerTransactionData() { | ||||
// this.txSnapshot = new TransactionStagedSnapshot(); | |||||
} | } | ||||
/** | /** | ||||
* @param blockHeight | |||||
* 区块链高度; | |||||
* @param txReq | |||||
* 交易请求; | |||||
* @param execState | |||||
* 执行状态; | |||||
* @param txSnapshot | |||||
* 交易级的系统快照; | |||||
* @param blockHeight 区块链高度; | |||||
* @param txReq 交易请求; | |||||
* @param execState 执行状态; | |||||
* @param txSnapshot 交易级的系统快照; | |||||
*/ | */ | ||||
public LedgerTransactionData(long blockHeight, TransactionRequest txReq, TransactionState execState, | public LedgerTransactionData(long blockHeight, TransactionRequest txReq, TransactionState execState, | ||||
TransactionStagedSnapshot txSnapshot, OperationResult... opResults) { | TransactionStagedSnapshot txSnapshot, OperationResult... opResults) { | ||||
this.blockHeight = blockHeight; | this.blockHeight = blockHeight; | ||||
// this.txSnapshot = txSnapshot == null ? new TransactionStagedSnapshot() : txSnapshot; | |||||
this.txSnapshot = txSnapshot; | this.txSnapshot = txSnapshot; | ||||
this.transactionContent = txReq.getTransactionContent(); | this.transactionContent = txReq.getTransactionContent(); | ||||
this.endpointSignatures = txReq.getEndpointSignatures(); | this.endpointSignatures = txReq.getEndpointSignatures(); | ||||
this.nodeSignatures = txReq.getNodeSignatures(); | this.nodeSignatures = txReq.getNodeSignatures(); | ||||
this.executionState = execState; | this.executionState = execState; | ||||
if (opResults != null) { | |||||
Arrays.sort(opResults, new Comparator<OperationResult>() { | |||||
@Override | |||||
public int compare(OperationResult o1, OperationResult o2) { | |||||
return o1.getIndex() - o2.getIndex(); | |||||
} | |||||
}); | |||||
} | |||||
this.operationResults = opResults; | this.operationResults = opResults; | ||||
} | } | ||||
@@ -116,17 +92,17 @@ public class LedgerTransactionData implements LedgerTransaction { | |||||
@Override | @Override | ||||
public HashDigest getUserAccountSetHash() { | public HashDigest getUserAccountSetHash() { | ||||
return txSnapshot == null ? null :txSnapshot.getUserAccountSetHash(); | |||||
return txSnapshot == null ? null : txSnapshot.getUserAccountSetHash(); | |||||
} | } | ||||
@Override | @Override | ||||
public HashDigest getDataAccountSetHash() { | public HashDigest getDataAccountSetHash() { | ||||
return txSnapshot == null ? null :txSnapshot.getDataAccountSetHash(); | |||||
return txSnapshot == null ? null : txSnapshot.getDataAccountSetHash(); | |||||
} | } | ||||
@Override | @Override | ||||
public HashDigest getContractAccountSetHash() { | public HashDigest getContractAccountSetHash() { | ||||
return txSnapshot == null ? null :txSnapshot.getContractAccountSetHash(); | |||||
return txSnapshot == null ? null : txSnapshot.getContractAccountSetHash(); | |||||
} | } | ||||
public void setTxSnapshot(TransactionStagedSnapshot txSnapshot) { | public void setTxSnapshot(TransactionStagedSnapshot txSnapshot) { | ||||
@@ -140,20 +116,22 @@ public class LedgerTransactionData implements LedgerTransaction { | |||||
this.transactionContent = content; | this.transactionContent = content; | ||||
} | } | ||||
public void setEndpointSignatures(Object[] participantSignatures) { | |||||
int length = participantSignatures.length; | |||||
this.endpointSignatures = new DigitalSignature[length]; | |||||
for (int i = 0; i < length; i++) { | |||||
this.endpointSignatures[i] = (DigitalSignature) participantSignatures[i]; | |||||
} | |||||
public void setEndpointSignatures(DigitalSignature[] participantSignatures) { | |||||
this.endpointSignatures = participantSignatures; | |||||
// int length = participantSignatures.length; | |||||
// this.endpointSignatures = new DigitalSignature[length]; | |||||
// for (int i = 0; i < length; i++) { | |||||
// this.endpointSignatures[i] = (DigitalSignature) participantSignatures[i]; | |||||
// } | |||||
} | } | ||||
public void setNodeSignatures(Object[] nodeSignatures) { | |||||
int length = nodeSignatures.length; | |||||
this.nodeSignatures = new DigitalSignature[length]; | |||||
for (int i = 0; i < length; i++) { | |||||
this.nodeSignatures[i] = (DigitalSignature) nodeSignatures[i]; | |||||
} | |||||
public void setNodeSignatures(DigitalSignature[] nodeSignatures) { | |||||
this.nodeSignatures = nodeSignatures; | |||||
// int length = nodeSignatures.length; | |||||
// this.nodeSignatures = new DigitalSignature[length]; | |||||
// for (int i = 0; i < length; i++) { | |||||
// this.nodeSignatures[i] = (DigitalSignature) nodeSignatures[i]; | |||||
// } | |||||
} | } | ||||
public void setExecutionState(TransactionState executionState) { | public void setExecutionState(TransactionState executionState) { | ||||
@@ -1,19 +1,35 @@ | |||||
package com.jd.blockchain.ledger.core.impl; | package com.jd.blockchain.ledger.core.impl; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Stack; | |||||
import com.jd.blockchain.binaryproto.BinaryProtocol; | import com.jd.blockchain.binaryproto.BinaryProtocol; | ||||
import com.jd.blockchain.crypto.Crypto; | import com.jd.blockchain.crypto.Crypto; | ||||
import com.jd.blockchain.crypto.HashDigest; | import com.jd.blockchain.crypto.HashDigest; | ||||
import com.jd.blockchain.ledger.*; | |||||
import com.jd.blockchain.ledger.BlockBody; | |||||
import com.jd.blockchain.ledger.BlockRollbackException; | |||||
import com.jd.blockchain.ledger.CryptoSetting; | |||||
import com.jd.blockchain.ledger.DigitalSignature; | |||||
import com.jd.blockchain.ledger.IllegalTransactionException; | |||||
import com.jd.blockchain.ledger.LedgerBlock; | |||||
import com.jd.blockchain.ledger.LedgerDataSnapshot; | |||||
import com.jd.blockchain.ledger.LedgerInitSetting; | |||||
import com.jd.blockchain.ledger.LedgerSetting; | |||||
import com.jd.blockchain.ledger.LedgerTransaction; | |||||
import com.jd.blockchain.ledger.OperationResult; | |||||
import com.jd.blockchain.ledger.TransactionContent; | |||||
import com.jd.blockchain.ledger.TransactionRequest; | |||||
import com.jd.blockchain.ledger.TransactionRollbackException; | |||||
import com.jd.blockchain.ledger.TransactionState; | |||||
import com.jd.blockchain.ledger.core.LedgerDataSet; | import com.jd.blockchain.ledger.core.LedgerDataSet; | ||||
import com.jd.blockchain.ledger.core.LedgerEditor; | import com.jd.blockchain.ledger.core.LedgerEditor; | ||||
import com.jd.blockchain.ledger.core.LedgerTransactionContext; | import com.jd.blockchain.ledger.core.LedgerTransactionContext; | ||||
import com.jd.blockchain.ledger.core.SettingContext; | |||||
import com.jd.blockchain.ledger.core.TransactionSet; | import com.jd.blockchain.ledger.core.TransactionSet; | ||||
import com.jd.blockchain.storage.service.ExPolicyKVStorage; | import com.jd.blockchain.storage.service.ExPolicyKVStorage; | ||||
import com.jd.blockchain.storage.service.VersioningKVStorage; | import com.jd.blockchain.storage.service.VersioningKVStorage; | ||||
import com.jd.blockchain.storage.service.utils.BufferedKVStorage; | import com.jd.blockchain.storage.service.utils.BufferedKVStorage; | ||||
import com.jd.blockchain.transaction.TxBuilder; | |||||
import com.jd.blockchain.transaction.TxRequestBuilder; | |||||
import com.jd.blockchain.utils.Bytes; | import com.jd.blockchain.utils.Bytes; | ||||
import com.jd.blockchain.utils.codec.Base58Utils; | import com.jd.blockchain.utils.codec.Base58Utils; | ||||
@@ -35,9 +51,9 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
private CryptoSetting cryptoSetting; | private CryptoSetting cryptoSetting; | ||||
private LedgerBlockData newlyBlock; | |||||
private LedgerBlockData currentBlock; | |||||
private Stack<StagedSnapshot> stagedSnapshots = new Stack<>(); | |||||
// private Stack<StagedSnapshot> stagedSnapshots = new Stack<>(); | |||||
private boolean prepared = false; | private boolean prepared = false; | ||||
@@ -45,43 +61,70 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
private boolean committed = false; | private boolean committed = false; | ||||
// private BufferedKVStorage baseStorage; | |||||
private BufferedKVStorage bufferedStorage; | |||||
private StagedSnapshot startingPoint; | |||||
/** | /** | ||||
* 最近一个交易上下文; | |||||
* 当前区块的存储; | |||||
*/ | */ | ||||
private LedgerDataContext lastTxCtx; | |||||
private BufferedKVStorage baseStorage; | |||||
private LedgerDataContext newTxCtx; | |||||
/** | |||||
* 上一个交易的上下文; | |||||
*/ | |||||
// private LedgerTransactionContextImpl previousTxCtx; | |||||
private TxSnapshot previousTxSnapshot; | |||||
/** | |||||
* 当前交易的上下文; | |||||
*/ | |||||
private volatile LedgerTransactionContextImpl currentTxCtx; | |||||
private LedgerTransactionalEditor(HashDigest ledgerHash, CryptoSetting cryptoSetting, LedgerBlockData newlyBlock, | |||||
/** | |||||
* @param ledgerHash | |||||
* @param cryptoSetting | |||||
* @param currentBlock | |||||
* @param startingPoint | |||||
* @param ledgerKeyPrefix | |||||
* @param bufferedStorage | |||||
* @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能; | |||||
*/ | |||||
private LedgerTransactionalEditor(HashDigest ledgerHash, CryptoSetting cryptoSetting, LedgerBlockData currentBlock, | |||||
StagedSnapshot startingPoint, String ledgerKeyPrefix, BufferedKVStorage bufferedStorage) { | StagedSnapshot startingPoint, String ledgerKeyPrefix, BufferedKVStorage bufferedStorage) { | ||||
this.ledgerHash = ledgerHash; | this.ledgerHash = ledgerHash; | ||||
this.ledgerKeyPrefix = ledgerKeyPrefix; | this.ledgerKeyPrefix = ledgerKeyPrefix; | ||||
this.cryptoSetting = cryptoSetting; | this.cryptoSetting = cryptoSetting; | ||||
this.newlyBlock = newlyBlock; | |||||
this.bufferedStorage = bufferedStorage; | |||||
this.currentBlock = currentBlock; | |||||
this.baseStorage = bufferedStorage; | |||||
this.startingPoint = startingPoint; | |||||
this.stagedSnapshots.push(startingPoint); | |||||
// this.stagedSnapshots.push(startingPoint); | |||||
} | } | ||||
/** | /** | ||||
* 创建账本新区块的编辑器; | * 创建账本新区块的编辑器; | ||||
* | * | ||||
* @param ledgerHash | |||||
* @param ledgerSetting | |||||
* @param previousBlock | |||||
* @param ledgerKeyPrefix | |||||
* @param ledgerExStorage | |||||
* @param ledgerVerStorage | |||||
* @param ledgerHash 账本哈希; | |||||
* @param ledgerSetting 账本设置; | |||||
* @param previousBlock 前置区块; | |||||
* @param ledgerKeyPrefix 账本数据前缀; | |||||
* @param ledgerExStorage 账本数据存储; | |||||
* @param ledgerVerStorage 账本数据版本化存储; | |||||
* @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能; | |||||
* @return | * @return | ||||
*/ | */ | ||||
public static LedgerTransactionalEditor createEditor(HashDigest ledgerHash, LedgerSetting ledgerSetting, | |||||
LedgerBlock previousBlock, String ledgerKeyPrefix, ExPolicyKVStorage ledgerExStorage, | |||||
VersioningKVStorage ledgerVerStorage) { | |||||
public static LedgerTransactionalEditor createEditor(LedgerBlock previousBlock, LedgerSetting ledgerSetting, | |||||
String ledgerKeyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) { | |||||
// new block; | // new block; | ||||
LedgerBlockData currBlock = new LedgerBlockData(previousBlock.getHeight() + 1, previousBlock.getLedgerHash(), | |||||
HashDigest ledgerHash = previousBlock.getLedgerHash(); | |||||
if (ledgerHash == null) { | |||||
ledgerHash = previousBlock.getHash(); | |||||
} | |||||
if (ledgerHash == null) { | |||||
throw new IllegalArgumentException("Illegal previous block was specified!"); | |||||
} | |||||
LedgerBlockData currBlock = new LedgerBlockData(previousBlock.getHeight() + 1, ledgerHash, | |||||
previousBlock.getHash()); | previousBlock.getHash()); | ||||
// init storage; | // init storage; | ||||
@@ -101,6 +144,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
* @param ledgerKeyPrefix | * @param ledgerKeyPrefix | ||||
* @param ledgerExStorage | * @param ledgerExStorage | ||||
* @param ledgerVerStorage | * @param ledgerVerStorage | ||||
* @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能; | |||||
* @return | * @return | ||||
*/ | */ | ||||
public static LedgerTransactionalEditor createEditor(LedgerInitSetting initSetting, String ledgerKeyPrefix, | public static LedgerTransactionalEditor createEditor(LedgerInitSetting initSetting, String ledgerKeyPrefix, | ||||
@@ -114,29 +158,21 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
} | } | ||||
private void commitTxSnapshot(TxSnapshot snapshot) { | private void commitTxSnapshot(TxSnapshot snapshot) { | ||||
lastTxCtx = newTxCtx; | |||||
newTxCtx = null; | |||||
stagedSnapshots.push(snapshot); | |||||
previousTxSnapshot = snapshot; | |||||
currentTxCtx = null; | |||||
} | } | ||||
private void rollbackNewTx() { | |||||
newTxCtx = null; | |||||
private void rollbackCurrentTx() { | |||||
currentTxCtx = null; | |||||
} | } | ||||
// public LedgerDataSet getLatestDataSet() { | |||||
// if (lastTxCtx == null) { | |||||
// return null; | |||||
// } | |||||
// return lastTxCtx.getDataSet(); | |||||
// } | |||||
LedgerBlock getNewlyBlock() { | |||||
return newlyBlock; | |||||
LedgerBlock getCurrentBlock() { | |||||
return currentBlock; | |||||
} | } | ||||
@Override | @Override | ||||
public long getBlockHeight() { | public long getBlockHeight() { | ||||
return newlyBlock.getHeight(); | |||||
return currentBlock.getHeight(); | |||||
} | } | ||||
@Override | @Override | ||||
@@ -161,134 +197,182 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
return ledgerHash.equals(reqLedgerHash); | return ledgerHash.equals(reqLedgerHash); | ||||
} | } | ||||
private boolean verifyTxContent(TransactionRequest request) { | |||||
TransactionContent txContent = request.getTransactionContent(); | |||||
if (!TxBuilder.verifyTxContentHash(txContent, txContent.getHash())) { | |||||
return false; | |||||
} | |||||
DigitalSignature[] endpointSignatures = request.getEndpointSignatures(); | |||||
if (endpointSignatures != null) { | |||||
for (DigitalSignature signature : endpointSignatures) { | |||||
if (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(), | |||||
signature.getPubKey())) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
DigitalSignature[] nodeSignatures = request.getNodeSignatures(); | |||||
if (nodeSignatures != null) { | |||||
for (DigitalSignature signature : nodeSignatures) { | |||||
if (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(), | |||||
signature.getPubKey())) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
@Override | @Override | ||||
public LedgerTransactionContext newTransaction(TransactionRequest txRequest) { | |||||
// 验证账本是否; | |||||
if (!isRequestedLedger(txRequest)) { | |||||
throw new LedgerException("This ledger is not the target ledger of transaction request[" | |||||
+ txRequest.getTransactionContent().getHash() + "]!"); | |||||
public synchronized LedgerTransactionContext newTransaction(TransactionRequest txRequest) { | |||||
if (SettingContext.txSettings().verifyLedger() && !isRequestedLedger(txRequest)) { | |||||
throw new IllegalTransactionException( | |||||
"Transaction request is dispatched to a wrong ledger! --[TxHash=" | |||||
+ txRequest.getTransactionContent().getHash() + "]!", | |||||
TransactionState.IGNORED_BY_WRONG_LEDGER); | |||||
} | |||||
// TODO: 把验签和创建交易并行化; | |||||
if (SettingContext.txSettings().verifySignature() && !verifyTxContent(txRequest)) { | |||||
// 抛弃哈希和签名校验失败的交易请求; | |||||
throw new IllegalTransactionException( | |||||
"Wrong transaction signature! --[TxHash=" + txRequest.getTransactionContent().getHash() + "]!", | |||||
TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); | |||||
} | |||||
if (currentTxCtx != null) { | |||||
throw new IllegalStateException( | |||||
"Unable to open another new transaction before the current transaction is completed! --[TxHash=" | |||||
+ txRequest.getTransactionContent().getHash() + "]!"); | |||||
} | } | ||||
// 检查状态是否允许创建新的交易请求;; | // 检查状态是否允许创建新的交易请求;; | ||||
checkState(); | checkState(); | ||||
BufferedKVStorage txBuffStorage = null; | |||||
// init storage of new transaction; | |||||
BufferedKVStorage txBufferedStorage = new BufferedKVStorage(baseStorage, baseStorage, false); | |||||
LedgerDataSetImpl txDataset = null; | LedgerDataSetImpl txDataset = null; | ||||
TransactionSet txset = null; | TransactionSet txset = null; | ||||
if (lastTxCtx == null) { | |||||
// init storage of new transaction; | |||||
// txBuffStorage = new BufferedKVStorage(bufferedStorage, bufferedStorage, | |||||
// false); | |||||
txBuffStorage = bufferedStorage; | |||||
if (previousTxSnapshot == null) { | |||||
// load the starting point of the new transaction; | // load the starting point of the new transaction; | ||||
StagedSnapshot previousSnapshot = stagedSnapshots.peek(); | |||||
if (previousSnapshot instanceof GenesisSnapshot) { | |||||
if (startingPoint instanceof GenesisSnapshot) { | |||||
// 准备生成创世区块; | // 准备生成创世区块; | ||||
GenesisSnapshot snpht = (GenesisSnapshot) previousSnapshot; | |||||
txDataset = LedgerRepositoryImpl.newDataSet(snpht.initSetting, ledgerKeyPrefix, txBuffStorage, | |||||
txBuffStorage); | |||||
GenesisSnapshot snpht = (GenesisSnapshot) startingPoint; | |||||
txDataset = LedgerRepositoryImpl.newDataSet(snpht.initSetting, ledgerKeyPrefix, txBufferedStorage, | |||||
txBufferedStorage); | |||||
txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminAccount().getSetting(), | txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminAccount().getSetting(), | ||||
ledgerKeyPrefix, txBuffStorage, txBuffStorage); | |||||
} else { | |||||
ledgerKeyPrefix, txBufferedStorage, txBufferedStorage); | |||||
} else if (startingPoint instanceof TxSnapshot) { | |||||
// 新的区块; | // 新的区块; | ||||
// TxSnapshot; reload dataset and txset; | // TxSnapshot; reload dataset and txset; | ||||
TxSnapshot snpht = (TxSnapshot) previousSnapshot; | |||||
TxSnapshot snpht = (TxSnapshot) startingPoint; | |||||
// load dataset; | // load dataset; | ||||
txDataset = LedgerRepositoryImpl.loadDataSet(snpht.dataSnapshot, ledgerKeyPrefix, txBuffStorage, | |||||
txBuffStorage, false); | |||||
txDataset = LedgerRepositoryImpl.loadDataSet(snpht.dataSnapshot, ledgerKeyPrefix, txBufferedStorage, | |||||
txBufferedStorage, false); | |||||
// load tx set; | |||||
txset = LedgerRepositoryImpl.loadTransactionSet(snpht.transactionSetHash, this.cryptoSetting, | |||||
ledgerKeyPrefix, txBuffStorage, txBuffStorage, false); | |||||
// load txset; | |||||
txset = LedgerRepositoryImpl.loadTransactionSet(snpht.txsetHash, this.cryptoSetting, ledgerKeyPrefix, | |||||
txBufferedStorage, txBufferedStorage, false); | |||||
} else { | |||||
// Unreachable; | |||||
throw new IllegalStateException("Unreachable code was accidentally executed!"); | |||||
} | } | ||||
lastTxCtx = new LedgerDataContext(txDataset, txset, txBuffStorage); | |||||
} else { | } else { | ||||
// Reuse previous object to optimize performance; | // Reuse previous object to optimize performance; | ||||
txBuffStorage = lastTxCtx.storage; | |||||
txDataset = lastTxCtx.dataset; | |||||
txset = lastTxCtx.txset; | |||||
// load dataset; | |||||
txDataset = LedgerRepositoryImpl.loadDataSet(previousTxSnapshot.dataSnapshot, ledgerKeyPrefix, | |||||
txBufferedStorage, txBufferedStorage, false); | |||||
// load txset; | |||||
txset = LedgerRepositoryImpl.loadTransactionSet(previousTxSnapshot.txsetHash, this.cryptoSetting, | |||||
ledgerKeyPrefix, txBufferedStorage, txBufferedStorage, false); | |||||
} | } | ||||
// newTxCtx = new LedgerTransactionContextImpl(newlyBlock.getHeight(), | |||||
// txRequest, txDataset, txset, txBuffStorage, | |||||
// this); | |||||
// return newTxCtx; | |||||
currentTxCtx = new LedgerTransactionContextImpl(txRequest, txDataset, txset, txBufferedStorage, this); | |||||
return new LedgerTransactionContextImpl(newlyBlock.getHeight(), txRequest, txDataset, txset, txBuffStorage, | |||||
this); | |||||
return currentTxCtx; | |||||
} | } | ||||
@Override | @Override | ||||
public LedgerBlock prepare() { | public LedgerBlock prepare() { | ||||
checkState(); | checkState(); | ||||
if (newTxCtx != null) { | |||||
throw new IllegalStateException("There is a opening transaction which isn't committed or rollbacked!"); | |||||
if (currentTxCtx != null) { | |||||
// 有进行中的交易尚未提交或回滚; | |||||
throw new IllegalStateException( | |||||
"There is an ongoing transaction that has been not committed or rolled back!"); | |||||
} | } | ||||
if (lastTxCtx == null) { | |||||
// Genesis; | |||||
throw new IllegalStateException("No transaction to prepare!"); | |||||
if (previousTxSnapshot == null) { | |||||
// 当前区块没有加入过交易,不允许产生空区块; | |||||
throw new IllegalStateException( | |||||
"There is no transaction in the current block, and no empty blocks is allowed!"); | |||||
} | } | ||||
// do commit when transaction isolation level is BLOCK; | // do commit when transaction isolation level is BLOCK; | ||||
lastTxCtx.dataset.commit(); | |||||
lastTxCtx.txset.commit(); | |||||
currentBlock.setAdminAccountHash(previousTxSnapshot.getAdminAccountHash()); | |||||
currentBlock.setUserAccountSetHash(previousTxSnapshot.getUserAccountSetHash()); | |||||
currentBlock.setDataAccountSetHash(previousTxSnapshot.getDataAccountSetHash()); | |||||
currentBlock.setContractAccountSetHash(previousTxSnapshot.getContractAccountSetHash()); | |||||
currentBlock.setTransactionSetHash(previousTxSnapshot.getTransactionSetHash()); | |||||
newlyBlock.setAdminAccountHash(lastTxCtx.dataset.getAdminAccount().getHash()); | |||||
newlyBlock.setContractAccountSetHash(lastTxCtx.dataset.getContractAccountSet().getRootHash()); | |||||
newlyBlock.setDataAccountSetHash(lastTxCtx.dataset.getDataAccountSet().getRootHash()); | |||||
newlyBlock.setUserAccountSetHash(lastTxCtx.dataset.getUserAccountSet().getRootHash()); | |||||
newlyBlock.setTransactionSetHash(lastTxCtx.txset.getRootHash()); | |||||
// TODO: 根据所有交易的时间戳的平均值来生成区块的时间戳; | |||||
// long timestamp = | |||||
// currentBlock.setTimestamp(timestamp); | |||||
// compute block hash; | // compute block hash; | ||||
byte[] blockBodyBytes = BinaryProtocol.encode(newlyBlock, BlockBody.class); | |||||
byte[] blockBodyBytes = BinaryProtocol.encode(currentBlock, BlockBody.class); | |||||
HashDigest blockHash = Crypto.getHashFunction(cryptoSetting.getHashAlgorithm()).hash(blockBodyBytes); | HashDigest blockHash = Crypto.getHashFunction(cryptoSetting.getHashAlgorithm()).hash(blockBodyBytes); | ||||
newlyBlock.setHash(blockHash); | |||||
if (newlyBlock.getLedgerHash() == null) { | |||||
// init GenesisBlock's ledger hash; | |||||
newlyBlock.setLedgerHash(blockHash); | |||||
} | |||||
currentBlock.setHash(blockHash); | |||||
// if (currentBlock.getLedgerHash() == null) { | |||||
// // init GenesisBlock's ledger hash; | |||||
// currentBlock.setLedgerHash(blockHash); | |||||
// } | |||||
// persist block bytes; | // persist block bytes; | ||||
// only one version per block; | // only one version per block; | ||||
byte[] blockBytes = BinaryProtocol.encode(newlyBlock, LedgerBlock.class); | |||||
Bytes blockStorageKey = LedgerRepositoryImpl.encodeBlockStorageKey(newlyBlock.getHash()); | |||||
long v = bufferedStorage.set(blockStorageKey, blockBytes, -1); | |||||
byte[] blockBytes = BinaryProtocol.encode(currentBlock, LedgerBlock.class); | |||||
Bytes blockStorageKey = LedgerRepositoryImpl.encodeBlockStorageKey(currentBlock.getHash()); | |||||
long v = baseStorage.set(blockStorageKey, blockBytes, -1); | |||||
if (v < 0) { | if (v < 0) { | ||||
throw new IllegalStateException( | throw new IllegalStateException( | ||||
"Block already exist! --[BlockHash=" + Base58Utils.encode(newlyBlock.getHash().toBytes()) + "]"); | |||||
"Block already exist! --[BlockHash=" + Base58Utils.encode(currentBlock.getHash().toBytes()) + "]"); | |||||
} | } | ||||
// persist block hash to ledger index; | // persist block hash to ledger index; | ||||
HashDigest ledgerHash = newlyBlock.getLedgerHash(); | |||||
HashDigest ledgerHash = currentBlock.getLedgerHash(); | |||||
if (ledgerHash == null) { | |||||
ledgerHash = blockHash; | |||||
} | |||||
Bytes ledgerIndexKey = LedgerRepositoryImpl.encodeLedgerIndexKey(ledgerHash); | Bytes ledgerIndexKey = LedgerRepositoryImpl.encodeLedgerIndexKey(ledgerHash); | ||||
long expectedVersion = newlyBlock.getHeight() - 1; | |||||
v = bufferedStorage.set(ledgerIndexKey, newlyBlock.getHash().toBytes(), expectedVersion); | |||||
long expectedVersion = currentBlock.getHeight() - 1; | |||||
v = baseStorage.set(ledgerIndexKey, currentBlock.getHash().toBytes(), expectedVersion); | |||||
if (v < 0) { | if (v < 0) { | ||||
throw new IllegalStateException("Index of BlockHash already exist! --[BlockHash=" | |||||
+ Base58Utils.encode(newlyBlock.getHash().toBytes()) + "]"); | |||||
throw new IllegalStateException( | |||||
String.format("Index of BlockHash already exist! --[BlockHeight=%s][BlockHash=%s]", | |||||
currentBlock.getHeight(), currentBlock.getHash())); | |||||
} | } | ||||
prepared = true; | prepared = true; | ||||
return newlyBlock; | |||||
return currentBlock; | |||||
} | } | ||||
@Override | @Override | ||||
public void commit() { | public void commit() { | ||||
if (committed) { | if (committed) { | ||||
throw new IllegalStateException("LedgerEditor had been committed!"); | |||||
throw new IllegalStateException("The current block has been committed!"); | |||||
} | } | ||||
if (canceled) { | if (canceled) { | ||||
throw new IllegalStateException("LedgerEditor had been canceled!"); | |||||
throw new IllegalStateException("The current block has been canceled!"); | |||||
} | } | ||||
if (!prepared) { | if (!prepared) { | ||||
// 未就绪; | // 未就绪; | ||||
throw new IllegalStateException("LedgerEditor has not prepared!"); | |||||
throw new IllegalStateException("The current block is not ready yet!"); | |||||
} | } | ||||
bufferedStorage.flush(); | |||||
baseStorage.flush(); | |||||
committed = true; | committed = true; | ||||
} | } | ||||
@@ -296,38 +380,47 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
@Override | @Override | ||||
public void cancel() { | public void cancel() { | ||||
if (committed) { | if (committed) { | ||||
throw new IllegalStateException("LedgerEditor had been committed!"); | |||||
throw new IllegalStateException("The current block has been committed!"); | |||||
} | } | ||||
if (canceled) { | if (canceled) { | ||||
return; | return; | ||||
} | } | ||||
canceled = true; | canceled = true; | ||||
// if (newTxCtx != null) { | |||||
// newTxCtx.rollback(); | |||||
// newTxCtx = null; | |||||
// } | |||||
bufferedStorage.cancel(); | |||||
baseStorage.cancel(); | |||||
} | } | ||||
private void checkState() { | private void checkState() { | ||||
if (prepared) { | if (prepared) { | ||||
throw new IllegalStateException("LedgerEditor has been prepared!"); | |||||
throw new IllegalStateException("The current block is ready!"); | |||||
} | } | ||||
if (committed) { | if (committed) { | ||||
throw new IllegalStateException("LedgerEditor has been committed!"); | |||||
throw new IllegalStateException("The current block has been committed!"); | |||||
} | } | ||||
if (canceled) { | if (canceled) { | ||||
throw new IllegalStateException("LedgerEditor has been canceled!"); | |||||
throw new IllegalStateException("The current block has been canceled!"); | |||||
} | } | ||||
} | } | ||||
// --------------------------- inner type -------------------------- | // --------------------------- inner type -------------------------- | ||||
/** | |||||
* 用于暂存交易上下文数据的快照对象; | |||||
* | |||||
* @author huanghaiquan | |||||
* | |||||
*/ | |||||
private static interface StagedSnapshot { | private static interface StagedSnapshot { | ||||
} | } | ||||
/** | |||||
* 创世区块的快照对象; | |||||
* | |||||
* @author huanghaiquan | |||||
* | |||||
*/ | |||||
private static class GenesisSnapshot implements StagedSnapshot { | private static class GenesisSnapshot implements StagedSnapshot { | ||||
private LedgerInitSetting initSetting; | private LedgerInitSetting initSetting; | ||||
@@ -337,6 +430,12 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
} | } | ||||
} | } | ||||
/** | |||||
* 交易执行完毕后的快照对象; | |||||
* | |||||
* @author huanghaiquan | |||||
* | |||||
*/ | |||||
private static class TxSnapshot implements StagedSnapshot { | private static class TxSnapshot implements StagedSnapshot { | ||||
/** | /** | ||||
@@ -347,58 +446,90 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
/** | /** | ||||
* 交易集合的快照(根哈希); | * 交易集合的快照(根哈希); | ||||
*/ | */ | ||||
private HashDigest transactionSetHash; | |||||
private HashDigest txsetHash; | |||||
public TxSnapshot(LedgerDataSnapshot dataSnapshot, HashDigest txSetHash) { | |||||
this.dataSnapshot = dataSnapshot; | |||||
this.transactionSetHash = txSetHash; | |||||
public HashDigest getAdminAccountHash() { | |||||
return dataSnapshot.getAdminAccountHash(); | |||||
} | } | ||||
} | |||||
private static class LedgerDataContext { | |||||
public HashDigest getUserAccountSetHash() { | |||||
return dataSnapshot.getUserAccountSetHash(); | |||||
} | |||||
protected LedgerDataSetImpl dataset; | |||||
public HashDigest getDataAccountSetHash() { | |||||
return dataSnapshot.getDataAccountSetHash(); | |||||
} | |||||
protected TransactionSet txset; | |||||
public HashDigest getContractAccountSetHash() { | |||||
return dataSnapshot.getContractAccountSetHash(); | |||||
} | |||||
protected BufferedKVStorage storage; | |||||
public HashDigest getTransactionSetHash() { | |||||
return txsetHash; | |||||
} | |||||
public LedgerDataContext(LedgerDataSetImpl dataset, TransactionSet txset, BufferedKVStorage storage) { | |||||
this.dataset = dataset; | |||||
this.txset = txset; | |||||
this.storage = storage; | |||||
public TxSnapshot(LedgerDataSnapshot dataSnapshot, HashDigest txsetHash) { | |||||
this.dataSnapshot = dataSnapshot; | |||||
this.txsetHash = txsetHash; | |||||
} | } | ||||
} | } | ||||
private static class LedgerTransactionContextImpl extends LedgerDataContext implements LedgerTransactionContext { | |||||
// /** | |||||
// * 账本的数据上下文; | |||||
// * | |||||
// * @author huanghaiquan | |||||
// * | |||||
// */ | |||||
// private static class LedgerDataContext { | |||||
// | |||||
// protected LedgerDataSetImpl dataset; | |||||
// | |||||
// protected TransactionSet txset; | |||||
// | |||||
// protected BufferedKVStorage storage; | |||||
// | |||||
// public LedgerDataContext(LedgerDataSetImpl dataset, TransactionSet txset, BufferedKVStorage storage) { | |||||
// this.dataset = dataset; | |||||
// this.txset = txset; | |||||
// this.storage = storage; | |||||
// } | |||||
// | |||||
// } | |||||
private long blockHeight; | |||||
/** | |||||
* 交易的上下文; | |||||
* | |||||
* @author huanghaiquan | |||||
* | |||||
*/ | |||||
private static class LedgerTransactionContextImpl implements LedgerTransactionContext { | |||||
private LedgerTransactionalEditor editor; | |||||
private LedgerTransactionalEditor blockEditor; | |||||
private TransactionRequest txRequest; | private TransactionRequest txRequest; | ||||
// private LedgerDataSetImpl dataset; | |||||
// | |||||
// private TransactionSet txset; | |||||
// | |||||
// private BufferedKVStorage storage; | |||||
private LedgerDataSetImpl dataset; | |||||
private TransactionSet txset; | |||||
private BufferedKVStorage storage; | |||||
private boolean committed = false; | private boolean committed = false; | ||||
private boolean rollbacked = false; | private boolean rollbacked = false; | ||||
private LedgerTransactionContextImpl(long blockHeight, TransactionRequest txRequest, LedgerDataSetImpl dataset, | |||||
private LedgerTransaction transaction; | |||||
private HashDigest txRootHash; | |||||
private LedgerTransactionContextImpl(TransactionRequest txRequest, LedgerDataSetImpl dataset, | |||||
TransactionSet txset, BufferedKVStorage storage, LedgerTransactionalEditor editor) { | TransactionSet txset, BufferedKVStorage storage, LedgerTransactionalEditor editor) { | ||||
super(dataset, txset, storage); | |||||
this.txRequest = txRequest; | this.txRequest = txRequest; | ||||
// this.dataset = dataset; | |||||
// this.txset = txset; | |||||
// this.storage = storage; | |||||
this.editor = editor; | |||||
this.blockHeight = blockHeight; | |||||
this.dataset = dataset; | |||||
this.txset = txset; | |||||
this.storage = storage; | |||||
this.blockEditor = editor; | |||||
} | } | ||||
@Override | @Override | ||||
@@ -407,7 +538,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
} | } | ||||
@Override | @Override | ||||
public TransactionRequest getRequestTX() { | |||||
public TransactionRequest getTransactionRequest() { | |||||
return txRequest; | return txRequest; | ||||
} | } | ||||
@@ -421,24 +552,28 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
checkTxState(); | checkTxState(); | ||||
// capture snapshot | // capture snapshot | ||||
// this.dataset.commit(); | |||||
// TransactionStagedSnapshot txDataSnapshot = takeSnapshot(); | |||||
// LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest, | |||||
// txResult, txDataSnapshot); | |||||
LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest, txResult, null, | |||||
operationResultArray(operationResults)); | |||||
this.txset.add(tx); | |||||
// this.txset.commit(); | |||||
// this.storage.flush(); | |||||
this.dataset.commit(); | |||||
TransactionStagedSnapshot txDataSnapshot = takeDataSnapshot(); | |||||
LedgerTransactionData tx; | |||||
try { | |||||
tx = new LedgerTransactionData(blockEditor.getBlockHeight(), txRequest, txResult, txDataSnapshot, | |||||
operationResultArray(operationResults)); | |||||
this.txset.add(tx); | |||||
this.txset.commit(); | |||||
} catch (Exception e) { | |||||
throw new TransactionRollbackException(e.getMessage(), e); | |||||
} | |||||
// TODO: 未处理出错时 dataset 和 txset 的内部状态恢复,有可能出现不一致的情况; | |||||
try { | |||||
this.storage.flush(); | |||||
} catch (Exception e) { | |||||
throw new BlockRollbackException(e.getMessage(), e); | |||||
} | |||||
// put snapshot into stack; | // put snapshot into stack; | ||||
// TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash()); | |||||
// editor.commitTxSnapshot(snapshot); | |||||
TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash()); | |||||
blockEditor.commitTxSnapshot(snapshot); | |||||
committed = true; | committed = true; | ||||
return tx; | return tx; | ||||
@@ -454,29 +589,35 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
checkTxState(); | checkTxState(); | ||||
// 未处理 | // 未处理 | ||||
// dataset.cancel(); | |||||
// TransactionStagedSnapshot txDataSnapshot = takeSnapshot(); | |||||
// LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest, | |||||
// txResult, txDataSnapshot); | |||||
LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest, txResult, null, | |||||
operationResultArray(operationResults)); | |||||
this.txset.add(tx); | |||||
// this.txset.commit(); | |||||
// this.storage.flush(); | |||||
dataset.cancel(); | |||||
TransactionStagedSnapshot txDataSnapshot = takeDataSnapshot(); | |||||
LedgerTransactionData tx; | |||||
try { | |||||
tx = new LedgerTransactionData(blockEditor.getBlockHeight(), txRequest, txResult, txDataSnapshot, | |||||
operationResultArray(operationResults)); | |||||
this.txset.add(tx); | |||||
this.txset.commit(); | |||||
} catch (Exception e) { | |||||
throw new TransactionRollbackException(e.getMessage(), e); | |||||
} | |||||
// TODO: 未处理出错时 dataset 和 txset 的内部状态恢复,有可能出现不一致的情况; | |||||
try { | |||||
this.storage.flush(); | |||||
} catch (Exception e) { | |||||
throw new BlockRollbackException(e.getMessage(), e); | |||||
} | |||||
// put snapshot into stack; | // put snapshot into stack; | ||||
// TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash()); | |||||
// editor.commitTxSnapshot(snapshot); | |||||
TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash()); | |||||
blockEditor.commitTxSnapshot(snapshot); | |||||
committed = true; | committed = true; | ||||
return tx; | return tx; | ||||
} | } | ||||
private TransactionStagedSnapshot takeSnapshot() { | |||||
private TransactionStagedSnapshot takeDataSnapshot() { | |||||
TransactionStagedSnapshot txDataSnapshot = new TransactionStagedSnapshot(); | TransactionStagedSnapshot txDataSnapshot = new TransactionStagedSnapshot(); | ||||
txDataSnapshot.setAdminAccountHash(dataset.getAdminAccount().getHash()); | txDataSnapshot.setAdminAccountHash(dataset.getAdminAccount().getHash()); | ||||
txDataSnapshot.setContractAccountSetHash(dataset.getContractAccountSet().getRootHash()); | txDataSnapshot.setContractAccountSetHash(dataset.getContractAccountSet().getRootHash()); | ||||
@@ -500,22 +641,22 @@ public class LedgerTransactionalEditor implements LedgerEditor { | |||||
return; | return; | ||||
} | } | ||||
if (this.committed) { | if (this.committed) { | ||||
throw new IllegalStateException("Transaction had been committed!"); | |||||
throw new IllegalStateException("This transaction had been committed!"); | |||||
} | } | ||||
// dataset.cancel(); | |||||
// storage.cancel(); | |||||
dataset.cancel(); | |||||
storage.cancel(); | |||||
// editor.rollbackNewTx(); | |||||
blockEditor.rollbackCurrentTx(); | |||||
rollbacked = true; | rollbacked = true; | ||||
} | } | ||||
private void checkTxState() { | private void checkTxState() { | ||||
if (this.committed) { | if (this.committed) { | ||||
throw new IllegalStateException("Transaction had been committed!"); | |||||
throw new IllegalStateException("This transaction had been committed!"); | |||||
} | } | ||||
if (this.rollbacked) { | if (this.rollbacked) { | ||||
throw new IllegalStateException("Transaction had been rollbacked!"); | |||||
throw new IllegalStateException("This transaction had been rollbacked!"); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -8,18 +8,19 @@ import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
import com.jd.blockchain.crypto.HashDigest; | import com.jd.blockchain.crypto.HashDigest; | ||||
import com.jd.blockchain.ledger.BlockRollbackException; | |||||
import com.jd.blockchain.ledger.BytesValue; | import com.jd.blockchain.ledger.BytesValue; | ||||
import com.jd.blockchain.ledger.ContractDoesNotExistException; | import com.jd.blockchain.ledger.ContractDoesNotExistException; | ||||
import com.jd.blockchain.ledger.DataAccountDoesNotExistException; | import com.jd.blockchain.ledger.DataAccountDoesNotExistException; | ||||
import com.jd.blockchain.ledger.DigitalSignature; | |||||
import com.jd.blockchain.ledger.IllegalTransactionException; | |||||
import com.jd.blockchain.ledger.LedgerBlock; | import com.jd.blockchain.ledger.LedgerBlock; | ||||
import com.jd.blockchain.ledger.LedgerException; | import com.jd.blockchain.ledger.LedgerException; | ||||
import com.jd.blockchain.ledger.Operation; | import com.jd.blockchain.ledger.Operation; | ||||
import com.jd.blockchain.ledger.OperationResult; | import com.jd.blockchain.ledger.OperationResult; | ||||
import com.jd.blockchain.ledger.OperationResultData; | import com.jd.blockchain.ledger.OperationResultData; | ||||
import com.jd.blockchain.ledger.TransactionContent; | |||||
import com.jd.blockchain.ledger.TransactionRequest; | import com.jd.blockchain.ledger.TransactionRequest; | ||||
import com.jd.blockchain.ledger.TransactionResponse; | import com.jd.blockchain.ledger.TransactionResponse; | ||||
import com.jd.blockchain.ledger.TransactionRollbackException; | |||||
import com.jd.blockchain.ledger.TransactionState; | import com.jd.blockchain.ledger.TransactionState; | ||||
import com.jd.blockchain.ledger.UserDoesNotExistException; | import com.jd.blockchain.ledger.UserDoesNotExistException; | ||||
import com.jd.blockchain.ledger.core.LedgerDataSet; | import com.jd.blockchain.ledger.core.LedgerDataSet; | ||||
@@ -31,8 +32,6 @@ import com.jd.blockchain.ledger.core.TransactionRequestContext; | |||||
import com.jd.blockchain.service.TransactionBatchProcess; | import com.jd.blockchain.service.TransactionBatchProcess; | ||||
import com.jd.blockchain.service.TransactionBatchResult; | import com.jd.blockchain.service.TransactionBatchResult; | ||||
import com.jd.blockchain.service.TransactionBatchResultHandle; | import com.jd.blockchain.service.TransactionBatchResultHandle; | ||||
import com.jd.blockchain.transaction.TxBuilder; | |||||
import com.jd.blockchain.transaction.TxRequestBuilder; | |||||
import com.jd.blockchain.transaction.TxResponseMessage; | import com.jd.blockchain.transaction.TxResponseMessage; | ||||
import com.jd.blockchain.utils.Bytes; | import com.jd.blockchain.utils.Bytes; | ||||
@@ -70,18 +69,6 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { | |||||
this.ledgerService = ledgerService; | this.ledgerService = ledgerService; | ||||
} | } | ||||
private boolean isRequestedLedger(TransactionRequest txRequest) { | |||||
HashDigest currLedgerHash = newBlockEditor.getLedgerHash(); | |||||
HashDigest reqLedgerHash = txRequest.getTransactionContent().getLedgerHash(); | |||||
if (currLedgerHash == reqLedgerHash) { | |||||
return true; | |||||
} | |||||
if (currLedgerHash == null || reqLedgerHash == null) { | |||||
return false; | |||||
} | |||||
return currLedgerHash.equals(reqLedgerHash); | |||||
} | |||||
/* | /* | ||||
* (non-Javadoc) | * (non-Javadoc) | ||||
* | * | ||||
@@ -93,19 +80,6 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { | |||||
public TransactionResponse schedule(TransactionRequest request) { | public TransactionResponse schedule(TransactionRequest request) { | ||||
TransactionResponse resp; | TransactionResponse resp; | ||||
try { | try { | ||||
if (!isRequestedLedger(request)) { | |||||
// 抛弃不属于当前账本的交易请求; | |||||
resp = discard(request, TransactionState.DISCARD_BY_WRONG_LEDGER); | |||||
responseList.add(resp); | |||||
return resp; | |||||
} | |||||
if (!verifyTxContent(request)) { | |||||
// 抛弃哈希和签名校验失败的交易请求; | |||||
resp = discard(request, TransactionState.DISCARD_BY_WRONG_CONTENT_SIGNATURE); | |||||
responseList.add(resp); | |||||
return resp; | |||||
} | |||||
LOGGER.debug("Start handling transaction... --[BlockHeight={}][RequestHash={}][TxHash={}]", | LOGGER.debug("Start handling transaction... --[BlockHeight={}][RequestHash={}][TxHash={}]", | ||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash()); | newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash()); | ||||
// 创建交易上下文; | // 创建交易上下文; | ||||
@@ -118,19 +92,34 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { | |||||
LOGGER.debug("Complete handling transaction. --[BlockHeight={}][RequestHash={}][TxHash={}]", | LOGGER.debug("Complete handling transaction. --[BlockHeight={}][RequestHash={}][TxHash={}]", | ||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash()); | newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash()); | ||||
responseList.add(resp); | |||||
return resp; | |||||
} catch (IllegalTransactionException e) { | |||||
// 抛弃发生处理异常的交易请求; | |||||
resp = discard(request, e.getTxState()); | |||||
LOGGER.error(String.format( | |||||
"Ignore transaction caused by IllegalTransactionException! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | |||||
e.getMessage()), e); | |||||
} catch (BlockRollbackException e) { | |||||
// 抛弃发生处理异常的交易请求; | |||||
// resp = discard(request, TransactionState.IGNORED_BY_BLOCK_FULL_ROLLBACK); | |||||
LOGGER.error(String.format( | |||||
"Ignore transaction caused by BlockRollbackException! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | |||||
e.getMessage()), e); | |||||
throw e; | |||||
} catch (Exception e) { | } catch (Exception e) { | ||||
// 抛弃发生处理异常的交易请求; | // 抛弃发生处理异常的交易请求; | ||||
resp = discard(request, TransactionState.SYSTEM_ERROR); | resp = discard(request, TransactionState.SYSTEM_ERROR); | ||||
LOGGER.error(String.format( | LOGGER.error(String.format( | ||||
"Discard transaction rollback caused by the system exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
"Ignore transaction caused by the system exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | ||||
e.getMessage()), e); | e.getMessage()), e); | ||||
responseList.add(resp); | |||||
return resp; | |||||
} | } | ||||
responseList.add(resp); | |||||
return resp; | |||||
} | } | ||||
/** | /** | ||||
@@ -186,6 +175,23 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { | |||||
// 提交交易(事务); | // 提交交易(事务); | ||||
result = TransactionState.SUCCESS; | result = TransactionState.SUCCESS; | ||||
txCtx.commit(result, operationResults); | txCtx.commit(result, operationResults); | ||||
} catch (TransactionRollbackException e) { | |||||
result = TransactionState.IGNORED_BY_TX_FULL_ROLLBACK; | |||||
txCtx.rollback(); | |||||
LOGGER.error(String.format( | |||||
"Transaction was full rolled back! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | |||||
e.getMessage()), e); | |||||
} catch (BlockRollbackException e) { | |||||
result = TransactionState.IGNORED_BY_BLOCK_FULL_ROLLBACK; | |||||
txCtx.rollback(); | |||||
LOGGER.error( | |||||
String.format("Transaction was rolled back! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), | |||||
request.getTransactionContent().getHash(), e.getMessage()), | |||||
e); | |||||
// 重新抛出由上层错误处理; | |||||
throw e; | |||||
} catch (LedgerException e) { | } catch (LedgerException e) { | ||||
// TODO: 识别更详细的异常类型以及执行对应的处理; | // TODO: 识别更详细的异常类型以及执行对应的处理; | ||||
result = TransactionState.LEDGER_ERROR; | result = TransactionState.LEDGER_ERROR; | ||||
@@ -198,14 +204,14 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { | |||||
} | } | ||||
txCtx.discardAndCommit(result, operationResults); | txCtx.discardAndCommit(result, operationResults); | ||||
LOGGER.error(String.format( | LOGGER.error(String.format( | ||||
"Transaction rollback caused by the ledger exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
"Due to ledger exception, the data changes resulting from the transaction will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | ||||
e.getMessage()), e); | e.getMessage()), e); | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
result = TransactionState.SYSTEM_ERROR; | result = TransactionState.SYSTEM_ERROR; | ||||
txCtx.discardAndCommit(TransactionState.SYSTEM_ERROR, operationResults); | txCtx.discardAndCommit(TransactionState.SYSTEM_ERROR, operationResults); | ||||
LOGGER.error(String.format( | LOGGER.error(String.format( | ||||
"Transaction rollback caused by the system exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
"Due to system exception, the data changes resulting from the transaction will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", | |||||
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), | ||||
e.getMessage()), e); | e.getMessage()), e); | ||||
} | } | ||||
@@ -218,32 +224,6 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { | |||||
return resp; | return resp; | ||||
} | } | ||||
private boolean verifyTxContent(TransactionRequest request) { | |||||
TransactionContent txContent = request.getTransactionContent(); | |||||
if (!TxBuilder.verifyTxContentHash(txContent, txContent.getHash())) { | |||||
return false; | |||||
} | |||||
DigitalSignature[] endpointSignatures = request.getEndpointSignatures(); | |||||
if (endpointSignatures != null) { | |||||
for (DigitalSignature signature : endpointSignatures) { | |||||
if (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(), | |||||
signature.getPubKey())) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
DigitalSignature[] nodeSignatures = request.getNodeSignatures(); | |||||
if (nodeSignatures != null) { | |||||
for (DigitalSignature signature : nodeSignatures) { | |||||
if (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(), | |||||
signature.getPubKey())) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/** | /** | ||||
* 直接丢弃交易; | * 直接丢弃交易; | ||||
* | * | ||||
@@ -138,7 +138,7 @@ public class LedgerEditorTest { | |||||
LedgerTransaction tx = genisisTxCtx.commit(TransactionState.SUCCESS); | LedgerTransaction tx = genisisTxCtx.commit(TransactionState.SUCCESS); | ||||
TransactionRequest genesisTxReq = genisisTxCtx.getRequestTX(); | |||||
TransactionRequest genesisTxReq = genisisTxCtx.getTransactionRequest(); | |||||
assertEquals(genesisTxReq.getTransactionContent().getHash(), tx.getTransactionContent().getHash()); | assertEquals(genesisTxReq.getTransactionContent().getHash(), tx.getTransactionContent().getHash()); | ||||
assertEquals(0, tx.getBlockHeight()); | assertEquals(0, tx.getBlockHeight()); | ||||
@@ -116,7 +116,8 @@ public class LedgerManagerTest { | |||||
assertEquals(0, genesisBlock.getHeight()); | assertEquals(0, genesisBlock.getHeight()); | ||||
assertNotNull(genesisBlock.getHash()); | assertNotNull(genesisBlock.getHash()); | ||||
assertNull(genesisBlock.getPreviousHash()); | assertNull(genesisBlock.getPreviousHash()); | ||||
assertEquals(ledgerHash, genesisBlock.getLedgerHash()); | |||||
// 创世区块的账本hash 为null;创世区块本身的哈希就代表了账本的哈希; | |||||
assertNull(genesisBlock.getLedgerHash()); | |||||
// 提交数据,写入存储; | // 提交数据,写入存储; | ||||
ldgEdt.commit(); | ldgEdt.commit(); | ||||
@@ -131,7 +132,8 @@ public class LedgerManagerTest { | |||||
LedgerBlock latestBlock = reloadLedgerRepo.getLatestBlock(); | LedgerBlock latestBlock = reloadLedgerRepo.getLatestBlock(); | ||||
assertEquals(0, latestBlock.getHeight()); | assertEquals(0, latestBlock.getHeight()); | ||||
assertEquals(ledgerHash, latestBlock.getHash()); | assertEquals(ledgerHash, latestBlock.getHash()); | ||||
assertEquals(ledgerHash, latestBlock.getLedgerHash()); | |||||
// 创世区块的账本hash 为null;创世区块本身的哈希就代表了账本的哈希; | |||||
assertNull(latestBlock.getLedgerHash()); | |||||
LedgerEditor editor1 = reloadLedgerRepo.createNextBlock(); | LedgerEditor editor1 = reloadLedgerRepo.createNextBlock(); | ||||
@@ -24,6 +24,7 @@ import com.jd.blockchain.transaction.ConsensusParticipantData; | |||||
import com.jd.blockchain.transaction.LedgerInitSettingData; | import com.jd.blockchain.transaction.LedgerInitSettingData; | ||||
import com.jd.blockchain.transaction.TransactionService; | import com.jd.blockchain.transaction.TransactionService; | ||||
import com.jd.blockchain.transaction.TxBuilder; | import com.jd.blockchain.transaction.TxBuilder; | ||||
import com.jd.blockchain.utils.Bytes; | |||||
import com.jd.blockchain.utils.io.BytesUtils; | import com.jd.blockchain.utils.io.BytesUtils; | ||||
import com.jd.blockchain.utils.net.NetworkAddress; | import com.jd.blockchain.utils.net.NetworkAddress; | ||||
@@ -124,6 +125,44 @@ public class LedgerTestUtils { | |||||
return txReqBuilder.buildRequest(); | return txReqBuilder.buildRequest(); | ||||
} | } | ||||
public static TransactionRequest createTxRequest_DataAccountReg(BlockchainKeypair dataAccountID, HashDigest ledgerHash, | |||||
BlockchainKeypair nodeKeypair, BlockchainKeypair... signers) { | |||||
TxBuilder txBuilder = new TxBuilder(ledgerHash); | |||||
txBuilder.dataAccounts().register(dataAccountID.getIdentity()); | |||||
TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); | |||||
if (signers != null) { | |||||
for (BlockchainKeypair signer : signers) { | |||||
txReqBuilder.signAsEndpoint(signer); | |||||
} | |||||
} | |||||
if (nodeKeypair != null) { | |||||
txReqBuilder.signAsNode(nodeKeypair); | |||||
} | |||||
return txReqBuilder.buildRequest(); | |||||
} | |||||
public static TransactionRequest createTxRequest_DataAccountWrite(Bytes dataAccountAddress, String key, String value, long version, HashDigest ledgerHash, | |||||
BlockchainKeypair nodeKeypair, BlockchainKeypair... signers) { | |||||
TxBuilder txBuilder = new TxBuilder(ledgerHash); | |||||
txBuilder.dataAccount(dataAccountAddress).setText(key, value, version); | |||||
TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); | |||||
if (signers != null) { | |||||
for (BlockchainKeypair signer : signers) { | |||||
txReqBuilder.signAsEndpoint(signer); | |||||
} | |||||
} | |||||
if (nodeKeypair != null) { | |||||
txReqBuilder.signAsNode(nodeKeypair); | |||||
} | |||||
return txReqBuilder.buildRequest(); | |||||
} | |||||
/** | /** | ||||
* @param userKeypair 要注册的用户key; | * @param userKeypair 要注册的用户key; | ||||
@@ -58,6 +58,7 @@ public class MerkleDataSetTest { | |||||
mds.setValue("C", "C".getBytes(), -1); | mds.setValue("C", "C".getBytes(), -1); | ||||
mds.commit(); | mds.commit(); | ||||
HashDigest root1 = mds.getRootHash(); | |||||
// 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量; | // 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量; | ||||
// 所以:3 项; | // 所以:3 项; | ||||
@@ -68,6 +69,8 @@ public class MerkleDataSetTest { | |||||
mds.setValue("B", "B".getBytes(), 0); | mds.setValue("B", "B".getBytes(), 0); | ||||
mds.setValue("C", "C".getBytes(), 0); | mds.setValue("C", "C".getBytes(), 0); | ||||
mds.commit(); | mds.commit(); | ||||
HashDigest root2 = mds.getRootHash(); | |||||
assertNotEquals(root1, root2); | |||||
// Version changed only;仅仅增加 merkle 节点,此时 Merkle 树只有 1 层路径节点,因此只更新2个数据节点和 1 | // Version changed only;仅仅增加 merkle 节点,此时 Merkle 树只有 1 层路径节点,因此只更新2个数据节点和 1 | ||||
// 个路径节点;(注:版本值是在同一个 key 下按序列保存的); | // 个路径节点;(注:版本值是在同一个 key 下按序列保存的); | ||||
@@ -76,6 +79,9 @@ public class MerkleDataSetTest { | |||||
mds.setValue("D", "DValue".getBytes(), -1); | mds.setValue("D", "DValue".getBytes(), -1); | ||||
mds.commit(); | mds.commit(); | ||||
HashDigest root3 = mds.getRootHash(); | |||||
assertNotEquals(root2, root3); | |||||
assertNotEquals(root1, root3); | |||||
// New key added, include 1 versioning kv, 1 sn key, 2 merkle nodes; | // New key added, include 1 versioning kv, 1 sn key, 2 merkle nodes; | ||||
// String[] keys = StringUtils.toStringArray(storage.keySet()); | // String[] keys = StringUtils.toStringArray(storage.keySet()); | ||||
@@ -12,6 +12,8 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; | |||||
import com.jd.blockchain.crypto.HashDigest; | import com.jd.blockchain.crypto.HashDigest; | ||||
import com.jd.blockchain.ledger.BlockchainKeyGenerator; | import com.jd.blockchain.ledger.BlockchainKeyGenerator; | ||||
import com.jd.blockchain.ledger.BlockchainKeypair; | import com.jd.blockchain.ledger.BlockchainKeypair; | ||||
import com.jd.blockchain.ledger.BytesValue; | |||||
import com.jd.blockchain.ledger.DataAccountRegisterOperation; | |||||
import com.jd.blockchain.ledger.EndpointRequest; | import com.jd.blockchain.ledger.EndpointRequest; | ||||
import com.jd.blockchain.ledger.LedgerBlock; | import com.jd.blockchain.ledger.LedgerBlock; | ||||
import com.jd.blockchain.ledger.LedgerInitSetting; | import com.jd.blockchain.ledger.LedgerInitSetting; | ||||
@@ -23,6 +25,7 @@ import com.jd.blockchain.ledger.TransactionRequest; | |||||
import com.jd.blockchain.ledger.TransactionResponse; | import com.jd.blockchain.ledger.TransactionResponse; | ||||
import com.jd.blockchain.ledger.TransactionState; | import com.jd.blockchain.ledger.TransactionState; | ||||
import com.jd.blockchain.ledger.UserRegisterOperation; | import com.jd.blockchain.ledger.UserRegisterOperation; | ||||
import com.jd.blockchain.ledger.core.DataAccount; | |||||
import com.jd.blockchain.ledger.core.LedgerDataSet; | import com.jd.blockchain.ledger.core.LedgerDataSet; | ||||
import com.jd.blockchain.ledger.core.LedgerEditor; | import com.jd.blockchain.ledger.core.LedgerEditor; | ||||
import com.jd.blockchain.ledger.core.LedgerRepository; | import com.jd.blockchain.ledger.core.LedgerRepository; | ||||
@@ -44,6 +47,7 @@ public class TransactionBatchProcessorTest { | |||||
DataContractRegistry.register(EndpointRequest.class); | DataContractRegistry.register(EndpointRequest.class); | ||||
DataContractRegistry.register(TransactionResponse.class); | DataContractRegistry.register(TransactionResponse.class); | ||||
DataContractRegistry.register(UserRegisterOperation.class); | DataContractRegistry.register(UserRegisterOperation.class); | ||||
DataContractRegistry.register(DataAccountRegisterOperation.class); | |||||
} | } | ||||
private static final String LEDGER_KEY_PREFIX = "LDG://"; | private static final String LEDGER_KEY_PREFIX = "LDG://"; | ||||
@@ -223,7 +227,7 @@ public class TransactionBatchProcessorTest { | |||||
.get(transactionRequest2.getTransactionContent().getHash()); | .get(transactionRequest2.getTransactionContent().getHash()); | ||||
LedgerTransaction tx3 = ledgerRepo.getTransactionSet() | LedgerTransaction tx3 = ledgerRepo.getTransactionSet() | ||||
.get(transactionRequest3.getTransactionContent().getHash()); | .get(transactionRequest3.getTransactionContent().getHash()); | ||||
assertNotNull(tx1); | assertNotNull(tx1); | ||||
assertEquals(TransactionState.SUCCESS, tx1.getExecutionState()); | assertEquals(TransactionState.SUCCESS, tx1.getExecutionState()); | ||||
assertNotNull(tx2); | assertNotNull(tx2); | ||||
@@ -240,6 +244,131 @@ public class TransactionBatchProcessorTest { | |||||
assertTrue(existUser3); | assertTrue(existUser3); | ||||
} | } | ||||
@Test | |||||
public void testTxRollbackByVersionsConfliction() { | |||||
final MemoryKVStorage STORAGE = new MemoryKVStorage(); | |||||
// 初始化账本到指定的存储库; | |||||
ledgerHash = initLedger(STORAGE, parti0, parti1, parti2, parti3); | |||||
// 加载账本; | |||||
LedgerManager ledgerManager = new LedgerManager(); | |||||
LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); | |||||
// 验证参与方账户的存在; | |||||
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); | |||||
UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); | |||||
assertNotNull(user0); | |||||
boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); | |||||
assertTrue(partiRegistered); | |||||
// 注册数据账户; | |||||
// 生成新区块; | |||||
LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||||
OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); | |||||
TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||||
opReg, ledgerManager); | |||||
BlockchainKeypair dataAccountKeypair = BlockchainKeyGenerator.getInstance().generate(); | |||||
TransactionRequest transactionRequest1 = LedgerTestUtils.createTxRequest_DataAccountReg(dataAccountKeypair, | |||||
ledgerHash, parti0, parti0); | |||||
TransactionResponse txResp1 = txbatchProcessor.schedule(transactionRequest1); | |||||
LedgerBlock newBlock = newBlockEditor.prepare(); | |||||
newBlockEditor.commit(); | |||||
assertEquals(TransactionState.SUCCESS, txResp1.getExecutionState()); | |||||
DataAccount dataAccount = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()); | |||||
assertNotNull(dataAccount); | |||||
// 正确写入 KV 数据; | |||||
TransactionRequest txreq1 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||||
"K1", "V-1-1", -1, ledgerHash, parti0, parti0); | |||||
TransactionRequest txreq2 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||||
"K2", "V-2-1", -1, ledgerHash, parti0, parti0); | |||||
TransactionRequest txreq3 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||||
"K3", "V-3-1", -1, ledgerHash, parti0, parti0); | |||||
TransactionRequest txreq4 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||||
"K1", "V-1-2", 0, ledgerHash, parti0, parti0); | |||||
newBlockEditor = ledgerRepo.createNextBlock(); | |||||
previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); | |||||
txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); | |||||
txbatchProcessor.schedule(txreq1); | |||||
txbatchProcessor.schedule(txreq2); | |||||
txbatchProcessor.schedule(txreq3); | |||||
txbatchProcessor.schedule(txreq4); | |||||
newBlock = newBlockEditor.prepare(); | |||||
newBlockEditor.commit(); | |||||
BytesValue v1_0 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1", | |||||
0); | |||||
BytesValue v1_1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1", | |||||
1); | |||||
BytesValue v2 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K2", | |||||
0); | |||||
BytesValue v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3", | |||||
0); | |||||
assertNotNull(v1_0); | |||||
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()); | |||||
// 提交多笔数据写入的交易,包含存在数据版本冲突的交易,验证交易是否正确回滚; | |||||
TransactionRequest txreq5 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||||
"K3", "V-3-2", 0, ledgerHash, parti0, parti0); | |||||
// 指定冲突的版本号,正确的应该是版本1; | |||||
TransactionRequest txreq6 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||||
"K1", "V-1-3", 0, ledgerHash, parti0, parti0); | |||||
newBlockEditor = ledgerRepo.createNextBlock(); | |||||
previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); | |||||
txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); | |||||
txbatchProcessor.schedule(txreq5); | |||||
txbatchProcessor.schedule(txreq6); | |||||
newBlock = newBlockEditor.prepare(); | |||||
newBlockEditor.commit(); | |||||
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"); | |||||
assertEquals(1, k1_version); | |||||
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()); | |||||
assertEquals("V-3-2", v3.getValue().toUTF8String()); | |||||
// // 验证正确性; | |||||
// ledgerManager = new LedgerManager(); | |||||
// ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); | |||||
// | |||||
// LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); | |||||
// assertEquals(newBlock.getHash(), latestBlock.getHash()); | |||||
// assertEquals(1, newBlock.getHeight()); | |||||
// | |||||
// LedgerTransaction tx1 = ledgerRepo.getTransactionSet() | |||||
// .get(transactionRequest1.getTransactionContent().getHash()); | |||||
// | |||||
// assertNotNull(tx1); | |||||
// assertEquals(TransactionState.SUCCESS, tx1.getExecutionState()); | |||||
} | |||||
private HashDigest initLedger(MemoryKVStorage storage, BlockchainKeypair... partiKeys) { | private HashDigest initLedger(MemoryKVStorage storage, BlockchainKeypair... partiKeys) { | ||||
// 创建初始化配置; | // 创建初始化配置; | ||||
LedgerInitSetting initSetting = LedgerTestUtils.createLedgerInitSetting(partiKeys); | LedgerInitSetting initSetting = LedgerTestUtils.createLedgerInitSetting(partiKeys); | ||||
@@ -269,7 +398,9 @@ public class TransactionBatchProcessorTest { | |||||
assertNotNull(block.getHash()); | assertNotNull(block.getHash()); | ||||
assertNull(block.getPreviousHash()); | assertNull(block.getPreviousHash()); | ||||
assertEquals(block.getHash(), block.getLedgerHash()); | |||||
// 创世区块的账本哈希为 null; | |||||
assertNull(block.getLedgerHash()); | |||||
assertNotNull(block.getHash()); | |||||
// 提交数据,写入存储; | // 提交数据,写入存储; | ||||
ldgEdt.commit(); | ldgEdt.commit(); | ||||
@@ -20,4 +20,7 @@ public interface BlockBody extends LedgerDataSnapshot{ | |||||
@DataField(order=5, primitiveType = PrimitiveType.BYTES) | @DataField(order=5, primitiveType = PrimitiveType.BYTES) | ||||
HashDigest getTransactionSetHash(); | HashDigest getTransactionSetHash(); | ||||
@DataField(order=6, primitiveType = PrimitiveType.INT64) | |||||
long getTimestamp(); | |||||
} | } |
@@ -0,0 +1,33 @@ | |||||
package com.jd.blockchain.ledger; | |||||
public class BlockRollbackException extends LedgerException { | |||||
private static final long serialVersionUID = 3583192000738807503L; | |||||
private TransactionState state; | |||||
public BlockRollbackException(String message) { | |||||
this(TransactionState.SYSTEM_ERROR, message); | |||||
} | |||||
public BlockRollbackException(TransactionState state, String message) { | |||||
super(message); | |||||
assert TransactionState.SUCCESS != state; | |||||
this.state = state; | |||||
} | |||||
public BlockRollbackException(String message, Throwable cause) { | |||||
this(TransactionState.SYSTEM_ERROR, message, cause); | |||||
} | |||||
public BlockRollbackException(TransactionState state, String message, Throwable cause) { | |||||
super(message, cause); | |||||
assert TransactionState.SUCCESS != state; | |||||
this.state = state; | |||||
} | |||||
public TransactionState getState() { | |||||
return state; | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
package com.jd.blockchain.ledger; | |||||
public class IllegalTransactionException extends RuntimeException { | |||||
private static final long serialVersionUID = 6348921847690512944L; | |||||
private TransactionState txState; | |||||
public IllegalTransactionException(String message) { | |||||
super(message); | |||||
this.txState = TransactionState.SYSTEM_ERROR; | |||||
} | |||||
public IllegalTransactionException(String message, TransactionState txState) { | |||||
super(message); | |||||
assert TransactionState.SUCCESS != txState; | |||||
this.txState = txState; | |||||
} | |||||
public IllegalTransactionException(String message, Throwable cause) { | |||||
super(message, cause); | |||||
this.txState = TransactionState.SYSTEM_ERROR; | |||||
} | |||||
public IllegalTransactionException(String message, Throwable cause, TransactionState txState) { | |||||
super(message, cause); | |||||
assert TransactionState.SUCCESS != txState; | |||||
this.txState = txState; | |||||
} | |||||
public TransactionState getTxState() { | |||||
return txState; | |||||
} | |||||
} |
@@ -40,6 +40,6 @@ public interface TransactionContentBody { | |||||
* @return | * @return | ||||
*/ | */ | ||||
@DataField(order = 3, primitiveType = PrimitiveType.INT64) | @DataField(order = 3, primitiveType = PrimitiveType.INT64) | ||||
long getTime(); | |||||
long getTimestamp(); | |||||
} | } |
@@ -1,22 +0,0 @@ | |||||
package com.jd.blockchain.ledger; | |||||
public class TransactionException extends Exception { | |||||
private static final long serialVersionUID = 3583192000738807503L; | |||||
private TransactionState state; | |||||
public TransactionException(TransactionState state) { | |||||
this.state = state; | |||||
} | |||||
public TransactionException(TransactionState state, String message) { | |||||
super(message); | |||||
this.state = state; | |||||
} | |||||
public TransactionState getState() { | |||||
return state; | |||||
} | |||||
} |
@@ -0,0 +1,16 @@ | |||||
package com.jd.blockchain.ledger; | |||||
public class TransactionRollbackException extends RuntimeException { | |||||
private static final long serialVersionUID = -1223140447229570029L; | |||||
public TransactionRollbackException(String message) { | |||||
super(message); | |||||
} | |||||
public TransactionRollbackException(String message, Throwable cause) { | |||||
super(message, cause); | |||||
} | |||||
} |
@@ -20,39 +20,58 @@ public enum TransactionState { | |||||
SUCCESS((byte) 0), | SUCCESS((byte) 0), | ||||
/** | /** | ||||
* 共识错误; | |||||
* 账本错误; | |||||
*/ | */ | ||||
CONSENSUS_ERROR((byte) 1), | |||||
LEDGER_ERROR((byte) 0x01), | |||||
/** | /** | ||||
* 账本错误; | |||||
* 数据账户不存在; | |||||
*/ | |||||
DATA_ACCOUNT_DOES_NOT_EXIST((byte) 0x02), | |||||
/** | |||||
* 用户不存在; | |||||
*/ | |||||
USER_DOES_NOT_EXIST((byte) 0x03), | |||||
/** | |||||
* 合约不存在; | |||||
*/ | */ | ||||
LEDGER_ERROR((byte) 2), | |||||
CONTRACT_DOES_NOT_EXIST((byte) 0x04), | |||||
/** | /** | ||||
* 由于在错误的账本上执行交易而被丢弃; | * 由于在错误的账本上执行交易而被丢弃; | ||||
*/ | */ | ||||
DISCARD_BY_WRONG_LEDGER((byte) 3), | |||||
IGNORED_BY_WRONG_LEDGER((byte) 0x40), | |||||
/** | /** | ||||
* 由于交易内容的验签失败而丢弃; | * 由于交易内容的验签失败而丢弃; | ||||
*/ | */ | ||||
DISCARD_BY_WRONG_CONTENT_SIGNATURE((byte) 4), | |||||
IGNORED_BY_WRONG_CONTENT_SIGNATURE((byte) 0x41), | |||||
/** | /** | ||||
* 数据账户不存在; | |||||
* 由于交易内容的验签失败而丢弃; | |||||
*/ | */ | ||||
DATA_ACCOUNT_DOES_NOT_EXIST((byte) 5), | |||||
IGNORED_BY_CONFLICTING_STATE((byte) 0x42), | |||||
/** | /** | ||||
* 用户不存在; | |||||
* 由于交易的整体回滚而丢弃; | |||||
* <p> | |||||
* | |||||
* 注: “整体回滚”是指把交易引入的数据更改以及交易记录本身全部都回滚;<br> | |||||
* “部分回滚”是指把交易引入的数据更改回滚了,但是交易记录本身以及相应的“交易结果({@link TransactionState})”都会提交;<br> | |||||
*/ | */ | ||||
USER_DOES_NOT_EXIST((byte) 6), | |||||
IGNORED_BY_TX_FULL_ROLLBACK((byte) 0x43), | |||||
/** | /** | ||||
* 合约不存在; | |||||
* 由于区块的整体回滚而丢弃; | |||||
* <p> | |||||
* | |||||
* 注: “整体回滚”是指把交易引入的数据更改以及交易记录本身全部都回滚;<br> | |||||
* | |||||
* “部分回滚”是指把交易引入的数据更改回滚了,但是交易记录本身以及相应的“交易结果({@link TransactionState})”都会提交;<br> | |||||
*/ | */ | ||||
CONTRACT_DOES_NOT_EXIST((byte) 6), | |||||
IGNORED_BY_BLOCK_FULL_ROLLBACK((byte) 0x44), | |||||
/** | /** | ||||
* 系统错误; | * 系统错误; | ||||
@@ -62,7 +81,12 @@ public enum TransactionState { | |||||
/** | /** | ||||
* 超时; | * 超时; | ||||
*/ | */ | ||||
TIMEOUT((byte) 0x81); | |||||
TIMEOUT((byte) 0x81), | |||||
/** | |||||
* 共识错误; | |||||
*/ | |||||
CONSENSUS_ERROR((byte) 0x82); | |||||
@EnumField(type = PrimitiveType.INT8) | @EnumField(type = PrimitiveType.INT8) | ||||
public final byte CODE; | public final byte CODE; | ||||
@@ -84,7 +84,7 @@ public class TxContentBlob implements TransactionContent { | |||||
} | } | ||||
@Override | @Override | ||||
public long getTime() { | |||||
public long getTimestamp() { | |||||
return time; | return time; | ||||
} | } | ||||
@@ -59,8 +59,8 @@ public interface VersioningKVStorage extends BatchStorageService { | |||||
/** | /** | ||||
* Update the value of the key;<br> | * Update the value of the key;<br> | ||||
* | * | ||||
* If key exist, and the specified version equals to latest , then the value is | |||||
* updated and version is increased by 1;<br> | |||||
* If key exist, and the specified version equals to it's latest version, then the value will be | |||||
* updated and version will be increased by 1;<br> | |||||
* If key not exist, and the specified version is -1, then the value will be | * If key not exist, and the specified version is -1, then the value will be | ||||
* created and initialized it's version by 0; <br> | * created and initialized it's version by 0; <br> | ||||
* | * | ||||