mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 23:30:36 +00:00
556 lines
16 KiB
Go
556 lines
16 KiB
Go
|
package core
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
|
||
|
"github.com/CityOfZion/neo-go/pkg/core/entities"
|
||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||
|
)
|
||
|
|
||
|
// dao is a data access object.
|
||
|
type dao struct {
|
||
|
store *storage.MemCachedStore
|
||
|
}
|
||
|
|
||
|
// GetAndDecode performs get operation and decoding with serializable structures.
|
||
|
func (dao *dao) GetAndDecode(entity io.Serializable, key []byte) error {
|
||
|
entityBytes, err := dao.store.Get(key)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
reader := io.NewBinReaderFromBuf(entityBytes)
|
||
|
entity.DecodeBinary(reader)
|
||
|
return reader.Err
|
||
|
}
|
||
|
|
||
|
// Put performs put operation with serializable structures.
|
||
|
func (dao *dao) Put(entity io.Serializable, key []byte) error {
|
||
|
buf := io.NewBufBinWriter()
|
||
|
entity.EncodeBinary(buf.BinWriter)
|
||
|
if buf.Err != nil {
|
||
|
return buf.Err
|
||
|
}
|
||
|
return dao.store.Put(key, buf.Bytes())
|
||
|
}
|
||
|
|
||
|
// -- start accounts.
|
||
|
|
||
|
// GetAccountStateOrNew retrieves AccountState from temporary or persistent Store
|
||
|
// or creates a new one if it doesn't exist and persists it.
|
||
|
func (dao *dao) GetAccountStateOrNew(hash util.Uint160) (*entities.AccountState, error) {
|
||
|
account, err := dao.GetAccountState(hash)
|
||
|
if err != nil {
|
||
|
if err != storage.ErrKeyNotFound {
|
||
|
return nil, err
|
||
|
}
|
||
|
account = entities.NewAccountState(hash)
|
||
|
if err = dao.PutAccountState(account); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return account, nil
|
||
|
}
|
||
|
|
||
|
// GetAccountState returns AccountState from the given Store if it's
|
||
|
// present there. Returns nil otherwise.
|
||
|
func (dao *dao) GetAccountState(hash util.Uint160) (*entities.AccountState, error) {
|
||
|
account := &entities.AccountState{}
|
||
|
key := storage.AppendPrefix(storage.STAccount, hash.BytesBE())
|
||
|
err := dao.GetAndDecode(account, key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return account, err
|
||
|
}
|
||
|
|
||
|
// PutAccountState puts given AccountState into the given store.
|
||
|
func (dao *dao) PutAccountState(as *entities.AccountState) error {
|
||
|
key := storage.AppendPrefix(storage.STAccount, as.ScriptHash.BytesBE())
|
||
|
return dao.Put(as, key)
|
||
|
}
|
||
|
|
||
|
// -- end accounts.
|
||
|
|
||
|
// -- start assets.
|
||
|
|
||
|
// GetAssetState returns given asset state as recorded in the given store.
|
||
|
func (dao *dao) GetAssetState(assetID util.Uint256) (*entities.AssetState, error) {
|
||
|
asset := &entities.AssetState{}
|
||
|
key := storage.AppendPrefix(storage.STAsset, assetID.BytesBE())
|
||
|
err := dao.GetAndDecode(asset, key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if asset.ID != assetID {
|
||
|
return nil, fmt.Errorf("found asset id is not equal to expected")
|
||
|
}
|
||
|
return asset, nil
|
||
|
}
|
||
|
|
||
|
// PutAssetState puts given asset state into the given store.
|
||
|
func (dao *dao) PutAssetState(as *entities.AssetState) error {
|
||
|
key := storage.AppendPrefix(storage.STAsset, as.ID.BytesBE())
|
||
|
return dao.Put(as, key)
|
||
|
}
|
||
|
|
||
|
// -- end assets.
|
||
|
|
||
|
// -- start contracts.
|
||
|
|
||
|
// GetContractState returns contract state as recorded in the given
|
||
|
// store by the given script hash.
|
||
|
func (dao *dao) GetContractState(hash util.Uint160) (*entities.ContractState, error) {
|
||
|
contract := &entities.ContractState{}
|
||
|
key := storage.AppendPrefix(storage.STContract, hash.BytesBE())
|
||
|
err := dao.GetAndDecode(contract, key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if contract.ScriptHash() != hash {
|
||
|
return nil, fmt.Errorf("found script hash is not equal to expected")
|
||
|
}
|
||
|
|
||
|
return contract, nil
|
||
|
}
|
||
|
|
||
|
// PutContractState puts given contract state into the given store.
|
||
|
func (dao *dao) PutContractState(cs *entities.ContractState) error {
|
||
|
key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE())
|
||
|
return dao.Put(cs, key)
|
||
|
}
|
||
|
|
||
|
// DeleteContractState deletes given contract state in the given store.
|
||
|
func (dao *dao) DeleteContractState(hash util.Uint160) error {
|
||
|
key := storage.AppendPrefix(storage.STContract, hash.BytesBE())
|
||
|
return dao.store.Delete(key)
|
||
|
}
|
||
|
|
||
|
// -- end contracts.
|
||
|
|
||
|
// -- 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: []entities.CoinState{},
|
||
|
}
|
||
|
if err = dao.PutUnspentCoinState(hash, unspent); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return unspent, nil
|
||
|
}
|
||
|
|
||
|
// GetUnspentCoinState retrieves UnspentCoinState from the given store.
|
||
|
func (dao *dao) GetUnspentCoinState(hash util.Uint256) (*UnspentCoinState, error) {
|
||
|
unspent := &UnspentCoinState{}
|
||
|
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
||
|
err := dao.GetAndDecode(unspent, key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return unspent, nil
|
||
|
}
|
||
|
|
||
|
// PutUnspentCoinState puts given UnspentCoinState into the given store.
|
||
|
func (dao *dao) PutUnspentCoinState(hash util.Uint256, ucs *UnspentCoinState) error {
|
||
|
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
||
|
return dao.Put(ucs, key)
|
||
|
}
|
||
|
|
||
|
// -- end unspent coins.
|
||
|
|
||
|
// -- start spent coins.
|
||
|
|
||
|
// GetSpentCoinsOrNew returns spent coins from store.
|
||
|
func (dao *dao) GetSpentCoinsOrNew(hash util.Uint256) (*SpentCoinState, error) {
|
||
|
spent, err := dao.GetSpentCoinState(hash)
|
||
|
if err != nil {
|
||
|
if err != storage.ErrKeyNotFound {
|
||
|
return nil, err
|
||
|
}
|
||
|
spent = &SpentCoinState{
|
||
|
items: make(map[uint16]uint32),
|
||
|
}
|
||
|
if err = dao.PutSpentCoinState(hash, spent); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
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.
|
||
|
|
||
|
// GetValidatorStateOrNew gets validator from store or created new one in case of error.
|
||
|
func (dao *dao) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*entities.ValidatorState, error) {
|
||
|
validatorState, err := dao.GetValidatorState(publicKey)
|
||
|
if err != nil {
|
||
|
if err != storage.ErrKeyNotFound {
|
||
|
return nil, err
|
||
|
}
|
||
|
validatorState = &entities.ValidatorState{PublicKey: publicKey}
|
||
|
if err = dao.PutValidatorState(validatorState); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return validatorState, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
// GetValidators returns all validators from store.
|
||
|
func (dao *dao) GetValidators() []*entities.ValidatorState {
|
||
|
var validators []*entities.ValidatorState
|
||
|
dao.store.Seek(storage.STValidator.Bytes(), func(k, v []byte) {
|
||
|
r := io.NewBinReaderFromBuf(v)
|
||
|
validator := &entities.ValidatorState{}
|
||
|
validator.DecodeBinary(r)
|
||
|
if r.Err != nil {
|
||
|
return
|
||
|
}
|
||
|
validators = append(validators, validator)
|
||
|
})
|
||
|
return validators
|
||
|
}
|
||
|
|
||
|
// GetValidatorState returns validator by publicKey.
|
||
|
func (dao *dao) GetValidatorState(publicKey *keys.PublicKey) (*entities.ValidatorState, error) {
|
||
|
validatorState := &entities.ValidatorState{}
|
||
|
key := storage.AppendPrefix(storage.STValidator, publicKey.Bytes())
|
||
|
err := dao.GetAndDecode(validatorState, key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return validatorState, nil
|
||
|
}
|
||
|
|
||
|
// PutValidatorState puts given ValidatorState into the given store.
|
||
|
func (dao *dao) PutValidatorState(vs *entities.ValidatorState) error {
|
||
|
key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes())
|
||
|
return dao.Put(vs, key)
|
||
|
}
|
||
|
|
||
|
// DeleteValidatorState deletes given ValidatorState into the given store.
|
||
|
func (dao *dao) DeleteValidatorState(vs *entities.ValidatorState) error {
|
||
|
key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes())
|
||
|
return dao.store.Delete(key)
|
||
|
}
|
||
|
|
||
|
// -- end validator.
|
||
|
|
||
|
// -- start notification event.
|
||
|
|
||
|
// GetAppExecResult gets application execution result from the
|
||
|
// given store.
|
||
|
func (dao *dao) GetAppExecResult(hash util.Uint256) (*entities.AppExecResult, error) {
|
||
|
aer := &entities.AppExecResult{}
|
||
|
key := storage.AppendPrefix(storage.STNotification, hash.BytesBE())
|
||
|
err := dao.GetAndDecode(aer, key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return aer, nil
|
||
|
}
|
||
|
|
||
|
// PutAppExecResult puts given application execution result into the
|
||
|
// given store.
|
||
|
func (dao *dao) PutAppExecResult(aer *entities.AppExecResult) error {
|
||
|
key := storage.AppendPrefix(storage.STNotification, aer.TxHash.BytesBE())
|
||
|
return dao.Put(aer, key)
|
||
|
}
|
||
|
|
||
|
// -- end notification event.
|
||
|
|
||
|
// -- start storage item.
|
||
|
|
||
|
// GetStorageItem returns StorageItem if it exists in the given Store.
|
||
|
func (dao *dao) GetStorageItem(scripthash util.Uint160, key []byte) *entities.StorageItem {
|
||
|
b, err := dao.store.Get(makeStorageItemKey(scripthash, key))
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
r := io.NewBinReaderFromBuf(b)
|
||
|
|
||
|
si := &entities.StorageItem{}
|
||
|
si.DecodeBinary(r)
|
||
|
if r.Err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return si
|
||
|
}
|
||
|
|
||
|
// PutStorageItem puts given StorageItem for given script with given
|
||
|
// key into the given Store.
|
||
|
func (dao *dao) PutStorageItem(scripthash util.Uint160, key []byte, si *entities.StorageItem) error {
|
||
|
return dao.Put(si, makeStorageItemKey(scripthash, key))
|
||
|
}
|
||
|
|
||
|
// DeleteStorageItem drops storage item for the given script with the
|
||
|
// given key from the Store.
|
||
|
func (dao *dao) DeleteStorageItem(scripthash util.Uint160, key []byte) error {
|
||
|
return dao.store.Delete(makeStorageItemKey(scripthash, key))
|
||
|
}
|
||
|
|
||
|
// GetStorageItems returns all storage items for a given scripthash.
|
||
|
func (dao *dao) GetStorageItems(hash util.Uint160) (map[string]*entities.StorageItem, error) {
|
||
|
var siMap = make(map[string]*entities.StorageItem)
|
||
|
var err error
|
||
|
|
||
|
saveToMap := func(k, v []byte) {
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
r := io.NewBinReaderFromBuf(v)
|
||
|
si := &entities.StorageItem{}
|
||
|
si.DecodeBinary(r)
|
||
|
if r.Err != nil {
|
||
|
err = r.Err
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Cut prefix and hash.
|
||
|
siMap[string(k[21:])] = si
|
||
|
}
|
||
|
dao.store.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesLE()), saveToMap)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return siMap, nil
|
||
|
}
|
||
|
|
||
|
// makeStorageItemKey returns a key used to store StorageItem in the DB.
|
||
|
func makeStorageItemKey(scripthash util.Uint160, key []byte) []byte {
|
||
|
return storage.AppendPrefix(storage.STStorage, append(scripthash.BytesLE(), key...))
|
||
|
}
|
||
|
|
||
|
// -- end storage item.
|
||
|
|
||
|
// -- other.
|
||
|
|
||
|
// GetBlock returns Block by the given hash if it exists in the store.
|
||
|
func (dao *dao) GetBlock(hash util.Uint256) (*Block, error) {
|
||
|
key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
|
||
|
b, err := dao.store.Get(key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
block, err := NewBlockFromTrimmedBytes(b)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return block, err
|
||
|
}
|
||
|
|
||
|
// GetVersion attempts to get the current version stored in the
|
||
|
// underlying Store.
|
||
|
func (dao *dao) GetVersion() (string, error) {
|
||
|
version, err := dao.store.Get(storage.SYSVersion.Bytes())
|
||
|
return string(version), err
|
||
|
}
|
||
|
|
||
|
// GetCurrentBlockHeight returns the current block height found in the
|
||
|
// underlying Store.
|
||
|
func (dao *dao) GetCurrentBlockHeight() (uint32, error) {
|
||
|
b, err := dao.store.Get(storage.SYSCurrentBlock.Bytes())
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return binary.LittleEndian.Uint32(b[32:36]), nil
|
||
|
}
|
||
|
|
||
|
// GetCurrentHeaderHeight returns the current header height and hash from
|
||
|
// the underlying Store.
|
||
|
func (dao *dao) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) {
|
||
|
var b []byte
|
||
|
b, err = dao.store.Get(storage.SYSCurrentHeader.Bytes())
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
i = binary.LittleEndian.Uint32(b[32:36])
|
||
|
h, err = util.Uint256DecodeBytesLE(b[:32])
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// GetHeaderHashes returns a sorted list of header hashes retrieved from
|
||
|
// the given underlying Store.
|
||
|
func (dao *dao) GetHeaderHashes() ([]util.Uint256, error) {
|
||
|
hashMap := make(map[uint32][]util.Uint256)
|
||
|
dao.store.Seek(storage.IXHeaderHashList.Bytes(), func(k, v []byte) {
|
||
|
storedCount := binary.LittleEndian.Uint32(k[1:])
|
||
|
hashes, err := read2000Uint256Hashes(v)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
hashMap[storedCount] = hashes
|
||
|
})
|
||
|
|
||
|
var (
|
||
|
hashes = make([]util.Uint256, 0, len(hashMap))
|
||
|
sortedKeys = make([]uint32, 0, len(hashMap))
|
||
|
)
|
||
|
|
||
|
for k := range hashMap {
|
||
|
sortedKeys = append(sortedKeys, k)
|
||
|
}
|
||
|
sort.Sort(slice(sortedKeys))
|
||
|
|
||
|
for _, key := range sortedKeys {
|
||
|
hashes = append(hashes[:key], hashMap[key]...)
|
||
|
}
|
||
|
|
||
|
return hashes, nil
|
||
|
}
|
||
|
|
||
|
// GetTransaction returns Transaction and its height by the given hash
|
||
|
// if it exists in the store.
|
||
|
func (dao *dao) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
||
|
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesLE())
|
||
|
b, err := dao.store.Get(key)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
r := io.NewBinReaderFromBuf(b)
|
||
|
|
||
|
var height uint32
|
||
|
r.ReadLE(&height)
|
||
|
|
||
|
tx := &transaction.Transaction{}
|
||
|
tx.DecodeBinary(r)
|
||
|
if r.Err != nil {
|
||
|
return nil, 0, r.Err
|
||
|
}
|
||
|
|
||
|
return tx, height, nil
|
||
|
}
|
||
|
|
||
|
// PutVersion stores the given version in the underlying Store.
|
||
|
func (dao *dao) PutVersion(v string) error {
|
||
|
return dao.store.Put(storage.SYSVersion.Bytes(), []byte(v))
|
||
|
}
|
||
|
|
||
|
// PutCurrentHeader stores current header.
|
||
|
func (dao *dao) PutCurrentHeader(hashAndIndex []byte) error {
|
||
|
return dao.store.Put(storage.SYSCurrentHeader.Bytes(), hashAndIndex)
|
||
|
}
|
||
|
|
||
|
// read2000Uint256Hashes attempts to read 2000 Uint256 hashes from
|
||
|
// the given byte array.
|
||
|
func read2000Uint256Hashes(b []byte) ([]util.Uint256, error) {
|
||
|
r := bytes.NewReader(b)
|
||
|
br := io.NewBinReaderFromIO(r)
|
||
|
lenHashes := br.ReadVarUint()
|
||
|
hashes := make([]util.Uint256, lenHashes)
|
||
|
br.ReadLE(hashes)
|
||
|
if br.Err != nil {
|
||
|
return nil, br.Err
|
||
|
}
|
||
|
return hashes, nil
|
||
|
}
|
||
|
|
||
|
// HasTransaction returns true if the given store contains the given
|
||
|
// Transaction hash.
|
||
|
func (dao *dao) HasTransaction(hash util.Uint256) bool {
|
||
|
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesLE())
|
||
|
if _, err := dao.store.Get(key); err == nil {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// StoreAsBlock stores the given block as DataBlock.
|
||
|
func (dao *dao) StoreAsBlock(block *Block, sysFee uint32) error {
|
||
|
var (
|
||
|
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
|
||
|
buf = io.NewBufBinWriter()
|
||
|
)
|
||
|
// sysFee needs to be handled somehow
|
||
|
// buf.WriteLE(sysFee)
|
||
|
b, err := block.Trim()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
buf.WriteLE(b)
|
||
|
if buf.Err != nil {
|
||
|
return buf.Err
|
||
|
}
|
||
|
return dao.store.Put(key, buf.Bytes())
|
||
|
}
|
||
|
|
||
|
// StoreAsCurrentBlock stores the given block witch prefix SYSCurrentBlock.
|
||
|
func (dao *dao) StoreAsCurrentBlock(block *Block) error {
|
||
|
buf := io.NewBufBinWriter()
|
||
|
buf.WriteLE(block.Hash().BytesLE())
|
||
|
buf.WriteLE(block.Index)
|
||
|
return dao.store.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes())
|
||
|
}
|
||
|
|
||
|
// StoreAsTransaction stores the given TX as DataTransaction.
|
||
|
func (dao *dao) StoreAsTransaction(tx *transaction.Transaction, index uint32) error {
|
||
|
key := storage.AppendPrefix(storage.DataTransaction, tx.Hash().BytesLE())
|
||
|
buf := io.NewBufBinWriter()
|
||
|
buf.WriteLE(index)
|
||
|
tx.EncodeBinary(buf.BinWriter)
|
||
|
if buf.Err != nil {
|
||
|
return buf.Err
|
||
|
}
|
||
|
return dao.store.Put(key, buf.Bytes())
|
||
|
}
|
||
|
|
||
|
// IsDoubleSpend verifies that the input transactions are not double spent.
|
||
|
func (dao *dao) IsDoubleSpend(tx *transaction.Transaction) bool {
|
||
|
if len(tx.Inputs) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
for prevHash, inputs := range tx.GroupInputsByPrevHash() {
|
||
|
unspent, err := dao.GetUnspentCoinState(prevHash)
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
for _, input := range inputs {
|
||
|
if int(input.PrevIndex) >= len(unspent.states) || unspent.states[input.PrevIndex] == entities.CoinStateSpent {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|