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"
"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/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
@ -46,8 +47,8 @@ var (
type Blockchain struct {
config config.ProtocolConfiguration
// Persistent storage wrapped around with a write memory caching layer.
store *storage.MemCachedStore
// Data access object for CRUD operations around storage.
dao *dao
// Current index/height of the highest block.
// 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) {
bc := &Blockchain{
config: cfg,
store: storage.NewMemCachedStore(s),
dao: &dao{store: storage.NewMemCachedStore(s)},
headersOp: make(chan headersOpFunc),
headersOpDone: 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 {
// 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 {
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
}
genesisBlock, err := createGenesisBlock(bc.config)
@ -114,7 +115,7 @@ func (bc *Blockchain) init() error {
return err
}
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 {
return err
}
@ -129,14 +130,14 @@ func (bc *Blockchain) init() error {
// and the genesis block as first block.
log.Infof("restoring blockchain with version: %s", version)
bHeight, err := storage.CurrentBlockHeight(bc.store)
bHeight, err := bc.dao.GetCurrentBlockHeight()
if err != nil {
return err
}
bc.blockHeight = bHeight
bc.persistedHeight = bHeight
hashes, err := storage.HeaderHashes(bc.store)
hashes, err := bc.dao.GetHeaderHashes()
if err != nil {
return err
}
@ -144,7 +145,7 @@ func (bc *Blockchain) init() error {
bc.headerList = NewHeaderHashList(hashes...)
bc.storedHeaderCount = uint32(len(hashes))
currHeaderHeight, currHeaderHash, err := storage.CurrentHeaderHeight(bc.store)
currHeaderHeight, currHeaderHash, err := bc.dao.GetCurrentHeaderHeight()
if err != nil {
return err
}
@ -198,7 +199,7 @@ func (bc *Blockchain) Run() {
if err := bc.persist(); err != nil {
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)
}
close(bc.runToExitCh)
@ -268,7 +269,7 @@ func (bc *Blockchain) AddBlock(block *Block) error {
func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
var (
start = time.Now()
batch = bc.store.Batch()
batch = bc.dao.store.Batch()
)
bc.headersOp <- func(headerList *HeaderHashList) {
@ -295,7 +296,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
if oldlen != headerList.Len() {
updateHeaderHeightMetric(headerList.Len() - 1)
if err = bc.store.PutBatch(batch); err != nil {
if err = bc.dao.store.PutBatch(batch); err != nil {
return
}
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
// and all tests are in place, we can make a more optimized and cleaner implementation.
func (bc *Blockchain) storeBlock(block *Block) error {
chainState := NewBlockChainState(bc.store)
if err := chainState.storeAsBlock(block, 0); err != nil {
cache := &dao{store: storage.NewMemCachedStore(bc.dao.store)}
if err := cache.StoreAsBlock(block, 0); err != nil {
return err
}
if err := chainState.storeAsCurrentBlock(block); err != nil {
if err := cache.StoreAsCurrentBlock(block); err != nil {
return err
}
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
}
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.
if err := processOutputs(tx, chainState); err != nil {
if err := processOutputs(tx, cache); err != nil {
return err
}
@ -372,14 +374,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
return fmt.Errorf("could not find previous TX: %s", prevHash)
}
for _, input := range inputs {
unspent, err := chainState.unspentCoins.getAndUpdate(chainState.store, input.PrevHash)
unspent, err := cache.GetUnspentCoinStateOrNew(input.PrevHash)
if err != nil {
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]
account, err := chainState.accounts.getAndUpdate(chainState.store, prevTXOutput.ScriptHash)
account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
if err != nil {
return err
}
@ -387,17 +391,24 @@ func (bc *Blockchain) storeBlock(block *Block) error {
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
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 {
for _, vote := range account.Votes {
validator, err := chainState.validators.getAndUpdate(chainState.store, vote)
validator, err := cache.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validator.Votes -= prevTXOutput.Amount
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]
}
}
if err = cache.PutAccountState(account); err != nil {
return err
}
}
}
// Process the underlying type of the TX.
switch t := tx.Data.(type) {
case *transaction.RegisterTX:
chainState.assets[tx.Hash()] = &AssetState{
err := cache.PutAssetState(&entities.AssetState{
ID: tx.Hash(),
AssetType: t.AssetType,
Name: t.Name,
@ -434,45 +448,50 @@ func (bc *Blockchain) storeBlock(block *Block) error {
Owner: t.Owner,
Admin: t.Admin,
Expiration: bc.BlockHeight() + registeredAssetLifetime,
})
if err != nil {
return err
}
case *transaction.IssueTX:
for _, res := range bc.GetTransactionResults(tx) {
if res.Amount < 0 {
var asset *AssetState
asset, ok := chainState.assets[res.AssetID]
if !ok {
asset = bc.GetAssetState(res.AssetID)
}
if asset == nil {
return fmt.Errorf("issue failed: no asset %s", res.AssetID)
asset, err := cache.GetAssetState(res.AssetID)
if asset == nil || err != nil {
return fmt.Errorf("issue failed: no asset %s or error %s", res.AssetID, err)
}
asset.Available -= res.Amount
chainState.assets[res.AssetID] = asset
if err := cache.PutAssetState(asset); err != nil {
return err
}
}
}
case *transaction.ClaimTX:
// Remove claimed NEO from spent coins making it unavalaible for
// additional claims.
for _, input := range t.Claims {
scs, err := chainState.spentCoins.getAndUpdate(bc.store, input.PrevHash)
scs, err := cache.GetSpentCoinsOrNew(input.PrevHash)
if err != nil {
return err
}
if scs.txHash == input.PrevHash {
// Existing scs.
delete(scs.items, input.PrevIndex)
if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil {
return err
}
} else {
// Uninitialized, new, forget about it.
delete(chainState.spentCoins, input.PrevHash)
if err = cache.DeleteSpentCoinState(input.PrevHash); err != nil {
return err
}
}
}
case *transaction.EnrollmentTX:
if err := processEnrollmentTX(chainState, t); err != nil {
if err := processEnrollmentTX(cache, t); err != nil {
return err
}
case *transaction.StateTX:
if err := processStateTX(chainState, t); err != nil {
if err := processStateTX(cache, t); err != nil {
return err
}
case *transaction.PublishTX:
@ -480,7 +499,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
if t.NeedStorage {
properties |= smartcontract.HasStorage
}
contract := &ContractState{
contract := &entities.ContractState{
Script: t.Script,
ParamList: t.ParamList,
ReturnType: t.ReturnType,
@ -491,15 +510,17 @@ func (bc *Blockchain) storeBlock(block *Block) error {
Email: t.Email,
Description: t.Description,
}
chainState.contracts[contract.ScriptHash()] = contract
if err := cache.PutContractState(contract); err != nil {
return err
}
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.SetCheckedHash(tx.VerificationHash().BytesBE())
v.LoadScript(t.Script)
err := v.Run()
if !v.HasFailed() {
_, err := systemInterop.mem.Persist()
_, err := systemInterop.dao.store.Persist()
if err != nil {
return errors.Wrap(err, "failed to persist invocation results")
}
@ -534,7 +555,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
"err": err,
}).Warn("contract invocation failed")
}
aer := &AppExecResult{
aer := &entities.AppExecResult{
TxHash: tx.Hash(),
Trigger: trigger.Application,
VMState: v.State(),
@ -542,17 +563,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
Stack: v.Stack("estack"),
Events: systemInterop.notifications,
}
err = putAppExecResultIntoStore(chainState.store, aer)
err = cache.PutAppExecResult(aer)
if err != nil {
return errors.Wrap(err, "failed to store notifications")
}
}
}
if err := chainState.commit(); err != nil {
_, err := cache.store.Persist()
if err!= nil {
return err
}
atomic.StoreUint32(&bc.blockHeight, block.Index)
updateBlockHeightMetric(block.Index)
for _, tx := range block.Transactions {
@ -562,37 +582,43 @@ func (bc *Blockchain) storeBlock(block *Block) error {
}
// 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 {
account, err := chainState.accounts.getAndUpdate(chainState.store, output.ScriptHash)
account, err := dao.GetAccountStateOrNew(output.ScriptHash)
if err != nil {
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(),
Index: uint16(index),
Value: output.Amount,
})
if err = dao.PutAccountState(account); err != nil {
return err
}
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
for _, vote := range account.Votes {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
validatorState, err := dao.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validatorState.Votes += output.Amount
if err = dao.PutValidatorState(validatorState); err != nil {
return err
}
}
}
}
return nil
}
func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, state *BlockChainState) error {
func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao) error {
publicKey := &keys.PublicKey{}
err := publicKey.DecodeBytes(descriptor.Key)
if err != nil {
return err
}
validatorState, err := state.validators.getAndUpdate(state.store, publicKey)
validatorState, err := dao.GetValidatorStateOrNew(publicKey)
if err != nil {
return err
}
@ -602,16 +628,17 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, st
return err
}
validatorState.Registered = isRegistered
return dao.PutValidatorState(validatorState)
}
return nil
}
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, state *BlockChainState) error {
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao) error {
hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
if err != nil {
return err
}
account, err := state.accounts.getAndUpdate(state.store, hash)
account, err := dao.GetAccountStateOrNew(hash)
if err != nil {
return err
}
@ -619,13 +646,19 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, stat
if descriptor.Field == "Votes" {
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
for _, vote := range account.Votes {
validator, err := state.validators.getAndUpdate(state.store, vote)
validator, err := dao.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validator.Votes -= balance
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) {
account.Votes = votes
for _, vote := range votes {
_, err := state.validators.getAndUpdate(state.store, vote)
validator, err := dao.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
if err := dao.PutValidatorState(validator); err != nil {
return err
}
}
}
}
@ -655,19 +691,19 @@ func (bc *Blockchain) persist() error {
err error
)
persisted, err = bc.store.Persist()
persisted, err = bc.dao.store.Persist()
if err != nil {
return err
}
if persisted > 0 {
bHeight, err := storage.CurrentBlockHeight(bc.store)
bHeight, err := bc.dao.GetCurrentBlockHeight()
if err != nil {
return err
}
oldHeight := atomic.SwapUint32(&bc.persistedHeight, bHeight)
diff := bHeight - oldHeight
storedHeaderHeight, _, err := storage.CurrentHeaderHeight(bc.store)
storedHeaderHeight, _, err := bc.dao.GetCurrentHeaderHeight()
if err != nil {
return err
}
@ -699,66 +735,22 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio
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 getTransactionFromStore(bc.store, 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
return bc.dao.GetTransaction(hash)
}
// GetStorageItem returns an item from storage.
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem {
return getStorageItemFromStore(bc.store, scripthash, key)
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *entities.StorageItem {
return bc.dao.GetStorageItem(scripthash, key)
}
// GetStorageItems returns all storage items for a given scripthash.
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error) {
var siMap = make(map[string]*StorageItem)
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
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*entities.StorageItem, error) {
return bc.dao.GetStorageItems(hash)
}
// GetBlock returns a Block by the given hash.
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
block, err := getBlockFromStore(bc.store, hash)
block, err := bc.dao.GetBlock(hash)
if err != nil {
return nil, err
}
@ -775,28 +767,9 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
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.
func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) {
return getHeaderFromStore(bc.store, 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)
block, err := bc.dao.GetBlock(hash)
if err != nil {
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
// transaction hash.
func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
return bc.memPool.ContainsKey(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
return bc.memPool.ContainsKey(hash) || bc.dao.HasTransaction(hash)
}
// 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.
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *AssetState {
return getAssetStateFromStore(bc.store, assetID)
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *entities.AssetState {
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
r := io.NewBinReaderFromBuf(asEncoded)
a.DecodeBinary(r)
if r.Err != nil || a.ID != assetID {
return nil
}
return &a
return asset
}
// GetContractState returns contract by its script hash.
func (bc *Blockchain) GetContractState(hash util.Uint160) *ContractState {
return getContractStateFromStore(bc.store, hash)
func (bc *Blockchain) GetContractState(hash util.Uint160) *entities.ContractState {
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
r := io.NewBinReaderFromBuf(contractBytes)
c.DecodeBinary(r)
if r.Err != nil || c.ScriptHash() != hash {
return nil
}
return &c
return contract
}
// GetAccountState returns the account state from its script hash.
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
as, err := getAccountStateFromStore(bc.store, scriptHash)
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *entities.AccountState {
as, err := bc.dao.GetAccountState(scriptHash)
if as == nil && err != storage.ErrKeyNotFound {
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.
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 {
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")
}
}
if IsDoubleSpend(bc.store, t) {
if bc.dao.IsDoubleSpend(t) {
return errors.New("invalid transaction caused by double spending")
}
if err := bc.verifyOutputs(t); err != nil {
@ -1223,24 +1157,33 @@ func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) {
// GetValidators returns validators.
// 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) {
chainState := NewBlockChainState(bc.store)
cache := &dao{store: storage.NewMemCachedStore(bc.dao.store)}
if len(txes) > 0 {
for _, tx := range txes {
// iterate through outputs
for index, output := range tx.Outputs {
accountState := bc.GetAccountState(output.ScriptHash)
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], UnspentBalance{
accountState, err := cache.GetAccountState(output.ScriptHash)
if err != nil {
return nil, err
}
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], entities.UnspentBalance{
Tx: tx.Hash(),
Index: uint16(index),
Value: output.Amount,
})
if err := cache.PutAccountState(accountState); err != nil {
return nil, err
}
if output.AssetID.Equals(governingTokenTX().Hash()) && len(accountState.Votes) > 0 {
for _, vote := range accountState.Votes {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
validatorState, err := cache.GetValidatorStateOrNew(vote)
if err != nil {
return nil, err
}
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 {
prevTx, _, err := bc.GetTransaction(hash)
prevTx, _, err := cache.GetTransaction(hash)
if err != nil {
return nil, err
}
// process inputs
for _, input := range inputs {
prevOutput := prevTx.Outputs[input.PrevIndex]
accountState, err := chainState.accounts.getAndUpdate(chainState.store, prevOutput.ScriptHash)
accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash)
if err != nil {
return nil, err
}
@ -1269,37 +1212,45 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
if prevOutput.AssetID.Equals(governingTokenTX().Hash()) {
if len(accountState.Votes) > 0 {
for _, vote := range accountState.Votes {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
validatorState, err := cache.GetValidatorStateOrNew(vote)
if err != nil {
return nil, err
}
validatorState.Votes -= prevOutput.Amount
if err = cache.PutValidatorState(validatorState); err != nil {
return nil, err
}
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)
if err = cache.PutAccountState(accountState); err != nil {
return nil, err
}
}
}
switch t := tx.Data.(type) {
case *transaction.EnrollmentTX:
if err := processEnrollmentTX(chainState, t); err != nil {
if err := processEnrollmentTX(cache, t); err != nil {
return nil, err
}
case *transaction.StateTX:
if err := processStateTX(chainState, t); err != nil {
if err := processStateTX(cache, t); err != nil {
return nil, err
}
}
}
}
validators := getValidatorsFromStore(chainState.store)
validators := cache.GetValidators()
count := GetValidatorsWeightedAverage(validators)
count := entities.GetValidatorsWeightedAverage(validators)
standByValidators, err := bc.GetStandByValidators()
if err != nil {
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++ {
result = append(result, uniqueSBValidators[i])
}
_, err = cache.store.Persist()
if err != nil {
return nil, err
}
return result, nil
}
func processStateTX(chainState *BlockChainState, tx *transaction.StateTX) error {
func processStateTX(dao *dao, tx *transaction.StateTX) error {
for _, desc := range tx.Descriptors {
switch desc.Type {
case transaction.Account:
if err := processAccountStateDescriptor(desc, chainState); err != nil {
if err := processAccountStateDescriptor(desc, dao); err != nil {
return err
}
case transaction.Validator:
if err := processValidatorStateDescriptor(desc, chainState); err != nil {
if err := processValidatorStateDescriptor(desc, dao); err != nil {
return err
}
}
@ -1343,13 +1298,13 @@ func processStateTX(chainState *BlockChainState, tx *transaction.StateTX) error
return nil
}
func processEnrollmentTX(chainState *BlockChainState, tx *transaction.EnrollmentTX) error {
validatorState, err := chainState.validators.getAndUpdate(chainState.store, &tx.PublicKey)
func processEnrollmentTX(dao *dao, tx *transaction.EnrollmentTX) error {
validatorState, err := dao.GetValidatorStateOrNew(&tx.PublicKey)
if err != nil {
return err
}
validatorState.Registered = true
return nil
return dao.PutValidatorState(validatorState)
}
// 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.
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)
vm := bc.spawnVMWithInterops(systemInterop)
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(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++ {
err := bc.verifyHashAgainstScript(hashes[i], &witnesses[i], t.VerificationHash(), interopCtx, false)
if err != nil {
@ -1515,7 +1470,7 @@ func (bc *Blockchain) verifyBlockWitnesses(block *Block, prevHeader *Header) err
} else {
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)
}

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 {
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())
}
}
@ -170,7 +170,7 @@ func TestClose(t *testing.T) {
// It's a hack, but we use internal knowledge of MemoryStore
// implementation which makes it completely unusable (up to panicing)
// after Close().
_ = bc.store.Put([]byte{0}, []byte{1})
_ = bc.dao.store.Put([]byte{0}, []byte{1})
// This should never be executed.
assert.Nil(t, t)

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package core
package entities
import (
"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"
@ -10,29 +9,6 @@ import (
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.
type AssetState struct {
ID util.Uint256

View file

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

View file

@ -1,16 +1,12 @@
package core
package entities
import (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"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.
type ContractState struct {
Script []byte
@ -26,16 +22,6 @@ type ContractState struct {
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.
func (cs *ContractState) DecodeBinary(br *io.BinReader) {
cs.Script = br.ReadVarBytes()
@ -63,23 +49,6 @@ func (cs *ContractState) EncodeBinary(bw *io.BinWriter) {
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.
func (cs *ContractState) ScriptHash() util.Uint160 {
if cs.scriptHash.Equals(util.Uint160{}) {

View file

@ -1,9 +1,8 @@
package core
package entities
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io"
"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.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 (
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
"github.com/pkg/errors"
)
// NotificationEvent is a tuple of scripthash that emitted the StackItem as a
@ -26,35 +24,6 @@ type AppExecResult struct {
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.
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
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 (
"fmt"
"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"
)
// 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.
type ValidatorState struct {
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"
"math"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
@ -316,7 +317,7 @@ func (ic *interopContext) bcGetAccount(v *vm.VM) error {
}
acc := ic.bc.GetAccountState(acchash)
if acc == nil {
acc = NewAccountState(acchash)
acc = entities.NewAccountState(acchash)
}
v.Estack().PushVal(vm.NewInteropItem(acc))
return nil
@ -340,7 +341,7 @@ func (ic *interopContext) bcGetAsset(v *vm.VM) error {
// accountGetBalance returns balance for a given account.
func (ic *interopContext) accountGetBalance(v *vm.VM) error {
accInterface := v.Estack().Pop().Value()
acc, ok := accInterface.(*AccountState)
acc, ok := accInterface.(*entities.AccountState)
if !ok {
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.
func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
accInterface := v.Estack().Pop().Value()
acc, ok := accInterface.(*AccountState)
acc, ok := accInterface.(*entities.AccountState)
if !ok {
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.
func (ic *interopContext) accountGetVotes(v *vm.VM) error {
accInterface := v.Estack().Pop().Value()
acc, ok := accInterface.(*AccountState)
acc, ok := accInterface.(*entities.AccountState)
if !ok {
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
// evaluation stack, does a lot of checks and returns ContractState if it
// 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 {
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 {
return nil, errors.New("too big description")
}
contract := &ContractState{
contract := &entities.ContractState{
Script: script,
ParamList: paramList,
ReturnType: retType,
@ -490,7 +491,7 @@ func (ic *interopContext) contractCreate(v *vm.VM) error {
contract := ic.bc.GetContractState(newcontract.ScriptHash())
if contract == nil {
contract = newcontract
err := putContractStateIntoStore(ic.mem, contract)
err := ic.dao.PutContractState(contract)
if err != nil {
return err
}
@ -502,7 +503,7 @@ func (ic *interopContext) contractCreate(v *vm.VM) error {
// contractGetScript returns a script associated with a contract.
func (ic *interopContext) contractGetScript(v *vm.VM) error {
csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*ContractState)
cs, ok := csInterface.(*entities.ContractState)
if !ok {
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.
func (ic *interopContext) contractIsPayable(v *vm.VM) error {
csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*ContractState)
cs, ok := csInterface.(*entities.ContractState)
if !ok {
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())
if contract == nil {
contract = newcontract
err := putContractStateIntoStore(ic.mem, contract)
err := ic.dao.PutContractState(contract)
if err != nil {
return err
}
@ -542,7 +543,7 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error {
}
for k, v := range siMap {
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 {
return gherr.Wrap(err, "failed to get issuer")
}
asset := &AssetState{
asset := &entities.AssetState{
ID: ic.tx.Hash(),
AssetType: atype,
Name: name,
@ -620,7 +621,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
Issuer: issuer,
Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime,
}
err = putAssetStateIntoStore(ic.mem, asset)
err = ic.dao.PutAssetState(asset)
if err != nil {
return gherr.Wrap(err, "failed to store asset")
}
@ -631,7 +632,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
// assetGetAdmin returns asset admin.
func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetAmount(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetOwner(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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.
func (ic *interopContext) assetGetPrecision(v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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")
}
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*AssetState)
as, ok := asInterface.(*entities.AssetState)
if !ok {
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
}
asset.Expiration = uint32(expiration)
err := putAssetStateIntoStore(ic.mem, asset)
err := ic.dao.PutAssetState(asset)
if err != nil {
return gherr.Wrap(err, "failed to store asset")
}

View file

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

View file

@ -5,6 +5,7 @@ import (
"fmt"
"math"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"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 {
// It can be just about anything.
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)
return nil
}
@ -410,11 +411,11 @@ func (ic *interopContext) storageDelete(v *vm.VM) error {
return err
}
key := v.Estack().Pop().Bytes()
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
si := ic.dao.GetStorageItem(stc.ScriptHash, key)
if si != nil && si.IsConst {
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.
@ -429,7 +430,7 @@ func (ic *interopContext) storageGet(v *vm.VM) error {
return err
}
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 {
v.Estack().PushVal(si.Value)
} else {
@ -472,16 +473,16 @@ func (ic *interopContext) putWithContextAndFlags(stc *StorageContext, key []byte
if err != nil {
return err
}
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
si := ic.dao.GetStorageItem(stc.ScriptHash, key)
if si == nil {
si = &StorageItem{}
si = &entities.StorageItem{}
}
if si.IsConst {
return errors.New("storage item exists and is read-only")
}
si.Value = value
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.
@ -538,7 +539,7 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
if cs == nil {
return nil
}
err := deleteContractStateInStore(ic.mem, hash)
err := ic.dao.DeleteContractState(hash)
if err != nil {
return err
}
@ -548,7 +549,7 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
return err
}
for k := range siMap {
_ = deleteStorageItemInStore(ic.mem, hash, []byte(k))
_ = ic.dao.DeleteStorageItem(hash, []byte(k))
}
}
return nil
@ -557,11 +558,12 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
// contractGetStorageContext retrieves StorageContext of a contract.
func (ic *interopContext) contractGetStorageContext(v *vm.VM) error {
csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*ContractState)
cs, ok := csInterface.(*entities.ContractState)
if !ok {
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")
}
stc := &StorageContext{

View file

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

View file

@ -1,60 +1,10 @@
package core
import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/io"
"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.
type SpentCoinState struct {
txHash util.Uint256

View file

@ -3,15 +3,14 @@ package core
import (
"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/util"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeSpentCoinState(t *testing.T) {
spent := &SpentCoinState{
txHash: randomUint256(),
txHash: testutil.RandomUint256(),
txHeight: 1001,
items: map[uint16]uint32{
1: 3,
@ -29,24 +28,3 @@ func TestEncodeDecodeSpentCoinState(t *testing.T) {
assert.Nil(t, r.Err)
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 (
"math/rand"
@ -9,29 +9,29 @@ import (
)
// 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)
for i := range b {
b[i] = byte(randomInt(65, 90))
b[i] = byte(RandomInt(65, 90))
}
return string(b)
}
// RandomInt returns a random integer between min and max.
func randomInt(min, max int) int {
// RandomInt returns a random integer in [min,max).
func RandomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// RandomUint256 returns a random Uint256.
func randomUint256() util.Uint256 {
str := randomString(20)
func RandomUint256() util.Uint256 {
str := RandomString(20)
return hash.Sha256([]byte(str))
}
// RandomUint160 returns a random Uint160.
func randomUint160() util.Uint160 {
str := randomString(20)
func RandomUint160() util.Uint160 {
str := RandomString(20)
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
import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"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.
type UnspentCoinState struct {
states []CoinState
states []entities.CoinState
}
// NewUnspentCoinState returns a new unspent coin state with N confirmed states.
func NewUnspentCoinState(n int) *UnspentCoinState {
u := &UnspentCoinState{
states: make([]CoinState, n),
states: make([]entities.CoinState, n),
}
for i := 0; i < n; i++ {
u.states[i] = CoinStateConfirmed
u.states[i] = entities.CoinStateConfirmed
}
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.
func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
bw.WriteVarUint(uint64(len(s.states)))
@ -99,40 +32,10 @@ func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
// DecodeBinary decodes UnspentCoinState from the given BinReader.
func (s *UnspentCoinState) DecodeBinary(br *io.BinReader) {
lenStates := br.ReadVarUint()
s.states = make([]CoinState, lenStates)
s.states = make([]entities.CoinState, lenStates)
for i := 0; i < int(lenStates); i++ {
var state uint8
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 (
"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/stretchr/testify/assert"
)
func TestDecodeEncodeUnspentCoinState(t *testing.T) {
unspent := &UnspentCoinState{
states: []CoinState{
CoinStateConfirmed,
CoinStateSpent,
CoinStateSpent,
CoinStateSpent,
CoinStateConfirmed,
states: []entities.CoinState{
entities.CoinStateConfirmed,
entities.CoinStateSpent,
entities.CoinStateSpent,
entities.CoinStateSpent,
entities.CoinStateConfirmed,
},
}
@ -27,33 +27,3 @@ func TestDecodeEncodeUnspentCoinState(t *testing.T) {
unspentDecode.DecodeBinary(r)
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/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/transaction"
"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) {
panic("TODO")
}
func (chain testChain) GetContractState(hash util.Uint160) *core.ContractState {
func (chain testChain) GetContractState(hash util.Uint160) *entities.ContractState {
panic("TODO")
}
func (chain testChain) GetHeaderHash(int) util.Uint256 {
@ -72,10 +73,10 @@ func (chain testChain) GetHeader(hash util.Uint256) (*core.Header, error) {
panic("TODO")
}
func (chain testChain) GetAssetState(util.Uint256) *core.AssetState {
func (chain testChain) GetAssetState(util.Uint256) *entities.AssetState {
panic("TODO")
}
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState {
func (chain testChain) GetAccountState(util.Uint160) *entities.AccountState {
panic("TODO")
}
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) {
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")
}
func (chain testChain) GetTestVM() (*vm.VM, storage.Store) {
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")
}
func (chain testChain) CurrentHeaderHash() util.Uint256 {

View file

@ -11,7 +11,7 @@ import (
"sync"
"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/crypto/keys"
"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
// 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) {
var utxos core.UnspentBalances
var utxos entities.UnspentBalances
resp, err := c.GetUnspents(address)
if err != nil || resp.Error != nil {

View file

@ -6,7 +6,7 @@ import (
"net/http"
"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/rpc/wrappers"
"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
// 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 (
num, i uint16
selected = util.Fixed8(0)

View file

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

View file

@ -2,9 +2,9 @@ package wrappers
import (
"bytes"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"sort"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/util"
)
@ -33,7 +33,7 @@ type Balance struct {
}
// 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))
for k, v := range a.GetBalanceValues() {
balances = append(balances, Balance{

View file

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

View file

@ -2,13 +2,14 @@ package wrappers
import (
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/entities"
"github.com/CityOfZion/neo-go/pkg/util"
)
// UnspentBalanceInfo wrapper is used to represent single unspent asset entry
// in `getunspents` output.
type UnspentBalanceInfo struct {
Unspents []core.UnspentBalance `json:"unspent"`
Unspents []entities.UnspentBalance `json:"unspent"`
AssetHash util.Uint256 `json:"asset_hash"`
Asset string `json:"asset"`
AssetSymbol string `json:"asset_symbol"`
@ -28,7 +29,7 @@ var GlobalAssets = map[string]string{
}
// 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{
Address: addr,
Balance: make([]UnspentBalanceInfo, 0, len(a.Balances)),