diff --git a/pkg/core/asset_state_test.go b/pkg/core/asset_state_test.go deleted file mode 100644 index ac9d1ef7b..000000000 --- a/pkg/core/asset_state_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package core - -import ( - "testing" - - "github.com/CityOfZion/neo-go/pkg/core/storage" - "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/io" - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/stretchr/testify/assert" -) - -func TestEncodeDecodeAssetState(t *testing.T) { - asset := &AssetState{ - ID: randomUint256(), - AssetType: transaction.Token, - Name: "super cool token", - Amount: util.Fixed8(1000000), - Available: util.Fixed8(100), - Precision: 0, - FeeMode: feeMode, - Admin: randomUint160(), - Issuer: randomUint160(), - Expiration: 10, - IsFrozen: false, - } - - buf := io.NewBufBinWriter() - asset.EncodeBinary(buf.BinWriter) - assert.Nil(t, buf.Err) - assetDecode := &AssetState{} - r := io.NewBinReaderFromBuf(buf.Bytes()) - assetDecode.DecodeBinary(r) - 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/blockchain.go b/pkg/core/blockchain.go index 51ec3eed8..8d43ff553 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/state" "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" @@ -26,7 +27,7 @@ import ( // Tuning parameters. const ( headerBatchCount = 2000 - version = "0.0.2" + version = "0.0.3" // This one comes from C# code and it's different from the constant used // when creating an asset with Neo.Asset.Create interop call. It looks @@ -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] = state.CoinSpent + 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,19 +391,11 @@ 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 len(account.Votes) > 0 { - for _, vote := range account.Votes { - validator, err := chainState.validators.getAndUpdate(chainState.store, vote) - if err != nil { - return err - } - validator.Votes -= prevTXOutput.Amount - if !validator.RegisteredAndHasVotes() { - delete(chainState.validators, vote) - } - } + if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil { + return err + } + if err = processTXWithValidatorsSubtract(account, cache, prevTXOutput.Amount); err != nil { + return err } } @@ -419,13 +415,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(&state.Asset{ ID: tx.Hash(), AssetType: t.AssetType, Name: t.Name, @@ -434,45 +433,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 +484,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { if t.NeedStorage { properties |= smartcontract.HasStorage } - contract := &ContractState{ + contract := &state.Contract{ Script: t.Script, ParamList: t.ParamList, ReturnType: t.ReturnType, @@ -491,15 +495,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 +540,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { "err": err, }).Warn("contract invocation failed") } - aer := &AppExecResult{ + aer := &state.AppExecResult{ TxHash: tx.Hash(), Trigger: trigger.Application, VMState: v.State(), @@ -542,17 +548,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 +567,70 @@ 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], state.UnspentBalance{ Tx: tx.Hash(), Index: uint16(index), Value: output.Amount, }) - if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { - for _, vote := range account.Votes { - validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote) - if err != nil { - return err - } - validatorState.Votes += output.Amount + if err = dao.PutAccountState(account); err != nil { + return err + } + if err = processTXWithValidatorsAdd(&output, account, dao); err != nil { + return err + } + } + return nil +} + +func processTXWithValidatorsAdd(output *transaction.Output, account *state.Account, dao *dao) error { + if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { + for _, vote := range account.Votes { + 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 processTXWithValidatorsSubtract(account *state.Account, dao *dao, toSubtract util.Fixed8) error { + for _, vote := range account.Votes { + validator, err := dao.GetValidatorStateOrNew(vote) + if err != nil { + return err + } + validator.Votes -= toSubtract + if !validator.RegisteredAndHasVotes() { + if err := dao.DeleteValidatorState(validator); err != nil { + return err + } + } else { + if err := dao.PutValidatorState(validator); err != nil { + return err + } + } + } + return nil +} + +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,31 +640,25 @@ 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 } if descriptor.Field == "Votes" { balance := account.GetBalanceValues()[governingTokenTX().Hash()] - for _, vote := range account.Votes { - validator, err := state.validators.getAndUpdate(state.store, vote) - if err != nil { - return err - } - validator.Votes -= balance - if !validator.RegisteredAndHasVotes() { - delete(state.validators, vote) - } + if err = processTXWithValidatorsSubtract(account, dao, balance); err != nil { + return err } votes := keys.PublicKeys{} @@ -637,10 +669,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 +690,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 +734,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) *state.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]*state.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 +766,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 +778,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 +829,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) *state.Asset { + 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) *state.Contract { + 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) *state.Account { + as, err := bc.dao.GetAccountState(scriptHash) if as == nil && err != storage.ErrKeyNotFound { log.Warnf("failed to get account state: %s", err) } @@ -924,7 +857,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 +962,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,25 +1156,25 @@ 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], state.UnspentBalance{ Tx: tx.Hash(), Index: uint16(index), Value: output.Amount, }) - if output.AssetID.Equals(governingTokenTX().Hash()) && len(accountState.Votes) > 0 { - for _, vote := range accountState.Votes { - validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote) - if err != nil { - return nil, err - } - validatorState.Votes += output.Amount - } + if err := cache.PutAccountState(accountState); err != nil { + return nil, err + } + if err = processTXWithValidatorsAdd(&output, accountState, cache); err != nil { + return nil, err } } @@ -1253,53 +1186,45 @@ 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 } // process account state votes: if there are any -> validators will be updated. - if prevOutput.AssetID.Equals(governingTokenTX().Hash()) { - if len(accountState.Votes) > 0 { - for _, vote := range accountState.Votes { - validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote) - if err != nil { - return nil, err - } - validatorState.Votes -= prevOutput.Amount - if !validatorState.Registered && validatorState.Votes.Equal(util.Fixed8(0)) { - delete(chainState.validators, vote) - } - } - } + if err = processTXWithValidatorsSubtract(accountState, cache, prevOutput.Amount); 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 := state.GetValidatorsWeightedAverage(validators) standByValidators, err := bc.GetStandByValidators() if err != nil { return nil, err @@ -1324,18 +1249,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 +1272,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 +1353,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 +1424,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 +1444,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..9d42d0af6 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/state" "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) *state.Contract 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) *state.Asset + GetAccountState(util.Uint160) *state.Account + 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) *state.StorageItem + GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetTestVM() (*vm.VM, storage.Store) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetUnspentCoinState(util.Uint256) *UnspentCoinState diff --git a/pkg/core/coin_state.go b/pkg/core/coin_state.go deleted file mode 100644 index 8232977f3..000000000 --- a/pkg/core/coin_state.go +++ /dev/null @@ -1,12 +0,0 @@ -package core - -// CoinState represents the state of a coin. -type CoinState uint8 - -// Viable CoinState constants. -const ( - CoinStateConfirmed CoinState = 0 - CoinStateSpent CoinState = 1 << 1 - CoinStateClaimed CoinState = 1 << 2 - CoinStateFrozen CoinState = 1 << 5 -) diff --git a/pkg/core/dao.go b/pkg/core/dao.go new file mode 100644 index 000000000..76a27ab3a --- /dev/null +++ b/pkg/core/dao.go @@ -0,0 +1,554 @@ +package core + +import ( + "bytes" + "encoding/binary" + "fmt" + "sort" + + "github.com/CityOfZion/neo-go/pkg/core/state" + "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 Account 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) (*state.Account, error) { + account, err := dao.GetAccountState(hash) + if err != nil { + if err != storage.ErrKeyNotFound { + return nil, err + } + account = state.NewAccount(hash) + if err = dao.PutAccountState(account); err != nil { + return nil, err + } + } + return account, nil +} + +// GetAccountState returns Account from the given Store if it's +// present there. Returns nil otherwise. +func (dao *dao) GetAccountState(hash util.Uint160) (*state.Account, error) { + account := &state.Account{} + key := storage.AppendPrefix(storage.STAccount, hash.BytesBE()) + err := dao.GetAndDecode(account, key) + if err != nil { + return nil, err + } + return account, err +} + +func (dao *dao) PutAccountState(as *state.Account) 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) (*state.Asset, error) { + asset := &state.Asset{} + 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 *state.Asset) 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) (*state.Contract, error) { + contract := &state.Contract{} + 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 *state.Contract) 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: []state.Coin{}, + } + 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) (*state.Validator, error) { + validatorState, err := dao.GetValidatorState(publicKey) + if err != nil { + if err != storage.ErrKeyNotFound { + return nil, err + } + validatorState = &state.Validator{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() []*state.Validator { + var validators []*state.Validator + dao.store.Seek(storage.STValidator.Bytes(), func(k, v []byte) { + r := io.NewBinReaderFromBuf(v) + validator := &state.Validator{} + 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) (*state.Validator, error) { + validatorState := &state.Validator{} + key := storage.AppendPrefix(storage.STValidator, publicKey.Bytes()) + err := dao.GetAndDecode(validatorState, key) + if err != nil { + return nil, err + } + return validatorState, nil +} + +// PutValidatorState puts given Validator into the given store. +func (dao *dao) PutValidatorState(vs *state.Validator) error { + key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes()) + return dao.Put(vs, key) +} + +// DeleteValidatorState deletes given Validator into the given store. +func (dao *dao) DeleteValidatorState(vs *state.Validator) 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) (*state.AppExecResult, error) { + aer := &state.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 *state.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) *state.StorageItem { + b, err := dao.store.Get(makeStorageItemKey(scripthash, key)) + if err != nil { + return nil + } + r := io.NewBinReaderFromBuf(b) + + si := &state.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 *state.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]*state.StorageItem, error) { + var siMap = make(map[string]*state.StorageItem) + var err error + + saveToMap := func(k, v []byte) { + if err != nil { + return + } + r := io.NewBinReaderFromBuf(v) + si := &state.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] == state.CoinSpent { + return true + } + } + } + return false +} diff --git a/pkg/core/dao_test.go b/pkg/core/dao_test.go new file mode 100644 index 000000000..5aa44fe9f --- /dev/null +++ b/pkg/core/dao_test.go @@ -0,0 +1,339 @@ +package core + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/core/state" + "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/internal/random" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/smartcontract" + "github.com/CityOfZion/neo-go/pkg/vm/opcode" + "github.com/stretchr/testify/require" +) + +func TestPutGetAndDecode(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + serializable := &TestSerializable{field: random.String(4)} + hash := []byte{1} + err := dao.Put(serializable, hash) + require.NoError(t, err) + + gotAndDecoded := &TestSerializable{} + err = dao.GetAndDecode(gotAndDecoded, hash) + require.NoError(t, err) +} + +// TestSerializable structure used in testing. +type TestSerializable struct { + field string +} + +func (t *TestSerializable) EncodeBinary(writer *io.BinWriter) { + writer.WriteString(t.field) +} + +func (t *TestSerializable) DecodeBinary(reader *io.BinReader) { + t.field = reader.ReadString() +} + +func TestGetAccountStateOrNew_New(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint160() + createdAccount, err := dao.GetAccountStateOrNew(hash) + require.NoError(t, err) + require.NotNil(t, createdAccount) + gotAccount, err := dao.GetAccountState(hash) + require.NoError(t, err) + require.Equal(t, createdAccount, gotAccount) +} + +func TestPutAndGetAccountStateOrNew(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint160() + accountState := &state.Account{ScriptHash: hash} + err := dao.PutAccountState(accountState) + require.NoError(t, err) + gotAccount, err := dao.GetAccountStateOrNew(hash) + require.NoError(t, err) + require.Equal(t, accountState.ScriptHash, gotAccount.ScriptHash) +} + +func TestPutAndGetAssetState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + id := random.Uint256() + assetState := &state.Asset{ID: id, Owner: keys.PublicKey{}} + err := dao.PutAssetState(assetState) + require.NoError(t, err) + gotAssetState, err := dao.GetAssetState(id) + require.NoError(t, err) + require.Equal(t, assetState, gotAssetState) +} + +func TestPutAndGetContractState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + contractState := &state.Contract{Script: []byte{}, ParamList:[]smartcontract.ParamType{}} + hash := contractState.ScriptHash() + err := dao.PutContractState(contractState) + require.NoError(t, err) + gotContractState, err := dao.GetContractState(hash) + require.NoError(t, err) + require.Equal(t, contractState, gotContractState) +} + +func TestDeleteContractState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + contractState := &state.Contract{Script: []byte{}, ParamList:[]smartcontract.ParamType{}} + hash := contractState.ScriptHash() + err := dao.PutContractState(contractState) + require.NoError(t, err) + err = dao.DeleteContractState(hash) + require.NoError(t, err) + gotContractState, err := dao.GetContractState(hash) + require.Error(t, err) + require.Nil(t, gotContractState) +} + +func TestGetUnspentCoinStateOrNew_New(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + unspentCoinState, err := dao.GetUnspentCoinStateOrNew(hash) + require.NoError(t, err) + require.NotNil(t, unspentCoinState) + gotUnspentCoinState, err := dao.GetUnspentCoinState(hash) + require.NoError(t, err) + require.Equal(t, unspentCoinState, gotUnspentCoinState) +} + +func TestGetUnspentCoinState_Err(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + gotUnspentCoinState, err := dao.GetUnspentCoinState(hash) + require.Error(t, err) + require.Nil(t, gotUnspentCoinState) +} + +func TestPutGetUnspentCoinState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + unspentCoinState := &UnspentCoinState{states:[]state.Coin{}} + err := dao.PutUnspentCoinState(hash, unspentCoinState) + require.NoError(t, err) + gotUnspentCoinState, err := dao.GetUnspentCoinState(hash) + require.NoError(t, err) + require.Equal(t, unspentCoinState, gotUnspentCoinState) +} + +func TestGetSpentCoinStateOrNew_New(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + spentCoinState, err := dao.GetSpentCoinsOrNew(hash) + require.NoError(t, err) + require.NotNil(t, spentCoinState) + gotSpentCoinState, err := dao.GetSpentCoinState(hash) + require.NoError(t, err) + require.Equal(t, spentCoinState, gotSpentCoinState) +} + +func TestPutAndGetSpentCoinState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + spentCoinState := &SpentCoinState{items:make(map[uint16]uint32)} + err := dao.PutSpentCoinState(hash, spentCoinState) + require.NoError(t, err) + gotSpentCoinState, err := dao.GetSpentCoinState(hash) + require.NoError(t, err) + require.Equal(t, spentCoinState, gotSpentCoinState) +} + +func TestGetSpentCoinState_Err(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + spentCoinState, err := dao.GetSpentCoinState(hash) + require.Error(t, err) + require.Nil(t, spentCoinState) +} + +func TestDeleteSpentCoinState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + spentCoinState := &SpentCoinState{items:make(map[uint16]uint32)} + err := dao.PutSpentCoinState(hash, spentCoinState) + require.NoError(t, err) + err = dao.DeleteSpentCoinState(hash) + require.NoError(t, err) + gotSpentCoinState, err := dao.GetSpentCoinState(hash) + require.Error(t, err) + require.Nil(t, gotSpentCoinState) +} + +func TestGetValidatorStateOrNew_New(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + publicKey := &keys.PublicKey{} + validatorState, err := dao.GetValidatorStateOrNew(publicKey) + require.NoError(t, err) + require.NotNil(t, validatorState) + gotValidatorState, err := dao.GetValidatorState(publicKey) + require.NoError(t, err) + require.Equal(t, validatorState, gotValidatorState) +} + +func TestPutGetValidatorState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + publicKey := &keys.PublicKey{} + validatorState := &state.Validator{ + PublicKey: publicKey, + Registered: false, + Votes: 0, + } + err := dao.PutValidatorState(validatorState) + require.NoError(t, err) + gotValidatorState, err := dao.GetValidatorState(publicKey) + require.NoError(t, err) + require.Equal(t, validatorState, gotValidatorState) +} + +func TestDeleteValidatorState(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + publicKey := &keys.PublicKey{} + validatorState := &state.Validator{ + PublicKey: publicKey, + Registered: false, + Votes: 0, + } + err := dao.PutValidatorState(validatorState) + require.NoError(t, err) + err = dao.DeleteValidatorState(validatorState) + require.NoError(t, err) + gotValidatorState, err := dao.GetValidatorState(publicKey) + require.Error(t, err) + require.Nil(t, gotValidatorState) +} + +func TestGetValidators(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + publicKey := &keys.PublicKey{} + validatorState := &state.Validator{ + PublicKey: publicKey, + Registered: false, + Votes: 0, + } + err := dao.PutValidatorState(validatorState) + require.NoError(t, err) + validators := dao.GetValidators() + require.Equal(t, validatorState, validators[0]) + require.Len(t, validators, 1) +} + +func TestPutGetAppExecResult(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + appExecResult := &state.AppExecResult{TxHash: hash, Events:[]state.NotificationEvent{}} + err := dao.PutAppExecResult(appExecResult) + require.NoError(t, err) + gotAppExecResult, err := dao.GetAppExecResult(hash) + require.NoError(t, err) + require.Equal(t, appExecResult, gotAppExecResult) +} + +func TestPutGetStorageItem(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint160() + key := []byte{0} + storageItem := &state.StorageItem{Value: []uint8{}} + err := dao.PutStorageItem(hash, key, storageItem) + require.NoError(t, err) + gotStorageItem := dao.GetStorageItem(hash, key) + require.Equal(t, storageItem, gotStorageItem) +} + +func TestDeleteStorageItem(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint160() + key := []byte{0} + storageItem := &state.StorageItem{Value: []uint8{}} + err := dao.PutStorageItem(hash, key, storageItem) + require.NoError(t, err) + err = dao.DeleteStorageItem(hash, key) + require.NoError(t, err) + gotStorageItem := dao.GetStorageItem(hash, key) + require.Nil(t, gotStorageItem) +} + +func TestGetBlock_NotExists(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + hash := random.Uint256() + block, err := dao.GetBlock(hash) + require.Error(t, err) + require.Nil(t, block) +} + +func TestPutGetBlock(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + block := &Block{ + BlockBase: BlockBase{ + Script: transaction.Witness{ + VerificationScript: []byte{byte(opcode.PUSH1)}, + InvocationScript: []byte{byte(opcode.NOP)}, + }, + }, + } + hash := block.Hash() + err := dao.StoreAsBlock(block, 0) + require.NoError(t, err) + gotBlock, err := dao.GetBlock(hash) + require.NoError(t, err) + require.NotNil(t, gotBlock) +} + +func TestGetVersion_NoVersion(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + version, err := dao.GetVersion() + require.Error(t, err) + require.Equal(t, "", version) +} + +func TestGetVersion(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + err := dao.PutVersion("testVersion") + require.NoError(t, err) + version, err := dao.GetVersion() + require.NoError(t, err) + require.NotNil(t, version) +} + +func TestGetCurrentHeaderHeight_NoHeader(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + height, err := dao.GetCurrentBlockHeight() + require.Error(t, err) + require.Equal(t, uint32(0), height) +} + +func TestGetCurrentHeaderHeight_Store(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + block := &Block{ + BlockBase: BlockBase{ + Script: transaction.Witness{ + VerificationScript: []byte{byte(opcode.PUSH1)}, + InvocationScript: []byte{byte(opcode.NOP)}, + }, + }, + } + err := dao.StoreAsCurrentBlock(block) + require.NoError(t, err) + height, err := dao.GetCurrentBlockHeight() + require.NoError(t, err) + require.Equal(t, uint32(0), height) +} + +func TestStoreAsTransaction(t *testing.T) { + dao := &dao{store: storage.NewMemCachedStore(storage.NewMemoryStore())} + tx := &transaction.Transaction{} + hash := tx.Hash() + err := dao.StoreAsTransaction(tx, 0) + require.NoError(t, err) + hasTransaction := dao.HasTransaction(hash) + require.True(t, hasTransaction) +} diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 17c100e1c..31576d95a 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/state" "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 = state.NewAccount(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.(*state.Account) 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.(*state.Account) 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.(*state.Account) if !ok { return fmt.Errorf("%T is not an account state", acc) } @@ -427,9 +428,9 @@ 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 +// evaluation stack, does a lot of checks and returns Contract if it // succeeds. -func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*ContractState, error) { +func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*state.Contract, 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 := &state.Contract{ 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.(*state.Contract) 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.(*state.Contract) 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 := &state.Asset{ 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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.(*state.Asset) 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..ffe48a92f 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -4,9 +4,11 @@ import ( "math/big" "testing" + "github.com/CityOfZion/neo-go/pkg/core/state" "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/internal/random" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/smartcontract/trigger" "github.com/CityOfZion/neo-go/pkg/util" @@ -321,7 +323,7 @@ func TestAssetGetPrecision(t *testing.T) { require.Equal(t, big.NewInt(int64(assetState.Precision)), precision) } -// Helper functions to create VM, InteropContext, TX, AccountState, ContractState, AssetState. +// Helper functions to create VM, InteropContext, TX, Account, Contract, Asset. func createVMAndPushBlock(t *testing.T) (*vm.VM, *Block, *interopContext) { v := vm.New() @@ -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, *state.Asset, *interopContext) { v := vm.New() - assetState := &AssetState{ + assetState := &state.Asset{ 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: random.Uint160(), Owner: keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)}, - Admin: randomUint160(), - Issuer: randomUint160(), + Admin: random.Uint160(), + Issuer: random.Uint160(), 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, *state.Contract, *interopContext) { v := vm.New() - contractState := &ContractState{ + contractState := &state.Contract{ 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: random.String(10), + CodeVersion: random.String(10), + Author: random.String(10), + Email: random.String(10), + Description: random.String(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, *state.Account, *interopContext) { v := vm.New() rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302" hash, err := util.Uint160DecodeStringBE(rawHash) - accountState := NewAccountState(hash) + accountState := state.NewAccount(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: random.Uint256(), PrevIndex: 1, }) outputs := append(tx.Outputs, transaction.Output{ - AssetID: randomUint256(), + AssetID: random.Uint256(), Amount: 10, - ScriptHash: randomUint160(), + ScriptHash: random.Uint160(), Position: 1, }) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 8de8aee8f..a399e67e7 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/state" "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 := state.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 = &state.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.(*state.Contract) 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..33fac36c6 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/state" "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 []state.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([]state.NotificationEvent, 0) + return &interopContext{bc, trigger, block, tx, dao, nes} } // All lists are sorted, keep 'em this way, please. diff --git a/pkg/core/random_util_test.go b/pkg/core/random_util_test.go deleted file mode 100644 index dc2b6150d..000000000 --- a/pkg/core/random_util_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package core - -import ( - "math/rand" - "time" - - "github.com/CityOfZion/neo-go/pkg/crypto/hash" - "github.com/CityOfZion/neo-go/pkg/util" -) - -// RandomString returns a random string with the n as its length. -func randomString(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = byte(randomInt(65, 90)) - } - - return string(b) -} - -// RandomInt returns a random integer between min and 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) - return hash.Sha256([]byte(str)) -} - -// RandomUint160 returns a random Uint160. -func randomUint160() util.Uint160 { - str := randomString(20) - return hash.RipeMD160([]byte(str)) -} - -func init() { - rand.Seed(time.Now().UTC().UnixNano()) -} 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..ec8176130 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/internal/random" "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: random.Uint256(), 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/account_state.go b/pkg/core/state/account.go similarity index 51% rename from pkg/core/account_state.go rename to pkg/core/state/account.go index cd79fe189..e72fdc875 100644 --- a/pkg/core/account_state.go +++ b/pkg/core/state/account.go @@ -1,74 +1,11 @@ -package core +package state 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 { @@ -80,8 +17,8 @@ type UnspentBalance struct { // UnspentBalances is a slice of UnspentBalance (mostly needed to sort them). type UnspentBalances []UnspentBalance -// AccountState represents the state of a NEO account. -type AccountState struct { +// Account represents the state of a NEO account. +type Account struct { Version uint8 ScriptHash util.Uint160 IsFrozen bool @@ -89,9 +26,9 @@ type AccountState struct { Balances map[util.Uint256][]UnspentBalance } -// NewAccountState returns a new AccountState object. -func NewAccountState(scriptHash util.Uint160) *AccountState { - return &AccountState{ +// NewAccount returns a new Account object. +func NewAccount(scriptHash util.Uint160) *Account { + return &Account{ Version: 0, ScriptHash: scriptHash, IsFrozen: false, @@ -100,8 +37,8 @@ func NewAccountState(scriptHash util.Uint160) *AccountState { } } -// DecodeBinary decodes AccountState from the given BinReader. -func (s *AccountState) DecodeBinary(br *io.BinReader) { +// DecodeBinary decodes Account from the given BinReader. +func (s *Account) DecodeBinary(br *io.BinReader) { br.ReadLE(&s.Version) br.ReadBytes(s.ScriptHash[:]) br.ReadLE(&s.IsFrozen) @@ -118,8 +55,8 @@ func (s *AccountState) DecodeBinary(br *io.BinReader) { } } -// EncodeBinary encodes AccountState to the given BinWriter. -func (s *AccountState) EncodeBinary(bw *io.BinWriter) { +// EncodeBinary encodes Account to the given BinWriter. +func (s *Account) EncodeBinary(bw *io.BinWriter) { bw.WriteLE(s.Version) bw.WriteBytes(s.ScriptHash[:]) bw.WriteLE(s.IsFrozen) @@ -148,7 +85,7 @@ func (u *UnspentBalance) EncodeBinary(w *io.BinWriter) { // GetBalanceValues sums all unspent outputs and returns a map of asset IDs to // overall balances. -func (s *AccountState) GetBalanceValues() map[util.Uint256]util.Fixed8 { +func (s *Account) GetBalanceValues() map[util.Uint256]util.Fixed8 { res := make(map[util.Uint256]util.Fixed8) for k, v := range s.Balances { balance := util.Fixed8(0) diff --git a/pkg/core/account_state_test.go b/pkg/core/state/account_test.go similarity index 78% rename from pkg/core/account_state_test.go rename to pkg/core/state/account_test.go index 6517a1679..3981222f7 100644 --- a/pkg/core/account_state_test.go +++ b/pkg/core/state/account_test.go @@ -1,9 +1,10 @@ -package core +package state import ( "testing" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/internal/random" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" @@ -16,12 +17,12 @@ func TestDecodeEncodeAccountState(t *testing.T) { votes = make([]*keys.PublicKey, n) ) for i := 0; i < n; i++ { - asset := randomUint256() + asset := random.Uint256() 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: random.Uint256(), + Index: uint16(random.Int(0, 65535)), + Value: util.Fixed8(int64(random.Int(1, 10000))), }) } k, err := keys.NewPrivateKey() @@ -29,9 +30,9 @@ func TestDecodeEncodeAccountState(t *testing.T) { votes[i] = k.PublicKey() } - a := &AccountState{ + a := &Account{ Version: 0, - ScriptHash: randomUint160(), + ScriptHash: random.Uint160(), IsFrozen: true, Votes: votes, Balances: balances, @@ -41,7 +42,7 @@ func TestDecodeEncodeAccountState(t *testing.T) { a.EncodeBinary(buf.BinWriter) assert.Nil(t, buf.Err) - aDecode := &AccountState{} + aDecode := &Account{} r := io.NewBinReaderFromBuf(buf.Bytes()) aDecode.DecodeBinary(r) assert.Nil(t, r.Err) @@ -57,9 +58,9 @@ func TestDecodeEncodeAccountState(t *testing.T) { } func TestAccountStateBalanceValues(t *testing.T) { - asset1 := randomUint256() - asset2 := randomUint256() - as := AccountState{Balances: make(map[util.Uint256][]UnspentBalance)} + asset1 := random.Uint256() + asset2 := random.Uint256() + as := Account{Balances: make(map[util.Uint256][]UnspentBalance)} ref := 0 for i := 0; i < 10; i++ { ref += i diff --git a/pkg/core/asset_state.go b/pkg/core/state/asset.go similarity index 62% rename from pkg/core/asset_state.go rename to pkg/core/state/asset.go index 6af69172c..b5268ce31 100644 --- a/pkg/core/asset_state.go +++ b/pkg/core/state/asset.go @@ -1,7 +1,6 @@ -package core +package state 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,31 +9,8 @@ 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 { +// Asset represents the state of an NEO registered Asset. +type Asset struct { ID util.Uint256 AssetType transaction.AssetType Name string @@ -51,7 +27,7 @@ type AssetState struct { } // DecodeBinary implements Serializable interface. -func (a *AssetState) DecodeBinary(br *io.BinReader) { +func (a *Asset) DecodeBinary(br *io.BinReader) { br.ReadBytes(a.ID[:]) br.ReadLE(&a.AssetType) @@ -71,7 +47,7 @@ func (a *AssetState) DecodeBinary(br *io.BinReader) { } // EncodeBinary implements Serializable interface. -func (a *AssetState) EncodeBinary(bw *io.BinWriter) { +func (a *Asset) EncodeBinary(bw *io.BinWriter) { bw.WriteBytes(a.ID[:]) bw.WriteLE(a.AssetType) bw.WriteString(a.Name) @@ -90,7 +66,7 @@ func (a *AssetState) EncodeBinary(bw *io.BinWriter) { } // GetName returns the asset name based on its type. -func (a *AssetState) GetName() string { +func (a *Asset) GetName() string { if a.AssetType == transaction.GoverningToken { return "NEO" diff --git a/pkg/core/state/asset_test.go b/pkg/core/state/asset_test.go new file mode 100644 index 000000000..5a0a1daed --- /dev/null +++ b/pkg/core/state/asset_test.go @@ -0,0 +1,48 @@ +package state + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/internal/random" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeAssetState(t *testing.T) { + asset := &Asset{ + ID: random.Uint256(), + AssetType: transaction.Token, + Name: "super cool token", + Amount: util.Fixed8(1000000), + Available: util.Fixed8(100), + Precision: 0, + FeeMode: feeMode, + Owner: keys.PublicKey{}, + Admin: random.Uint160(), + Issuer: random.Uint160(), + Expiration: 10, + IsFrozen: false, + } + + buf := io.NewBufBinWriter() + asset.EncodeBinary(buf.BinWriter) + assert.Nil(t, buf.Err) + assetDecode := &Asset{} + r := io.NewBinReaderFromBuf(buf.Bytes()) + assetDecode.DecodeBinary(r) + assert.Nil(t, r.Err) + assert.Equal(t, asset, assetDecode) +} + +func TestAssetState_GetName_NEO(t *testing.T) { + asset := &Asset{AssetType: transaction.GoverningToken} + assert.Equal(t, "NEO", asset.GetName()) +} + +func TestAssetState_GetName_NEOGas(t *testing.T) { + asset := &Asset{AssetType: transaction.UtilityToken} + assert.Equal(t, "NEOGas", asset.GetName()) +} diff --git a/pkg/core/state/coin.go b/pkg/core/state/coin.go new file mode 100644 index 000000000..650d3f7fa --- /dev/null +++ b/pkg/core/state/coin.go @@ -0,0 +1,12 @@ +package state + +// Coin represents the state of a coin. +type Coin uint8 + +// Viable Coin constants. +const ( + CoinConfirmed Coin = 0 + CoinSpent Coin = 1 << 1 + CoinClaimed Coin = 1 << 2 + CoinFrozen Coin = 1 << 5 +) diff --git a/pkg/core/contract_state.go b/pkg/core/state/contract.go similarity index 53% rename from pkg/core/contract_state.go rename to pkg/core/state/contract.go index 5af26e1a8..443bf918c 100644 --- a/pkg/core/contract_state.go +++ b/pkg/core/state/contract.go @@ -1,18 +1,14 @@ -package core +package state 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 { +// Contract holds information about a smart contract in the NEO blockchain. +type Contract struct { Script []byte ParamList []smartcontract.ParamType ReturnType smartcontract.ParamType @@ -26,18 +22,8 @@ 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) { +func (cs *Contract) DecodeBinary(br *io.BinReader) { cs.Script = br.ReadVarBytes() br.ReadArray(&cs.ParamList) br.ReadLE(&cs.ReturnType) @@ -51,7 +37,7 @@ func (cs *ContractState) DecodeBinary(br *io.BinReader) { } // EncodeBinary implements Serializable interface. -func (cs *ContractState) EncodeBinary(bw *io.BinWriter) { +func (cs *Contract) EncodeBinary(bw *io.BinWriter) { bw.WriteVarBytes(cs.Script) bw.WriteArray(cs.ParamList) bw.WriteLE(cs.ReturnType) @@ -63,25 +49,8 @@ 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 { +func (cs *Contract) ScriptHash() util.Uint160 { if cs.scriptHash.Equals(util.Uint160{}) { cs.createHash() } @@ -89,21 +58,21 @@ func (cs *ContractState) ScriptHash() util.Uint160 { } // createHash creates contract script hash. -func (cs *ContractState) createHash() { +func (cs *Contract) createHash() { cs.scriptHash = hash.Hash160(cs.Script) } // HasStorage checks whether the contract has storage property set. -func (cs *ContractState) HasStorage() bool { +func (cs *Contract) HasStorage() bool { return (cs.Properties & smartcontract.HasStorage) != 0 } // HasDynamicInvoke checks whether the contract has dynamic invoke property set. -func (cs *ContractState) HasDynamicInvoke() bool { +func (cs *Contract) HasDynamicInvoke() bool { return (cs.Properties & smartcontract.HasDynamicInvoke) != 0 } // IsPayable checks whether the contract has payable property set. -func (cs *ContractState) IsPayable() bool { +func (cs *Contract) IsPayable() bool { return (cs.Properties & smartcontract.IsPayable) != 0 } diff --git a/pkg/core/contract_state_test.go b/pkg/core/state/contract_test.go similarity index 59% rename from pkg/core/contract_state_test.go rename to pkg/core/state/contract_test.go index 9ab2e8233..9f57d51e9 100644 --- a/pkg/core/contract_state_test.go +++ b/pkg/core/state/contract_test.go @@ -1,9 +1,8 @@ -package core +package state 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" @@ -13,7 +12,7 @@ import ( func TestEncodeDecodeContractState(t *testing.T) { script := []byte("testscript") - contract := &ContractState{ + contract := &Contract{ Script: script, ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type}, ReturnType: smartcontract.BoolType, @@ -29,7 +28,7 @@ func TestEncodeDecodeContractState(t *testing.T) { buf := io.NewBufBinWriter() contract.EncodeBinary(buf.BinWriter) assert.Nil(t, buf.Err) - contractDecoded := &ContractState{} + contractDecoded := &Contract{} r := io.NewBinReaderFromBuf(buf.Bytes()) contractDecoded.DecodeBinary(r) assert.Nil(t, r.Err) @@ -38,10 +37,10 @@ func TestEncodeDecodeContractState(t *testing.T) { } func TestContractStateProperties(t *testing.T) { - flaggedContract := ContractState{ + flaggedContract := Contract{ Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable, } - nonFlaggedContract := ContractState{ + nonFlaggedContract := Contract{ ReturnType: smartcontract.BoolType, } assert.Equal(t, true, flaggedContract.HasStorage()) @@ -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/state/notification_event.go similarity index 58% rename from pkg/core/notification_event.go rename to pkg/core/state/notification_event.go index ee0086cc3..d86165be9 100644 --- a/pkg/core/notification_event.go +++ b/pkg/core/state/notification_event.go @@ -1,11 +1,9 @@ -package core +package state 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[:]) @@ -70,11 +39,19 @@ func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) { // EncodeBinary implements the Serializable interface. func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) { w.WriteBytes(aer.TxHash[:]) + w.WriteLE(aer.Trigger) + w.WriteString(aer.VMState) + w.WriteLE(aer.GasConsumed) + w.WriteString(aer.Stack) w.WriteArray(aer.Events) } // DecodeBinary implements the Serializable interface. func (aer *AppExecResult) DecodeBinary(r *io.BinReader) { r.ReadBytes(aer.TxHash[:]) + r.ReadLE(&aer.Trigger) + aer.VMState = r.ReadString() + r.ReadLE(&aer.GasConsumed) + aer.Stack = r.ReadString() r.ReadArray(&aer.Events) } diff --git a/pkg/core/state/notification_event_test.go b/pkg/core/state/notification_event_test.go new file mode 100644 index 000000000..0e92ae898 --- /dev/null +++ b/pkg/core/state/notification_event_test.go @@ -0,0 +1,44 @@ +package state + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/internal/random" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeNotificationEvent(t *testing.T) { + event := &NotificationEvent{ + ScriptHash: random.Uint160(), + Item: nil, + } + + buf := io.NewBufBinWriter() + event.EncodeBinary(buf.BinWriter) + assert.Nil(t, buf.Err) + + eventDecoded := &NotificationEvent{} + reader := io.NewBinReaderFromBuf(buf.Bytes()) + eventDecoded.DecodeBinary(reader) + assert.Equal(t, event, eventDecoded) +} + +func TestEncodeDecodeAppExecResult(t *testing.T) { + appExecResult := &AppExecResult{ + TxHash: random.Uint256(), + Trigger: 1, + VMState: "Hault", + GasConsumed: 10, + Stack: "", + Events: []NotificationEvent{}, + } + buf := io.NewBufBinWriter() + appExecResult.EncodeBinary(buf.BinWriter) + assert.Nil(t, buf.Err) + + appExecResultDecoded := &AppExecResult{} + reader := io.NewBinReaderFromBuf(buf.Bytes()) + appExecResultDecoded.DecodeBinary(reader) + assert.Equal(t, appExecResult, appExecResultDecoded) +} diff --git a/pkg/core/state/storage_item.go b/pkg/core/state/storage_item.go new file mode 100644 index 000000000..49845f07b --- /dev/null +++ b/pkg/core/state/storage_item.go @@ -0,0 +1,23 @@ +package state + +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/state/storage_item_test.go b/pkg/core/state/storage_item_test.go new file mode 100644 index 000000000..aeb7a5b80 --- /dev/null +++ b/pkg/core/state/storage_item_test.go @@ -0,0 +1,26 @@ +package state + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncodeDecodeStorageItem(t *testing.T) { + storageItem := &StorageItem{ + Value: []byte{}, + IsConst: false, + } + buf := io.NewBufBinWriter() + storageItem.EncodeBinary(buf.BinWriter) + require.NoError(t, buf.Err) + + decodedStorageItem := &StorageItem{} + r := io.NewBinReaderFromBuf(buf.Bytes()) + decodedStorageItem.DecodeBinary(r) + require.NoError(t, r.Err) + + assert.Equal(t, storageItem, decodedStorageItem) +} diff --git a/pkg/core/state/validator.go b/pkg/core/state/validator.go new file mode 100644 index 000000000..c9f5a5862 --- /dev/null +++ b/pkg/core/state/validator.go @@ -0,0 +1,99 @@ +package state + +import ( + "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/util" +) + +// Validator holds the state of a validator. +type Validator struct { + PublicKey *keys.PublicKey + Registered bool + Votes util.Fixed8 +} + +// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes. +func (vs *Validator) RegisteredAndHasVotes() bool { + return vs.Registered && vs.Votes > util.Fixed8(0) +} + +// EncodeBinary encodes Validator to the given BinWriter. +func (vs *Validator) EncodeBinary(bw *io.BinWriter) { + vs.PublicKey.EncodeBinary(bw) + bw.WriteLE(vs.Registered) + bw.WriteLE(vs.Votes) +} + +// DecodeBinary decodes Validator from the given BinReader. +func (vs *Validator) DecodeBinary(reader *io.BinReader) { + vs.PublicKey = &keys.PublicKey{} + vs.PublicKey.DecodeBinary(reader) + reader.ReadLE(&vs.Registered) + reader.ReadLE(&vs.Votes) +} + +// GetValidatorsWeightedAverage applies weighted filter based on votes for validator and returns number of validators. +// Get back to it with further investigation in https://github.com/nspcc-dev/neo-go/issues/512. +func GetValidatorsWeightedAverage(validators []*Validator) int { + return int(weightedAverage(applyWeightedFilter(validators))) +} + +// applyWeightedFilter is an implementation of the filter for validators votes. +// C# reference https://github.com/neo-project/neo/blob/41caff115c28d6c7665b2a7ac72967e7ce82e921/neo/Helper.cs#L273 +func applyWeightedFilter(validators []*Validator) map[*Validator]float64 { + var validatorsWithVotes []*Validator + var amount float64 + + weightedVotes := make(map[*Validator]float64) + start := 0.25 + end := 0.75 + sum := float64(0) + current := float64(0) + + for _, validator := range validators { + if validator.Votes > util.Fixed8(0) { + validatorsWithVotes = append(validatorsWithVotes, validator) + amount += validator.Votes.FloatValue() + } + } + + for _, validator := range validatorsWithVotes { + if current >= end { + break + } + weight := validator.Votes.FloatValue() + sum += weight + old := current + current = sum / amount + + if current <= start { + continue + } + + if old < start { + if current > end { + weight = (end - start) * amount + } else { + weight = (current - start) * amount + } + } else if current > end { + weight = (end - old) * amount + } + weightedVotes[validator] = weight + } + return weightedVotes +} + +func weightedAverage(weightedVotes map[*Validator]float64) float64 { + sumWeight := float64(0) + sumValue := float64(0) + for vState, weight := range weightedVotes { + sumWeight += weight + sumValue += vState.Votes.FloatValue() * weight + } + if sumValue == 0 || sumWeight == 0 { + return 0 + } + return sumValue / sumWeight +} diff --git a/pkg/core/state/validator_test.go b/pkg/core/state/validator_test.go new file mode 100644 index 000000000..ccd9d18d9 --- /dev/null +++ b/pkg/core/state/validator_test.go @@ -0,0 +1,64 @@ +package state + +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 := &Validator{ + PublicKey: &keys.PublicKey{}, + Registered: false, + Votes: util.Fixed8(10), + } + buf := io.NewBufBinWriter() + state.EncodeBinary(buf.BinWriter) + require.NoError(t, buf.Err) + + decodedState := &Validator{} + 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 := &Validator{ + 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 := &Validator{ + 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 := &Validator{ + 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/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/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..c4c8f9723 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/state" "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 []state.Coin } // NewUnspentCoinState returns a new unspent coin state with N confirmed states. func NewUnspentCoinState(n int) *UnspentCoinState { u := &UnspentCoinState{ - states: make([]CoinState, n), + states: make([]state.Coin, n), } for i := 0; i < n; i++ { - u.states[i] = CoinStateConfirmed + u.states[i] = state.CoinConfirmed } 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([]state.Coin, lenStates) for i := 0; i < int(lenStates); i++ { - var state uint8 - br.ReadLE(&state) - s.states[i] = CoinState(state) + var coinState uint8 + br.ReadLE(&coinState) + s.states[i] = state.Coin(coinState) } } - -// 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..8864619c6 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/state" "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: []state.Coin{ + state.CoinConfirmed, + state.CoinSpent, + state.CoinSpent, + state.CoinSpent, + state.CoinConfirmed, }, } @@ -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.go b/pkg/core/validator_state.go deleted file mode 100644 index 36fe3a996..000000000 --- a/pkg/core/validator_state.go +++ /dev/null @@ -1,174 +0,0 @@ -package core - -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 - Registered bool - Votes util.Fixed8 -} - -// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes. -func (vs *ValidatorState) RegisteredAndHasVotes() bool { - return vs.Registered && vs.Votes > util.Fixed8(0) -} - -// EncodeBinary encodes ValidatorState to the given BinWriter. -func (vs *ValidatorState) EncodeBinary(bw *io.BinWriter) { - vs.PublicKey.EncodeBinary(bw) - bw.WriteLE(vs.Registered) - bw.WriteLE(vs.Votes) -} - -// DecodeBinary decodes ValidatorState from the given BinReader. -func (vs *ValidatorState) DecodeBinary(reader *io.BinReader) { - vs.PublicKey = &keys.PublicKey{} - vs.PublicKey.DecodeBinary(reader) - reader.ReadLE(&vs.Registered) - reader.ReadLE(&vs.Votes) -} - -// GetValidatorsWeightedAverage applies weighted filter based on votes for validator and returns number of validators. -// Get back to it with further investigation in https://github.com/nspcc-dev/neo-go/issues/512. -func GetValidatorsWeightedAverage(validators []*ValidatorState) int { - return int(weightedAverage(applyWeightedFilter(validators))) -} - -// applyWeightedFilter is an implementation of the filter for validators votes. -// C# reference https://github.com/neo-project/neo/blob/41caff115c28d6c7665b2a7ac72967e7ce82e921/neo/Helper.cs#L273 -func applyWeightedFilter(validators []*ValidatorState) map[*ValidatorState]float64 { - var validatorsWithVotes []*ValidatorState - var amount float64 - - weightedVotes := make(map[*ValidatorState]float64) - start := 0.25 - end := 0.75 - sum := float64(0) - current := float64(0) - - for _, validator := range validators { - if validator.Votes > util.Fixed8(0) { - validatorsWithVotes = append(validatorsWithVotes, validator) - amount += validator.Votes.FloatValue() - } - } - - for _, validator := range validatorsWithVotes { - if current >= end { - break - } - weight := validator.Votes.FloatValue() - sum += weight - old := current - current = sum / amount - - if current <= start { - continue - } - - if old < start { - if current > end { - weight = (end - start) * amount - } else { - weight = (current - start) * amount - } - } else if current > end { - weight = (end - old) * amount - } - weightedVotes[validator] = weight - } - return weightedVotes -} - -func weightedAverage(weightedVotes map[*ValidatorState]float64) float64 { - sumWeight := float64(0) - sumValue := float64(0) - for vState, weight := range weightedVotes { - sumWeight += weight - sumValue += vState.Votes.FloatValue() * weight - } - if sumValue == 0 || sumWeight == 0 { - return 0 - } - return sumValue / sumWeight -} 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/internal/random/random_util.go b/pkg/internal/random/random_util.go new file mode 100644 index 000000000..d07e4da50 --- /dev/null +++ b/pkg/internal/random/random_util.go @@ -0,0 +1,40 @@ +package random + +import ( + "math/rand" + "time" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/util" +) + +// String returns a random string with the n as its length. +func String(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = byte(Int(65, 90)) + } + + return string(b) +} + +// Int returns a random integer in [min,max). +func Int(min, max int) int { + return min + rand.Intn(max-min) +} + +// Uint256 returns a random Uint256. +func Uint256() util.Uint256 { + str := String(20) + return hash.Sha256([]byte(str)) +} + +// Uint160 returns a random Uint160. +func Uint160() util.Uint160 { + str := String(20) + return hash.RipeMD160([]byte(str)) +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 14daf897e..da2039650 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/state" "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) *state.Contract { 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) *state.Asset { panic("TODO") } -func (chain testChain) GetAccountState(util.Uint160) *core.AccountState { +func (chain testChain) GetAccountState(util.Uint160) *state.Account { 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) *state.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]*state.StorageItem, error) { panic("TODO") } func (chain testChain) CurrentHeaderHash() util.Uint256 { diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index f15f5c9b8..e8a75bc81 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/state" "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 state.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..c0e95c595 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/state" "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 state.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..beb1f8fe1 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/state" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -20,7 +20,7 @@ type ( // Unspent stores Unspents per asset Unspent struct { - Unspent core.UnspentBalances + Unspent state.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..2d0ff5d79 100644 --- a/pkg/rpc/wrappers/account_state.go +++ b/pkg/rpc/wrappers/account_state.go @@ -2,15 +2,15 @@ package wrappers import ( "bytes" + "github.com/CityOfZion/neo-go/pkg/core/state" "sort" - "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" ) // AccountState wrapper used for the representation of -// core.AccountState on the RPC Server. +// state.Account on the RPC Server. type AccountState struct { Version uint8 `json:"version"` ScriptHash util.Uint160 `json:"script_hash"` @@ -32,8 +32,8 @@ type Balance struct { Value util.Fixed8 `json:"value"` } -// NewAccountState creates a new AccountState wrapper. -func NewAccountState(a *core.AccountState) AccountState { +// NewAccountState creates a new Account wrapper. +func NewAccountState(a *state.Account) 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..b9b08c3ce 100644 --- a/pkg/rpc/wrappers/asset_state.go +++ b/pkg/rpc/wrappers/asset_state.go @@ -1,14 +1,14 @@ package wrappers import ( - "github.com/CityOfZion/neo-go/pkg/core" + "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/util" ) // AssetState wrapper used for the representation of -// core.AssetState on the RPC Server. +// state.Asset on the RPC Server. type AssetState struct { ID util.Uint256 `json:"assetID"` AssetType transaction.AssetType `json:"assetType"` @@ -25,8 +25,8 @@ type AssetState struct { IsFrozen bool `json:"is_frozen"` } -// NewAssetState creates a new AssetState wrapper. -func NewAssetState(a *core.AssetState) AssetState { +// NewAssetState creates a new Asset wrapper. +func NewAssetState(a *state.Asset) 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..38c14b901 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/state" "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 []state.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. @@ -27,8 +28,8 @@ var GlobalAssets = map[string]string{ "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "GAS", } -// NewUnspents creates a new AccountState wrapper using given Blockchainer. -func NewUnspents(a *core.AccountState, chain core.Blockchainer, addr string) Unspents { +// NewUnspents creates a new Account wrapper using given Blockchainer. +func NewUnspents(a *state.Account, chain core.Blockchainer, addr string) Unspents { res := Unspents{ Address: addr, Balance: make([]UnspentBalanceInfo, 0, len(a.Balances)),