core: refactoring blockchain state and storage

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
This commit is contained in:
Vsevolod Brekelov 2019-11-25 20:39:11 +03:00
parent c43ff15c78
commit ec17654986
39 changed files with 958 additions and 1258 deletions

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
@ -46,8 +47,8 @@ var (
type Blockchain struct { type Blockchain struct {
config config.ProtocolConfiguration config config.ProtocolConfiguration
// Persistent storage wrapped around with a write memory caching layer. // Data access object for CRUD operations around storage.
store *storage.MemCachedStore dao *dao
// Current index/height of the highest block. // Current index/height of the highest block.
// Read access should always be called by BlockHeight(). // Read access should always be called by BlockHeight().
@ -85,7 +86,7 @@ type headersOpFunc func(headerList *HeaderHashList)
func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockchain, error) { func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockchain, error) {
bc := &Blockchain{ bc := &Blockchain{
config: cfg, config: cfg,
store: storage.NewMemCachedStore(s), dao: &dao{store: storage.NewMemCachedStore(s)},
headersOp: make(chan headersOpFunc), headersOp: make(chan headersOpFunc),
headersOpDone: make(chan struct{}), headersOpDone: make(chan struct{}),
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
@ -103,10 +104,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockcha
func (bc *Blockchain) init() error { func (bc *Blockchain) init() error {
// If we could not find the version in the Store, we know that there is nothing stored. // If we could not find the version in the Store, we know that there is nothing stored.
ver, err := storage.Version(bc.store) ver, err := bc.dao.GetVersion()
if err != nil { if err != nil {
log.Infof("no storage version found! creating genesis block") log.Infof("no storage version found! creating genesis block")
if err = storage.PutVersion(bc.store, version); err != nil { if err = bc.dao.PutVersion(version); err != nil {
return err return err
} }
genesisBlock, err := createGenesisBlock(bc.config) genesisBlock, err := createGenesisBlock(bc.config)
@ -114,7 +115,7 @@ func (bc *Blockchain) init() error {
return err return err
} }
bc.headerList = NewHeaderHashList(genesisBlock.Hash()) bc.headerList = NewHeaderHashList(genesisBlock.Hash())
err = bc.store.Put(storage.SYSCurrentHeader.Bytes(), hashAndIndexToBytes(genesisBlock.Hash(), genesisBlock.Index)) err = bc.dao.PutCurrentHeader(hashAndIndexToBytes(genesisBlock.Hash(), genesisBlock.Index))
if err != nil { if err != nil {
return err return err
} }
@ -129,14 +130,14 @@ func (bc *Blockchain) init() error {
// and the genesis block as first block. // and the genesis block as first block.
log.Infof("restoring blockchain with version: %s", version) log.Infof("restoring blockchain with version: %s", version)
bHeight, err := storage.CurrentBlockHeight(bc.store) bHeight, err := bc.dao.GetCurrentBlockHeight()
if err != nil { if err != nil {
return err return err
} }
bc.blockHeight = bHeight bc.blockHeight = bHeight
bc.persistedHeight = bHeight bc.persistedHeight = bHeight
hashes, err := storage.HeaderHashes(bc.store) hashes, err := bc.dao.GetHeaderHashes()
if err != nil { if err != nil {
return err return err
} }
@ -144,7 +145,7 @@ func (bc *Blockchain) init() error {
bc.headerList = NewHeaderHashList(hashes...) bc.headerList = NewHeaderHashList(hashes...)
bc.storedHeaderCount = uint32(len(hashes)) bc.storedHeaderCount = uint32(len(hashes))
currHeaderHeight, currHeaderHash, err := storage.CurrentHeaderHeight(bc.store) currHeaderHeight, currHeaderHash, err := bc.dao.GetCurrentHeaderHeight()
if err != nil { if err != nil {
return err return err
} }
@ -198,7 +199,7 @@ func (bc *Blockchain) Run() {
if err := bc.persist(); err != nil { if err := bc.persist(); err != nil {
log.Warnf("failed to persist: %s", err) log.Warnf("failed to persist: %s", err)
} }
if err := bc.store.Close(); err != nil { if err := bc.dao.store.Close(); err != nil {
log.Warnf("failed to close db: %s", err) log.Warnf("failed to close db: %s", err)
} }
close(bc.runToExitCh) close(bc.runToExitCh)
@ -268,7 +269,7 @@ func (bc *Blockchain) AddBlock(block *Block) error {
func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) { func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
var ( var (
start = time.Now() start = time.Now()
batch = bc.store.Batch() batch = bc.dao.store.Batch()
) )
bc.headersOp <- func(headerList *HeaderHashList) { bc.headersOp <- func(headerList *HeaderHashList) {
@ -295,7 +296,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
if oldlen != headerList.Len() { if oldlen != headerList.Len() {
updateHeaderHeightMetric(headerList.Len() - 1) updateHeaderHeightMetric(headerList.Len() - 1)
if err = bc.store.PutBatch(batch); err != nil { if err = bc.dao.store.PutBatch(batch); err != nil {
return return
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@ -343,25 +344,26 @@ func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList *
// is happening here, quite allot as you can see :). If things are wired together // is happening here, quite allot as you can see :). If things are wired together
// and all tests are in place, we can make a more optimized and cleaner implementation. // and all tests are in place, we can make a more optimized and cleaner implementation.
func (bc *Blockchain) storeBlock(block *Block) error { func (bc *Blockchain) storeBlock(block *Block) error {
chainState := NewBlockChainState(bc.store) cache := &dao{store: storage.NewMemCachedStore(bc.dao.store)}
if err := cache.StoreAsBlock(block, 0); err != nil {
if err := chainState.storeAsBlock(block, 0); err != nil {
return err return err
} }
if err := chainState.storeAsCurrentBlock(block); err != nil { if err := cache.StoreAsCurrentBlock(block); err != nil {
return err return err
} }
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
if err := chainState.storeAsTransaction(tx, block.Index); err != nil { if err := cache.StoreAsTransaction(tx, block.Index); err != nil {
return err return err
} }
chainState.unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs)) if err := cache.PutUnspentCoinState(tx.Hash(), NewUnspentCoinState(len(tx.Outputs))); err != nil {
return err
}
// Process TX outputs. // Process TX outputs.
if err := processOutputs(tx, chainState); err != nil { if err := processOutputs(tx, cache); err != nil {
return err return err
} }
@ -372,14 +374,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
return fmt.Errorf("could not find previous TX: %s", prevHash) return fmt.Errorf("could not find previous TX: %s", prevHash)
} }
for _, input := range inputs { for _, input := range inputs {
unspent, err := chainState.unspentCoins.getAndUpdate(chainState.store, input.PrevHash) unspent, err := cache.GetUnspentCoinStateOrNew(input.PrevHash)
if err != nil { if err != nil {
return err return err
} }
unspent.states[input.PrevIndex] = CoinStateSpent unspent.states[input.PrevIndex] = entities.CoinStateSpent
if err = cache.PutUnspentCoinState(input.PrevHash, unspent); err != nil {
return err
}
prevTXOutput := prevTX.Outputs[input.PrevIndex] prevTXOutput := prevTX.Outputs[input.PrevIndex]
account, err := chainState.accounts.getAndUpdate(chainState.store, prevTXOutput.ScriptHash) account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
if err != nil { if err != nil {
return err return err
} }
@ -387,17 +391,24 @@ func (bc *Blockchain) storeBlock(block *Block) error {
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) { if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight) spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
spentCoin.items[input.PrevIndex] = block.Index spentCoin.items[input.PrevIndex] = block.Index
chainState.spentCoins[input.PrevHash] = spentCoin if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil {
return err
}
if len(account.Votes) > 0 { if len(account.Votes) > 0 {
for _, vote := range account.Votes { for _, vote := range account.Votes {
validator, err := chainState.validators.getAndUpdate(chainState.store, vote) validator, err := cache.GetValidatorStateOrNew(vote)
if err != nil { if err != nil {
return err return err
} }
validator.Votes -= prevTXOutput.Amount validator.Votes -= prevTXOutput.Amount
if !validator.RegisteredAndHasVotes() { if !validator.RegisteredAndHasVotes() {
delete(chainState.validators, vote) if err = cache.DeleteValidatorState(validator); err != nil {
return err
}
} else {
if err = cache.PutValidatorState(validator); err != nil {
return err
}
} }
} }
} }
@ -419,13 +430,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
account.Balances[prevTXOutput.AssetID] = account.Balances[prevTXOutput.AssetID][:balancesLen-1] account.Balances[prevTXOutput.AssetID] = account.Balances[prevTXOutput.AssetID][:balancesLen-1]
} }
} }
if err = cache.PutAccountState(account); err != nil {
return err
}
} }
} }
// Process the underlying type of the TX. // Process the underlying type of the TX.
switch t := tx.Data.(type) { switch t := tx.Data.(type) {
case *transaction.RegisterTX: case *transaction.RegisterTX:
chainState.assets[tx.Hash()] = &AssetState{ err := cache.PutAssetState(&entities.AssetState{
ID: tx.Hash(), ID: tx.Hash(),
AssetType: t.AssetType, AssetType: t.AssetType,
Name: t.Name, Name: t.Name,
@ -434,45 +448,50 @@ func (bc *Blockchain) storeBlock(block *Block) error {
Owner: t.Owner, Owner: t.Owner,
Admin: t.Admin, Admin: t.Admin,
Expiration: bc.BlockHeight() + registeredAssetLifetime, Expiration: bc.BlockHeight() + registeredAssetLifetime,
})
if err != nil {
return err
} }
case *transaction.IssueTX: case *transaction.IssueTX:
for _, res := range bc.GetTransactionResults(tx) { for _, res := range bc.GetTransactionResults(tx) {
if res.Amount < 0 { if res.Amount < 0 {
var asset *AssetState asset, err := cache.GetAssetState(res.AssetID)
if asset == nil || err != nil {
asset, ok := chainState.assets[res.AssetID] return fmt.Errorf("issue failed: no asset %s or error %s", res.AssetID, err)
if !ok {
asset = bc.GetAssetState(res.AssetID)
}
if asset == nil {
return fmt.Errorf("issue failed: no asset %s", res.AssetID)
} }
asset.Available -= res.Amount asset.Available -= res.Amount
chainState.assets[res.AssetID] = asset if err := cache.PutAssetState(asset); err != nil {
return err
}
} }
} }
case *transaction.ClaimTX: case *transaction.ClaimTX:
// 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 := chainState.spentCoins.getAndUpdate(bc.store, input.PrevHash) scs, err := cache.GetSpentCoinsOrNew(input.PrevHash)
if err != nil { if err != nil {
return err return err
} }
if scs.txHash == input.PrevHash { if scs.txHash == input.PrevHash {
// Existing scs. // Existing scs.
delete(scs.items, input.PrevIndex) delete(scs.items, input.PrevIndex)
if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil {
return err
}
} else { } else {
// Uninitialized, new, forget about it. // Uninitialized, new, forget about it.
delete(chainState.spentCoins, input.PrevHash) if err = cache.DeleteSpentCoinState(input.PrevHash); err != nil {
return err
}
} }
} }
case *transaction.EnrollmentTX: case *transaction.EnrollmentTX:
if err := processEnrollmentTX(chainState, t); err != nil { if err := processEnrollmentTX(cache, t); err != nil {
return err return err
} }
case *transaction.StateTX: case *transaction.StateTX:
if err := processStateTX(chainState, t); err != nil { if err := processStateTX(cache, t); err != nil {
return err return err
} }
case *transaction.PublishTX: case *transaction.PublishTX:
@ -480,7 +499,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
if t.NeedStorage { if t.NeedStorage {
properties |= smartcontract.HasStorage properties |= smartcontract.HasStorage
} }
contract := &ContractState{ contract := &entities.ContractState{
Script: t.Script, Script: t.Script,
ParamList: t.ParamList, ParamList: t.ParamList,
ReturnType: t.ReturnType, ReturnType: t.ReturnType,
@ -491,15 +510,17 @@ func (bc *Blockchain) storeBlock(block *Block) error {
Email: t.Email, Email: t.Email,
Description: t.Description, Description: t.Description,
} }
chainState.contracts[contract.ScriptHash()] = contract if err := cache.PutContractState(contract); err != nil {
return err
}
case *transaction.InvocationTX: case *transaction.InvocationTX:
systemInterop := newInteropContext(trigger.Application, bc, chainState.store, block, tx) systemInterop := newInteropContext(trigger.Application, bc, cache.store, block, tx)
v := bc.spawnVMWithInterops(systemInterop) v := bc.spawnVMWithInterops(systemInterop)
v.SetCheckedHash(tx.VerificationHash().BytesBE()) v.SetCheckedHash(tx.VerificationHash().BytesBE())
v.LoadScript(t.Script) v.LoadScript(t.Script)
err := v.Run() err := v.Run()
if !v.HasFailed() { if !v.HasFailed() {
_, err := systemInterop.mem.Persist() _, err := systemInterop.dao.store.Persist()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to persist invocation results") return errors.Wrap(err, "failed to persist invocation results")
} }
@ -534,7 +555,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
"err": err, "err": err,
}).Warn("contract invocation failed") }).Warn("contract invocation failed")
} }
aer := &AppExecResult{ aer := &entities.AppExecResult{
TxHash: tx.Hash(), TxHash: tx.Hash(),
Trigger: trigger.Application, Trigger: trigger.Application,
VMState: v.State(), VMState: v.State(),
@ -542,17 +563,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
Stack: v.Stack("estack"), Stack: v.Stack("estack"),
Events: systemInterop.notifications, Events: systemInterop.notifications,
} }
err = putAppExecResultIntoStore(chainState.store, aer) err = cache.PutAppExecResult(aer)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to store notifications") return errors.Wrap(err, "failed to store notifications")
} }
} }
} }
_, err := cache.store.Persist()
if err := chainState.commit(); err != nil { if err!= nil {
return err return err
} }
atomic.StoreUint32(&bc.blockHeight, block.Index) atomic.StoreUint32(&bc.blockHeight, block.Index)
updateBlockHeightMetric(block.Index) updateBlockHeightMetric(block.Index)
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
@ -562,37 +582,43 @@ func (bc *Blockchain) storeBlock(block *Block) error {
} }
// processOutputs processes transaction outputs. // processOutputs processes transaction outputs.
func processOutputs(tx *transaction.Transaction, chainState *BlockChainState) error { func processOutputs(tx *transaction.Transaction, dao *dao) error {
for index, output := range tx.Outputs { for index, output := range tx.Outputs {
account, err := chainState.accounts.getAndUpdate(chainState.store, output.ScriptHash) account, err := dao.GetAccountStateOrNew(output.ScriptHash)
if err != nil { if err != nil {
return err return err
} }
account.Balances[output.AssetID] = append(account.Balances[output.AssetID], UnspentBalance{ account.Balances[output.AssetID] = append(account.Balances[output.AssetID], entities.UnspentBalance{
Tx: tx.Hash(), Tx: tx.Hash(),
Index: uint16(index), Index: uint16(index),
Value: output.Amount, Value: output.Amount,
}) })
if err = dao.PutAccountState(account); err != nil {
return err
}
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
for _, vote := range account.Votes { for _, vote := range account.Votes {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote) validatorState, err := dao.GetValidatorStateOrNew(vote)
if err != nil { if err != nil {
return err return err
} }
validatorState.Votes += output.Amount validatorState.Votes += output.Amount
if err = dao.PutValidatorState(validatorState); err != nil {
return err
}
} }
} }
} }
return nil return nil
} }
func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, state *BlockChainState) error { func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao) error {
publicKey := &keys.PublicKey{} publicKey := &keys.PublicKey{}
err := publicKey.DecodeBytes(descriptor.Key) err := publicKey.DecodeBytes(descriptor.Key)
if err != nil { if err != nil {
return err return err
} }
validatorState, err := state.validators.getAndUpdate(state.store, publicKey) validatorState, err := dao.GetValidatorStateOrNew(publicKey)
if err != nil { if err != nil {
return err return err
} }
@ -602,16 +628,17 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, st
return err return err
} }
validatorState.Registered = isRegistered validatorState.Registered = isRegistered
return dao.PutValidatorState(validatorState)
} }
return nil return nil
} }
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, state *BlockChainState) error { func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao) error {
hash, err := util.Uint160DecodeBytesBE(descriptor.Key) hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
if err != nil { if err != nil {
return err return err
} }
account, err := state.accounts.getAndUpdate(state.store, hash) account, err := dao.GetAccountStateOrNew(hash)
if err != nil { if err != nil {
return err return err
} }
@ -619,13 +646,19 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, stat
if descriptor.Field == "Votes" { if descriptor.Field == "Votes" {
balance := account.GetBalanceValues()[governingTokenTX().Hash()] balance := account.GetBalanceValues()[governingTokenTX().Hash()]
for _, vote := range account.Votes { for _, vote := range account.Votes {
validator, err := state.validators.getAndUpdate(state.store, vote) validator, err := dao.GetValidatorStateOrNew(vote)
if err != nil { if err != nil {
return err return err
} }
validator.Votes -= balance validator.Votes -= balance
if !validator.RegisteredAndHasVotes() { if !validator.RegisteredAndHasVotes() {
delete(state.validators, vote) if err := dao.DeleteValidatorState(validator); err != nil {
return err
}
} else {
if err := dao.PutValidatorState(validator); err != nil {
return err
}
} }
} }
@ -637,10 +670,13 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, stat
if votes.Len() != len(account.Votes) { if votes.Len() != len(account.Votes) {
account.Votes = votes account.Votes = votes
for _, vote := range votes { for _, vote := range votes {
_, err := state.validators.getAndUpdate(state.store, vote) validator, err := dao.GetValidatorStateOrNew(vote)
if err != nil { if err != nil {
return err return err
} }
if err := dao.PutValidatorState(validator); err != nil {
return err
}
} }
} }
} }
@ -655,19 +691,19 @@ func (bc *Blockchain) persist() error {
err error err error
) )
persisted, err = bc.store.Persist() persisted, err = bc.dao.store.Persist()
if err != nil { if err != nil {
return err return err
} }
if persisted > 0 { if persisted > 0 {
bHeight, err := storage.CurrentBlockHeight(bc.store) bHeight, err := bc.dao.GetCurrentBlockHeight()
if err != nil { if err != nil {
return err return err
} }
oldHeight := atomic.SwapUint32(&bc.persistedHeight, bHeight) oldHeight := atomic.SwapUint32(&bc.persistedHeight, bHeight)
diff := bHeight - oldHeight diff := bHeight - oldHeight
storedHeaderHeight, _, err := storage.CurrentHeaderHeight(bc.store) storedHeaderHeight, _, err := bc.dao.GetCurrentHeaderHeight()
if err != nil { if err != nil {
return err return err
} }
@ -699,66 +735,22 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio
if tx, ok := bc.memPool.TryGetValue(hash); ok { if tx, ok := bc.memPool.TryGetValue(hash); ok {
return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case. return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case.
} }
return getTransactionFromStore(bc.store, hash) return bc.dao.GetTransaction(hash)
}
// getTransactionFromStore returns Transaction and its height by the given hash
// if it exists in the store.
func getTransactionFromStore(s storage.Store, hash util.Uint256) (*transaction.Transaction, uint32, error) {
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesLE())
b, err := s.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
} }
// GetStorageItem returns an item from storage. // GetStorageItem returns an item from storage.
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem { func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *entities.StorageItem {
return getStorageItemFromStore(bc.store, scripthash, key) return bc.dao.GetStorageItem(scripthash, key)
} }
// GetStorageItems returns all storage items for a given scripthash. // GetStorageItems returns all storage items for a given scripthash.
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error) { func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*entities.StorageItem, error) {
var siMap = make(map[string]*StorageItem) return bc.dao.GetStorageItems(hash)
var err error
saveToMap := func(k, v []byte) {
if err != nil {
return
}
r := io.NewBinReaderFromBuf(v)
si := &StorageItem{}
si.DecodeBinary(r)
if r.Err != nil {
err = r.Err
return
}
// Cut prefix and hash.
siMap[string(k[21:])] = si
}
bc.store.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesLE()), saveToMap)
if err != nil {
return nil, err
}
return siMap, nil
} }
// GetBlock returns a Block by the given hash. // GetBlock returns a Block by the given hash.
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) { func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
block, err := getBlockFromStore(bc.store, hash) block, err := bc.dao.GetBlock(hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -775,28 +767,9 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
return block, nil return block, nil
} }
// getBlockFromStore returns Block by the given hash if it exists in the store.
func getBlockFromStore(s storage.Store, hash util.Uint256) (*Block, error) {
key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
b, err := s.Get(key)
if err != nil {
return nil, err
}
block, err := NewBlockFromTrimmedBytes(b)
if err != nil {
return nil, err
}
return block, err
}
// GetHeader returns data block header identified with the given hash value. // GetHeader returns data block header identified with the given hash value.
func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) { func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) {
return getHeaderFromStore(bc.store, hash) block, err := bc.dao.GetBlock(hash)
}
// getHeaderFromStore returns Header by the given hash from the store.
func getHeaderFromStore(s storage.Store, hash util.Uint256) (*Header, error) {
block, err := getBlockFromStore(s, hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -806,18 +779,7 @@ func getHeaderFromStore(s storage.Store, hash util.Uint256) (*Header, error) {
// HasTransaction returns true if the blockchain contains he given // HasTransaction returns true if the blockchain contains he given
// transaction hash. // transaction hash.
func (bc *Blockchain) HasTransaction(hash util.Uint256) bool { func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
return bc.memPool.ContainsKey(hash) || return bc.memPool.ContainsKey(hash) || bc.dao.HasTransaction(hash)
checkTransactionInStore(bc.store, hash)
}
// checkTransactionInStore returns true if the given store contains the given
// Transaction hash.
func checkTransactionInStore(s storage.Store, hash util.Uint256) bool {
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesLE())
if _, err := s.Get(key); err == nil {
return true
}
return false
} }
// HasBlock returns true if the blockchain contains the given // HasBlock returns true if the blockchain contains the given
@ -868,54 +830,26 @@ func (bc *Blockchain) HeaderHeight() uint32 {
} }
// GetAssetState returns asset state from its assetID. // GetAssetState returns asset state from its assetID.
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *AssetState { func (bc *Blockchain) GetAssetState(assetID util.Uint256) *entities.AssetState {
return getAssetStateFromStore(bc.store, assetID) asset, err := bc.dao.GetAssetState(assetID)
} if asset == nil && err != storage.ErrKeyNotFound {
log.Warnf("failed to get asset state %s : %s", assetID, err)
// getAssetStateFromStore returns given asset state as recorded in the given
// store.
func getAssetStateFromStore(s storage.Store, assetID util.Uint256) *AssetState {
key := storage.AppendPrefix(storage.STAsset, assetID.BytesBE())
asEncoded, err := s.Get(key)
if err != nil {
return nil
} }
var a AssetState return asset
r := io.NewBinReaderFromBuf(asEncoded)
a.DecodeBinary(r)
if r.Err != nil || a.ID != assetID {
return nil
}
return &a
} }
// GetContractState returns contract by its script hash. // GetContractState returns contract by its script hash.
func (bc *Blockchain) GetContractState(hash util.Uint160) *ContractState { func (bc *Blockchain) GetContractState(hash util.Uint160) *entities.ContractState {
return getContractStateFromStore(bc.store, hash) contract, err := bc.dao.GetContractState(hash)
} if contract == nil && err != storage.ErrKeyNotFound {
log.Warnf("failed to get contract state: %s", err)
// getContractStateFromStore returns contract state as recorded in the given
// store by the given script hash.
func getContractStateFromStore(s storage.Store, hash util.Uint160) *ContractState {
key := storage.AppendPrefix(storage.STContract, hash.BytesBE())
contractBytes, err := s.Get(key)
if err != nil {
return nil
} }
var c ContractState return contract
r := io.NewBinReaderFromBuf(contractBytes)
c.DecodeBinary(r)
if r.Err != nil || c.ScriptHash() != hash {
return nil
}
return &c
} }
// GetAccountState returns the account state from its script hash. // GetAccountState returns the account state from its script hash.
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState { func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *entities.AccountState {
as, err := getAccountStateFromStore(bc.store, scriptHash) as, err := bc.dao.GetAccountState(scriptHash)
if as == nil && err != storage.ErrKeyNotFound { if as == nil && err != storage.ErrKeyNotFound {
log.Warnf("failed to get account state: %s", err) log.Warnf("failed to get account state: %s", err)
} }
@ -924,7 +858,7 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
// 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) *UnspentCoinState {
ucs, err := getUnspentCoinStateFromStore(bc.store, hash) ucs, err := bc.dao.GetUnspentCoinState(hash)
if ucs == nil && err != storage.ErrKeyNotFound { if ucs == nil && err != storage.ErrKeyNotFound {
log.Warnf("failed to get unspent coin state: %s", err) log.Warnf("failed to get unspent coin state: %s", err)
} }
@ -1029,7 +963,7 @@ func (bc *Blockchain) VerifyTx(t *transaction.Transaction, block *Block) error {
return errors.New("invalid transaction due to conflicts with the memory pool") return errors.New("invalid transaction due to conflicts with the memory pool")
} }
} }
if IsDoubleSpend(bc.store, t) { if bc.dao.IsDoubleSpend(t) {
return errors.New("invalid transaction caused by double spending") return errors.New("invalid transaction caused by double spending")
} }
if err := bc.verifyOutputs(t); err != nil { if err := bc.verifyOutputs(t); err != nil {
@ -1223,24 +1157,33 @@ func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) {
// GetValidators returns validators. // GetValidators returns validators.
// Golang implementation of GetValidators method in C# (https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/Persistence/Snapshot.cs#L182) // Golang implementation of GetValidators method in C# (https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/Persistence/Snapshot.cs#L182)
func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) { func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) {
chainState := NewBlockChainState(bc.store) cache := &dao{store: storage.NewMemCachedStore(bc.dao.store)}
if len(txes) > 0 { if len(txes) > 0 {
for _, tx := range txes { for _, tx := range txes {
// iterate through outputs // iterate through outputs
for index, output := range tx.Outputs { for index, output := range tx.Outputs {
accountState := bc.GetAccountState(output.ScriptHash) accountState, err := cache.GetAccountState(output.ScriptHash)
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], UnspentBalance{ if err != nil {
return nil, err
}
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], entities.UnspentBalance{
Tx: tx.Hash(), Tx: tx.Hash(),
Index: uint16(index), Index: uint16(index),
Value: output.Amount, Value: output.Amount,
}) })
if err := cache.PutAccountState(accountState); err != nil {
return nil, err
}
if output.AssetID.Equals(governingTokenTX().Hash()) && len(accountState.Votes) > 0 { if output.AssetID.Equals(governingTokenTX().Hash()) && len(accountState.Votes) > 0 {
for _, vote := range accountState.Votes { for _, vote := range accountState.Votes {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote) validatorState, err := cache.GetValidatorStateOrNew(vote)
if err != nil { if err != nil {
return nil, err return nil, err
} }
validatorState.Votes += output.Amount validatorState.Votes += output.Amount
if err = cache.PutValidatorState(validatorState); err != nil {
return nil, err
}
} }
} }
} }
@ -1253,14 +1196,14 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
} }
for hash, inputs := range group { for hash, inputs := range group {
prevTx, _, err := bc.GetTransaction(hash) prevTx, _, err := cache.GetTransaction(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 := prevTx.Outputs[input.PrevIndex]
accountState, err := chainState.accounts.getAndUpdate(chainState.store, prevOutput.ScriptHash) accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1269,37 +1212,45 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
if prevOutput.AssetID.Equals(governingTokenTX().Hash()) { if prevOutput.AssetID.Equals(governingTokenTX().Hash()) {
if len(accountState.Votes) > 0 { if len(accountState.Votes) > 0 {
for _, vote := range accountState.Votes { for _, vote := range accountState.Votes {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote) validatorState, err := cache.GetValidatorStateOrNew(vote)
if err != nil { if err != nil {
return nil, err return nil, err
} }
validatorState.Votes -= prevOutput.Amount validatorState.Votes -= prevOutput.Amount
if err = cache.PutValidatorState(validatorState); err != nil {
return nil, err
}
if !validatorState.Registered && validatorState.Votes.Equal(util.Fixed8(0)) { if !validatorState.Registered && validatorState.Votes.Equal(util.Fixed8(0)) {
delete(chainState.validators, vote) if err = cache.DeleteValidatorState(validatorState); err != nil {
return nil, err
}
} }
} }
} }
} }
delete(accountState.Balances, prevOutput.AssetID) delete(accountState.Balances, prevOutput.AssetID)
if err = cache.PutAccountState(accountState); err != nil {
return nil, err
}
} }
} }
switch t := tx.Data.(type) { switch t := tx.Data.(type) {
case *transaction.EnrollmentTX: case *transaction.EnrollmentTX:
if err := processEnrollmentTX(chainState, t); err != nil { if err := processEnrollmentTX(cache, t); err != nil {
return nil, err return nil, err
} }
case *transaction.StateTX: case *transaction.StateTX:
if err := processStateTX(chainState, t); err != nil { if err := processStateTX(cache, t); err != nil {
return nil, err return nil, err
} }
} }
} }
} }
validators := getValidatorsFromStore(chainState.store) validators := cache.GetValidators()
count := GetValidatorsWeightedAverage(validators) count := entities.GetValidatorsWeightedAverage(validators)
standByValidators, err := bc.GetStandByValidators() standByValidators, err := bc.GetStandByValidators()
if err != nil { if err != nil {
return nil, err return nil, err
@ -1324,18 +1275,22 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ { for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
result = append(result, uniqueSBValidators[i]) result = append(result, uniqueSBValidators[i])
} }
_, err = cache.store.Persist()
if err != nil {
return nil, err
}
return result, nil return result, nil
} }
func processStateTX(chainState *BlockChainState, tx *transaction.StateTX) error { func processStateTX(dao *dao, tx *transaction.StateTX) error {
for _, desc := range tx.Descriptors { for _, desc := range tx.Descriptors {
switch desc.Type { switch desc.Type {
case transaction.Account: case transaction.Account:
if err := processAccountStateDescriptor(desc, chainState); err != nil { if err := processAccountStateDescriptor(desc, dao); err != nil {
return err return err
} }
case transaction.Validator: case transaction.Validator:
if err := processValidatorStateDescriptor(desc, chainState); err != nil { if err := processValidatorStateDescriptor(desc, dao); err != nil {
return err return err
} }
} }
@ -1343,13 +1298,13 @@ func processStateTX(chainState *BlockChainState, tx *transaction.StateTX) error
return nil return nil
} }
func processEnrollmentTX(chainState *BlockChainState, tx *transaction.EnrollmentTX) error { func processEnrollmentTX(dao *dao, tx *transaction.EnrollmentTX) error {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, &tx.PublicKey) validatorState, err := dao.GetValidatorStateOrNew(&tx.PublicKey)
if err != nil { if err != nil {
return err return err
} }
validatorState.Registered = true validatorState.Registered = true
return nil return dao.PutValidatorState(validatorState)
} }
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use // GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use
@ -1424,7 +1379,7 @@ func (bc *Blockchain) spawnVMWithInterops(interopCtx *interopContext) *vm.VM {
// GetTestVM returns a VM and a Store setup for a test run of some sort of code. // GetTestVM returns a VM and a Store setup for a test run of some sort of code.
func (bc *Blockchain) GetTestVM() (*vm.VM, storage.Store) { func (bc *Blockchain) GetTestVM() (*vm.VM, storage.Store) {
tmpStore := storage.NewMemCachedStore(bc.store) tmpStore := storage.NewMemCachedStore(bc.dao.store)
systemInterop := newInteropContext(trigger.Application, bc, tmpStore, nil, nil) systemInterop := newInteropContext(trigger.Application, bc, tmpStore, nil, nil)
vm := bc.spawnVMWithInterops(systemInterop) vm := bc.spawnVMWithInterops(systemInterop)
return vm, tmpStore return vm, tmpStore
@ -1495,7 +1450,7 @@ func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *Block
} }
sort.Slice(hashes, func(i, j int) bool { return hashes[i].Less(hashes[j]) }) sort.Slice(hashes, func(i, j int) bool { return hashes[i].Less(hashes[j]) })
sort.Slice(witnesses, func(i, j int) bool { return witnesses[i].ScriptHash().Less(witnesses[j].ScriptHash()) }) sort.Slice(witnesses, func(i, j int) bool { return witnesses[i].ScriptHash().Less(witnesses[j].ScriptHash()) })
interopCtx := newInteropContext(trigger.Verification, bc, bc.store, block, t) interopCtx := newInteropContext(trigger.Verification, bc, bc.dao.store, block, t)
for i := 0; i < len(hashes); i++ { for i := 0; i < len(hashes); i++ {
err := bc.verifyHashAgainstScript(hashes[i], &witnesses[i], t.VerificationHash(), interopCtx, false) err := bc.verifyHashAgainstScript(hashes[i], &witnesses[i], t.VerificationHash(), interopCtx, false)
if err != nil { if err != nil {
@ -1515,7 +1470,7 @@ func (bc *Blockchain) verifyBlockWitnesses(block *Block, prevHeader *Header) err
} else { } else {
hash = prevHeader.NextConsensus hash = prevHeader.NextConsensus
} }
interopCtx := newInteropContext(trigger.Verification, bc, bc.store, nil, nil) interopCtx := newInteropContext(trigger.Verification, bc, bc.dao.store, nil, nil)
return bc.verifyHashAgainstScript(hash, &block.Script, block.VerificationHash(), interopCtx, true) return bc.verifyHashAgainstScript(hash, &block.Script, block.VerificationHash(), interopCtx, true)
} }

View file

@ -1,97 +0,0 @@
package core
import (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/io"
)
// BlockChainState represents Blockchain state structure with mempool.
type BlockChainState struct {
store *storage.MemCachedStore
unspentCoins UnspentCoins
spentCoins SpentCoins
accounts Accounts
assets Assets
contracts Contracts
validators Validators
}
// NewBlockChainState creates blockchain state with it's memchached store.
func NewBlockChainState(store *storage.MemCachedStore) *BlockChainState {
tmpStore := storage.NewMemCachedStore(store)
return &BlockChainState{
store: tmpStore,
unspentCoins: make(UnspentCoins),
spentCoins: make(SpentCoins),
accounts: make(Accounts),
assets: make(Assets),
contracts: make(Contracts),
validators: make(Validators),
}
}
// commit commits all the data in current state into storage.
func (state *BlockChainState) commit() error {
if err := state.accounts.commit(state.store); err != nil {
return err
}
if err := state.unspentCoins.commit(state.store); err != nil {
return err
}
if err := state.spentCoins.commit(state.store); err != nil {
return err
}
if err := state.assets.commit(state.store); err != nil {
return err
}
if err := state.contracts.commit(state.store); err != nil {
return err
}
if err := state.validators.commit(state.store); err != nil {
return err
}
if _, err := state.store.Persist(); err != nil {
return err
}
return nil
}
// storeAsBlock stores the given block as DataBlock.
func (state *BlockChainState) 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.WriteBytes(b)
if buf.Err != nil {
return buf.Err
}
return state.store.Put(key, buf.Bytes())
}
// storeAsCurrentBlock stores the given block witch prefix SYSCurrentBlock.
func (state *BlockChainState) storeAsCurrentBlock(block *Block) error {
buf := io.NewBufBinWriter()
buf.WriteBytes(block.Hash().BytesLE())
buf.WriteLE(block.Index)
return state.store.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes())
}
// storeAsTransaction stores the given TX as DataTransaction.
func (state *BlockChainState) 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 state.store.Put(key, buf.Bytes())
}

View file

@ -1,46 +0,0 @@
package core
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/stretchr/testify/require"
)
func TestNewBlockChainStateAndCommit(t *testing.T) {
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
bcState := NewBlockChainState(memCachedStore)
err := bcState.commit()
require.NoError(t, err)
}
func TestStoreAsBlock(t *testing.T) {
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
bcState := NewBlockChainState(memCachedStore)
block := newBlock(0, newMinerTX())
err := bcState.storeAsBlock(block, 0)
require.NoError(t, err)
}
func TestStoreAsCurrentBlock(t *testing.T) {
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
bcState := NewBlockChainState(memCachedStore)
block := newBlock(0, newMinerTX())
err := bcState.storeAsCurrentBlock(block)
require.NoError(t, err)
}
func TestStoreAsTransaction(t *testing.T) {
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
bcState := NewBlockChainState(memCachedStore)
tx := &transaction.Transaction{
Type: transaction.MinerType,
Data: &transaction.MinerTX{},
}
err := bcState.storeAsTransaction(tx, 0)
require.NoError(t, err)
}

View file

@ -56,7 +56,7 @@ func TestAddBlock(t *testing.T) {
for _, block := range blocks { for _, block := range blocks {
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE()) key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
if _, err := bc.store.Get(key); err != nil { if _, err := bc.dao.store.Get(key); err != nil {
t.Fatalf("block %s not persisted", block.Hash()) t.Fatalf("block %s not persisted", block.Hash())
} }
} }
@ -170,7 +170,7 @@ func TestClose(t *testing.T) {
// It's a hack, but we use internal knowledge of MemoryStore // It's a hack, but we use internal knowledge of MemoryStore
// implementation which makes it completely unusable (up to panicing) // implementation which makes it completely unusable (up to panicing)
// after Close(). // after Close().
_ = bc.store.Put([]byte{0}, []byte{1}) _ = bc.dao.store.Put([]byte{0}, []byte{1})
// This should never be executed. // This should never be executed.
assert.Nil(t, t) assert.Nil(t, t)

View file

@ -2,6 +2,7 @@ package core
import ( import (
"github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
@ -19,19 +20,19 @@ type Blockchainer interface {
Close() Close()
HeaderHeight() uint32 HeaderHeight() uint32
GetBlock(hash util.Uint256) (*Block, error) GetBlock(hash util.Uint256) (*Block, error)
GetContractState(hash util.Uint160) *ContractState GetContractState(hash util.Uint160) *entities.ContractState
GetHeaderHash(int) util.Uint256 GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*Header, error) GetHeader(hash util.Uint256) (*Header, error)
CurrentHeaderHash() util.Uint256 CurrentHeaderHash() util.Uint256
CurrentBlockHash() util.Uint256 CurrentBlockHash() util.Uint256
HasBlock(util.Uint256) bool HasBlock(util.Uint256) bool
HasTransaction(util.Uint256) bool HasTransaction(util.Uint256) bool
GetAssetState(util.Uint256) *AssetState GetAssetState(util.Uint256) *entities.AssetState
GetAccountState(util.Uint160) *AccountState GetAccountState(util.Uint160) *entities.AccountState
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) GetValidators(txes... *transaction.Transaction) ([]*keys.PublicKey, error)
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *entities.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*entities.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) *UnspentCoinState

555
pkg/core/dao.go Normal file
View file

@ -0,0 +1,555 @@
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
}

View file

@ -1,74 +1,11 @@
package core package entities
import ( import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// Accounts is mapping between a account address and AccountState.
type Accounts map[util.Uint160]*AccountState
// getAndUpdate retrieves AccountState from temporary or persistent Store
// or creates a new one if it doesn't exist.
func (a Accounts) getAndUpdate(s storage.Store, hash util.Uint160) (*AccountState, error) {
if account, ok := a[hash]; ok {
return account, nil
}
account, err := getAccountStateFromStore(s, hash)
if err != nil {
if err != storage.ErrKeyNotFound {
return nil, err
}
account = NewAccountState(hash)
}
a[hash] = account
return account, nil
}
// getAccountStateFromStore returns AccountState from the given Store if it's
// present there. Returns nil otherwise.
func getAccountStateFromStore(s storage.Store, hash util.Uint160) (*AccountState, error) {
var account *AccountState
key := storage.AppendPrefix(storage.STAccount, hash.BytesBE())
b, err := s.Get(key)
if err == nil {
account = new(AccountState)
r := io.NewBinReaderFromBuf(b)
account.DecodeBinary(r)
if r.Err != nil {
return nil, fmt.Errorf("failed to decode (AccountState): %s", r.Err)
}
}
return account, err
}
// putAccountStateIntoStore puts given AccountState into the given store.
func putAccountStateIntoStore(store storage.Store, as *AccountState) error {
buf := io.NewBufBinWriter()
as.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STAccount, as.ScriptHash.BytesBE())
return store.Put(key, buf.Bytes())
}
// commit writes all account states to the given Batch.
func (a Accounts) commit(store storage.Store) error {
for _, state := range a {
if err := putAccountStateIntoStore(store, state); err != nil {
return err
}
}
return nil
}
// UnspentBalance contains input/output transactons that sum up into the // UnspentBalance contains input/output transactons that sum up into the
// account balance for the given asset. // account balance for the given asset.
type UnspentBalance struct { type UnspentBalance struct {

View file

@ -1,8 +1,9 @@
package core package entities
import ( import (
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/testutil"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
@ -16,12 +17,12 @@ func TestDecodeEncodeAccountState(t *testing.T) {
votes = make([]*keys.PublicKey, n) votes = make([]*keys.PublicKey, n)
) )
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
asset := randomUint256() asset := testutil.RandomUint256()
for j := 0; j < i+1; j++ { for j := 0; j < i+1; j++ {
balances[asset] = append(balances[asset], UnspentBalance{ balances[asset] = append(balances[asset], UnspentBalance{
Tx: randomUint256(), Tx: testutil.RandomUint256(),
Index: uint16(randomInt(0, 65535)), Index: uint16(testutil.RandomInt(0, 65535)),
Value: util.Fixed8(int64(randomInt(1, 10000))), Value: util.Fixed8(int64(testutil.RandomInt(1, 10000))),
}) })
} }
k, err := keys.NewPrivateKey() k, err := keys.NewPrivateKey()
@ -31,7 +32,7 @@ func TestDecodeEncodeAccountState(t *testing.T) {
a := &AccountState{ a := &AccountState{
Version: 0, Version: 0,
ScriptHash: randomUint160(), ScriptHash: testutil.RandomUint160(),
IsFrozen: true, IsFrozen: true,
Votes: votes, Votes: votes,
Balances: balances, Balances: balances,
@ -57,8 +58,8 @@ func TestDecodeEncodeAccountState(t *testing.T) {
} }
func TestAccountStateBalanceValues(t *testing.T) { func TestAccountStateBalanceValues(t *testing.T) {
asset1 := randomUint256() asset1 := testutil.RandomUint256()
asset2 := randomUint256() asset2 := testutil.RandomUint256()
as := AccountState{Balances: make(map[util.Uint256][]UnspentBalance)} as := AccountState{Balances: make(map[util.Uint256][]UnspentBalance)}
ref := 0 ref := 0
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {

View file

@ -1,7 +1,6 @@
package core package entities
import ( import (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
@ -10,29 +9,6 @@ import (
const feeMode = 0x0 const feeMode = 0x0
// Assets is mapping between AssetID and the AssetState.
type Assets map[util.Uint256]*AssetState
func (a Assets) commit(store storage.Store) error {
for _, state := range a {
if err := putAssetStateIntoStore(store, state); err != nil {
return err
}
}
return nil
}
// putAssetStateIntoStore puts given asset state into the given store.
func putAssetStateIntoStore(s storage.Store, as *AssetState) error {
buf := io.NewBufBinWriter()
as.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STAsset, as.ID.BytesBE())
return s.Put(key, buf.Bytes())
}
// AssetState represents the state of an NEO registered Asset. // AssetState represents the state of an NEO registered Asset.
type AssetState struct { type AssetState struct {
ID util.Uint256 ID util.Uint256

View file

@ -1,10 +1,11 @@
package core package entities
import ( import (
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/testutil"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "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/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -12,15 +13,16 @@ import (
func TestEncodeDecodeAssetState(t *testing.T) { func TestEncodeDecodeAssetState(t *testing.T) {
asset := &AssetState{ asset := &AssetState{
ID: randomUint256(), ID: testutil.RandomUint256(),
AssetType: transaction.Token, AssetType: transaction.Token,
Name: "super cool token", Name: "super cool token",
Amount: util.Fixed8(1000000), Amount: util.Fixed8(1000000),
Available: util.Fixed8(100), Available: util.Fixed8(100),
Precision: 0, Precision: 0,
FeeMode: feeMode, FeeMode: feeMode,
Admin: randomUint160(), Owner: keys.PublicKey{},
Issuer: randomUint160(), Admin: testutil.RandomUint160(),
Issuer: testutil.RandomUint160(),
Expiration: 10, Expiration: 10,
IsFrozen: false, IsFrozen: false,
} }
@ -34,24 +36,3 @@ func TestEncodeDecodeAssetState(t *testing.T) {
assert.Nil(t, r.Err) assert.Nil(t, r.Err)
assert.Equal(t, asset, assetDecode) assert.Equal(t, asset, assetDecode)
} }
func TestPutGetAssetState(t *testing.T) {
s := storage.NewMemoryStore()
asset := &AssetState{
ID: randomUint256(),
AssetType: transaction.Token,
Name: "super cool token",
Amount: util.Fixed8(1000000),
Available: util.Fixed8(100),
Precision: 8,
FeeMode: feeMode,
Admin: randomUint160(),
Issuer: randomUint160(),
Expiration: 10,
IsFrozen: false,
}
assert.NoError(t, putAssetStateIntoStore(s, asset))
asRead := getAssetStateFromStore(s, asset.ID)
assert.NotNil(t, asRead)
assert.Equal(t, asset, asRead)
}

View file

@ -1,4 +1,4 @@
package core package entities
// CoinState represents the state of a coin. // CoinState represents the state of a coin.
type CoinState uint8 type CoinState uint8

View file

@ -1,16 +1,12 @@
package core package entities
import ( import (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// Contracts is a mapping between scripthash and ContractState.
type Contracts map[util.Uint160]*ContractState
// ContractState holds information about a smart contract in the NEO blockchain. // ContractState holds information about a smart contract in the NEO blockchain.
type ContractState struct { type ContractState struct {
Script []byte Script []byte
@ -26,16 +22,6 @@ type ContractState struct {
scriptHash util.Uint160 scriptHash util.Uint160
} }
// commit flushes all contracts to the given storage.Batch.
func (a Contracts) commit(store storage.Store) error {
for _, contract := range a {
if err := putContractStateIntoStore(store, contract); err != nil {
return err
}
}
return nil
}
// DecodeBinary implements Serializable interface. // DecodeBinary implements Serializable interface.
func (cs *ContractState) DecodeBinary(br *io.BinReader) { func (cs *ContractState) DecodeBinary(br *io.BinReader) {
cs.Script = br.ReadVarBytes() cs.Script = br.ReadVarBytes()
@ -63,23 +49,6 @@ func (cs *ContractState) EncodeBinary(bw *io.BinWriter) {
bw.WriteString(cs.Description) bw.WriteString(cs.Description)
} }
// putContractStateIntoStore puts given contract state into the given store.
func putContractStateIntoStore(s storage.Store, cs *ContractState) error {
buf := io.NewBufBinWriter()
cs.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE())
return s.Put(key, buf.Bytes())
}
// deleteContractStateInStore deletes given contract state in the given store.
func deleteContractStateInStore(s storage.Store, hash util.Uint160) error {
key := storage.AppendPrefix(storage.STContract, hash.BytesBE())
return s.Delete(key)
}
// ScriptHash returns a contract script hash. // ScriptHash returns a contract script hash.
func (cs *ContractState) ScriptHash() util.Uint160 { func (cs *ContractState) ScriptHash() util.Uint160 {
if cs.scriptHash.Equals(util.Uint160{}) { if cs.scriptHash.Equals(util.Uint160{}) {

View file

@ -1,9 +1,8 @@
package core package entities
import ( import (
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/smartcontract"
@ -51,27 +50,3 @@ func TestContractStateProperties(t *testing.T) {
assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke()) assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke())
assert.Equal(t, false, nonFlaggedContract.IsPayable()) assert.Equal(t, false, nonFlaggedContract.IsPayable())
} }
func TestPutGetDeleteContractState(t *testing.T) {
s := storage.NewMemoryStore()
script := []byte("testscript")
contract := &ContractState{
Script: script,
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
ReturnType: smartcontract.BoolType,
Properties: smartcontract.HasStorage,
Name: "Contrato",
CodeVersion: "1.0.0",
Author: "Joe Random",
Email: "joe@example.com",
Description: "Test contract",
}
assert.NoError(t, putContractStateIntoStore(s, contract))
csRead := getContractStateFromStore(s, contract.ScriptHash())
assert.NotNil(t, csRead)
assert.Equal(t, contract, csRead)
assert.NoError(t, deleteContractStateInStore(s, contract.ScriptHash()))
csRead2 := getContractStateFromStore(s, contract.ScriptHash())
assert.Nil(t, csRead2)
}

View file

@ -1,11 +1,9 @@
package core package entities
import ( import (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm" "github.com/CityOfZion/neo-go/pkg/vm"
"github.com/pkg/errors"
) )
// NotificationEvent is a tuple of scripthash that emitted the StackItem as a // NotificationEvent is a tuple of scripthash that emitted the StackItem as a
@ -26,35 +24,6 @@ type AppExecResult struct {
Events []NotificationEvent Events []NotificationEvent
} }
// putAppExecResultIntoStore puts given application execution result into the
// given store.
func putAppExecResultIntoStore(s storage.Store, aer *AppExecResult) error {
buf := io.NewBufBinWriter()
aer.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STNotification, aer.TxHash.BytesBE())
return s.Put(key, buf.Bytes())
}
// getAppExecResultFromStore gets application execution result from the
// given store.
func getAppExecResultFromStore(s storage.Store, hash util.Uint256) (*AppExecResult, error) {
aer := &AppExecResult{}
key := storage.AppendPrefix(storage.STNotification, hash.BytesBE())
if b, err := s.Get(key); err == nil {
r := io.NewBinReaderFromBuf(b)
aer.DecodeBinary(r)
if r.Err != nil {
return nil, errors.Wrap(r.Err, "decoding failure:")
}
} else {
return nil, err
}
return aer, nil
}
// EncodeBinary implements the Serializable interface. // EncodeBinary implements the Serializable interface.
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) { func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(ne.ScriptHash[:]) w.WriteBytes(ne.ScriptHash[:])

View file

@ -0,0 +1,23 @@
package entities
import (
"github.com/CityOfZion/neo-go/pkg/io"
)
// StorageItem is the value to be stored with read-only flag.
type StorageItem struct {
Value []byte
IsConst bool
}
// EncodeBinary implements Serializable interface.
func (si *StorageItem) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes(si.Value)
w.WriteLE(si.IsConst)
}
// DecodeBinary implements Serializable interface.
func (si *StorageItem) DecodeBinary(r *io.BinReader) {
si.Value = r.ReadVarBytes()
r.ReadLE(&si.IsConst)
}

View file

@ -0,0 +1,2 @@
package entities

View file

@ -1,86 +1,11 @@
package core package entities
import ( import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// Validators is a mapping between public keys and ValidatorState.
type Validators map[*keys.PublicKey]*ValidatorState
func (v Validators) getAndUpdate(s storage.Store, publicKey *keys.PublicKey) (*ValidatorState, error) {
if validator, ok := v[publicKey]; ok {
return validator, nil
}
validatorState, err := getValidatorStateFromStore(s, publicKey)
if err != nil {
if err != storage.ErrKeyNotFound {
return nil, err
}
validatorState = &ValidatorState{PublicKey: publicKey}
}
v[publicKey] = validatorState
return validatorState, nil
}
// getValidatorsFromStore returns all validators from store.
func getValidatorsFromStore(s storage.Store) []*ValidatorState {
var validators []*ValidatorState
s.Seek(storage.STValidator.Bytes(), func(k, v []byte) {
r := io.NewBinReaderFromBuf(v)
validator := &ValidatorState{}
validator.DecodeBinary(r)
if r.Err != nil {
return
}
validators = append(validators, validator)
})
return validators
}
// getValidatorStateFromStore returns validator by publicKey.
func getValidatorStateFromStore(s storage.Store, publicKey *keys.PublicKey) (*ValidatorState, error) {
validatorState := &ValidatorState{}
key := storage.AppendPrefix(storage.STValidator, publicKey.Bytes())
if b, err := s.Get(key); err == nil {
r := io.NewBinReaderFromBuf(b)
validatorState.DecodeBinary(r)
if r.Err != nil {
return nil, fmt.Errorf("failed to decode (ValidatorState): %s", r.Err)
}
} else {
return nil, err
}
return validatorState, nil
}
// commit writes all validator states to the given Batch.
func (v Validators) commit(store storage.Store) error {
for _, validator := range v {
if err := putValidatorStateIntoStore(store, validator); err != nil {
return err
}
}
return nil
}
// putValidatorStateIntoStore puts given ValidatorState into the given store.
func putValidatorStateIntoStore(store storage.Store, vs *ValidatorState) error {
buf := io.NewBufBinWriter()
vs.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes())
return store.Put(key, buf.Bytes())
}
// ValidatorState holds the state of a validator. // ValidatorState holds the state of a validator.
type ValidatorState struct { type ValidatorState struct {
PublicKey *keys.PublicKey PublicKey *keys.PublicKey

View file

@ -0,0 +1,64 @@
package entities
import (
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestValidatorState_DecodeEncodeBinary(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{},
Registered: false,
Votes: util.Fixed8(10),
}
buf := io.NewBufBinWriter()
state.EncodeBinary(buf.BinWriter)
require.NoError(t, buf.Err)
decodedState := &ValidatorState{}
reader := io.NewBinReaderFromBuf(buf.Bytes())
decodedState.DecodeBinary(reader)
require.NoError(t, reader.Err)
require.Equal(t, state, decodedState)
}
func TestRegisteredAndHasVotes_Registered(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: true,
Votes: 0,
}
require.False(t, state.RegisteredAndHasVotes())
}
func TestRegisteredAndHasVotes_RegisteredWithVotes(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: true,
Votes: 1,
}
require.True(t, state.RegisteredAndHasVotes())
}
func TestRegisteredAndHasVotes_NotRegisteredWithVotes(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: false,
Votes: 1,
}
require.False(t, state.RegisteredAndHasVotes())
}

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"math" "math"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/smartcontract"
@ -316,7 +317,7 @@ func (ic *interopContext) bcGetAccount(v *vm.VM) error {
} }
acc := ic.bc.GetAccountState(acchash) acc := ic.bc.GetAccountState(acchash)
if acc == nil { if acc == nil {
acc = NewAccountState(acchash) acc = entities.NewAccountState(acchash)
} }
v.Estack().PushVal(vm.NewInteropItem(acc)) v.Estack().PushVal(vm.NewInteropItem(acc))
return nil return nil
@ -340,7 +341,7 @@ func (ic *interopContext) bcGetAsset(v *vm.VM) error {
// accountGetBalance returns balance for a given account. // accountGetBalance returns balance for a given account.
func (ic *interopContext) accountGetBalance(v *vm.VM) error { func (ic *interopContext) accountGetBalance(v *vm.VM) error {
accInterface := v.Estack().Pop().Value() accInterface := v.Estack().Pop().Value()
acc, ok := accInterface.(*AccountState) acc, ok := accInterface.(*entities.AccountState)
if !ok { if !ok {
return fmt.Errorf("%T is not an account state", acc) return fmt.Errorf("%T is not an account state", acc)
} }
@ -360,7 +361,7 @@ func (ic *interopContext) accountGetBalance(v *vm.VM) error {
// accountGetScriptHash returns script hash of a given account. // accountGetScriptHash returns script hash of a given account.
func (ic *interopContext) accountGetScriptHash(v *vm.VM) error { func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
accInterface := v.Estack().Pop().Value() accInterface := v.Estack().Pop().Value()
acc, ok := accInterface.(*AccountState) acc, ok := accInterface.(*entities.AccountState)
if !ok { if !ok {
return fmt.Errorf("%T is not an account state", acc) return fmt.Errorf("%T is not an account state", acc)
} }
@ -371,7 +372,7 @@ func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
// accountGetVotes returns votes of a given account. // accountGetVotes returns votes of a given account.
func (ic *interopContext) accountGetVotes(v *vm.VM) error { func (ic *interopContext) accountGetVotes(v *vm.VM) error {
accInterface := v.Estack().Pop().Value() accInterface := v.Estack().Pop().Value()
acc, ok := accInterface.(*AccountState) acc, ok := accInterface.(*entities.AccountState)
if !ok { if !ok {
return fmt.Errorf("%T is not an account state", acc) return fmt.Errorf("%T is not an account state", acc)
} }
@ -429,7 +430,7 @@ func (ic *interopContext) storageFind(v *vm.VM) error {
// createContractStateFromVM pops all contract state elements from the VM // createContractStateFromVM pops all contract state elements from the VM
// evaluation stack, does a lot of checks and returns ContractState if it // evaluation stack, does a lot of checks and returns ContractState if it
// succeeds. // succeeds.
func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*ContractState, error) { func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*entities.ContractState, error) {
if ic.trigger != trigger.Application { if ic.trigger != trigger.Application {
return nil, errors.New("can't create contract when not triggered by an application") return nil, errors.New("can't create contract when not triggered by an application")
} }
@ -467,7 +468,7 @@ func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*ContractState, e
if len(desc) > MaxContractStringLen { if len(desc) > MaxContractStringLen {
return nil, errors.New("too big description") return nil, errors.New("too big description")
} }
contract := &ContractState{ contract := &entities.ContractState{
Script: script, Script: script,
ParamList: paramList, ParamList: paramList,
ReturnType: retType, ReturnType: retType,
@ -490,7 +491,7 @@ func (ic *interopContext) contractCreate(v *vm.VM) error {
contract := ic.bc.GetContractState(newcontract.ScriptHash()) contract := ic.bc.GetContractState(newcontract.ScriptHash())
if contract == nil { if contract == nil {
contract = newcontract contract = newcontract
err := putContractStateIntoStore(ic.mem, contract) err := ic.dao.PutContractState(contract)
if err != nil { if err != nil {
return err return err
} }
@ -502,7 +503,7 @@ func (ic *interopContext) contractCreate(v *vm.VM) error {
// contractGetScript returns a script associated with a contract. // contractGetScript returns a script associated with a contract.
func (ic *interopContext) contractGetScript(v *vm.VM) error { func (ic *interopContext) contractGetScript(v *vm.VM) error {
csInterface := v.Estack().Pop().Value() csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*ContractState) cs, ok := csInterface.(*entities.ContractState)
if !ok { if !ok {
return fmt.Errorf("%T is not a contract state", cs) return fmt.Errorf("%T is not a contract state", cs)
} }
@ -513,7 +514,7 @@ func (ic *interopContext) contractGetScript(v *vm.VM) error {
// contractIsPayable returns whether contract is payable. // contractIsPayable returns whether contract is payable.
func (ic *interopContext) contractIsPayable(v *vm.VM) error { func (ic *interopContext) contractIsPayable(v *vm.VM) error {
csInterface := v.Estack().Pop().Value() csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*ContractState) cs, ok := csInterface.(*entities.ContractState)
if !ok { if !ok {
return fmt.Errorf("%T is not a contract state", cs) return fmt.Errorf("%T is not a contract state", cs)
} }
@ -530,7 +531,7 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error {
contract := ic.bc.GetContractState(newcontract.ScriptHash()) contract := ic.bc.GetContractState(newcontract.ScriptHash())
if contract == nil { if contract == nil {
contract = newcontract contract = newcontract
err := putContractStateIntoStore(ic.mem, contract) err := ic.dao.PutContractState(contract)
if err != nil { if err != nil {
return err return err
} }
@ -542,7 +543,7 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error {
} }
for k, v := range siMap { for k, v := range siMap {
v.IsConst = false v.IsConst = false
_ = putStorageItemIntoStore(ic.mem, hash, []byte(k), v) _ = ic.dao.PutStorageItem(hash, []byte(k), v)
} }
} }
} }
@ -609,7 +610,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
if err != nil { if err != nil {
return gherr.Wrap(err, "failed to get issuer") return gherr.Wrap(err, "failed to get issuer")
} }
asset := &AssetState{ asset := &entities.AssetState{
ID: ic.tx.Hash(), ID: ic.tx.Hash(),
AssetType: atype, AssetType: atype,
Name: name, Name: name,
@ -620,7 +621,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
Issuer: issuer, Issuer: issuer,
Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime, Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime,
} }
err = putAssetStateIntoStore(ic.mem, asset) err = ic.dao.PutAssetState(asset)
if err != nil { if err != nil {
return gherr.Wrap(err, "failed to store asset") return gherr.Wrap(err, "failed to store asset")
} }
@ -631,7 +632,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
// assetGetAdmin returns asset admin. // assetGetAdmin returns asset admin.
func (ic *interopContext) assetGetAdmin(v *vm.VM) error { func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -642,7 +643,7 @@ func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
// assetGetAmount returns the overall amount of asset available. // assetGetAmount returns the overall amount of asset available.
func (ic *interopContext) assetGetAmount(v *vm.VM) error { func (ic *interopContext) assetGetAmount(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -653,7 +654,7 @@ func (ic *interopContext) assetGetAmount(v *vm.VM) error {
// assetGetAssetId returns the id of an asset. // assetGetAssetId returns the id of an asset.
func (ic *interopContext) assetGetAssetID(v *vm.VM) error { func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -664,7 +665,7 @@ func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
// assetGetAssetType returns type of an asset. // assetGetAssetType returns type of an asset.
func (ic *interopContext) assetGetAssetType(v *vm.VM) error { func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -675,7 +676,7 @@ func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
// assetGetAvailable returns available (not yet issued) amount of asset. // assetGetAvailable returns available (not yet issued) amount of asset.
func (ic *interopContext) assetGetAvailable(v *vm.VM) error { func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -686,7 +687,7 @@ func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
// assetGetIssuer returns issuer of an asset. // assetGetIssuer returns issuer of an asset.
func (ic *interopContext) assetGetIssuer(v *vm.VM) error { func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -697,7 +698,7 @@ func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
// assetGetOwner returns owner of an asset. // assetGetOwner returns owner of an asset.
func (ic *interopContext) assetGetOwner(v *vm.VM) error { func (ic *interopContext) assetGetOwner(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -708,7 +709,7 @@ func (ic *interopContext) assetGetOwner(v *vm.VM) error {
// assetGetPrecision returns precision used to measure this asset. // assetGetPrecision returns precision used to measure this asset.
func (ic *interopContext) assetGetPrecision(v *vm.VM) error { func (ic *interopContext) assetGetPrecision(v *vm.VM) error {
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -722,7 +723,7 @@ func (ic *interopContext) assetRenew(v *vm.VM) error {
return errors.New("can't create asset when not triggered by an application") return errors.New("can't create asset when not triggered by an application")
} }
asInterface := v.Estack().Pop().Value() asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState) as, ok := asInterface.(*entities.AssetState)
if !ok { if !ok {
return fmt.Errorf("%T is not an asset state", as) return fmt.Errorf("%T is not an asset state", as)
} }
@ -740,7 +741,7 @@ func (ic *interopContext) assetRenew(v *vm.VM) error {
expiration = math.MaxUint32 expiration = math.MaxUint32
} }
asset.Expiration = uint32(expiration) asset.Expiration = uint32(expiration)
err := putAssetStateIntoStore(ic.mem, asset) err := ic.dao.PutAssetState(asset)
if err != nil { if err != nil {
return gherr.Wrap(err, "failed to store asset") return gherr.Wrap(err, "failed to store asset")
} }

View file

@ -4,7 +4,9 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/testutil"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/smartcontract"
@ -337,9 +339,9 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop
return v, tx, context return v, tx, context
} }
func createVMAndAssetState(t *testing.T) (*vm.VM, *AssetState, *interopContext) { func createVMAndAssetState(t *testing.T) (*vm.VM, *entities.AssetState, *interopContext) {
v := vm.New() v := vm.New()
assetState := &AssetState{ assetState := &entities.AssetState{
ID: util.Uint256{}, ID: util.Uint256{},
AssetType: transaction.GoverningToken, AssetType: transaction.GoverningToken,
Name: "TestAsset", Name: "TestAsset",
@ -347,10 +349,10 @@ func createVMAndAssetState(t *testing.T) (*vm.VM, *AssetState, *interopContext)
Available: 2, Available: 2,
Precision: 1, Precision: 1,
FeeMode: 1, FeeMode: 1,
FeeAddress: randomUint160(), FeeAddress: testutil.RandomUint160(),
Owner: keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)}, Owner: keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)},
Admin: randomUint160(), Admin: testutil.RandomUint160(),
Issuer: randomUint160(), Issuer: testutil.RandomUint160(),
Expiration: 10, Expiration: 10,
IsFrozen: false, IsFrozen: false,
} }
@ -359,30 +361,29 @@ func createVMAndAssetState(t *testing.T) (*vm.VM, *AssetState, *interopContext)
return v, assetState, context return v, assetState, context
} }
func createVMAndContractState(t *testing.T) (*vm.VM, *ContractState, *interopContext) { func createVMAndContractState(t *testing.T) (*vm.VM, *entities.ContractState, *interopContext) {
v := vm.New() v := vm.New()
contractState := &ContractState{ contractState := &entities.ContractState{
Script: []byte("testscript"), Script: []byte("testscript"),
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type}, ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
ReturnType: smartcontract.ArrayType, ReturnType: smartcontract.ArrayType,
Properties: smartcontract.HasStorage, Properties: smartcontract.HasStorage,
Name: randomString(10), Name: testutil.RandomString(10),
CodeVersion: randomString(10), CodeVersion: testutil.RandomString(10),
Author: randomString(10), Author: testutil.RandomString(10),
Email: randomString(10), Email: testutil.RandomString(10),
Description: randomString(10), Description: testutil.RandomString(10),
scriptHash: randomUint160(),
} }
context := newInteropContext(trigger.Application, newTestChain(t), storage.NewMemoryStore(), nil, nil) context := newInteropContext(trigger.Application, newTestChain(t), storage.NewMemoryStore(), nil, nil)
return v, contractState, context return v, contractState, context
} }
func createVMAndAccState(t *testing.T) (*vm.VM, *AccountState, *interopContext) { func createVMAndAccState(t *testing.T) (*vm.VM, *entities.AccountState, *interopContext) {
v := vm.New() v := vm.New()
rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302" rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302"
hash, err := util.Uint160DecodeStringBE(rawHash) hash, err := util.Uint160DecodeStringBE(rawHash)
accountState := NewAccountState(hash) accountState := entities.NewAccountState(hash)
key := &keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)} key := &keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)}
accountState.Votes = []*keys.PublicKey{key} accountState.Votes = []*keys.PublicKey{key}
@ -403,14 +404,14 @@ func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interopCont
}) })
inputs := append(tx.Inputs, transaction.Input{ inputs := append(tx.Inputs, transaction.Input{
PrevHash: randomUint256(), PrevHash: testutil.RandomUint256(),
PrevIndex: 1, PrevIndex: 1,
}) })
outputs := append(tx.Outputs, transaction.Output{ outputs := append(tx.Outputs, transaction.Output{
AssetID: randomUint256(), AssetID: testutil.RandomUint256(),
Amount: 10, Amount: 10,
ScriptHash: randomUint160(), ScriptHash: testutil.RandomUint160(),
Position: 1, Position: 1,
}) })

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"math" "math"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
@ -341,7 +342,7 @@ func (ic *interopContext) runtimeCheckWitness(v *vm.VM) error {
func (ic *interopContext) runtimeNotify(v *vm.VM) error { func (ic *interopContext) runtimeNotify(v *vm.VM) error {
// It can be just about anything. // It can be just about anything.
e := v.Estack().Pop() e := v.Estack().Pop()
ne := NotificationEvent{getContextScriptHash(v, 0), e.Item()} ne := entities.NotificationEvent{ScriptHash:getContextScriptHash(v, 0), Item:e.Item()}
ic.notifications = append(ic.notifications, ne) ic.notifications = append(ic.notifications, ne)
return nil return nil
} }
@ -410,11 +411,11 @@ func (ic *interopContext) storageDelete(v *vm.VM) error {
return err return err
} }
key := v.Estack().Pop().Bytes() key := v.Estack().Pop().Bytes()
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key) si := ic.dao.GetStorageItem(stc.ScriptHash, key)
if si != nil && si.IsConst { if si != nil && si.IsConst {
return errors.New("storage item is constant") return errors.New("storage item is constant")
} }
return deleteStorageItemInStore(ic.mem, stc.ScriptHash, key) return ic.dao.DeleteStorageItem(stc.ScriptHash, key)
} }
// storageGet returns stored key-value pair. // storageGet returns stored key-value pair.
@ -429,7 +430,7 @@ func (ic *interopContext) storageGet(v *vm.VM) error {
return err return err
} }
key := v.Estack().Pop().Bytes() key := v.Estack().Pop().Bytes()
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key) si := ic.dao.GetStorageItem(stc.ScriptHash, key)
if si != nil && si.Value != nil { if si != nil && si.Value != nil {
v.Estack().PushVal(si.Value) v.Estack().PushVal(si.Value)
} else { } else {
@ -472,16 +473,16 @@ func (ic *interopContext) putWithContextAndFlags(stc *StorageContext, key []byte
if err != nil { if err != nil {
return err return err
} }
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key) si := ic.dao.GetStorageItem(stc.ScriptHash, key)
if si == nil { if si == nil {
si = &StorageItem{} si = &entities.StorageItem{}
} }
if si.IsConst { if si.IsConst {
return errors.New("storage item exists and is read-only") return errors.New("storage item exists and is read-only")
} }
si.Value = value si.Value = value
si.IsConst = isConst si.IsConst = isConst
return putStorageItemIntoStore(ic.mem, stc.ScriptHash, key, si) return ic.dao.PutStorageItem(stc.ScriptHash, key, si)
} }
// storagePutInternal is a unified implementation of storagePut and storagePutEx. // storagePutInternal is a unified implementation of storagePut and storagePutEx.
@ -538,7 +539,7 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
if cs == nil { if cs == nil {
return nil return nil
} }
err := deleteContractStateInStore(ic.mem, hash) err := ic.dao.DeleteContractState(hash)
if err != nil { if err != nil {
return err return err
} }
@ -548,7 +549,7 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
return err return err
} }
for k := range siMap { for k := range siMap {
_ = deleteStorageItemInStore(ic.mem, hash, []byte(k)) _ = ic.dao.DeleteStorageItem(hash, []byte(k))
} }
} }
return nil return nil
@ -557,11 +558,12 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
// contractGetStorageContext retrieves StorageContext of a contract. // contractGetStorageContext retrieves StorageContext of a contract.
func (ic *interopContext) contractGetStorageContext(v *vm.VM) error { func (ic *interopContext) contractGetStorageContext(v *vm.VM) error {
csInterface := v.Estack().Pop().Value() csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*ContractState) cs, ok := csInterface.(*entities.ContractState)
if !ok { if !ok {
return fmt.Errorf("%T is not a contract state", cs) return fmt.Errorf("%T is not a contract state", cs)
} }
if getContractStateFromStore(ic.mem, cs.ScriptHash()) == nil { contractState, err := ic.dao.GetContractState(cs.ScriptHash())
if contractState == nil || err != nil {
return fmt.Errorf("contract was not created in this transaction") return fmt.Errorf("contract was not created in this transaction")
} }
stc := &StorageContext{ stc := &StorageContext{

View file

@ -8,6 +8,7 @@ package core
*/ */
import ( import (
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/vm" "github.com/CityOfZion/neo-go/pkg/vm"
@ -18,14 +19,14 @@ type interopContext struct {
trigger byte trigger byte
block *Block block *Block
tx *transaction.Transaction tx *transaction.Transaction
mem *storage.MemCachedStore dao *dao
notifications []NotificationEvent notifications []entities.NotificationEvent
} }
func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext { func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext {
mem := storage.NewMemCachedStore(s) dao := &dao{store: storage.NewMemCachedStore(s)}
nes := make([]NotificationEvent, 0) nes := make([]entities.NotificationEvent, 0)
return &interopContext{bc, trigger, block, tx, mem, nes} return &interopContext{bc, trigger, block, tx, dao, nes}
} }
// All lists are sorted, keep 'em this way, please. // All lists are sorted, keep 'em this way, please.

View file

@ -1,60 +1,10 @@
package core package core
import ( import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// SpentCoins is mapping between transactions and their spent
// coin state.
type SpentCoins map[util.Uint256]*SpentCoinState
func (s SpentCoins) getAndUpdate(store storage.Store, hash util.Uint256) (*SpentCoinState, error) {
if spent, ok := s[hash]; ok {
return spent, nil
}
spent := &SpentCoinState{}
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
if b, err := store.Get(key); err == nil {
r := io.NewBinReaderFromBuf(b)
spent.DecodeBinary(r)
if r.Err != nil {
return nil, fmt.Errorf("failed to decode (UnspentCoinState): %s", r.Err)
}
} else {
spent = &SpentCoinState{
items: make(map[uint16]uint32),
}
}
s[hash] = spent
return spent, nil
}
// putSpentCoinStateIntoStore puts given SpentCoinState into the given store.
func putSpentCoinStateIntoStore(store storage.Store, hash util.Uint256, scs *SpentCoinState) error {
buf := io.NewBufBinWriter()
scs.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
return store.Put(key, buf.Bytes())
}
func (s SpentCoins) commit(store storage.Store) error {
for hash, state := range s {
if err := putSpentCoinStateIntoStore(store, hash, state); err != nil {
return err
}
}
return nil
}
// SpentCoinState represents the state of a spent coin. // SpentCoinState represents the state of a spent coin.
type SpentCoinState struct { type SpentCoinState struct {
txHash util.Uint256 txHash util.Uint256

View file

@ -3,15 +3,14 @@ package core
import ( import (
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/testutil"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEncodeDecodeSpentCoinState(t *testing.T) { func TestEncodeDecodeSpentCoinState(t *testing.T) {
spent := &SpentCoinState{ spent := &SpentCoinState{
txHash: randomUint256(), txHash: testutil.RandomUint256(),
txHeight: 1001, txHeight: 1001,
items: map[uint16]uint32{ items: map[uint16]uint32{
1: 3, 1: 3,
@ -29,24 +28,3 @@ func TestEncodeDecodeSpentCoinState(t *testing.T) {
assert.Nil(t, r.Err) assert.Nil(t, r.Err)
assert.Equal(t, spent, spentDecode) assert.Equal(t, spent, spentDecode)
} }
func TestCommitSpentCoins(t *testing.T) {
var (
store = storage.NewMemoryStore()
spentCoins = make(SpentCoins)
)
txx := []util.Uint256{
randomUint256(),
randomUint256(),
randomUint256(),
}
for i := 0; i < len(txx); i++ {
spentCoins[txx[i]] = &SpentCoinState{
txHash: txx[i],
txHeight: 1,
}
}
assert.Nil(t, spentCoins.commit(store))
}

View file

@ -1,96 +0,0 @@
package storage
import (
"bytes"
"encoding/binary"
"sort"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
)
// Version attempts to get the current version stored in the
// underlying Store.
func Version(s Store) (string, error) {
version, err := s.Get(SYSVersion.Bytes())
return string(version), err
}
// PutVersion stores the given version in the underlying Store.
func PutVersion(s Store, v string) error {
return s.Put(SYSVersion.Bytes(), []byte(v))
}
// CurrentBlockHeight returns the current block height found in the
// underlying Store.
func CurrentBlockHeight(s Store) (uint32, error) {
b, err := s.Get(SYSCurrentBlock.Bytes())
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint32(b[32:36]), nil
}
// CurrentHeaderHeight returns the current header height and hash from
// the underlying Store.
func CurrentHeaderHeight(s Store) (i uint32, h util.Uint256, err error) {
var b []byte
b, err = s.Get(SYSCurrentHeader.Bytes())
if err != nil {
return
}
i = binary.LittleEndian.Uint32(b[32:36])
h, err = util.Uint256DecodeBytesLE(b[:32])
return
}
// uint32Slice attaches the methods of Interface to []int, sorting in increasing order.
type uint32Slice []uint32
func (p uint32Slice) Len() int { return len(p) }
func (p uint32Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p uint32Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// HeaderHashes returns a sorted list of header hashes retrieved from
// the given underlying Store.
func HeaderHashes(s Store) ([]util.Uint256, error) {
hashMap := make(map[uint32][]util.Uint256)
s.Seek(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(uint32Slice(sortedKeys))
for _, key := range sortedKeys {
hashes = append(hashes[:key], hashMap[key]...)
}
return hashes, nil
}
// 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
}

View file

@ -1,64 +0,0 @@
package core
import (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
)
// StorageItem is the value to be stored with read-only flag.
type StorageItem struct {
Value []byte
IsConst bool
}
// 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...))
}
// getStorageItemFromStore returns StorageItem if it exists in the given Store.
func getStorageItemFromStore(s storage.Store, scripthash util.Uint160, key []byte) *StorageItem {
b, err := s.Get(makeStorageItemKey(scripthash, key))
if err != nil {
return nil
}
r := io.NewBinReaderFromBuf(b)
si := &StorageItem{}
si.DecodeBinary(r)
if r.Err != nil {
return nil
}
return si
}
// putStorageItemIntoStore puts given StorageItem for given script with given
// key into the given Store.
func putStorageItemIntoStore(s storage.Store, scripthash util.Uint160, key []byte, si *StorageItem) error {
buf := io.NewBufBinWriter()
si.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
return s.Put(makeStorageItemKey(scripthash, key), buf.Bytes())
}
// deleteStorageItemInStore drops storage item for the given script with the
// given key from the Store.
func deleteStorageItemInStore(s storage.Store, scripthash util.Uint160, key []byte) error {
return s.Delete(makeStorageItemKey(scripthash, key))
}
// EncodeBinary implements Serializable interface.
func (si *StorageItem) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes(si.Value)
w.WriteLE(si.IsConst)
}
// DecodeBinary implements Serializable interface.
func (si *StorageItem) DecodeBinary(r *io.BinReader) {
si.Value = r.ReadVarBytes()
r.ReadLE(&si.IsConst)
}

View file

@ -1,26 +0,0 @@
package core
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestPutGetDeleteStorageItem(t *testing.T) {
s := storage.NewMemoryStore()
si := &StorageItem{
Value: []byte("smth"),
}
key := []byte("key")
cHash, err := util.Uint160DecodeBytesBE([]byte("abcdefghijklmnopqrst"))
assert.Nil(t, err)
assert.NoError(t, putStorageItemIntoStore(s, cHash, key, si))
siRead := getStorageItemFromStore(s, cHash, key)
assert.NotNil(t, siRead)
assert.Equal(t, si, siRead)
assert.NoError(t, deleteStorageItemInStore(s, cHash, key))
siRead2 := getStorageItemFromStore(s, cHash, key)
assert.Nil(t, siRead2)
}

View file

@ -1,4 +1,4 @@
package core package testutil
import ( import (
"math/rand" "math/rand"
@ -9,29 +9,29 @@ import (
) )
// RandomString returns a random string with the n as its length. // RandomString returns a random string with the n as its length.
func randomString(n int) string { func RandomString(n int) string {
b := make([]byte, n) b := make([]byte, n)
for i := range b { for i := range b {
b[i] = byte(randomInt(65, 90)) b[i] = byte(RandomInt(65, 90))
} }
return string(b) return string(b)
} }
// RandomInt returns a random integer between min and max. // RandomInt returns a random integer in [min,max).
func randomInt(min, max int) int { func RandomInt(min, max int) int {
return min + rand.Intn(max-min) return min + rand.Intn(max-min)
} }
// RandomUint256 returns a random Uint256. // RandomUint256 returns a random Uint256.
func randomUint256() util.Uint256 { func RandomUint256() util.Uint256 {
str := randomString(20) str := RandomString(20)
return hash.Sha256([]byte(str)) return hash.Sha256([]byte(str))
} }
// RandomUint160 returns a random Uint160. // RandomUint160 returns a random Uint160.
func randomUint160() util.Uint160 { func RandomUint160() util.Uint160 {
str := randomString(20) str := RandomString(20)
return hash.RipeMD160([]byte(str)) return hash.RipeMD160([]byte(str))
} }

9
pkg/core/uint32.go Normal file
View file

@ -0,0 +1,9 @@
package core
// slice attaches the methods of Interface to []int, sorting in increasing order.
type slice []uint32
func (p slice) Len() int { return len(p) }
func (p slice) Less(i, j int) bool { return p[i] < p[j] }
func (p slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

View file

@ -1,93 +1,26 @@
package core package core
import ( import (
"fmt" "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/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
) )
// UnspentCoins is mapping between transactions and their unspent
// coin state.
type UnspentCoins map[util.Uint256]*UnspentCoinState
// getAndUpdate retreives UnspentCoinState from temporary or persistent Store
// and return it. If it's not present in both stores, returns a new
// UnspentCoinState.
func (u UnspentCoins) getAndUpdate(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
if unspent, ok := u[hash]; ok {
return unspent, nil
}
unspent, err := getUnspentCoinStateFromStore(s, hash)
if err != nil {
if err != storage.ErrKeyNotFound {
return nil, err
}
unspent = &UnspentCoinState{
states: []CoinState{},
}
}
u[hash] = unspent
return unspent, nil
}
// getUnspentCoinStateFromStore retrieves UnspentCoinState from the given store
func getUnspentCoinStateFromStore(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
unspent := &UnspentCoinState{}
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
if b, err := s.Get(key); err == nil {
r := io.NewBinReaderFromBuf(b)
unspent.DecodeBinary(r)
if r.Err != nil {
return nil, fmt.Errorf("failed to decode (UnspentCoinState): %s", r.Err)
}
} else {
return nil, err
}
return unspent, nil
}
// putUnspentCoinStateIntoStore puts given UnspentCoinState into the given store.
func putUnspentCoinStateIntoStore(store storage.Store, hash util.Uint256, ucs *UnspentCoinState) error {
buf := io.NewBufBinWriter()
ucs.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
return store.Put(key, buf.Bytes())
}
// UnspentCoinState hold the state of a unspent coin. // UnspentCoinState hold the state of a unspent coin.
type UnspentCoinState struct { type UnspentCoinState struct {
states []CoinState states []entities.CoinState
} }
// NewUnspentCoinState returns a new unspent coin state with N confirmed states. // NewUnspentCoinState returns a new unspent coin state with N confirmed states.
func NewUnspentCoinState(n int) *UnspentCoinState { func NewUnspentCoinState(n int) *UnspentCoinState {
u := &UnspentCoinState{ u := &UnspentCoinState{
states: make([]CoinState, n), states: make([]entities.CoinState, n),
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
u.states[i] = CoinStateConfirmed u.states[i] = entities.CoinStateConfirmed
} }
return u return u
} }
// commit writes all unspent coin states to the given Batch.
func (u UnspentCoins) commit(store storage.Store) error {
for hash, state := range u {
if err := putUnspentCoinStateIntoStore(store, hash, state); err != nil {
return err
}
}
return nil
}
// EncodeBinary encodes UnspentCoinState to the given BinWriter. // EncodeBinary encodes UnspentCoinState to the given BinWriter.
func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) { func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
bw.WriteVarUint(uint64(len(s.states))) bw.WriteVarUint(uint64(len(s.states)))
@ -99,40 +32,10 @@ func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
// DecodeBinary decodes UnspentCoinState from the given BinReader. // DecodeBinary decodes UnspentCoinState from the given BinReader.
func (s *UnspentCoinState) DecodeBinary(br *io.BinReader) { func (s *UnspentCoinState) DecodeBinary(br *io.BinReader) {
lenStates := br.ReadVarUint() lenStates := br.ReadVarUint()
s.states = make([]CoinState, lenStates) s.states = make([]entities.CoinState, lenStates)
for i := 0; i < int(lenStates); i++ { for i := 0; i < int(lenStates); i++ {
var state uint8 var state uint8
br.ReadLE(&state) br.ReadLE(&state)
s.states[i] = CoinState(state) s.states[i] = entities.CoinState(state)
} }
} }
// IsDoubleSpend verifies that the input transactions are not double spent.
func IsDoubleSpend(s storage.Store, tx *transaction.Transaction) bool {
if len(tx.Inputs) == 0 {
return false
}
for prevHash, inputs := range tx.GroupInputsByPrevHash() {
unspent := &UnspentCoinState{}
key := storage.AppendPrefix(storage.STCoin, prevHash.BytesLE())
if b, err := s.Get(key); err == nil {
r := io.NewBinReaderFromBuf(b)
unspent.DecodeBinary(r)
if r.Err != nil {
return false
}
for _, input := range inputs {
if int(input.PrevIndex) >= len(unspent.states) || unspent.states[input.PrevIndex] == CoinStateSpent {
return true
}
}
} else {
return true
}
}
return false
}

View file

@ -3,19 +3,19 @@ package core
import ( import (
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestDecodeEncodeUnspentCoinState(t *testing.T) { func TestDecodeEncodeUnspentCoinState(t *testing.T) {
unspent := &UnspentCoinState{ unspent := &UnspentCoinState{
states: []CoinState{ states: []entities.CoinState{
CoinStateConfirmed, entities.CoinStateConfirmed,
CoinStateSpent, entities.CoinStateSpent,
CoinStateSpent, entities.CoinStateSpent,
CoinStateSpent, entities.CoinStateSpent,
CoinStateConfirmed, entities.CoinStateConfirmed,
}, },
} }
@ -27,33 +27,3 @@ func TestDecodeEncodeUnspentCoinState(t *testing.T) {
unspentDecode.DecodeBinary(r) unspentDecode.DecodeBinary(r)
assert.Nil(t, r.Err) assert.Nil(t, r.Err)
} }
func TestCommitUnspentCoins(t *testing.T) {
var (
store = storage.NewMemoryStore()
unspentCoins = make(UnspentCoins)
)
txA := randomUint256()
txB := randomUint256()
txC := randomUint256()
unspentCoins[txA] = &UnspentCoinState{
states: []CoinState{CoinStateConfirmed},
}
unspentCoins[txB] = &UnspentCoinState{
states: []CoinState{
CoinStateConfirmed,
CoinStateConfirmed,
},
}
unspentCoins[txC] = &UnspentCoinState{
states: []CoinState{
CoinStateConfirmed,
CoinStateConfirmed,
CoinStateConfirmed,
},
}
assert.Nil(t, unspentCoins.commit(store))
}

View file

@ -1,121 +0,0 @@
package core
import (
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestGetAndUpdate(t *testing.T) {
store := storage.NewMemoryStore()
state1 := getDefaultValidator()
state2 := getDefaultValidator()
validators := make(Validators)
validators[state1.PublicKey] = state1
validators[state2.PublicKey] = state2
err := validators.commit(store)
require.NoError(t, err)
state, err := validators.getAndUpdate(store, state1.PublicKey)
require.NoError(t, err)
require.Equal(t, state1, state)
}
func TestCommit(t *testing.T) {
store := storage.NewMemoryStore()
state1 := getDefaultValidator()
state2 := getDefaultValidator()
validators := make(Validators)
validators[state1.PublicKey] = state1
validators[state2.PublicKey] = state2
err := validators.commit(store)
require.NoError(t, err)
validatorsFromStore := getValidatorsFromStore(store)
// 2 equal validators will be stored as 1 unique
require.Len(t, validatorsFromStore, 1)
require.Equal(t, state1, validatorsFromStore[0])
}
func TestPutAndGet(t *testing.T) {
store := storage.NewMemoryStore()
state := getDefaultValidator()
err := putValidatorStateIntoStore(store, state)
require.NoError(t, err)
validatorFromStore, err := getValidatorStateFromStore(store, state.PublicKey)
require.NoError(t, err)
require.Equal(t, state.PublicKey, validatorFromStore.PublicKey)
}
func TestGetFromStore_NoKey(t *testing.T) {
store := storage.NewMemoryStore()
state := getDefaultValidator()
_, err := getValidatorStateFromStore(store, state.PublicKey)
require.Errorf(t, err, "key not found")
}
func TestValidatorState_DecodeEncodeBinary(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{},
Registered: false,
Votes: util.Fixed8(10),
}
buf := io.NewBufBinWriter()
state.EncodeBinary(buf.BinWriter)
require.NoError(t, buf.Err)
decodedState := &ValidatorState{}
reader := io.NewBinReaderFromBuf(buf.Bytes())
decodedState.DecodeBinary(reader)
require.NoError(t, reader.Err)
require.Equal(t, state, decodedState)
}
func TestRegisteredAndHasVotes_Registered(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: true,
Votes: 0,
}
require.False(t, state.RegisteredAndHasVotes())
}
func TestRegisteredAndHasVotes_RegisteredWithVotes(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: true,
Votes: 1,
}
require.True(t, state.RegisteredAndHasVotes())
}
func TestRegisteredAndHasVotes_NotRegisteredWithVotes(t *testing.T) {
state := &ValidatorState{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: false,
Votes: 1,
}
require.False(t, state.RegisteredAndHasVotes())
}
func getDefaultValidator() *ValidatorState {
return &ValidatorState{
PublicKey: &keys.PublicKey{},
Registered: false,
Votes: 0,
}
}

View file

@ -9,6 +9,7 @@ import (
"github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
@ -62,7 +63,7 @@ func (chain testChain) HeaderHeight() uint32 {
func (chain testChain) GetBlock(hash util.Uint256) (*core.Block, error) { func (chain testChain) GetBlock(hash util.Uint256) (*core.Block, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetContractState(hash util.Uint160) *core.ContractState { func (chain testChain) GetContractState(hash util.Uint160) *entities.ContractState {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetHeaderHash(int) util.Uint256 { func (chain testChain) GetHeaderHash(int) util.Uint256 {
@ -72,10 +73,10 @@ func (chain testChain) GetHeader(hash util.Uint256) (*core.Header, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetAssetState(util.Uint256) *core.AssetState { func (chain testChain) GetAssetState(util.Uint256) *entities.AssetState {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState { func (chain testChain) GetAccountState(util.Uint160) *entities.AccountState {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) { func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
@ -84,13 +85,13 @@ func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.Publi
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) { func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *core.StorageItem { func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *entities.StorageItem {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetTestVM() (*vm.VM, storage.Store) { func (chain testChain) GetTestVM() (*vm.VM, storage.Store) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*core.StorageItem, error) { func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*entities.StorageItem, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) CurrentHeaderHash() util.Uint256 { func (chain testChain) CurrentHeaderHash() util.Uint256 {

View file

@ -11,7 +11,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
@ -158,7 +158,7 @@ func (c *Client) SetClient(cli *http.Client) {
// asset belonging to specified address. This implementation uses GetUnspents // asset belonging to specified address. This implementation uses GetUnspents
// JSON-RPC call internally, so make sure your RPC server suppors that. // JSON-RPC call internally, so make sure your RPC server suppors that.
func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) { func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
var utxos core.UnspentBalances var utxos entities.UnspentBalances
resp, err := c.GetUnspents(address) resp, err := c.GetUnspents(address)
if err != nil || resp.Error != nil { if err != nil || resp.Error != nil {

View file

@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"sort" "sort"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers" "github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
@ -68,7 +68,7 @@ func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256,
// unspentsToInputs uses UnspentBalances to create a slice of inputs for a new // unspentsToInputs uses UnspentBalances to create a slice of inputs for a new
// transcation containing the required amount of asset. // transcation containing the required amount of asset.
func unspentsToInputs(utxos core.UnspentBalances, required util.Fixed8) ([]transaction.Input, util.Fixed8, error) { func unspentsToInputs(utxos entities.UnspentBalances, required util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
var ( var (
num, i uint16 num, i uint16
selected = util.Fixed8(0) selected = util.Fixed8(0)

View file

@ -1,7 +1,7 @@
package rpc package rpc
import ( import (
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
@ -20,7 +20,7 @@ type (
// Unspent stores Unspents per asset // Unspent stores Unspents per asset
Unspent struct { Unspent struct {
Unspent core.UnspentBalances Unspent entities.UnspentBalances
Asset string // "NEO" / "GAS" Asset string // "NEO" / "GAS"
Amount util.Fixed8 // total unspent of this asset Amount util.Fixed8 // total unspent of this asset
} }

View file

@ -2,9 +2,9 @@ package wrappers
import ( import (
"bytes" "bytes"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"sort" "sort"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
@ -33,7 +33,7 @@ type Balance struct {
} }
// NewAccountState creates a new AccountState wrapper. // NewAccountState creates a new AccountState wrapper.
func NewAccountState(a *core.AccountState) AccountState { func NewAccountState(a *entities.AccountState) AccountState {
balances := make(Balances, 0, len(a.Balances)) balances := make(Balances, 0, len(a.Balances))
for k, v := range a.GetBalanceValues() { for k, v := range a.GetBalanceValues() {
balances = append(balances, Balance{ balances = append(balances, Balance{

View file

@ -1,7 +1,7 @@
package wrappers package wrappers
import ( import (
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
@ -26,7 +26,7 @@ type AssetState struct {
} }
// NewAssetState creates a new AssetState wrapper. // NewAssetState creates a new AssetState wrapper.
func NewAssetState(a *core.AssetState) AssetState { func NewAssetState(a *entities.AssetState) AssetState {
return AssetState{ return AssetState{
ID: a.ID, ID: a.ID,
AssetType: a.AssetType, AssetType: a.AssetType,

View file

@ -2,17 +2,18 @@ package wrappers
import ( import (
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// UnspentBalanceInfo wrapper is used to represent single unspent asset entry // UnspentBalanceInfo wrapper is used to represent single unspent asset entry
// in `getunspents` output. // in `getunspents` output.
type UnspentBalanceInfo struct { type UnspentBalanceInfo struct {
Unspents []core.UnspentBalance `json:"unspent"` Unspents []entities.UnspentBalance `json:"unspent"`
AssetHash util.Uint256 `json:"asset_hash"` AssetHash util.Uint256 `json:"asset_hash"`
Asset string `json:"asset"` Asset string `json:"asset"`
AssetSymbol string `json:"asset_symbol"` AssetSymbol string `json:"asset_symbol"`
Amount util.Fixed8 `json:"amount"` Amount util.Fixed8 `json:"amount"`
} }
// Unspents wrapper is used to represent getunspents return result. // Unspents wrapper is used to represent getunspents return result.
@ -28,7 +29,7 @@ var GlobalAssets = map[string]string{
} }
// NewUnspents creates a new AccountState wrapper using given Blockchainer. // NewUnspents creates a new AccountState wrapper using given Blockchainer.
func NewUnspents(a *core.AccountState, chain core.Blockchainer, addr string) Unspents { func NewUnspents(a *entities.AccountState, chain core.Blockchainer, addr string) Unspents {
res := Unspents{ res := Unspents{
Address: addr, Address: addr,
Balance: make([]UnspentBalanceInfo, 0, len(a.Balances)), Balance: make([]UnspentBalanceInfo, 0, len(a.Balances)),