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)),