mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 23:30:36 +00:00
ec17654986
add dao which takes care about all CRUD operations on storage remove blockchain state since everything is stored on change remove storage operations from structs(entities) move structs to entities package
555 lines
16 KiB
Go
555 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
|
|
}
|