forked from TrueCloudLab/neoneo-go
Merge pull request #743 from nspcc-dev/optimize-some-db-accesses
Optimize some db accesses
This commit is contained in:
commit
5f1868af28
13 changed files with 227 additions and 384 deletions
|
@ -29,7 +29,7 @@ import (
|
||||||
// Tuning parameters.
|
// Tuning parameters.
|
||||||
const (
|
const (
|
||||||
headerBatchCount = 2000
|
headerBatchCount = 2000
|
||||||
version = "0.0.6"
|
version = "0.0.7"
|
||||||
|
|
||||||
// This one comes from C# code and it's different from the constant used
|
// This one comes from C# code and it's different from the constant used
|
||||||
// when creating an asset with Neo.Asset.Create interop call. It looks
|
// when creating an asset with Neo.Asset.Create interop call. It looks
|
||||||
|
@ -475,7 +475,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cache.PutUnspentCoinState(tx.Hash(), NewUnspentCoinState(len(tx.Outputs))); err != nil {
|
if err := cache.PutUnspentCoinState(tx.Hash(), state.NewUnspentCoin(block.Index, tx)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,22 +487,20 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
// Process TX inputs that are grouped by previous hash.
|
// Process TX inputs that are grouped by previous hash.
|
||||||
for _, inputs := range transaction.GroupInputsByPrevHash(tx.Inputs) {
|
for _, inputs := range transaction.GroupInputsByPrevHash(tx.Inputs) {
|
||||||
prevHash := inputs[0].PrevHash
|
prevHash := inputs[0].PrevHash
|
||||||
prevTX, prevTXHeight, err := bc.dao.GetTransaction(prevHash)
|
unspent, err := cache.GetUnspentCoinState(prevHash)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not find previous TX: %s", prevHash)
|
|
||||||
}
|
|
||||||
unspent, err := cache.GetUnspentCoinStateOrNew(prevHash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
spentCoin, err := cache.GetSpentCoinsOrNew(prevHash, prevTXHeight)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
oldSpentCoinLen := len(spentCoin.items)
|
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
unspent.states[input.PrevIndex] = state.CoinSpent
|
if len(unspent.States) <= int(input.PrevIndex) {
|
||||||
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
return fmt.Errorf("bad input: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
|
||||||
|
}
|
||||||
|
if unspent.States[input.PrevIndex].State&state.CoinSpent != 0 {
|
||||||
|
return fmt.Errorf("double spend: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
|
||||||
|
}
|
||||||
|
unspent.States[input.PrevIndex].State |= state.CoinSpent
|
||||||
|
unspent.States[input.PrevIndex].SpendHeight = block.Index
|
||||||
|
prevTXOutput := &unspent.States[input.PrevIndex].Output
|
||||||
account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
|
account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -510,14 +508,13 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
|
|
||||||
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
|
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
|
||||||
account.Unclaimed = append(account.Unclaimed, state.UnclaimedBalance{
|
account.Unclaimed = append(account.Unclaimed, state.UnclaimedBalance{
|
||||||
Tx: prevTX.Hash(),
|
Tx: input.PrevHash,
|
||||||
Index: input.PrevIndex,
|
Index: input.PrevIndex,
|
||||||
Start: prevTXHeight,
|
Start: unspent.Height,
|
||||||
End: block.Index,
|
End: block.Index,
|
||||||
Value: prevTXOutput.Amount,
|
Value: prevTXOutput.Amount,
|
||||||
})
|
})
|
||||||
spentCoin.items[input.PrevIndex] = block.Index
|
if err = processTXWithValidatorsSubtract(prevTXOutput, account, cache); err != nil {
|
||||||
if err = processTXWithValidatorsSubtract(&prevTXOutput, account, cache); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -548,11 +545,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
if err = cache.PutUnspentCoinState(prevHash, unspent); err != nil {
|
if err = cache.PutUnspentCoinState(prevHash, unspent); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if oldSpentCoinLen != len(spentCoin.items) {
|
|
||||||
if err = cache.PutSpentCoinState(prevHash, spentCoin); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the underlying type of the TX.
|
// Process the underlying type of the TX.
|
||||||
|
@ -588,17 +580,18 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
// Remove claimed NEO from spent coins making it unavalaible for
|
// Remove claimed NEO from spent coins making it unavalaible for
|
||||||
// additional claims.
|
// additional claims.
|
||||||
for _, input := range t.Claims {
|
for _, input := range t.Claims {
|
||||||
scs, err := cache.GetSpentCoinState(input.PrevHash)
|
scs, err := cache.GetUnspentCoinState(input.PrevHash)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, ok := scs.items[input.PrevIndex]
|
if len(scs.States) <= int(input.PrevIndex) {
|
||||||
if !ok {
|
err = errors.New("invalid claim index")
|
||||||
err = errors.New("no spent coin state")
|
} else if scs.States[input.PrevIndex].State&state.CoinClaimed != 0 {
|
||||||
|
err = errors.New("double claim")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We can't really do anything about it
|
// We can't really do anything about it
|
||||||
// as it's a transaction in a signed block.
|
// as it's a transaction in a signed block.
|
||||||
bc.log.Warn("DOUBLE CLAIM",
|
bc.log.Warn("FALSE OR DOUBLE CLAIM",
|
||||||
zap.String("PrevHash", input.PrevHash.StringLE()),
|
zap.String("PrevHash", input.PrevHash.StringLE()),
|
||||||
zap.Uint16("PrevIndex", input.PrevIndex),
|
zap.Uint16("PrevIndex", input.PrevIndex),
|
||||||
zap.String("tx", tx.Hash().StringLE()),
|
zap.String("tx", tx.Hash().StringLE()),
|
||||||
|
@ -611,14 +604,13 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
prevTx, _, err := cache.GetTransaction(input.PrevHash)
|
acc, err := cache.GetAccountState(scs.States[input.PrevIndex].ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if int(input.PrevIndex) > len(prevTx.Outputs) {
|
|
||||||
return errors.New("invalid input in claim")
|
|
||||||
}
|
}
|
||||||
acc, err := cache.GetAccountState(prevTx.Outputs[input.PrevIndex].ScriptHash)
|
|
||||||
if err != nil {
|
scs.States[input.PrevIndex].State |= state.CoinClaimed
|
||||||
|
if err = cache.PutUnspentCoinState(input.PrevHash, scs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,17 +635,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
} else if err := cache.PutAccountState(acc); err != nil {
|
} else if err := cache.PutAccountState(acc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(scs.items, input.PrevIndex)
|
|
||||||
if len(scs.items) > 0 {
|
|
||||||
if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err = cache.DeleteSpentCoinState(input.PrevHash); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case *transaction.EnrollmentTX:
|
case *transaction.EnrollmentTX:
|
||||||
if err := processEnrollmentTX(cache, t); err != nil {
|
if err := processEnrollmentTX(cache, t); err != nil {
|
||||||
|
@ -1174,7 +1155,7 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUnspentCoinState returns unspent coin state for given tx hash.
|
// GetUnspentCoinState returns unspent coin state for given tx hash.
|
||||||
func (bc *Blockchain) GetUnspentCoinState(hash util.Uint256) *UnspentCoinState {
|
func (bc *Blockchain) GetUnspentCoinState(hash util.Uint256) *state.UnspentCoin {
|
||||||
ucs, err := bc.dao.GetUnspentCoinState(hash)
|
ucs, err := bc.dao.GetUnspentCoinState(hash)
|
||||||
if ucs == nil && err != storage.ErrKeyNotFound {
|
if ucs == nil && err != storage.ErrKeyNotFound {
|
||||||
bc.log.Warn("failed to get unspent coin state", zap.Error(err))
|
bc.log.Warn("failed to get unspent coin state", zap.Error(err))
|
||||||
|
@ -1245,15 +1226,15 @@ func (bc *Blockchain) references(ins []transaction.Input) ([]transaction.InOut,
|
||||||
|
|
||||||
for _, inputs := range transaction.GroupInputsByPrevHash(ins) {
|
for _, inputs := range transaction.GroupInputsByPrevHash(ins) {
|
||||||
prevHash := inputs[0].PrevHash
|
prevHash := inputs[0].PrevHash
|
||||||
tx, _, err := bc.dao.GetTransaction(prevHash)
|
unspent, err := bc.dao.GetUnspentCoinState(prevHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("bad input reference")
|
return nil, errors.New("bad input reference")
|
||||||
}
|
}
|
||||||
for _, in := range inputs {
|
for _, in := range inputs {
|
||||||
if int(in.PrevIndex) > len(tx.Outputs)-1 {
|
if int(in.PrevIndex) > len(unspent.States)-1 {
|
||||||
return nil, errors.New("bad input reference")
|
return nil, errors.New("bad input reference")
|
||||||
}
|
}
|
||||||
references = append(references, transaction.InOut{In: *in, Out: tx.Outputs[in.PrevIndex]})
|
references = append(references, transaction.InOut{In: *in, Out: unspent.States[in.PrevIndex].Output})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return references, nil
|
return references, nil
|
||||||
|
@ -1364,7 +1345,12 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
||||||
if err := bc.verifyOutputs(t); err != nil {
|
if err := bc.verifyOutputs(t); err != nil {
|
||||||
return errors.Wrap(err, "wrong outputs")
|
return errors.Wrap(err, "wrong outputs")
|
||||||
}
|
}
|
||||||
if err := bc.verifyResults(t); err != nil {
|
refs, err := bc.References(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results := refsAndOutsToResults(refs, t.Outputs)
|
||||||
|
if err := bc.verifyResults(t, results); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1383,7 +1369,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
||||||
if bc.dao.IsDoubleClaim(claim) {
|
if bc.dao.IsDoubleClaim(claim) {
|
||||||
return errors.New("double claim")
|
return errors.New("double claim")
|
||||||
}
|
}
|
||||||
if err := bc.verifyClaims(t); err != nil {
|
if err := bc.verifyClaims(t, results); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case transaction.InvocationType:
|
case transaction.InvocationType:
|
||||||
|
@ -1396,10 +1382,9 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
||||||
return bc.verifyTxWitnesses(t, block)
|
return bc.verifyTxWitnesses(t, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) verifyClaims(tx *transaction.Transaction) (err error) {
|
func (bc *Blockchain) verifyClaims(tx *transaction.Transaction, results []*transaction.Result) (err error) {
|
||||||
t := tx.Data.(*transaction.ClaimTX)
|
t := tx.Data.(*transaction.ClaimTX)
|
||||||
var result *transaction.Result
|
var result *transaction.Result
|
||||||
results := bc.GetTransactionResults(tx)
|
|
||||||
for i := range results {
|
for i := range results {
|
||||||
if results[i].AssetID == UtilityTokenID() {
|
if results[i].AssetID == UtilityTokenID() {
|
||||||
result = results[i]
|
result = results[i]
|
||||||
|
@ -1426,17 +1411,26 @@ func (bc *Blockchain) calculateBonus(claims []transaction.Input) (util.Fixed8, e
|
||||||
|
|
||||||
for _, group := range inputs {
|
for _, group := range inputs {
|
||||||
h := group[0].PrevHash
|
h := group[0].PrevHash
|
||||||
claimable, err := bc.getUnclaimed(h)
|
unspent, err := bc.dao.GetUnspentCoinState(h)
|
||||||
if err != nil || len(claimable) == 0 {
|
if err != nil {
|
||||||
return 0, errors.New("no unclaimed inputs")
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range group {
|
for _, c := range group {
|
||||||
s, ok := claimable[c.PrevIndex]
|
if len(unspent.States) <= int(c.PrevIndex) {
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("can't find spent coins for %s (%d)", c.PrevHash.StringLE(), c.PrevIndex)
|
return 0, fmt.Errorf("can't find spent coins for %s (%d)", c.PrevHash.StringLE(), c.PrevIndex)
|
||||||
}
|
}
|
||||||
unclaimed = append(unclaimed, s)
|
if unspent.States[c.PrevIndex].State&state.CoinSpent == 0 {
|
||||||
|
return 0, fmt.Errorf("not spent yet: %s/%d", c.PrevHash.StringLE(), c.PrevIndex)
|
||||||
|
}
|
||||||
|
if unspent.States[c.PrevIndex].State&state.CoinClaimed != 0 {
|
||||||
|
return 0, fmt.Errorf("already claimed: %s/%d", c.PrevHash.StringLE(), c.PrevIndex)
|
||||||
|
}
|
||||||
|
unclaimed = append(unclaimed, &spentCoin{
|
||||||
|
Output: &unspent.States[c.PrevIndex].Output,
|
||||||
|
StartHeight: unspent.Height,
|
||||||
|
EndHeight: unspent.States[c.PrevIndex].SpendHeight,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1456,29 +1450,6 @@ func (bc *Blockchain) calculateBonusInternal(scs []*spentCoin) (util.Fixed8, err
|
||||||
return claimed, nil
|
return claimed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) getUnclaimed(h util.Uint256) (map[uint16]*spentCoin, error) {
|
|
||||||
tx, txHeight, err := bc.GetTransaction(h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
scs, err := bc.dao.GetSpentCoinState(h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[uint16]*spentCoin)
|
|
||||||
for i, height := range scs.items {
|
|
||||||
result[i] = &spentCoin{
|
|
||||||
Output: &tx.Outputs[i],
|
|
||||||
StartHeight: txHeight,
|
|
||||||
EndHeight: height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
||||||
// new block addition. It returns false for transactions already present in the
|
// new block addition. It returns false for transactions already present in the
|
||||||
// chain (added by the new block), transactions using some inputs that are
|
// chain (added by the new block), transactions using some inputs that are
|
||||||
|
@ -1581,11 +1552,7 @@ func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
|
func (bc *Blockchain) verifyResults(t *transaction.Transaction, results []*transaction.Result) error {
|
||||||
results := bc.GetTransactionResults(t)
|
|
||||||
if results == nil {
|
|
||||||
return errors.New("tx has no results")
|
|
||||||
}
|
|
||||||
var resultsDestroy []*transaction.Result
|
var resultsDestroy []*transaction.Result
|
||||||
var resultsIssue []*transaction.Result
|
var resultsIssue []*transaction.Result
|
||||||
for _, re := range results {
|
for _, re := range results {
|
||||||
|
@ -1641,36 +1608,30 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
|
||||||
// GetTransactionResults returns the transaction results aggregate by assetID.
|
// GetTransactionResults returns the transaction results aggregate by assetID.
|
||||||
// Golang of GetTransationResults method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L207)
|
// Golang of GetTransationResults method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L207)
|
||||||
func (bc *Blockchain) GetTransactionResults(t *transaction.Transaction) []*transaction.Result {
|
func (bc *Blockchain) GetTransactionResults(t *transaction.Transaction) []*transaction.Result {
|
||||||
var tempResults []*transaction.Result
|
|
||||||
var results []*transaction.Result
|
|
||||||
tempGroupResult := make(map[util.Uint256]util.Fixed8)
|
|
||||||
|
|
||||||
references, err := bc.References(t)
|
references, err := bc.References(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return refsAndOutsToResults(references, t.Outputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapReferencesToResults returns cumulative results of transaction based in its
|
||||||
|
// references and outputs.
|
||||||
|
func refsAndOutsToResults(references []transaction.InOut, outputs []transaction.Output) []*transaction.Result {
|
||||||
|
var results []*transaction.Result
|
||||||
|
tempResult := make(map[util.Uint256]util.Fixed8)
|
||||||
|
|
||||||
for _, inout := range references {
|
for _, inout := range references {
|
||||||
tempResults = append(tempResults, &transaction.Result{
|
c := tempResult[inout.Out.AssetID]
|
||||||
AssetID: inout.Out.AssetID,
|
tempResult[inout.Out.AssetID] = c.Add(inout.Out.Amount)
|
||||||
Amount: inout.Out.Amount,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, output := range t.Outputs {
|
|
||||||
tempResults = append(tempResults, &transaction.Result{
|
|
||||||
AssetID: output.AssetID,
|
|
||||||
Amount: -output.Amount,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, r := range tempResults {
|
|
||||||
if amount, ok := tempGroupResult[r.AssetID]; ok {
|
|
||||||
tempGroupResult[r.AssetID] = amount.Add(r.Amount)
|
|
||||||
} else {
|
|
||||||
tempGroupResult[r.AssetID] = r.Amount
|
|
||||||
}
|
}
|
||||||
|
for _, output := range outputs {
|
||||||
|
c := tempResult[output.AssetID]
|
||||||
|
tempResult[output.AssetID] = c.Sub(output.Amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
results = []*transaction.Result{} // this assignment is necessary. (Most of the time amount == 0 and results is the empty slice.)
|
results = []*transaction.Result{} // this assignment is necessary. (Most of the time amount == 0 and results is the empty slice.)
|
||||||
for assetID, amount := range tempGroupResult {
|
for assetID, amount := range tempResult {
|
||||||
if amount != util.Fixed8(0) {
|
if amount != util.Fixed8(0) {
|
||||||
results = append(results, &transaction.Result{
|
results = append(results, &transaction.Result{
|
||||||
AssetID: assetID,
|
AssetID: assetID,
|
||||||
|
@ -1720,20 +1681,20 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
}
|
}
|
||||||
|
|
||||||
for hash, inputs := range group {
|
for hash, inputs := range group {
|
||||||
prevTx, _, err := cache.GetTransaction(hash)
|
unspent, err := cache.GetUnspentCoinState(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// process inputs
|
// process inputs
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
prevOutput := prevTx.Outputs[input.PrevIndex]
|
prevOutput := &unspent.States[input.PrevIndex].Output
|
||||||
accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash)
|
accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// process account state votes: if there are any -> validators will be updated.
|
// process account state votes: if there are any -> validators will be updated.
|
||||||
if err = processTXWithValidatorsSubtract(&prevOutput, accountState, cache); err != nil {
|
if err = processTXWithValidatorsSubtract(prevOutput, accountState, cache); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
delete(accountState.Balances, prevOutput.AssetID)
|
delete(accountState.Balances, prevOutput.AssetID)
|
||||||
|
|
|
@ -42,7 +42,7 @@ type Blockchainer interface {
|
||||||
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
||||||
GetTestVM() (*vm.VM, storage.Store)
|
GetTestVM() (*vm.VM, storage.Store)
|
||||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
GetUnspentCoinState(util.Uint256) *UnspentCoinState
|
GetUnspentCoinState(util.Uint256) *state.UnspentCoin
|
||||||
References(t *transaction.Transaction) ([]transaction.InOut, error)
|
References(t *transaction.Transaction) ([]transaction.InOut, error)
|
||||||
mempool.Feer // fee interface
|
mempool.Feer // fee interface
|
||||||
PoolTx(*transaction.Transaction) error
|
PoolTx(*transaction.Transaction) error
|
||||||
|
|
|
@ -13,13 +13,15 @@ type cachedDao struct {
|
||||||
dao
|
dao
|
||||||
accounts map[util.Uint160]*state.Account
|
accounts map[util.Uint160]*state.Account
|
||||||
contracts map[util.Uint160]*state.Contract
|
contracts map[util.Uint160]*state.Contract
|
||||||
|
unspents map[util.Uint256]*state.UnspentCoin
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCachedDao returns new cachedDao wrapping around given backing store.
|
// newCachedDao returns new cachedDao wrapping around given backing store.
|
||||||
func newCachedDao(backend storage.Store) *cachedDao {
|
func newCachedDao(backend storage.Store) *cachedDao {
|
||||||
accs := make(map[util.Uint160]*state.Account)
|
accs := make(map[util.Uint160]*state.Account)
|
||||||
ctrs := make(map[util.Uint160]*state.Contract)
|
ctrs := make(map[util.Uint160]*state.Contract)
|
||||||
return &cachedDao{*newDao(backend), accs, ctrs}
|
unspents := make(map[util.Uint256]*state.UnspentCoin)
|
||||||
|
return &cachedDao{*newDao(backend), accs, ctrs, unspents}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountStateOrNew retrieves Account from cache or underlying Store
|
// GetAccountStateOrNew retrieves Account from cache or underlying Store
|
||||||
|
@ -69,6 +71,20 @@ func (cd *cachedDao) DeleteContractState(hash util.Uint160) error {
|
||||||
return cd.dao.DeleteContractState(hash)
|
return cd.dao.DeleteContractState(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUnspentCoinState retrieves UnspentCoin from cache or underlying Store.
|
||||||
|
func (cd *cachedDao) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) {
|
||||||
|
if cd.unspents[hash] != nil {
|
||||||
|
return cd.unspents[hash], nil
|
||||||
|
}
|
||||||
|
return cd.dao.GetUnspentCoinState(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutUnspentCoinState saves given UnspentCoin in the cache.
|
||||||
|
func (cd *cachedDao) PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error {
|
||||||
|
cd.unspents[hash] = ucs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Persist flushes all the changes made into the (supposedly) persistent
|
// Persist flushes all the changes made into the (supposedly) persistent
|
||||||
// underlying store.
|
// underlying store.
|
||||||
func (cd *cachedDao) Persist() (int, error) {
|
func (cd *cachedDao) Persist() (int, error) {
|
||||||
|
@ -78,5 +94,11 @@ func (cd *cachedDao) Persist() (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for hash := range cd.unspents {
|
||||||
|
err := cd.dao.PutUnspentCoinState(hash, cd.unspents[hash])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return cd.dao.Persist()
|
return cd.dao.Persist()
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,25 +175,9 @@ func (dao *dao) AppendNEP5Transfer(acc util.Uint160, tr *state.NEP5Transfer) err
|
||||||
|
|
||||||
// -- start unspent coins.
|
// -- start unspent coins.
|
||||||
|
|
||||||
// GetUnspentCoinStateOrNew gets UnspentCoinState from temporary or persistent Store
|
|
||||||
// and return it. If it's not present in both stores, returns a new
|
|
||||||
// UnspentCoinState.
|
|
||||||
func (dao *dao) GetUnspentCoinStateOrNew(hash util.Uint256) (*UnspentCoinState, error) {
|
|
||||||
unspent, err := dao.GetUnspentCoinState(hash)
|
|
||||||
if err != nil {
|
|
||||||
if err != storage.ErrKeyNotFound {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
unspent = &UnspentCoinState{
|
|
||||||
states: []state.Coin{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unspent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUnspentCoinState retrieves UnspentCoinState from the given store.
|
// GetUnspentCoinState retrieves UnspentCoinState from the given store.
|
||||||
func (dao *dao) GetUnspentCoinState(hash util.Uint256) (*UnspentCoinState, error) {
|
func (dao *dao) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) {
|
||||||
unspent := &UnspentCoinState{}
|
unspent := &state.UnspentCoin{}
|
||||||
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
||||||
err := dao.GetAndDecode(unspent, key)
|
err := dao.GetAndDecode(unspent, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -203,52 +187,13 @@ func (dao *dao) GetUnspentCoinState(hash util.Uint256) (*UnspentCoinState, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutUnspentCoinState puts given UnspentCoinState into the given store.
|
// PutUnspentCoinState puts given UnspentCoinState into the given store.
|
||||||
func (dao *dao) PutUnspentCoinState(hash util.Uint256, ucs *UnspentCoinState) error {
|
func (dao *dao) PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error {
|
||||||
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
||||||
return dao.Put(ucs, key)
|
return dao.Put(ucs, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- end unspent coins.
|
// -- end unspent coins.
|
||||||
|
|
||||||
// -- start spent coins.
|
|
||||||
|
|
||||||
// GetSpentCoinsOrNew returns spent coins from store.
|
|
||||||
func (dao *dao) GetSpentCoinsOrNew(hash util.Uint256, height uint32) (*SpentCoinState, error) {
|
|
||||||
spent, err := dao.GetSpentCoinState(hash)
|
|
||||||
if err != nil {
|
|
||||||
if err != storage.ErrKeyNotFound {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
spent = NewSpentCoinState(hash, height)
|
|
||||||
}
|
|
||||||
return spent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSpentCoinState gets SpentCoinState from the given store.
|
|
||||||
func (dao *dao) GetSpentCoinState(hash util.Uint256) (*SpentCoinState, error) {
|
|
||||||
spent := &SpentCoinState{}
|
|
||||||
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
|
|
||||||
err := dao.GetAndDecode(spent, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return spent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutSpentCoinState puts given SpentCoinState into the given store.
|
|
||||||
func (dao *dao) PutSpentCoinState(hash util.Uint256, scs *SpentCoinState) error {
|
|
||||||
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
|
|
||||||
return dao.Put(scs, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSpentCoinState deletes given SpentCoinState from the given store.
|
|
||||||
func (dao *dao) DeleteSpentCoinState(hash util.Uint256) error {
|
|
||||||
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
|
|
||||||
return dao.store.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- end spent coins.
|
|
||||||
|
|
||||||
// -- start validator.
|
// -- start validator.
|
||||||
|
|
||||||
// GetValidatorStateOrNew gets validator from store or created new one in case of error.
|
// GetValidatorStateOrNew gets validator from store or created new one in case of error.
|
||||||
|
@ -592,7 +537,7 @@ func (dao *dao) IsDoubleSpend(tx *transaction.Transaction) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
if int(input.PrevIndex) >= len(unspent.states) || unspent.states[input.PrevIndex] == state.CoinSpent {
|
if int(input.PrevIndex) >= len(unspent.States) || (unspent.States[input.PrevIndex].State&state.CoinSpent) != 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -607,13 +552,12 @@ func (dao *dao) IsDoubleClaim(claim *transaction.ClaimTX) bool {
|
||||||
}
|
}
|
||||||
for _, inputs := range transaction.GroupInputsByPrevHash(claim.Claims) {
|
for _, inputs := range transaction.GroupInputsByPrevHash(claim.Claims) {
|
||||||
prevHash := inputs[0].PrevHash
|
prevHash := inputs[0].PrevHash
|
||||||
scs, err := dao.GetSpentCoinState(prevHash)
|
unspent, err := dao.GetUnspentCoinState(prevHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
_, ok := scs.items[input.PrevIndex]
|
if int(input.PrevIndex) >= len(unspent.States) || (unspent.States[input.PrevIndex].State&state.CoinClaimed) != 0 {
|
||||||
if !ok {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,14 +94,6 @@ func TestDeleteContractState(t *testing.T) {
|
||||||
require.Nil(t, gotContractState)
|
require.Nil(t, gotContractState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUnspentCoinStateOrNew_New(t *testing.T) {
|
|
||||||
dao := newDao(storage.NewMemoryStore())
|
|
||||||
hash := random.Uint256()
|
|
||||||
unspentCoinState, err := dao.GetUnspentCoinStateOrNew(hash)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, unspentCoinState)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetUnspentCoinState_Err(t *testing.T) {
|
func TestGetUnspentCoinState_Err(t *testing.T) {
|
||||||
dao := newDao(storage.NewMemoryStore())
|
dao := newDao(storage.NewMemoryStore())
|
||||||
hash := random.Uint256()
|
hash := random.Uint256()
|
||||||
|
@ -113,7 +105,7 @@ func TestGetUnspentCoinState_Err(t *testing.T) {
|
||||||
func TestPutGetUnspentCoinState(t *testing.T) {
|
func TestPutGetUnspentCoinState(t *testing.T) {
|
||||||
dao := newDao(storage.NewMemoryStore())
|
dao := newDao(storage.NewMemoryStore())
|
||||||
hash := random.Uint256()
|
hash := random.Uint256()
|
||||||
unspentCoinState := &UnspentCoinState{states: []state.Coin{}}
|
unspentCoinState := &state.UnspentCoin{Height: 42, States: []state.OutputState{}}
|
||||||
err := dao.PutUnspentCoinState(hash, unspentCoinState)
|
err := dao.PutUnspentCoinState(hash, unspentCoinState)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gotUnspentCoinState, err := dao.GetUnspentCoinState(hash)
|
gotUnspentCoinState, err := dao.GetUnspentCoinState(hash)
|
||||||
|
@ -121,46 +113,6 @@ func TestPutGetUnspentCoinState(t *testing.T) {
|
||||||
require.Equal(t, unspentCoinState, gotUnspentCoinState)
|
require.Equal(t, unspentCoinState, gotUnspentCoinState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSpentCoinStateOrNew_New(t *testing.T) {
|
|
||||||
dao := newDao(storage.NewMemoryStore())
|
|
||||||
hash := random.Uint256()
|
|
||||||
spentCoinState, err := dao.GetSpentCoinsOrNew(hash, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, spentCoinState)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPutAndGetSpentCoinState(t *testing.T) {
|
|
||||||
dao := newDao(storage.NewMemoryStore())
|
|
||||||
hash := random.Uint256()
|
|
||||||
spentCoinState := &SpentCoinState{items: make(map[uint16]uint32)}
|
|
||||||
err := dao.PutSpentCoinState(hash, spentCoinState)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gotSpentCoinState, err := dao.GetSpentCoinState(hash)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, spentCoinState, gotSpentCoinState)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSpentCoinState_Err(t *testing.T) {
|
|
||||||
dao := newDao(storage.NewMemoryStore())
|
|
||||||
hash := random.Uint256()
|
|
||||||
spentCoinState, err := dao.GetSpentCoinState(hash)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, spentCoinState)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteSpentCoinState(t *testing.T) {
|
|
||||||
dao := newDao(storage.NewMemoryStore())
|
|
||||||
hash := random.Uint256()
|
|
||||||
spentCoinState := &SpentCoinState{items: make(map[uint16]uint32)}
|
|
||||||
err := dao.PutSpentCoinState(hash, spentCoinState)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = dao.DeleteSpentCoinState(hash)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gotSpentCoinState, err := dao.GetSpentCoinState(hash)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, gotSpentCoinState)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetValidatorStateOrNew_New(t *testing.T) {
|
func TestGetValidatorStateOrNew_New(t *testing.T) {
|
||||||
dao := newDao(storage.NewMemoryStore())
|
dao := newDao(storage.NewMemoryStore())
|
||||||
publicKey := &keys.PublicKey{}
|
publicKey := &keys.PublicKey{}
|
||||||
|
|
10
pkg/core/spent_coin.go
Normal file
10
pkg/core/spent_coin.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
|
||||||
|
// spentCoin represents the state of a single spent coin output.
|
||||||
|
type spentCoin struct {
|
||||||
|
Output *transaction.Output
|
||||||
|
StartHeight uint32
|
||||||
|
EndHeight uint32
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SpentCoinState represents the state of a spent coin.
|
|
||||||
type SpentCoinState struct {
|
|
||||||
txHash util.Uint256
|
|
||||||
txHeight uint32
|
|
||||||
|
|
||||||
// A mapping between the index of the prevIndex and block height.
|
|
||||||
items map[uint16]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// spentCoin represents the state of a single spent coin output.
|
|
||||||
type spentCoin struct {
|
|
||||||
Output *transaction.Output
|
|
||||||
StartHeight uint32
|
|
||||||
EndHeight uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSpentCoinState returns a new SpentCoinState object.
|
|
||||||
func NewSpentCoinState(hash util.Uint256, height uint32) *SpentCoinState {
|
|
||||||
return &SpentCoinState{
|
|
||||||
txHash: hash,
|
|
||||||
txHeight: height,
|
|
||||||
items: make(map[uint16]uint32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeBinary implements Serializable interface.
|
|
||||||
func (s *SpentCoinState) DecodeBinary(br *io.BinReader) {
|
|
||||||
br.ReadBytes(s.txHash[:])
|
|
||||||
s.txHeight = br.ReadU32LE()
|
|
||||||
|
|
||||||
s.items = make(map[uint16]uint32)
|
|
||||||
lenItems := br.ReadVarUint()
|
|
||||||
for i := 0; i < int(lenItems); i++ {
|
|
||||||
var (
|
|
||||||
key uint16
|
|
||||||
value uint32
|
|
||||||
)
|
|
||||||
key = br.ReadU16LE()
|
|
||||||
value = br.ReadU32LE()
|
|
||||||
s.items[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary implements Serializable interface.
|
|
||||||
func (s *SpentCoinState) EncodeBinary(bw *io.BinWriter) {
|
|
||||||
bw.WriteBytes(s.txHash[:])
|
|
||||||
bw.WriteU32LE(s.txHeight)
|
|
||||||
bw.WriteVarUint(uint64(len(s.items)))
|
|
||||||
for k, v := range s.items {
|
|
||||||
bw.WriteU16LE(k)
|
|
||||||
bw.WriteU32LE(v)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncodeDecodeSpentCoinState(t *testing.T) {
|
|
||||||
spent := &SpentCoinState{
|
|
||||||
txHash: random.Uint256(),
|
|
||||||
txHeight: 1001,
|
|
||||||
items: map[uint16]uint32{
|
|
||||||
1: 3,
|
|
||||||
2: 8,
|
|
||||||
4: 100,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
spent.EncodeBinary(buf.BinWriter)
|
|
||||||
assert.Nil(t, buf.Err)
|
|
||||||
spentDecode := new(SpentCoinState)
|
|
||||||
r := io.NewBinReaderFromBuf(buf.Bytes())
|
|
||||||
spentDecode.DecodeBinary(r)
|
|
||||||
assert.Nil(t, r.Err)
|
|
||||||
assert.Equal(t, spent, spentDecode)
|
|
||||||
}
|
|
60
pkg/core/state/unspent_coin.go
Normal file
60
pkg/core/state/unspent_coin.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnspentCoin hold the state of a unspent coin.
|
||||||
|
type UnspentCoin struct {
|
||||||
|
Height uint32
|
||||||
|
States []OutputState
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputState combines transaction output (UTXO) and its state
|
||||||
|
// (spent/claimed...) along with the height of spend (if it's spent).
|
||||||
|
type OutputState struct {
|
||||||
|
transaction.Output
|
||||||
|
|
||||||
|
SpendHeight uint32
|
||||||
|
State Coin
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnspentCoin returns a new unspent coin state with N confirmed states.
|
||||||
|
func NewUnspentCoin(height uint32, tx *transaction.Transaction) *UnspentCoin {
|
||||||
|
u := &UnspentCoin{
|
||||||
|
Height: height,
|
||||||
|
States: make([]OutputState, len(tx.Outputs)),
|
||||||
|
}
|
||||||
|
for i := range tx.Outputs {
|
||||||
|
u.States[i] = OutputState{Output: tx.Outputs[i]}
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary encodes UnspentCoin to the given BinWriter.
|
||||||
|
func (s *UnspentCoin) EncodeBinary(bw *io.BinWriter) {
|
||||||
|
bw.WriteU32LE(s.Height)
|
||||||
|
bw.WriteArray(s.States)
|
||||||
|
bw.WriteVarUint(uint64(len(s.States)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary decodes UnspentCoin from the given BinReader.
|
||||||
|
func (s *UnspentCoin) DecodeBinary(br *io.BinReader) {
|
||||||
|
s.Height = br.ReadU32LE()
|
||||||
|
br.ReadArray(&s.States)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements Serializable interface.
|
||||||
|
func (o *OutputState) EncodeBinary(w *io.BinWriter) {
|
||||||
|
o.Output.EncodeBinary(w)
|
||||||
|
w.WriteU32LE(o.SpendHeight)
|
||||||
|
w.WriteB(byte(o.State))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements Serializable interface.
|
||||||
|
func (o *OutputState) DecodeBinary(r *io.BinReader) {
|
||||||
|
o.Output.DecodeBinary(r)
|
||||||
|
o.SpendHeight = r.ReadU32LE()
|
||||||
|
o.State = Coin(r.ReadB())
|
||||||
|
}
|
54
pkg/core/state/unspent_coin_test.go
Normal file
54
pkg/core/state/unspent_coin_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeEncodeUnspentCoin(t *testing.T) {
|
||||||
|
unspent := &UnspentCoin{
|
||||||
|
Height: 100500,
|
||||||
|
States: []OutputState{
|
||||||
|
{
|
||||||
|
Output: transaction.Output{
|
||||||
|
AssetID: random.Uint256(),
|
||||||
|
Amount: util.Fixed8(42),
|
||||||
|
ScriptHash: random.Uint160(),
|
||||||
|
},
|
||||||
|
SpendHeight: 201000,
|
||||||
|
State: CoinSpent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Output: transaction.Output{
|
||||||
|
AssetID: random.Uint256(),
|
||||||
|
Amount: util.Fixed8(420),
|
||||||
|
ScriptHash: random.Uint160(),
|
||||||
|
},
|
||||||
|
SpendHeight: 0,
|
||||||
|
State: CoinConfirmed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Output: transaction.Output{
|
||||||
|
AssetID: random.Uint256(),
|
||||||
|
Amount: util.Fixed8(4200),
|
||||||
|
ScriptHash: random.Uint160(),
|
||||||
|
},
|
||||||
|
SpendHeight: 111000,
|
||||||
|
State: CoinSpent & CoinClaimed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
unspent.EncodeBinary(buf.BinWriter)
|
||||||
|
assert.Nil(t, buf.Err)
|
||||||
|
unspentDecode := &UnspentCoin{}
|
||||||
|
r := io.NewBinReaderFromBuf(buf.Bytes())
|
||||||
|
unspentDecode.DecodeBinary(r)
|
||||||
|
assert.Nil(t, r.Err)
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnspentCoinState hold the state of a unspent coin.
|
|
||||||
type UnspentCoinState struct {
|
|
||||||
states []state.Coin
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnspentCoinState returns a new unspent coin state with N confirmed states.
|
|
||||||
func NewUnspentCoinState(n int) *UnspentCoinState {
|
|
||||||
u := &UnspentCoinState{
|
|
||||||
states: make([]state.Coin, n),
|
|
||||||
}
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
u.states[i] = state.CoinConfirmed
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary encodes UnspentCoinState to the given BinWriter.
|
|
||||||
func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
|
|
||||||
bw.WriteVarUint(uint64(len(s.states)))
|
|
||||||
for _, state := range s.states {
|
|
||||||
bw.WriteB(byte(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeBinary decodes UnspentCoinState from the given BinReader.
|
|
||||||
func (s *UnspentCoinState) DecodeBinary(br *io.BinReader) {
|
|
||||||
lenStates := br.ReadVarUint()
|
|
||||||
s.states = make([]state.Coin, lenStates)
|
|
||||||
for i := 0; i < int(lenStates); i++ {
|
|
||||||
s.states[i] = state.Coin(br.ReadB())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDecodeEncodeUnspentCoinState(t *testing.T) {
|
|
||||||
unspent := &UnspentCoinState{
|
|
||||||
states: []state.Coin{
|
|
||||||
state.CoinConfirmed,
|
|
||||||
state.CoinSpent,
|
|
||||||
state.CoinSpent,
|
|
||||||
state.CoinSpent,
|
|
||||||
state.CoinConfirmed,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
unspent.EncodeBinary(buf.BinWriter)
|
|
||||||
assert.Nil(t, buf.Err)
|
|
||||||
unspentDecode := &UnspentCoinState{}
|
|
||||||
r := io.NewBinReaderFromBuf(buf.Bytes())
|
|
||||||
unspentDecode.DecodeBinary(r)
|
|
||||||
assert.Nil(t, r.Err)
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/config"
|
"github.com/nspcc-dev/neo-go/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
@ -129,7 +128,7 @@ func (chain testChain) GetTransaction(util.Uint256) (*transaction.Transaction, u
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain testChain) GetUnspentCoinState(util.Uint256) *core.UnspentCoinState {
|
func (chain testChain) GetUnspentCoinState(util.Uint256) *state.UnspentCoin {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue