From ec17654986f272429bd03cc5fe0879fbd11b2e49 Mon Sep 17 00:00:00 2001 From: Vsevolod Brekelov Date: Mon, 25 Nov 2019 20:39:11 +0300 Subject: [PATCH] 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 --- pkg/core/blockchain.go | 373 ++++++------ pkg/core/blockchain_state.go | 97 --- pkg/core/blockchain_state_test.go | 46 -- pkg/core/blockchain_test.go | 4 +- pkg/core/blockchainer.go | 13 +- pkg/core/dao.go | 555 ++++++++++++++++++ pkg/core/{ => entities}/account_state.go | 65 +- pkg/core/{ => entities}/account_state_test.go | 17 +- pkg/core/{ => entities}/asset_state.go | 26 +- pkg/core/{ => entities}/asset_state_test.go | 33 +- pkg/core/{ => entities}/coin_state.go | 2 +- pkg/core/{ => entities}/contract_state.go | 33 +- .../{ => entities}/contract_state_test.go | 27 +- pkg/core/{ => entities}/notification_event.go | 33 +- pkg/core/entities/storage_item.go | 23 + pkg/core/entities/storage_item_test.go | 2 + pkg/core/{ => entities}/validator_state.go | 77 +-- pkg/core/entities/validator_state_test.go | 64 ++ pkg/core/interop_neo.go | 47 +- pkg/core/interop_neo_test.go | 37 +- pkg/core/interop_system.go | 24 +- pkg/core/interops.go | 11 +- pkg/core/spent_coin_state.go | 50 -- pkg/core/spent_coin_state_test.go | 26 +- pkg/core/storage/helpers.go | 96 --- pkg/core/storage_item.go | 64 -- pkg/core/storage_item_test.go | 26 - .../random_util.go} | 18 +- pkg/core/uint32.go | 9 + pkg/core/unspent_coin_state.go | 109 +--- pkg/core/unspent_coint_state_test.go | 44 +- pkg/core/validator_state_test.go | 121 ---- pkg/network/helper_test.go | 11 +- pkg/rpc/client.go | 4 +- pkg/rpc/neoScanBalanceGetter.go | 4 +- pkg/rpc/neoScanTypes.go | 4 +- pkg/rpc/wrappers/account_state.go | 4 +- pkg/rpc/wrappers/asset_state.go | 4 +- pkg/rpc/wrappers/unspents.go | 13 +- 39 files changed, 958 insertions(+), 1258 deletions(-) delete mode 100644 pkg/core/blockchain_state.go delete mode 100644 pkg/core/blockchain_state_test.go create mode 100644 pkg/core/dao.go rename pkg/core/{ => entities}/account_state.go (62%) rename pkg/core/{ => entities}/account_state_test.go (82%) rename pkg/core/{ => entities}/asset_state.go (71%) rename pkg/core/{ => entities}/asset_state_test.go (50%) rename pkg/core/{ => entities}/coin_state.go (93%) rename pkg/core/{ => entities}/contract_state.go (67%) rename pkg/core/{ => entities}/contract_state_test.go (64%) rename pkg/core/{ => entities}/notification_event.go (58%) create mode 100644 pkg/core/entities/storage_item.go create mode 100644 pkg/core/entities/storage_item_test.go rename pkg/core/{ => entities}/validator_state.go (55%) create mode 100644 pkg/core/entities/validator_state_test.go delete mode 100644 pkg/core/storage/helpers.go delete mode 100644 pkg/core/storage_item.go delete mode 100644 pkg/core/storage_item_test.go rename pkg/core/{random_util_test.go => testutil/random_util.go} (63%) create mode 100644 pkg/core/uint32.go delete mode 100644 pkg/core/validator_state_test.go diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 51ec3eed8..1dc6dfb78 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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) -} - -// 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 +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) } - 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) -} - -// 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 +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) } - 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) } diff --git a/pkg/core/blockchain_state.go b/pkg/core/blockchain_state.go deleted file mode 100644 index 4ce0e0d88..000000000 --- a/pkg/core/blockchain_state.go +++ /dev/null @@ -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()) -} diff --git a/pkg/core/blockchain_state_test.go b/pkg/core/blockchain_state_test.go deleted file mode 100644 index 3a625c784..000000000 --- a/pkg/core/blockchain_state_test.go +++ /dev/null @@ -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) -} diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index b3f997d48..f539b865b 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -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) diff --git a/pkg/core/blockchainer.go b/pkg/core/blockchainer.go index a65d69847..9e87ff209 100644 --- a/pkg/core/blockchainer.go +++ b/pkg/core/blockchainer.go @@ -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 - GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) + 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 diff --git a/pkg/core/dao.go b/pkg/core/dao.go new file mode 100644 index 000000000..bafe70b73 --- /dev/null +++ b/pkg/core/dao.go @@ -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 +} diff --git a/pkg/core/account_state.go b/pkg/core/entities/account_state.go similarity index 62% rename from pkg/core/account_state.go rename to pkg/core/entities/account_state.go index cd79fe189..f06b94ae3 100644 --- a/pkg/core/account_state.go +++ b/pkg/core/entities/account_state.go @@ -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 { diff --git a/pkg/core/account_state_test.go b/pkg/core/entities/account_state_test.go similarity index 82% rename from pkg/core/account_state_test.go rename to pkg/core/entities/account_state_test.go index 6517a1679..89d8d3026 100644 --- a/pkg/core/account_state_test.go +++ b/pkg/core/entities/account_state_test.go @@ -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++ { diff --git a/pkg/core/asset_state.go b/pkg/core/entities/asset_state.go similarity index 71% rename from pkg/core/asset_state.go rename to pkg/core/entities/asset_state.go index 6af69172c..c81d3b5e3 100644 --- a/pkg/core/asset_state.go +++ b/pkg/core/entities/asset_state.go @@ -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 diff --git a/pkg/core/asset_state_test.go b/pkg/core/entities/asset_state_test.go similarity index 50% rename from pkg/core/asset_state_test.go rename to pkg/core/entities/asset_state_test.go index ac9d1ef7b..084f1cd3c 100644 --- a/pkg/core/asset_state_test.go +++ b/pkg/core/entities/asset_state_test.go @@ -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) -} diff --git a/pkg/core/coin_state.go b/pkg/core/entities/coin_state.go similarity index 93% rename from pkg/core/coin_state.go rename to pkg/core/entities/coin_state.go index 8232977f3..cefc25de7 100644 --- a/pkg/core/coin_state.go +++ b/pkg/core/entities/coin_state.go @@ -1,4 +1,4 @@ -package core +package entities // CoinState represents the state of a coin. type CoinState uint8 diff --git a/pkg/core/contract_state.go b/pkg/core/entities/contract_state.go similarity index 67% rename from pkg/core/contract_state.go rename to pkg/core/entities/contract_state.go index 5af26e1a8..812d7766d 100644 --- a/pkg/core/contract_state.go +++ b/pkg/core/entities/contract_state.go @@ -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{}) { diff --git a/pkg/core/contract_state_test.go b/pkg/core/entities/contract_state_test.go similarity index 64% rename from pkg/core/contract_state_test.go rename to pkg/core/entities/contract_state_test.go index 9ab2e8233..ba9e5f44a 100644 --- a/pkg/core/contract_state_test.go +++ b/pkg/core/entities/contract_state_test.go @@ -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) -} diff --git a/pkg/core/notification_event.go b/pkg/core/entities/notification_event.go similarity index 58% rename from pkg/core/notification_event.go rename to pkg/core/entities/notification_event.go index ee0086cc3..cf69815b2 100644 --- a/pkg/core/notification_event.go +++ b/pkg/core/entities/notification_event.go @@ -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[:]) diff --git a/pkg/core/entities/storage_item.go b/pkg/core/entities/storage_item.go new file mode 100644 index 000000000..2e5965616 --- /dev/null +++ b/pkg/core/entities/storage_item.go @@ -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) +} diff --git a/pkg/core/entities/storage_item_test.go b/pkg/core/entities/storage_item_test.go new file mode 100644 index 000000000..870b5d166 --- /dev/null +++ b/pkg/core/entities/storage_item_test.go @@ -0,0 +1,2 @@ +package entities + diff --git a/pkg/core/validator_state.go b/pkg/core/entities/validator_state.go similarity index 55% rename from pkg/core/validator_state.go rename to pkg/core/entities/validator_state.go index 36fe3a996..48e6601de 100644 --- a/pkg/core/validator_state.go +++ b/pkg/core/entities/validator_state.go @@ -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 diff --git a/pkg/core/entities/validator_state_test.go b/pkg/core/entities/validator_state_test.go new file mode 100644 index 000000000..521cf0827 --- /dev/null +++ b/pkg/core/entities/validator_state_test.go @@ -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()) +} diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 17c100e1c..632181384 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -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") } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 1fee8ae98..a20d38064 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -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, }) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 8de8aee8f..ce104bb09 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -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{ diff --git a/pkg/core/interops.go b/pkg/core/interops.go index deafb6e8b..53691f5bf 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -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. diff --git a/pkg/core/spent_coin_state.go b/pkg/core/spent_coin_state.go index f298ff9f5..67332f766 100644 --- a/pkg/core/spent_coin_state.go +++ b/pkg/core/spent_coin_state.go @@ -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 diff --git a/pkg/core/spent_coin_state_test.go b/pkg/core/spent_coin_state_test.go index 0aa1c2fad..0e3b65474 100644 --- a/pkg/core/spent_coin_state_test.go +++ b/pkg/core/spent_coin_state_test.go @@ -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)) -} diff --git a/pkg/core/storage/helpers.go b/pkg/core/storage/helpers.go deleted file mode 100644 index 575cbe84e..000000000 --- a/pkg/core/storage/helpers.go +++ /dev/null @@ -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 -} diff --git a/pkg/core/storage_item.go b/pkg/core/storage_item.go deleted file mode 100644 index b61b66a2d..000000000 --- a/pkg/core/storage_item.go +++ /dev/null @@ -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) -} diff --git a/pkg/core/storage_item_test.go b/pkg/core/storage_item_test.go deleted file mode 100644 index 41bca1a94..000000000 --- a/pkg/core/storage_item_test.go +++ /dev/null @@ -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) -} diff --git a/pkg/core/random_util_test.go b/pkg/core/testutil/random_util.go similarity index 63% rename from pkg/core/random_util_test.go rename to pkg/core/testutil/random_util.go index dc2b6150d..c58832e89 100644 --- a/pkg/core/random_util_test.go +++ b/pkg/core/testutil/random_util.go @@ -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)) } diff --git a/pkg/core/uint32.go b/pkg/core/uint32.go new file mode 100644 index 000000000..27e27fb94 --- /dev/null +++ b/pkg/core/uint32.go @@ -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] } + diff --git a/pkg/core/unspent_coin_state.go b/pkg/core/unspent_coin_state.go index 2dffd98df..3823a8879 100644 --- a/pkg/core/unspent_coin_state.go +++ b/pkg/core/unspent_coin_state.go @@ -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 -} diff --git a/pkg/core/unspent_coint_state_test.go b/pkg/core/unspent_coint_state_test.go index 5a475b605..18627feab 100644 --- a/pkg/core/unspent_coint_state_test.go +++ b/pkg/core/unspent_coint_state_test.go @@ -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)) -} diff --git a/pkg/core/validator_state_test.go b/pkg/core/validator_state_test.go deleted file mode 100644 index 617e3ff50..000000000 --- a/pkg/core/validator_state_test.go +++ /dev/null @@ -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, - } -} diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 14daf897e..afbcc921e 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -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 { diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index f15f5c9b8..054861b64 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -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 { diff --git a/pkg/rpc/neoScanBalanceGetter.go b/pkg/rpc/neoScanBalanceGetter.go index 257bda0cb..709cb64e5 100644 --- a/pkg/rpc/neoScanBalanceGetter.go +++ b/pkg/rpc/neoScanBalanceGetter.go @@ -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) diff --git a/pkg/rpc/neoScanTypes.go b/pkg/rpc/neoScanTypes.go index bafd0b648..7e050348e 100644 --- a/pkg/rpc/neoScanTypes.go +++ b/pkg/rpc/neoScanTypes.go @@ -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 } diff --git a/pkg/rpc/wrappers/account_state.go b/pkg/rpc/wrappers/account_state.go index dcb6530f8..56f5d8569 100644 --- a/pkg/rpc/wrappers/account_state.go +++ b/pkg/rpc/wrappers/account_state.go @@ -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{ diff --git a/pkg/rpc/wrappers/asset_state.go b/pkg/rpc/wrappers/asset_state.go index c439af8d9..e0f3e472c 100644 --- a/pkg/rpc/wrappers/asset_state.go +++ b/pkg/rpc/wrappers/asset_state.go @@ -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, diff --git a/pkg/rpc/wrappers/unspents.go b/pkg/rpc/wrappers/unspents.go index 6355b7d0c..ef10fb279 100644 --- a/pkg/rpc/wrappers/unspents.go +++ b/pkg/rpc/wrappers/unspents.go @@ -2,17 +2,18 @@ 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"` - AssetHash util.Uint256 `json:"asset_hash"` - Asset string `json:"asset"` - AssetSymbol string `json:"asset_symbol"` - Amount util.Fixed8 `json:"amount"` + Unspents []entities.UnspentBalance `json:"unspent"` + AssetHash util.Uint256 `json:"asset_hash"` + Asset string `json:"asset"` + AssetSymbol string `json:"asset_symbol"` + Amount util.Fixed8 `json:"amount"` } // Unspents wrapper is used to represent getunspents return result. @@ -28,7 +29,7 @@ var GlobalAssets = map[string]string{ } // NewUnspents creates a new AccountState wrapper using given Blockchainer. -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)),