forked from TrueCloudLab/neoneo-go
Merge pull request #517 from nspcc-dev/refactor_blockchain_storage
core: refactoring blockchain state and storage
This commit is contained in:
commit
710520a999
45 changed files with 1608 additions and 1510 deletions
|
@ -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)
|
|
||||||
}
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
@ -26,7 +27,7 @@ import (
|
||||||
// Tuning parameters.
|
// Tuning parameters.
|
||||||
const (
|
const (
|
||||||
headerBatchCount = 2000
|
headerBatchCount = 2000
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
|
|
||||||
// This one comes from C# code and it's different from the constant used
|
// 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
|
// when creating an asset with Neo.Asset.Create interop call. It looks
|
||||||
|
@ -46,8 +47,8 @@ var (
|
||||||
type Blockchain struct {
|
type Blockchain struct {
|
||||||
config config.ProtocolConfiguration
|
config config.ProtocolConfiguration
|
||||||
|
|
||||||
// Persistent storage wrapped around with a write memory caching layer.
|
// Data access object for CRUD operations around storage.
|
||||||
store *storage.MemCachedStore
|
dao *dao
|
||||||
|
|
||||||
// Current index/height of the highest block.
|
// Current index/height of the highest block.
|
||||||
// Read access should always be called by BlockHeight().
|
// Read access should always be called by BlockHeight().
|
||||||
|
@ -85,7 +86,7 @@ type headersOpFunc func(headerList *HeaderHashList)
|
||||||
func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockchain, error) {
|
func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockchain, error) {
|
||||||
bc := &Blockchain{
|
bc := &Blockchain{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
store: storage.NewMemCachedStore(s),
|
dao: &dao{store: storage.NewMemCachedStore(s)},
|
||||||
headersOp: make(chan headersOpFunc),
|
headersOp: make(chan headersOpFunc),
|
||||||
headersOpDone: make(chan struct{}),
|
headersOpDone: make(chan struct{}),
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
|
@ -103,10 +104,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockcha
|
||||||
|
|
||||||
func (bc *Blockchain) init() error {
|
func (bc *Blockchain) init() error {
|
||||||
// If we could not find the version in the Store, we know that there is nothing stored.
|
// If we could not find the version in the Store, we know that there is nothing stored.
|
||||||
ver, err := storage.Version(bc.store)
|
ver, err := bc.dao.GetVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("no storage version found! creating genesis block")
|
log.Infof("no storage version found! creating genesis block")
|
||||||
if err = storage.PutVersion(bc.store, version); err != nil {
|
if err = bc.dao.PutVersion(version); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
genesisBlock, err := createGenesisBlock(bc.config)
|
genesisBlock, err := createGenesisBlock(bc.config)
|
||||||
|
@ -114,7 +115,7 @@ func (bc *Blockchain) init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bc.headerList = NewHeaderHashList(genesisBlock.Hash())
|
bc.headerList = NewHeaderHashList(genesisBlock.Hash())
|
||||||
err = bc.store.Put(storage.SYSCurrentHeader.Bytes(), hashAndIndexToBytes(genesisBlock.Hash(), genesisBlock.Index))
|
err = bc.dao.PutCurrentHeader(hashAndIndexToBytes(genesisBlock.Hash(), genesisBlock.Index))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -129,14 +130,14 @@ func (bc *Blockchain) init() error {
|
||||||
// and the genesis block as first block.
|
// and the genesis block as first block.
|
||||||
log.Infof("restoring blockchain with version: %s", version)
|
log.Infof("restoring blockchain with version: %s", version)
|
||||||
|
|
||||||
bHeight, err := storage.CurrentBlockHeight(bc.store)
|
bHeight, err := bc.dao.GetCurrentBlockHeight()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bc.blockHeight = bHeight
|
bc.blockHeight = bHeight
|
||||||
bc.persistedHeight = bHeight
|
bc.persistedHeight = bHeight
|
||||||
|
|
||||||
hashes, err := storage.HeaderHashes(bc.store)
|
hashes, err := bc.dao.GetHeaderHashes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -144,7 +145,7 @@ func (bc *Blockchain) init() error {
|
||||||
bc.headerList = NewHeaderHashList(hashes...)
|
bc.headerList = NewHeaderHashList(hashes...)
|
||||||
bc.storedHeaderCount = uint32(len(hashes))
|
bc.storedHeaderCount = uint32(len(hashes))
|
||||||
|
|
||||||
currHeaderHeight, currHeaderHash, err := storage.CurrentHeaderHeight(bc.store)
|
currHeaderHeight, currHeaderHash, err := bc.dao.GetCurrentHeaderHeight()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -198,7 +199,7 @@ func (bc *Blockchain) Run() {
|
||||||
if err := bc.persist(); err != nil {
|
if err := bc.persist(); err != nil {
|
||||||
log.Warnf("failed to persist: %s", err)
|
log.Warnf("failed to persist: %s", err)
|
||||||
}
|
}
|
||||||
if err := bc.store.Close(); err != nil {
|
if err := bc.dao.store.Close(); err != nil {
|
||||||
log.Warnf("failed to close db: %s", err)
|
log.Warnf("failed to close db: %s", err)
|
||||||
}
|
}
|
||||||
close(bc.runToExitCh)
|
close(bc.runToExitCh)
|
||||||
|
@ -268,7 +269,7 @@ func (bc *Blockchain) AddBlock(block *Block) error {
|
||||||
func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
batch = bc.store.Batch()
|
batch = bc.dao.store.Batch()
|
||||||
)
|
)
|
||||||
|
|
||||||
bc.headersOp <- func(headerList *HeaderHashList) {
|
bc.headersOp <- func(headerList *HeaderHashList) {
|
||||||
|
@ -295,7 +296,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
||||||
|
|
||||||
if oldlen != headerList.Len() {
|
if oldlen != headerList.Len() {
|
||||||
updateHeaderHeightMetric(headerList.Len() - 1)
|
updateHeaderHeightMetric(headerList.Len() - 1)
|
||||||
if err = bc.store.PutBatch(batch); err != nil {
|
if err = bc.dao.store.PutBatch(batch); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
@ -343,25 +344,26 @@ func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList *
|
||||||
// is happening here, quite allot as you can see :). If things are wired together
|
// is happening here, quite allot as you can see :). If things are wired together
|
||||||
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
||||||
func (bc *Blockchain) storeBlock(block *Block) error {
|
func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
chainState := NewBlockChainState(bc.store)
|
cache := &dao{store: storage.NewMemCachedStore(bc.dao.store)}
|
||||||
|
if err := cache.StoreAsBlock(block, 0); err != nil {
|
||||||
if err := chainState.storeAsBlock(block, 0); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := chainState.storeAsCurrentBlock(block); err != nil {
|
if err := cache.StoreAsCurrentBlock(block); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
if err := chainState.storeAsTransaction(tx, block.Index); err != nil {
|
if err := cache.StoreAsTransaction(tx, block.Index); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
chainState.unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs))
|
if err := cache.PutUnspentCoinState(tx.Hash(), NewUnspentCoinState(len(tx.Outputs))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Process TX outputs.
|
// Process TX outputs.
|
||||||
if err := processOutputs(tx, chainState); err != nil {
|
if err := processOutputs(tx, cache); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,14 +374,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
return fmt.Errorf("could not find previous TX: %s", prevHash)
|
return fmt.Errorf("could not find previous TX: %s", prevHash)
|
||||||
}
|
}
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
unspent, err := chainState.unspentCoins.getAndUpdate(chainState.store, input.PrevHash)
|
unspent, err := cache.GetUnspentCoinStateOrNew(input.PrevHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
unspent.states[input.PrevIndex] = CoinStateSpent
|
unspent.states[input.PrevIndex] = state.CoinSpent
|
||||||
|
if err = cache.PutUnspentCoinState(input.PrevHash, unspent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
||||||
account, err := chainState.accounts.getAndUpdate(chainState.store, prevTXOutput.ScriptHash)
|
account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -387,19 +391,11 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
||||||
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
|
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
|
||||||
spentCoin.items[input.PrevIndex] = block.Index
|
spentCoin.items[input.PrevIndex] = block.Index
|
||||||
chainState.spentCoins[input.PrevHash] = spentCoin
|
if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil {
|
||||||
|
return err
|
||||||
if len(account.Votes) > 0 {
|
}
|
||||||
for _, vote := range account.Votes {
|
if err = processTXWithValidatorsSubtract(account, cache, prevTXOutput.Amount); err != nil {
|
||||||
validator, err := chainState.validators.getAndUpdate(chainState.store, vote)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validator.Votes -= prevTXOutput.Amount
|
|
||||||
if !validator.RegisteredAndHasVotes() {
|
|
||||||
delete(chainState.validators, vote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,13 +415,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
account.Balances[prevTXOutput.AssetID] = account.Balances[prevTXOutput.AssetID][:balancesLen-1]
|
account.Balances[prevTXOutput.AssetID] = account.Balances[prevTXOutput.AssetID][:balancesLen-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err = cache.PutAccountState(account); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the underlying type of the TX.
|
// Process the underlying type of the TX.
|
||||||
switch t := tx.Data.(type) {
|
switch t := tx.Data.(type) {
|
||||||
case *transaction.RegisterTX:
|
case *transaction.RegisterTX:
|
||||||
chainState.assets[tx.Hash()] = &AssetState{
|
err := cache.PutAssetState(&state.Asset{
|
||||||
ID: tx.Hash(),
|
ID: tx.Hash(),
|
||||||
AssetType: t.AssetType,
|
AssetType: t.AssetType,
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
|
@ -434,45 +433,50 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
Owner: t.Owner,
|
Owner: t.Owner,
|
||||||
Admin: t.Admin,
|
Admin: t.Admin,
|
||||||
Expiration: bc.BlockHeight() + registeredAssetLifetime,
|
Expiration: bc.BlockHeight() + registeredAssetLifetime,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
case *transaction.IssueTX:
|
case *transaction.IssueTX:
|
||||||
for _, res := range bc.GetTransactionResults(tx) {
|
for _, res := range bc.GetTransactionResults(tx) {
|
||||||
if res.Amount < 0 {
|
if res.Amount < 0 {
|
||||||
var asset *AssetState
|
asset, err := cache.GetAssetState(res.AssetID)
|
||||||
|
if asset == nil || err != nil {
|
||||||
asset, ok := chainState.assets[res.AssetID]
|
return fmt.Errorf("issue failed: no asset %s or error %s", res.AssetID, err)
|
||||||
if !ok {
|
|
||||||
asset = bc.GetAssetState(res.AssetID)
|
|
||||||
}
|
|
||||||
if asset == nil {
|
|
||||||
return fmt.Errorf("issue failed: no asset %s", res.AssetID)
|
|
||||||
}
|
}
|
||||||
asset.Available -= res.Amount
|
asset.Available -= res.Amount
|
||||||
chainState.assets[res.AssetID] = asset
|
if err := cache.PutAssetState(asset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *transaction.ClaimTX:
|
case *transaction.ClaimTX:
|
||||||
// Remove claimed NEO from spent coins making it unavalaible for
|
// Remove claimed NEO from spent coins making it unavalaible for
|
||||||
// additional claims.
|
// additional claims.
|
||||||
for _, input := range t.Claims {
|
for _, input := range t.Claims {
|
||||||
scs, err := chainState.spentCoins.getAndUpdate(bc.store, input.PrevHash)
|
scs, err := cache.GetSpentCoinsOrNew(input.PrevHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if scs.txHash == input.PrevHash {
|
if scs.txHash == input.PrevHash {
|
||||||
// Existing scs.
|
// Existing scs.
|
||||||
delete(scs.items, input.PrevIndex)
|
delete(scs.items, input.PrevIndex)
|
||||||
|
if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Uninitialized, new, forget about it.
|
// Uninitialized, new, forget about it.
|
||||||
delete(chainState.spentCoins, input.PrevHash)
|
if err = cache.DeleteSpentCoinState(input.PrevHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *transaction.EnrollmentTX:
|
case *transaction.EnrollmentTX:
|
||||||
if err := processEnrollmentTX(chainState, t); err != nil {
|
if err := processEnrollmentTX(cache, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *transaction.StateTX:
|
case *transaction.StateTX:
|
||||||
if err := processStateTX(chainState, t); err != nil {
|
if err := processStateTX(cache, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *transaction.PublishTX:
|
case *transaction.PublishTX:
|
||||||
|
@ -480,7 +484,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
if t.NeedStorage {
|
if t.NeedStorage {
|
||||||
properties |= smartcontract.HasStorage
|
properties |= smartcontract.HasStorage
|
||||||
}
|
}
|
||||||
contract := &ContractState{
|
contract := &state.Contract{
|
||||||
Script: t.Script,
|
Script: t.Script,
|
||||||
ParamList: t.ParamList,
|
ParamList: t.ParamList,
|
||||||
ReturnType: t.ReturnType,
|
ReturnType: t.ReturnType,
|
||||||
|
@ -491,15 +495,17 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
Email: t.Email,
|
Email: t.Email,
|
||||||
Description: t.Description,
|
Description: t.Description,
|
||||||
}
|
}
|
||||||
chainState.contracts[contract.ScriptHash()] = contract
|
if err := cache.PutContractState(contract); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case *transaction.InvocationTX:
|
case *transaction.InvocationTX:
|
||||||
systemInterop := newInteropContext(trigger.Application, bc, chainState.store, block, tx)
|
systemInterop := newInteropContext(trigger.Application, bc, cache.store, block, tx)
|
||||||
v := bc.spawnVMWithInterops(systemInterop)
|
v := bc.spawnVMWithInterops(systemInterop)
|
||||||
v.SetCheckedHash(tx.VerificationHash().BytesBE())
|
v.SetCheckedHash(tx.VerificationHash().BytesBE())
|
||||||
v.LoadScript(t.Script)
|
v.LoadScript(t.Script)
|
||||||
err := v.Run()
|
err := v.Run()
|
||||||
if !v.HasFailed() {
|
if !v.HasFailed() {
|
||||||
_, err := systemInterop.mem.Persist()
|
_, err := systemInterop.dao.store.Persist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to persist invocation results")
|
return errors.Wrap(err, "failed to persist invocation results")
|
||||||
}
|
}
|
||||||
|
@ -534,7 +540,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
"err": err,
|
"err": err,
|
||||||
}).Warn("contract invocation failed")
|
}).Warn("contract invocation failed")
|
||||||
}
|
}
|
||||||
aer := &AppExecResult{
|
aer := &state.AppExecResult{
|
||||||
TxHash: tx.Hash(),
|
TxHash: tx.Hash(),
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: v.State(),
|
VMState: v.State(),
|
||||||
|
@ -542,17 +548,16 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
Stack: v.Stack("estack"),
|
Stack: v.Stack("estack"),
|
||||||
Events: systemInterop.notifications,
|
Events: systemInterop.notifications,
|
||||||
}
|
}
|
||||||
err = putAppExecResultIntoStore(chainState.store, aer)
|
err = cache.PutAppExecResult(aer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to store notifications")
|
return errors.Wrap(err, "failed to store notifications")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_, err := cache.store.Persist()
|
||||||
if err := chainState.commit(); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
||||||
updateBlockHeightMetric(block.Index)
|
updateBlockHeightMetric(block.Index)
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
|
@ -562,37 +567,70 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processOutputs processes transaction outputs.
|
// processOutputs processes transaction outputs.
|
||||||
func processOutputs(tx *transaction.Transaction, chainState *BlockChainState) error {
|
func processOutputs(tx *transaction.Transaction, dao *dao) error {
|
||||||
for index, output := range tx.Outputs {
|
for index, output := range tx.Outputs {
|
||||||
account, err := chainState.accounts.getAndUpdate(chainState.store, output.ScriptHash)
|
account, err := dao.GetAccountStateOrNew(output.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
account.Balances[output.AssetID] = append(account.Balances[output.AssetID], UnspentBalance{
|
account.Balances[output.AssetID] = append(account.Balances[output.AssetID], state.UnspentBalance{
|
||||||
Tx: tx.Hash(),
|
Tx: tx.Hash(),
|
||||||
Index: uint16(index),
|
Index: uint16(index),
|
||||||
Value: output.Amount,
|
Value: output.Amount,
|
||||||
})
|
})
|
||||||
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
|
if err = dao.PutAccountState(account); err != nil {
|
||||||
for _, vote := range account.Votes {
|
return err
|
||||||
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
|
}
|
||||||
if err != nil {
|
if err = processTXWithValidatorsAdd(&output, account, dao); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
validatorState.Votes += output.Amount
|
}
|
||||||
|
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
|
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{}
|
publicKey := &keys.PublicKey{}
|
||||||
err := publicKey.DecodeBytes(descriptor.Key)
|
err := publicKey.DecodeBytes(descriptor.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
validatorState, err := state.validators.getAndUpdate(state.store, publicKey)
|
validatorState, err := dao.GetValidatorStateOrNew(publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -602,31 +640,25 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, st
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
validatorState.Registered = isRegistered
|
validatorState.Registered = isRegistered
|
||||||
|
return dao.PutValidatorState(validatorState)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, state *BlockChainState) error {
|
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao) error {
|
||||||
hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
|
hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
account, err := state.accounts.getAndUpdate(state.store, hash)
|
account, err := dao.GetAccountStateOrNew(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if descriptor.Field == "Votes" {
|
if descriptor.Field == "Votes" {
|
||||||
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
|
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
|
||||||
for _, vote := range account.Votes {
|
if err = processTXWithValidatorsSubtract(account, dao, balance); err != nil {
|
||||||
validator, err := state.validators.getAndUpdate(state.store, vote)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validator.Votes -= balance
|
|
||||||
if !validator.RegisteredAndHasVotes() {
|
|
||||||
delete(state.validators, vote)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
votes := keys.PublicKeys{}
|
votes := keys.PublicKeys{}
|
||||||
|
@ -637,10 +669,13 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, stat
|
||||||
if votes.Len() != len(account.Votes) {
|
if votes.Len() != len(account.Votes) {
|
||||||
account.Votes = votes
|
account.Votes = votes
|
||||||
for _, vote := range votes {
|
for _, vote := range votes {
|
||||||
_, err := state.validators.getAndUpdate(state.store, vote)
|
validator, err := dao.GetValidatorStateOrNew(vote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := dao.PutValidatorState(validator); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -655,19 +690,19 @@ func (bc *Blockchain) persist() error {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
persisted, err = bc.store.Persist()
|
persisted, err = bc.dao.store.Persist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if persisted > 0 {
|
if persisted > 0 {
|
||||||
bHeight, err := storage.CurrentBlockHeight(bc.store)
|
bHeight, err := bc.dao.GetCurrentBlockHeight()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
oldHeight := atomic.SwapUint32(&bc.persistedHeight, bHeight)
|
oldHeight := atomic.SwapUint32(&bc.persistedHeight, bHeight)
|
||||||
diff := bHeight - oldHeight
|
diff := bHeight - oldHeight
|
||||||
|
|
||||||
storedHeaderHeight, _, err := storage.CurrentHeaderHeight(bc.store)
|
storedHeaderHeight, _, err := bc.dao.GetCurrentHeaderHeight()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -699,66 +734,22 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio
|
||||||
if tx, ok := bc.memPool.TryGetValue(hash); ok {
|
if tx, ok := bc.memPool.TryGetValue(hash); ok {
|
||||||
return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case.
|
return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case.
|
||||||
}
|
}
|
||||||
return getTransactionFromStore(bc.store, hash)
|
return bc.dao.GetTransaction(hash)
|
||||||
}
|
|
||||||
|
|
||||||
// getTransactionFromStore returns Transaction and its height by the given hash
|
|
||||||
// if it exists in the store.
|
|
||||||
func getTransactionFromStore(s storage.Store, hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
|
||||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesLE())
|
|
||||||
b, err := s.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
r := io.NewBinReaderFromBuf(b)
|
|
||||||
|
|
||||||
var height uint32
|
|
||||||
r.ReadLE(&height)
|
|
||||||
|
|
||||||
tx := &transaction.Transaction{}
|
|
||||||
tx.DecodeBinary(r)
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, 0, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx, height, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStorageItem returns an item from storage.
|
// GetStorageItem returns an item from storage.
|
||||||
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem {
|
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
||||||
return getStorageItemFromStore(bc.store, scripthash, key)
|
return bc.dao.GetStorageItem(scripthash, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStorageItems returns all storage items for a given scripthash.
|
// GetStorageItems returns all storage items for a given scripthash.
|
||||||
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error) {
|
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) {
|
||||||
var siMap = make(map[string]*StorageItem)
|
return bc.dao.GetStorageItems(hash)
|
||||||
var err error
|
|
||||||
|
|
||||||
saveToMap := func(k, v []byte) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r := io.NewBinReaderFromBuf(v)
|
|
||||||
si := &StorageItem{}
|
|
||||||
si.DecodeBinary(r)
|
|
||||||
if r.Err != nil {
|
|
||||||
err = r.Err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cut prefix and hash.
|
|
||||||
siMap[string(k[21:])] = si
|
|
||||||
}
|
|
||||||
bc.store.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesLE()), saveToMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return siMap, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlock returns a Block by the given hash.
|
// GetBlock returns a Block by the given hash.
|
||||||
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
||||||
block, err := getBlockFromStore(bc.store, hash)
|
block, err := bc.dao.GetBlock(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -775,28 +766,9 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBlockFromStore returns Block by the given hash if it exists in the store.
|
|
||||||
func getBlockFromStore(s storage.Store, hash util.Uint256) (*Block, error) {
|
|
||||||
key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
|
|
||||||
b, err := s.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
block, err := NewBlockFromTrimmedBytes(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return block, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeader returns data block header identified with the given hash value.
|
// GetHeader returns data block header identified with the given hash value.
|
||||||
func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) {
|
func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) {
|
||||||
return getHeaderFromStore(bc.store, hash)
|
block, err := bc.dao.GetBlock(hash)
|
||||||
}
|
|
||||||
|
|
||||||
// getHeaderFromStore returns Header by the given hash from the store.
|
|
||||||
func getHeaderFromStore(s storage.Store, hash util.Uint256) (*Header, error) {
|
|
||||||
block, err := getBlockFromStore(s, hash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -806,18 +778,7 @@ func getHeaderFromStore(s storage.Store, hash util.Uint256) (*Header, error) {
|
||||||
// HasTransaction returns true if the blockchain contains he given
|
// HasTransaction returns true if the blockchain contains he given
|
||||||
// transaction hash.
|
// transaction hash.
|
||||||
func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
|
func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
|
||||||
return bc.memPool.ContainsKey(hash) ||
|
return bc.memPool.ContainsKey(hash) || bc.dao.HasTransaction(hash)
|
||||||
checkTransactionInStore(bc.store, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkTransactionInStore returns true if the given store contains the given
|
|
||||||
// Transaction hash.
|
|
||||||
func checkTransactionInStore(s storage.Store, hash util.Uint256) bool {
|
|
||||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesLE())
|
|
||||||
if _, err := s.Get(key); err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasBlock returns true if the blockchain contains the given
|
// HasBlock returns true if the blockchain contains the given
|
||||||
|
@ -868,54 +829,26 @@ func (bc *Blockchain) HeaderHeight() uint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetState returns asset state from its assetID.
|
// GetAssetState returns asset state from its assetID.
|
||||||
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *AssetState {
|
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *state.Asset {
|
||||||
return getAssetStateFromStore(bc.store, assetID)
|
asset, err := bc.dao.GetAssetState(assetID)
|
||||||
}
|
if asset == nil && err != storage.ErrKeyNotFound {
|
||||||
|
log.Warnf("failed to get asset state %s : %s", assetID, err)
|
||||||
// getAssetStateFromStore returns given asset state as recorded in the given
|
|
||||||
// store.
|
|
||||||
func getAssetStateFromStore(s storage.Store, assetID util.Uint256) *AssetState {
|
|
||||||
key := storage.AppendPrefix(storage.STAsset, assetID.BytesBE())
|
|
||||||
asEncoded, err := s.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
var a AssetState
|
return asset
|
||||||
r := io.NewBinReaderFromBuf(asEncoded)
|
|
||||||
a.DecodeBinary(r)
|
|
||||||
if r.Err != nil || a.ID != assetID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContractState returns contract by its script hash.
|
// GetContractState returns contract by its script hash.
|
||||||
func (bc *Blockchain) GetContractState(hash util.Uint160) *ContractState {
|
func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
|
||||||
return getContractStateFromStore(bc.store, hash)
|
contract, err := bc.dao.GetContractState(hash)
|
||||||
}
|
if contract == nil && err != storage.ErrKeyNotFound {
|
||||||
|
log.Warnf("failed to get contract state: %s", err)
|
||||||
// getContractStateFromStore returns contract state as recorded in the given
|
|
||||||
// store by the given script hash.
|
|
||||||
func getContractStateFromStore(s storage.Store, hash util.Uint160) *ContractState {
|
|
||||||
key := storage.AppendPrefix(storage.STContract, hash.BytesBE())
|
|
||||||
contractBytes, err := s.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
var c ContractState
|
return contract
|
||||||
r := io.NewBinReaderFromBuf(contractBytes)
|
|
||||||
c.DecodeBinary(r)
|
|
||||||
if r.Err != nil || c.ScriptHash() != hash {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountState returns the account state from its script hash.
|
// GetAccountState returns the account state from its script hash.
|
||||||
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
|
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account {
|
||||||
as, err := getAccountStateFromStore(bc.store, scriptHash)
|
as, err := bc.dao.GetAccountState(scriptHash)
|
||||||
if as == nil && err != storage.ErrKeyNotFound {
|
if as == nil && err != storage.ErrKeyNotFound {
|
||||||
log.Warnf("failed to get account state: %s", err)
|
log.Warnf("failed to get account state: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -924,7 +857,7 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
|
||||||
|
|
||||||
// GetUnspentCoinState returns unspent coin state for given tx hash.
|
// GetUnspentCoinState returns unspent coin state for given tx hash.
|
||||||
func (bc *Blockchain) GetUnspentCoinState(hash util.Uint256) *UnspentCoinState {
|
func (bc *Blockchain) GetUnspentCoinState(hash util.Uint256) *UnspentCoinState {
|
||||||
ucs, err := getUnspentCoinStateFromStore(bc.store, hash)
|
ucs, err := bc.dao.GetUnspentCoinState(hash)
|
||||||
if ucs == nil && err != storage.ErrKeyNotFound {
|
if ucs == nil && err != storage.ErrKeyNotFound {
|
||||||
log.Warnf("failed to get unspent coin state: %s", err)
|
log.Warnf("failed to get unspent coin state: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -1029,7 +962,7 @@ func (bc *Blockchain) VerifyTx(t *transaction.Transaction, block *Block) error {
|
||||||
return errors.New("invalid transaction due to conflicts with the memory pool")
|
return errors.New("invalid transaction due to conflicts with the memory pool")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if IsDoubleSpend(bc.store, t) {
|
if bc.dao.IsDoubleSpend(t) {
|
||||||
return errors.New("invalid transaction caused by double spending")
|
return errors.New("invalid transaction caused by double spending")
|
||||||
}
|
}
|
||||||
if err := bc.verifyOutputs(t); err != nil {
|
if err := bc.verifyOutputs(t); err != nil {
|
||||||
|
@ -1223,25 +1156,25 @@ func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) {
|
||||||
// GetValidators returns validators.
|
// GetValidators returns validators.
|
||||||
// Golang implementation of GetValidators method in C# (https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/Persistence/Snapshot.cs#L182)
|
// Golang implementation of GetValidators method in C# (https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/Persistence/Snapshot.cs#L182)
|
||||||
func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
||||||
chainState := NewBlockChainState(bc.store)
|
cache := &dao{store: storage.NewMemCachedStore(bc.dao.store)}
|
||||||
if len(txes) > 0 {
|
if len(txes) > 0 {
|
||||||
for _, tx := range txes {
|
for _, tx := range txes {
|
||||||
// iterate through outputs
|
// iterate through outputs
|
||||||
for index, output := range tx.Outputs {
|
for index, output := range tx.Outputs {
|
||||||
accountState := bc.GetAccountState(output.ScriptHash)
|
accountState, err := cache.GetAccountState(output.ScriptHash)
|
||||||
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], UnspentBalance{
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], state.UnspentBalance{
|
||||||
Tx: tx.Hash(),
|
Tx: tx.Hash(),
|
||||||
Index: uint16(index),
|
Index: uint16(index),
|
||||||
Value: output.Amount,
|
Value: output.Amount,
|
||||||
})
|
})
|
||||||
if output.AssetID.Equals(governingTokenTX().Hash()) && len(accountState.Votes) > 0 {
|
if err := cache.PutAccountState(accountState); err != nil {
|
||||||
for _, vote := range accountState.Votes {
|
return nil, err
|
||||||
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
|
}
|
||||||
if err != nil {
|
if err = processTXWithValidatorsAdd(&output, accountState, cache); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
validatorState.Votes += output.Amount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1253,53 +1186,45 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
}
|
}
|
||||||
|
|
||||||
for hash, inputs := range group {
|
for hash, inputs := range group {
|
||||||
prevTx, _, err := bc.GetTransaction(hash)
|
prevTx, _, err := cache.GetTransaction(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// process inputs
|
// process inputs
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
prevOutput := prevTx.Outputs[input.PrevIndex]
|
prevOutput := prevTx.Outputs[input.PrevIndex]
|
||||||
accountState, err := chainState.accounts.getAndUpdate(chainState.store, prevOutput.ScriptHash)
|
accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// process account state votes: if there are any -> validators will be updated.
|
// process account state votes: if there are any -> validators will be updated.
|
||||||
if prevOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
if err = processTXWithValidatorsSubtract(accountState, cache, prevOutput.Amount); err != nil {
|
||||||
if len(accountState.Votes) > 0 {
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
delete(accountState.Balances, prevOutput.AssetID)
|
delete(accountState.Balances, prevOutput.AssetID)
|
||||||
|
if err = cache.PutAccountState(accountState); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := tx.Data.(type) {
|
switch t := tx.Data.(type) {
|
||||||
case *transaction.EnrollmentTX:
|
case *transaction.EnrollmentTX:
|
||||||
if err := processEnrollmentTX(chainState, t); err != nil {
|
if err := processEnrollmentTX(cache, t); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *transaction.StateTX:
|
case *transaction.StateTX:
|
||||||
if err := processStateTX(chainState, t); err != nil {
|
if err := processStateTX(cache, t); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validators := getValidatorsFromStore(chainState.store)
|
validators := cache.GetValidators()
|
||||||
|
|
||||||
count := GetValidatorsWeightedAverage(validators)
|
count := state.GetValidatorsWeightedAverage(validators)
|
||||||
standByValidators, err := bc.GetStandByValidators()
|
standByValidators, err := bc.GetStandByValidators()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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++ {
|
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
|
||||||
result = append(result, uniqueSBValidators[i])
|
result = append(result, uniqueSBValidators[i])
|
||||||
}
|
}
|
||||||
|
_, err = cache.store.Persist()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processStateTX(chainState *BlockChainState, tx *transaction.StateTX) error {
|
func processStateTX(dao *dao, tx *transaction.StateTX) error {
|
||||||
for _, desc := range tx.Descriptors {
|
for _, desc := range tx.Descriptors {
|
||||||
switch desc.Type {
|
switch desc.Type {
|
||||||
case transaction.Account:
|
case transaction.Account:
|
||||||
if err := processAccountStateDescriptor(desc, chainState); err != nil {
|
if err := processAccountStateDescriptor(desc, dao); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case transaction.Validator:
|
case transaction.Validator:
|
||||||
if err := processValidatorStateDescriptor(desc, chainState); err != nil {
|
if err := processValidatorStateDescriptor(desc, dao); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1343,13 +1272,13 @@ func processStateTX(chainState *BlockChainState, tx *transaction.StateTX) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processEnrollmentTX(chainState *BlockChainState, tx *transaction.EnrollmentTX) error {
|
func processEnrollmentTX(dao *dao, tx *transaction.EnrollmentTX) error {
|
||||||
validatorState, err := chainState.validators.getAndUpdate(chainState.store, &tx.PublicKey)
|
validatorState, err := dao.GetValidatorStateOrNew(&tx.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
validatorState.Registered = true
|
validatorState.Registered = true
|
||||||
return nil
|
return dao.PutValidatorState(validatorState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use
|
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use
|
||||||
|
@ -1424,7 +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.
|
// GetTestVM returns a VM and a Store setup for a test run of some sort of code.
|
||||||
func (bc *Blockchain) GetTestVM() (*vm.VM, storage.Store) {
|
func (bc *Blockchain) GetTestVM() (*vm.VM, storage.Store) {
|
||||||
tmpStore := storage.NewMemCachedStore(bc.store)
|
tmpStore := storage.NewMemCachedStore(bc.dao.store)
|
||||||
systemInterop := newInteropContext(trigger.Application, bc, tmpStore, nil, nil)
|
systemInterop := newInteropContext(trigger.Application, bc, tmpStore, nil, nil)
|
||||||
vm := bc.spawnVMWithInterops(systemInterop)
|
vm := bc.spawnVMWithInterops(systemInterop)
|
||||||
return vm, tmpStore
|
return vm, tmpStore
|
||||||
|
@ -1495,7 +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(hashes, func(i, j int) bool { return hashes[i].Less(hashes[j]) })
|
||||||
sort.Slice(witnesses, func(i, j int) bool { return witnesses[i].ScriptHash().Less(witnesses[j].ScriptHash()) })
|
sort.Slice(witnesses, func(i, j int) bool { return witnesses[i].ScriptHash().Less(witnesses[j].ScriptHash()) })
|
||||||
interopCtx := newInteropContext(trigger.Verification, bc, bc.store, block, t)
|
interopCtx := newInteropContext(trigger.Verification, bc, bc.dao.store, block, t)
|
||||||
for i := 0; i < len(hashes); i++ {
|
for i := 0; i < len(hashes); i++ {
|
||||||
err := bc.verifyHashAgainstScript(hashes[i], &witnesses[i], t.VerificationHash(), interopCtx, false)
|
err := bc.verifyHashAgainstScript(hashes[i], &witnesses[i], t.VerificationHash(), interopCtx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1515,7 +1444,7 @@ func (bc *Blockchain) verifyBlockWitnesses(block *Block, prevHeader *Header) err
|
||||||
} else {
|
} else {
|
||||||
hash = prevHeader.NextConsensus
|
hash = prevHeader.NextConsensus
|
||||||
}
|
}
|
||||||
interopCtx := newInteropContext(trigger.Verification, bc, bc.store, nil, nil)
|
interopCtx := newInteropContext(trigger.Verification, bc, bc.dao.store, nil, nil)
|
||||||
return bc.verifyHashAgainstScript(hash, &block.Script, block.VerificationHash(), interopCtx, true)
|
return bc.verifyHashAgainstScript(hash, &block.Script, block.VerificationHash(), interopCtx, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -56,7 +56,7 @@ func TestAddBlock(t *testing.T) {
|
||||||
|
|
||||||
for _, block := range blocks {
|
for _, block := range blocks {
|
||||||
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
|
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
|
||||||
if _, err := bc.store.Get(key); err != nil {
|
if _, err := bc.dao.store.Get(key); err != nil {
|
||||||
t.Fatalf("block %s not persisted", block.Hash())
|
t.Fatalf("block %s not persisted", block.Hash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ func TestClose(t *testing.T) {
|
||||||
// It's a hack, but we use internal knowledge of MemoryStore
|
// It's a hack, but we use internal knowledge of MemoryStore
|
||||||
// implementation which makes it completely unusable (up to panicing)
|
// implementation which makes it completely unusable (up to panicing)
|
||||||
// after Close().
|
// after Close().
|
||||||
_ = bc.store.Put([]byte{0}, []byte{1})
|
_ = bc.dao.store.Put([]byte{0}, []byte{1})
|
||||||
|
|
||||||
// This should never be executed.
|
// This should never be executed.
|
||||||
assert.Nil(t, t)
|
assert.Nil(t, t)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
@ -19,19 +20,19 @@ type Blockchainer interface {
|
||||||
Close()
|
Close()
|
||||||
HeaderHeight() uint32
|
HeaderHeight() uint32
|
||||||
GetBlock(hash util.Uint256) (*Block, error)
|
GetBlock(hash util.Uint256) (*Block, error)
|
||||||
GetContractState(hash util.Uint160) *ContractState
|
GetContractState(hash util.Uint160) *state.Contract
|
||||||
GetHeaderHash(int) util.Uint256
|
GetHeaderHash(int) util.Uint256
|
||||||
GetHeader(hash util.Uint256) (*Header, error)
|
GetHeader(hash util.Uint256) (*Header, error)
|
||||||
CurrentHeaderHash() util.Uint256
|
CurrentHeaderHash() util.Uint256
|
||||||
CurrentBlockHash() util.Uint256
|
CurrentBlockHash() util.Uint256
|
||||||
HasBlock(util.Uint256) bool
|
HasBlock(util.Uint256) bool
|
||||||
HasTransaction(util.Uint256) bool
|
HasTransaction(util.Uint256) bool
|
||||||
GetAssetState(util.Uint256) *AssetState
|
GetAssetState(util.Uint256) *state.Asset
|
||||||
GetAccountState(util.Uint160) *AccountState
|
GetAccountState(util.Uint160) *state.Account
|
||||||
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
GetValidators(txes... *transaction.Transaction) ([]*keys.PublicKey, error)
|
||||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||||
GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem
|
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||||
GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error)
|
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
||||||
GetTestVM() (*vm.VM, storage.Store)
|
GetTestVM() (*vm.VM, storage.Store)
|
||||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
GetUnspentCoinState(util.Uint256) *UnspentCoinState
|
GetUnspentCoinState(util.Uint256) *UnspentCoinState
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
554
pkg/core/dao.go
Normal file
554
pkg/core/dao.go
Normal file
|
@ -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
|
||||||
|
}
|
339
pkg/core/dao_test.go
Normal file
339
pkg/core/dao_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
@ -316,7 +317,7 @@ func (ic *interopContext) bcGetAccount(v *vm.VM) error {
|
||||||
}
|
}
|
||||||
acc := ic.bc.GetAccountState(acchash)
|
acc := ic.bc.GetAccountState(acchash)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
acc = NewAccountState(acchash)
|
acc = state.NewAccount(acchash)
|
||||||
}
|
}
|
||||||
v.Estack().PushVal(vm.NewInteropItem(acc))
|
v.Estack().PushVal(vm.NewInteropItem(acc))
|
||||||
return nil
|
return nil
|
||||||
|
@ -340,7 +341,7 @@ func (ic *interopContext) bcGetAsset(v *vm.VM) error {
|
||||||
// accountGetBalance returns balance for a given account.
|
// accountGetBalance returns balance for a given account.
|
||||||
func (ic *interopContext) accountGetBalance(v *vm.VM) error {
|
func (ic *interopContext) accountGetBalance(v *vm.VM) error {
|
||||||
accInterface := v.Estack().Pop().Value()
|
accInterface := v.Estack().Pop().Value()
|
||||||
acc, ok := accInterface.(*AccountState)
|
acc, ok := accInterface.(*state.Account)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an account state", acc)
|
return fmt.Errorf("%T is not an account state", acc)
|
||||||
}
|
}
|
||||||
|
@ -360,7 +361,7 @@ func (ic *interopContext) accountGetBalance(v *vm.VM) error {
|
||||||
// accountGetScriptHash returns script hash of a given account.
|
// accountGetScriptHash returns script hash of a given account.
|
||||||
func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
|
func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
|
||||||
accInterface := v.Estack().Pop().Value()
|
accInterface := v.Estack().Pop().Value()
|
||||||
acc, ok := accInterface.(*AccountState)
|
acc, ok := accInterface.(*state.Account)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an account state", acc)
|
return fmt.Errorf("%T is not an account state", acc)
|
||||||
}
|
}
|
||||||
|
@ -371,7 +372,7 @@ func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
|
||||||
// accountGetVotes returns votes of a given account.
|
// accountGetVotes returns votes of a given account.
|
||||||
func (ic *interopContext) accountGetVotes(v *vm.VM) error {
|
func (ic *interopContext) accountGetVotes(v *vm.VM) error {
|
||||||
accInterface := v.Estack().Pop().Value()
|
accInterface := v.Estack().Pop().Value()
|
||||||
acc, ok := accInterface.(*AccountState)
|
acc, ok := accInterface.(*state.Account)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an account state", acc)
|
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
|
// 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.
|
// 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 {
|
if ic.trigger != trigger.Application {
|
||||||
return nil, errors.New("can't create contract when not triggered by an application")
|
return nil, errors.New("can't create contract when not triggered by an application")
|
||||||
}
|
}
|
||||||
|
@ -467,7 +468,7 @@ func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*ContractState, e
|
||||||
if len(desc) > MaxContractStringLen {
|
if len(desc) > MaxContractStringLen {
|
||||||
return nil, errors.New("too big description")
|
return nil, errors.New("too big description")
|
||||||
}
|
}
|
||||||
contract := &ContractState{
|
contract := &state.Contract{
|
||||||
Script: script,
|
Script: script,
|
||||||
ParamList: paramList,
|
ParamList: paramList,
|
||||||
ReturnType: retType,
|
ReturnType: retType,
|
||||||
|
@ -490,7 +491,7 @@ func (ic *interopContext) contractCreate(v *vm.VM) error {
|
||||||
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
||||||
if contract == nil {
|
if contract == nil {
|
||||||
contract = newcontract
|
contract = newcontract
|
||||||
err := putContractStateIntoStore(ic.mem, contract)
|
err := ic.dao.PutContractState(contract)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -502,7 +503,7 @@ func (ic *interopContext) contractCreate(v *vm.VM) error {
|
||||||
// contractGetScript returns a script associated with a contract.
|
// contractGetScript returns a script associated with a contract.
|
||||||
func (ic *interopContext) contractGetScript(v *vm.VM) error {
|
func (ic *interopContext) contractGetScript(v *vm.VM) error {
|
||||||
csInterface := v.Estack().Pop().Value()
|
csInterface := v.Estack().Pop().Value()
|
||||||
cs, ok := csInterface.(*ContractState)
|
cs, ok := csInterface.(*state.Contract)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not a contract state", cs)
|
return fmt.Errorf("%T is not a contract state", cs)
|
||||||
}
|
}
|
||||||
|
@ -513,7 +514,7 @@ func (ic *interopContext) contractGetScript(v *vm.VM) error {
|
||||||
// contractIsPayable returns whether contract is payable.
|
// contractIsPayable returns whether contract is payable.
|
||||||
func (ic *interopContext) contractIsPayable(v *vm.VM) error {
|
func (ic *interopContext) contractIsPayable(v *vm.VM) error {
|
||||||
csInterface := v.Estack().Pop().Value()
|
csInterface := v.Estack().Pop().Value()
|
||||||
cs, ok := csInterface.(*ContractState)
|
cs, ok := csInterface.(*state.Contract)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not a contract state", cs)
|
return fmt.Errorf("%T is not a contract state", cs)
|
||||||
}
|
}
|
||||||
|
@ -530,7 +531,7 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error {
|
||||||
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
||||||
if contract == nil {
|
if contract == nil {
|
||||||
contract = newcontract
|
contract = newcontract
|
||||||
err := putContractStateIntoStore(ic.mem, contract)
|
err := ic.dao.PutContractState(contract)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -542,7 +543,7 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error {
|
||||||
}
|
}
|
||||||
for k, v := range siMap {
|
for k, v := range siMap {
|
||||||
v.IsConst = false
|
v.IsConst = false
|
||||||
_ = putStorageItemIntoStore(ic.mem, hash, []byte(k), v)
|
_ = ic.dao.PutStorageItem(hash, []byte(k), v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -609,7 +610,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gherr.Wrap(err, "failed to get issuer")
|
return gherr.Wrap(err, "failed to get issuer")
|
||||||
}
|
}
|
||||||
asset := &AssetState{
|
asset := &state.Asset{
|
||||||
ID: ic.tx.Hash(),
|
ID: ic.tx.Hash(),
|
||||||
AssetType: atype,
|
AssetType: atype,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -620,7 +621,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
|
||||||
Issuer: issuer,
|
Issuer: issuer,
|
||||||
Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime,
|
Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime,
|
||||||
}
|
}
|
||||||
err = putAssetStateIntoStore(ic.mem, asset)
|
err = ic.dao.PutAssetState(asset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gherr.Wrap(err, "failed to store asset")
|
return gherr.Wrap(err, "failed to store asset")
|
||||||
}
|
}
|
||||||
|
@ -631,7 +632,7 @@ func (ic *interopContext) assetCreate(v *vm.VM) error {
|
||||||
// assetGetAdmin returns asset admin.
|
// assetGetAdmin returns asset admin.
|
||||||
func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
|
func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -642,7 +643,7 @@ func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
|
||||||
// assetGetAmount returns the overall amount of asset available.
|
// assetGetAmount returns the overall amount of asset available.
|
||||||
func (ic *interopContext) assetGetAmount(v *vm.VM) error {
|
func (ic *interopContext) assetGetAmount(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -653,7 +654,7 @@ func (ic *interopContext) assetGetAmount(v *vm.VM) error {
|
||||||
// assetGetAssetId returns the id of an asset.
|
// assetGetAssetId returns the id of an asset.
|
||||||
func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
|
func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -664,7 +665,7 @@ func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
|
||||||
// assetGetAssetType returns type of an asset.
|
// assetGetAssetType returns type of an asset.
|
||||||
func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
|
func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -675,7 +676,7 @@ func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
|
||||||
// assetGetAvailable returns available (not yet issued) amount of asset.
|
// assetGetAvailable returns available (not yet issued) amount of asset.
|
||||||
func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
|
func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -686,7 +687,7 @@ func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
|
||||||
// assetGetIssuer returns issuer of an asset.
|
// assetGetIssuer returns issuer of an asset.
|
||||||
func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
|
func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -697,7 +698,7 @@ func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
|
||||||
// assetGetOwner returns owner of an asset.
|
// assetGetOwner returns owner of an asset.
|
||||||
func (ic *interopContext) assetGetOwner(v *vm.VM) error {
|
func (ic *interopContext) assetGetOwner(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -708,7 +709,7 @@ func (ic *interopContext) assetGetOwner(v *vm.VM) error {
|
||||||
// assetGetPrecision returns precision used to measure this asset.
|
// assetGetPrecision returns precision used to measure this asset.
|
||||||
func (ic *interopContext) assetGetPrecision(v *vm.VM) error {
|
func (ic *interopContext) assetGetPrecision(v *vm.VM) error {
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -722,7 +723,7 @@ func (ic *interopContext) assetRenew(v *vm.VM) error {
|
||||||
return errors.New("can't create asset when not triggered by an application")
|
return errors.New("can't create asset when not triggered by an application")
|
||||||
}
|
}
|
||||||
asInterface := v.Estack().Pop().Value()
|
asInterface := v.Estack().Pop().Value()
|
||||||
as, ok := asInterface.(*AssetState)
|
as, ok := asInterface.(*state.Asset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not an asset state", as)
|
return fmt.Errorf("%T is not an asset state", as)
|
||||||
}
|
}
|
||||||
|
@ -740,7 +741,7 @@ func (ic *interopContext) assetRenew(v *vm.VM) error {
|
||||||
expiration = math.MaxUint32
|
expiration = math.MaxUint32
|
||||||
}
|
}
|
||||||
asset.Expiration = uint32(expiration)
|
asset.Expiration = uint32(expiration)
|
||||||
err := putAssetStateIntoStore(ic.mem, asset)
|
err := ic.dao.PutAssetState(asset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gherr.Wrap(err, "failed to store asset")
|
return gherr.Wrap(err, "failed to store asset")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/internal/random"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"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)
|
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) {
|
func createVMAndPushBlock(t *testing.T) (*vm.VM, *Block, *interopContext) {
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
|
@ -337,9 +339,9 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop
|
||||||
return v, tx, context
|
return v, tx, context
|
||||||
}
|
}
|
||||||
|
|
||||||
func createVMAndAssetState(t *testing.T) (*vm.VM, *AssetState, *interopContext) {
|
func createVMAndAssetState(t *testing.T) (*vm.VM, *state.Asset, *interopContext) {
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
assetState := &AssetState{
|
assetState := &state.Asset{
|
||||||
ID: util.Uint256{},
|
ID: util.Uint256{},
|
||||||
AssetType: transaction.GoverningToken,
|
AssetType: transaction.GoverningToken,
|
||||||
Name: "TestAsset",
|
Name: "TestAsset",
|
||||||
|
@ -347,10 +349,10 @@ func createVMAndAssetState(t *testing.T) (*vm.VM, *AssetState, *interopContext)
|
||||||
Available: 2,
|
Available: 2,
|
||||||
Precision: 1,
|
Precision: 1,
|
||||||
FeeMode: 1,
|
FeeMode: 1,
|
||||||
FeeAddress: randomUint160(),
|
FeeAddress: random.Uint160(),
|
||||||
Owner: keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)},
|
Owner: keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)},
|
||||||
Admin: randomUint160(),
|
Admin: random.Uint160(),
|
||||||
Issuer: randomUint160(),
|
Issuer: random.Uint160(),
|
||||||
Expiration: 10,
|
Expiration: 10,
|
||||||
IsFrozen: false,
|
IsFrozen: false,
|
||||||
}
|
}
|
||||||
|
@ -359,30 +361,29 @@ func createVMAndAssetState(t *testing.T) (*vm.VM, *AssetState, *interopContext)
|
||||||
return v, assetState, context
|
return v, assetState, context
|
||||||
}
|
}
|
||||||
|
|
||||||
func createVMAndContractState(t *testing.T) (*vm.VM, *ContractState, *interopContext) {
|
func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interopContext) {
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
contractState := &ContractState{
|
contractState := &state.Contract{
|
||||||
Script: []byte("testscript"),
|
Script: []byte("testscript"),
|
||||||
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
||||||
ReturnType: smartcontract.ArrayType,
|
ReturnType: smartcontract.ArrayType,
|
||||||
Properties: smartcontract.HasStorage,
|
Properties: smartcontract.HasStorage,
|
||||||
Name: randomString(10),
|
Name: random.String(10),
|
||||||
CodeVersion: randomString(10),
|
CodeVersion: random.String(10),
|
||||||
Author: randomString(10),
|
Author: random.String(10),
|
||||||
Email: randomString(10),
|
Email: random.String(10),
|
||||||
Description: randomString(10),
|
Description: random.String(10),
|
||||||
scriptHash: randomUint160(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context := newInteropContext(trigger.Application, newTestChain(t), storage.NewMemoryStore(), nil, nil)
|
context := newInteropContext(trigger.Application, newTestChain(t), storage.NewMemoryStore(), nil, nil)
|
||||||
return v, contractState, context
|
return v, contractState, context
|
||||||
}
|
}
|
||||||
|
|
||||||
func createVMAndAccState(t *testing.T) (*vm.VM, *AccountState, *interopContext) {
|
func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interopContext) {
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302"
|
rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302"
|
||||||
hash, err := util.Uint160DecodeStringBE(rawHash)
|
hash, err := util.Uint160DecodeStringBE(rawHash)
|
||||||
accountState := NewAccountState(hash)
|
accountState := state.NewAccount(hash)
|
||||||
|
|
||||||
key := &keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)}
|
key := &keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)}
|
||||||
accountState.Votes = []*keys.PublicKey{key}
|
accountState.Votes = []*keys.PublicKey{key}
|
||||||
|
@ -403,14 +404,14 @@ func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interopCont
|
||||||
})
|
})
|
||||||
|
|
||||||
inputs := append(tx.Inputs, transaction.Input{
|
inputs := append(tx.Inputs, transaction.Input{
|
||||||
PrevHash: randomUint256(),
|
PrevHash: random.Uint256(),
|
||||||
PrevIndex: 1,
|
PrevIndex: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
outputs := append(tx.Outputs, transaction.Output{
|
outputs := append(tx.Outputs, transaction.Output{
|
||||||
AssetID: randomUint256(),
|
AssetID: random.Uint256(),
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
ScriptHash: randomUint160(),
|
ScriptHash: random.Uint160(),
|
||||||
Position: 1,
|
Position: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
@ -341,7 +342,7 @@ func (ic *interopContext) runtimeCheckWitness(v *vm.VM) error {
|
||||||
func (ic *interopContext) runtimeNotify(v *vm.VM) error {
|
func (ic *interopContext) runtimeNotify(v *vm.VM) error {
|
||||||
// It can be just about anything.
|
// It can be just about anything.
|
||||||
e := v.Estack().Pop()
|
e := v.Estack().Pop()
|
||||||
ne := NotificationEvent{getContextScriptHash(v, 0), e.Item()}
|
ne := state.NotificationEvent{ScriptHash: getContextScriptHash(v, 0), Item: e.Item()}
|
||||||
ic.notifications = append(ic.notifications, ne)
|
ic.notifications = append(ic.notifications, ne)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -410,11 +411,11 @@ func (ic *interopContext) storageDelete(v *vm.VM) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
key := v.Estack().Pop().Bytes()
|
key := v.Estack().Pop().Bytes()
|
||||||
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
si := ic.dao.GetStorageItem(stc.ScriptHash, key)
|
||||||
if si != nil && si.IsConst {
|
if si != nil && si.IsConst {
|
||||||
return errors.New("storage item is constant")
|
return errors.New("storage item is constant")
|
||||||
}
|
}
|
||||||
return deleteStorageItemInStore(ic.mem, stc.ScriptHash, key)
|
return ic.dao.DeleteStorageItem(stc.ScriptHash, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// storageGet returns stored key-value pair.
|
// storageGet returns stored key-value pair.
|
||||||
|
@ -429,7 +430,7 @@ func (ic *interopContext) storageGet(v *vm.VM) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
key := v.Estack().Pop().Bytes()
|
key := v.Estack().Pop().Bytes()
|
||||||
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
si := ic.dao.GetStorageItem(stc.ScriptHash, key)
|
||||||
if si != nil && si.Value != nil {
|
if si != nil && si.Value != nil {
|
||||||
v.Estack().PushVal(si.Value)
|
v.Estack().PushVal(si.Value)
|
||||||
} else {
|
} else {
|
||||||
|
@ -472,16 +473,16 @@ func (ic *interopContext) putWithContextAndFlags(stc *StorageContext, key []byte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
si := ic.dao.GetStorageItem(stc.ScriptHash, key)
|
||||||
if si == nil {
|
if si == nil {
|
||||||
si = &StorageItem{}
|
si = &state.StorageItem{}
|
||||||
}
|
}
|
||||||
if si.IsConst {
|
if si.IsConst {
|
||||||
return errors.New("storage item exists and is read-only")
|
return errors.New("storage item exists and is read-only")
|
||||||
}
|
}
|
||||||
si.Value = value
|
si.Value = value
|
||||||
si.IsConst = isConst
|
si.IsConst = isConst
|
||||||
return putStorageItemIntoStore(ic.mem, stc.ScriptHash, key, si)
|
return ic.dao.PutStorageItem(stc.ScriptHash, key, si)
|
||||||
}
|
}
|
||||||
|
|
||||||
// storagePutInternal is a unified implementation of storagePut and storagePutEx.
|
// storagePutInternal is a unified implementation of storagePut and storagePutEx.
|
||||||
|
@ -538,7 +539,7 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
|
||||||
if cs == nil {
|
if cs == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := deleteContractStateInStore(ic.mem, hash)
|
err := ic.dao.DeleteContractState(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -548,7 +549,7 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for k := range siMap {
|
for k := range siMap {
|
||||||
_ = deleteStorageItemInStore(ic.mem, hash, []byte(k))
|
_ = ic.dao.DeleteStorageItem(hash, []byte(k))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -557,11 +558,12 @@ func (ic *interopContext) contractDestroy(v *vm.VM) error {
|
||||||
// contractGetStorageContext retrieves StorageContext of a contract.
|
// contractGetStorageContext retrieves StorageContext of a contract.
|
||||||
func (ic *interopContext) contractGetStorageContext(v *vm.VM) error {
|
func (ic *interopContext) contractGetStorageContext(v *vm.VM) error {
|
||||||
csInterface := v.Estack().Pop().Value()
|
csInterface := v.Estack().Pop().Value()
|
||||||
cs, ok := csInterface.(*ContractState)
|
cs, ok := csInterface.(*state.Contract)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("%T is not a contract state", cs)
|
return fmt.Errorf("%T is not a contract state", cs)
|
||||||
}
|
}
|
||||||
if getContractStateFromStore(ic.mem, cs.ScriptHash()) == nil {
|
contractState, err := ic.dao.GetContractState(cs.ScriptHash())
|
||||||
|
if contractState == nil || err != nil {
|
||||||
return fmt.Errorf("contract was not created in this transaction")
|
return fmt.Errorf("contract was not created in this transaction")
|
||||||
}
|
}
|
||||||
stc := &StorageContext{
|
stc := &StorageContext{
|
||||||
|
|
|
@ -8,6 +8,7 @@ package core
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
@ -18,14 +19,14 @@ type interopContext struct {
|
||||||
trigger byte
|
trigger byte
|
||||||
block *Block
|
block *Block
|
||||||
tx *transaction.Transaction
|
tx *transaction.Transaction
|
||||||
mem *storage.MemCachedStore
|
dao *dao
|
||||||
notifications []NotificationEvent
|
notifications []state.NotificationEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext {
|
func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext {
|
||||||
mem := storage.NewMemCachedStore(s)
|
dao := &dao{store: storage.NewMemCachedStore(s)}
|
||||||
nes := make([]NotificationEvent, 0)
|
nes := make([]state.NotificationEvent, 0)
|
||||||
return &interopContext{bc, trigger, block, tx, mem, nes}
|
return &interopContext{bc, trigger, block, tx, dao, nes}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All lists are sorted, keep 'em this way, please.
|
// All lists are sorted, keep 'em this way, please.
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -1,60 +1,10 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SpentCoins is mapping between transactions and their spent
|
|
||||||
// coin state.
|
|
||||||
type SpentCoins map[util.Uint256]*SpentCoinState
|
|
||||||
|
|
||||||
func (s SpentCoins) getAndUpdate(store storage.Store, hash util.Uint256) (*SpentCoinState, error) {
|
|
||||||
if spent, ok := s[hash]; ok {
|
|
||||||
return spent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
spent := &SpentCoinState{}
|
|
||||||
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
|
|
||||||
if b, err := store.Get(key); err == nil {
|
|
||||||
r := io.NewBinReaderFromBuf(b)
|
|
||||||
spent.DecodeBinary(r)
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode (UnspentCoinState): %s", r.Err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
spent = &SpentCoinState{
|
|
||||||
items: make(map[uint16]uint32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s[hash] = spent
|
|
||||||
return spent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// putSpentCoinStateIntoStore puts given SpentCoinState into the given store.
|
|
||||||
func putSpentCoinStateIntoStore(store storage.Store, hash util.Uint256, scs *SpentCoinState) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
scs.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesLE())
|
|
||||||
return store.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SpentCoins) commit(store storage.Store) error {
|
|
||||||
for hash, state := range s {
|
|
||||||
if err := putSpentCoinStateIntoStore(store, hash, state); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpentCoinState represents the state of a spent coin.
|
// SpentCoinState represents the state of a spent coin.
|
||||||
type SpentCoinState struct {
|
type SpentCoinState struct {
|
||||||
txHash util.Uint256
|
txHash util.Uint256
|
||||||
|
|
|
@ -3,15 +3,14 @@ package core
|
||||||
import (
|
import (
|
||||||
"testing"
|
"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/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodeDecodeSpentCoinState(t *testing.T) {
|
func TestEncodeDecodeSpentCoinState(t *testing.T) {
|
||||||
spent := &SpentCoinState{
|
spent := &SpentCoinState{
|
||||||
txHash: randomUint256(),
|
txHash: random.Uint256(),
|
||||||
txHeight: 1001,
|
txHeight: 1001,
|
||||||
items: map[uint16]uint32{
|
items: map[uint16]uint32{
|
||||||
1: 3,
|
1: 3,
|
||||||
|
@ -29,24 +28,3 @@ func TestEncodeDecodeSpentCoinState(t *testing.T) {
|
||||||
assert.Nil(t, r.Err)
|
assert.Nil(t, r.Err)
|
||||||
assert.Equal(t, spent, spentDecode)
|
assert.Equal(t, spent, spentDecode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCommitSpentCoins(t *testing.T) {
|
|
||||||
var (
|
|
||||||
store = storage.NewMemoryStore()
|
|
||||||
spentCoins = make(SpentCoins)
|
|
||||||
)
|
|
||||||
|
|
||||||
txx := []util.Uint256{
|
|
||||||
randomUint256(),
|
|
||||||
randomUint256(),
|
|
||||||
randomUint256(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(txx); i++ {
|
|
||||||
spentCoins[txx[i]] = &SpentCoinState{
|
|
||||||
txHash: txx[i],
|
|
||||||
txHeight: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.Nil(t, spentCoins.commit(store))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,74 +1,11 @@
|
||||||
package core
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Accounts is mapping between a account address and AccountState.
|
|
||||||
type Accounts map[util.Uint160]*AccountState
|
|
||||||
|
|
||||||
// getAndUpdate retrieves AccountState from temporary or persistent Store
|
|
||||||
// or creates a new one if it doesn't exist.
|
|
||||||
func (a Accounts) getAndUpdate(s storage.Store, hash util.Uint160) (*AccountState, error) {
|
|
||||||
if account, ok := a[hash]; ok {
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := getAccountStateFromStore(s, hash)
|
|
||||||
if err != nil {
|
|
||||||
if err != storage.ErrKeyNotFound {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
account = NewAccountState(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
a[hash] = account
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAccountStateFromStore returns AccountState from the given Store if it's
|
|
||||||
// present there. Returns nil otherwise.
|
|
||||||
func getAccountStateFromStore(s storage.Store, hash util.Uint160) (*AccountState, error) {
|
|
||||||
var account *AccountState
|
|
||||||
key := storage.AppendPrefix(storage.STAccount, hash.BytesBE())
|
|
||||||
b, err := s.Get(key)
|
|
||||||
if err == nil {
|
|
||||||
account = new(AccountState)
|
|
||||||
r := io.NewBinReaderFromBuf(b)
|
|
||||||
account.DecodeBinary(r)
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode (AccountState): %s", r.Err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return account, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// putAccountStateIntoStore puts given AccountState into the given store.
|
|
||||||
func putAccountStateIntoStore(store storage.Store, as *AccountState) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
as.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
key := storage.AppendPrefix(storage.STAccount, as.ScriptHash.BytesBE())
|
|
||||||
return store.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit writes all account states to the given Batch.
|
|
||||||
func (a Accounts) commit(store storage.Store) error {
|
|
||||||
for _, state := range a {
|
|
||||||
if err := putAccountStateIntoStore(store, state); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnspentBalance contains input/output transactons that sum up into the
|
// UnspentBalance contains input/output transactons that sum up into the
|
||||||
// account balance for the given asset.
|
// account balance for the given asset.
|
||||||
type UnspentBalance struct {
|
type UnspentBalance struct {
|
||||||
|
@ -80,8 +17,8 @@ type UnspentBalance struct {
|
||||||
// UnspentBalances is a slice of UnspentBalance (mostly needed to sort them).
|
// UnspentBalances is a slice of UnspentBalance (mostly needed to sort them).
|
||||||
type UnspentBalances []UnspentBalance
|
type UnspentBalances []UnspentBalance
|
||||||
|
|
||||||
// AccountState represents the state of a NEO account.
|
// Account represents the state of a NEO account.
|
||||||
type AccountState struct {
|
type Account struct {
|
||||||
Version uint8
|
Version uint8
|
||||||
ScriptHash util.Uint160
|
ScriptHash util.Uint160
|
||||||
IsFrozen bool
|
IsFrozen bool
|
||||||
|
@ -89,9 +26,9 @@ type AccountState struct {
|
||||||
Balances map[util.Uint256][]UnspentBalance
|
Balances map[util.Uint256][]UnspentBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountState returns a new AccountState object.
|
// NewAccount returns a new Account object.
|
||||||
func NewAccountState(scriptHash util.Uint160) *AccountState {
|
func NewAccount(scriptHash util.Uint160) *Account {
|
||||||
return &AccountState{
|
return &Account{
|
||||||
Version: 0,
|
Version: 0,
|
||||||
ScriptHash: scriptHash,
|
ScriptHash: scriptHash,
|
||||||
IsFrozen: false,
|
IsFrozen: false,
|
||||||
|
@ -100,8 +37,8 @@ func NewAccountState(scriptHash util.Uint160) *AccountState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary decodes AccountState from the given BinReader.
|
// DecodeBinary decodes Account from the given BinReader.
|
||||||
func (s *AccountState) DecodeBinary(br *io.BinReader) {
|
func (s *Account) DecodeBinary(br *io.BinReader) {
|
||||||
br.ReadLE(&s.Version)
|
br.ReadLE(&s.Version)
|
||||||
br.ReadBytes(s.ScriptHash[:])
|
br.ReadBytes(s.ScriptHash[:])
|
||||||
br.ReadLE(&s.IsFrozen)
|
br.ReadLE(&s.IsFrozen)
|
||||||
|
@ -118,8 +55,8 @@ func (s *AccountState) DecodeBinary(br *io.BinReader) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary encodes AccountState to the given BinWriter.
|
// EncodeBinary encodes Account to the given BinWriter.
|
||||||
func (s *AccountState) EncodeBinary(bw *io.BinWriter) {
|
func (s *Account) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteLE(s.Version)
|
bw.WriteLE(s.Version)
|
||||||
bw.WriteBytes(s.ScriptHash[:])
|
bw.WriteBytes(s.ScriptHash[:])
|
||||||
bw.WriteLE(s.IsFrozen)
|
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
|
// GetBalanceValues sums all unspent outputs and returns a map of asset IDs to
|
||||||
// overall balances.
|
// 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)
|
res := make(map[util.Uint256]util.Fixed8)
|
||||||
for k, v := range s.Balances {
|
for k, v := range s.Balances {
|
||||||
balance := util.Fixed8(0)
|
balance := util.Fixed8(0)
|
|
@ -1,9 +1,10 @@
|
||||||
package core
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"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/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -16,12 +17,12 @@ func TestDecodeEncodeAccountState(t *testing.T) {
|
||||||
votes = make([]*keys.PublicKey, n)
|
votes = make([]*keys.PublicKey, n)
|
||||||
)
|
)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
asset := randomUint256()
|
asset := random.Uint256()
|
||||||
for j := 0; j < i+1; j++ {
|
for j := 0; j < i+1; j++ {
|
||||||
balances[asset] = append(balances[asset], UnspentBalance{
|
balances[asset] = append(balances[asset], UnspentBalance{
|
||||||
Tx: randomUint256(),
|
Tx: random.Uint256(),
|
||||||
Index: uint16(randomInt(0, 65535)),
|
Index: uint16(random.Int(0, 65535)),
|
||||||
Value: util.Fixed8(int64(randomInt(1, 10000))),
|
Value: util.Fixed8(int64(random.Int(1, 10000))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
k, err := keys.NewPrivateKey()
|
k, err := keys.NewPrivateKey()
|
||||||
|
@ -29,9 +30,9 @@ func TestDecodeEncodeAccountState(t *testing.T) {
|
||||||
votes[i] = k.PublicKey()
|
votes[i] = k.PublicKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &AccountState{
|
a := &Account{
|
||||||
Version: 0,
|
Version: 0,
|
||||||
ScriptHash: randomUint160(),
|
ScriptHash: random.Uint160(),
|
||||||
IsFrozen: true,
|
IsFrozen: true,
|
||||||
Votes: votes,
|
Votes: votes,
|
||||||
Balances: balances,
|
Balances: balances,
|
||||||
|
@ -41,7 +42,7 @@ func TestDecodeEncodeAccountState(t *testing.T) {
|
||||||
a.EncodeBinary(buf.BinWriter)
|
a.EncodeBinary(buf.BinWriter)
|
||||||
assert.Nil(t, buf.Err)
|
assert.Nil(t, buf.Err)
|
||||||
|
|
||||||
aDecode := &AccountState{}
|
aDecode := &Account{}
|
||||||
r := io.NewBinReaderFromBuf(buf.Bytes())
|
r := io.NewBinReaderFromBuf(buf.Bytes())
|
||||||
aDecode.DecodeBinary(r)
|
aDecode.DecodeBinary(r)
|
||||||
assert.Nil(t, r.Err)
|
assert.Nil(t, r.Err)
|
||||||
|
@ -57,9 +58,9 @@ func TestDecodeEncodeAccountState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountStateBalanceValues(t *testing.T) {
|
func TestAccountStateBalanceValues(t *testing.T) {
|
||||||
asset1 := randomUint256()
|
asset1 := random.Uint256()
|
||||||
asset2 := randomUint256()
|
asset2 := random.Uint256()
|
||||||
as := AccountState{Balances: make(map[util.Uint256][]UnspentBalance)}
|
as := Account{Balances: make(map[util.Uint256][]UnspentBalance)}
|
||||||
ref := 0
|
ref := 0
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
ref += i
|
ref += i
|
|
@ -1,7 +1,6 @@
|
||||||
package core
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
|
@ -10,31 +9,8 @@ import (
|
||||||
|
|
||||||
const feeMode = 0x0
|
const feeMode = 0x0
|
||||||
|
|
||||||
// Assets is mapping between AssetID and the AssetState.
|
// Asset represents the state of an NEO registered Asset.
|
||||||
type Assets map[util.Uint256]*AssetState
|
type Asset struct {
|
||||||
|
|
||||||
func (a Assets) commit(store storage.Store) error {
|
|
||||||
for _, state := range a {
|
|
||||||
if err := putAssetStateIntoStore(store, state); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// putAssetStateIntoStore puts given asset state into the given store.
|
|
||||||
func putAssetStateIntoStore(s storage.Store, as *AssetState) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
as.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
key := storage.AppendPrefix(storage.STAsset, as.ID.BytesBE())
|
|
||||||
return s.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetState represents the state of an NEO registered Asset.
|
|
||||||
type AssetState struct {
|
|
||||||
ID util.Uint256
|
ID util.Uint256
|
||||||
AssetType transaction.AssetType
|
AssetType transaction.AssetType
|
||||||
Name string
|
Name string
|
||||||
|
@ -51,7 +27,7 @@ type AssetState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements Serializable interface.
|
// DecodeBinary implements Serializable interface.
|
||||||
func (a *AssetState) DecodeBinary(br *io.BinReader) {
|
func (a *Asset) DecodeBinary(br *io.BinReader) {
|
||||||
br.ReadBytes(a.ID[:])
|
br.ReadBytes(a.ID[:])
|
||||||
br.ReadLE(&a.AssetType)
|
br.ReadLE(&a.AssetType)
|
||||||
|
|
||||||
|
@ -71,7 +47,7 @@ func (a *AssetState) DecodeBinary(br *io.BinReader) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements Serializable interface.
|
// EncodeBinary implements Serializable interface.
|
||||||
func (a *AssetState) EncodeBinary(bw *io.BinWriter) {
|
func (a *Asset) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteBytes(a.ID[:])
|
bw.WriteBytes(a.ID[:])
|
||||||
bw.WriteLE(a.AssetType)
|
bw.WriteLE(a.AssetType)
|
||||||
bw.WriteString(a.Name)
|
bw.WriteString(a.Name)
|
||||||
|
@ -90,7 +66,7 @@ func (a *AssetState) EncodeBinary(bw *io.BinWriter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the asset name based on its type.
|
// GetName returns the asset name based on its type.
|
||||||
func (a *AssetState) GetName() string {
|
func (a *Asset) GetName() string {
|
||||||
|
|
||||||
if a.AssetType == transaction.GoverningToken {
|
if a.AssetType == transaction.GoverningToken {
|
||||||
return "NEO"
|
return "NEO"
|
48
pkg/core/state/asset_test.go
Normal file
48
pkg/core/state/asset_test.go
Normal file
|
@ -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())
|
||||||
|
}
|
12
pkg/core/state/coin.go
Normal file
12
pkg/core/state/coin.go
Normal file
|
@ -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
|
||||||
|
)
|
|
@ -1,18 +1,14 @@
|
||||||
package core
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contracts is a mapping between scripthash and ContractState.
|
// Contract holds information about a smart contract in the NEO blockchain.
|
||||||
type Contracts map[util.Uint160]*ContractState
|
type Contract struct {
|
||||||
|
|
||||||
// ContractState holds information about a smart contract in the NEO blockchain.
|
|
||||||
type ContractState struct {
|
|
||||||
Script []byte
|
Script []byte
|
||||||
ParamList []smartcontract.ParamType
|
ParamList []smartcontract.ParamType
|
||||||
ReturnType smartcontract.ParamType
|
ReturnType smartcontract.ParamType
|
||||||
|
@ -26,18 +22,8 @@ type ContractState struct {
|
||||||
scriptHash util.Uint160
|
scriptHash util.Uint160
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit flushes all contracts to the given storage.Batch.
|
|
||||||
func (a Contracts) commit(store storage.Store) error {
|
|
||||||
for _, contract := range a {
|
|
||||||
if err := putContractStateIntoStore(store, contract); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeBinary implements Serializable interface.
|
// DecodeBinary implements Serializable interface.
|
||||||
func (cs *ContractState) DecodeBinary(br *io.BinReader) {
|
func (cs *Contract) DecodeBinary(br *io.BinReader) {
|
||||||
cs.Script = br.ReadVarBytes()
|
cs.Script = br.ReadVarBytes()
|
||||||
br.ReadArray(&cs.ParamList)
|
br.ReadArray(&cs.ParamList)
|
||||||
br.ReadLE(&cs.ReturnType)
|
br.ReadLE(&cs.ReturnType)
|
||||||
|
@ -51,7 +37,7 @@ func (cs *ContractState) DecodeBinary(br *io.BinReader) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements Serializable interface.
|
// EncodeBinary implements Serializable interface.
|
||||||
func (cs *ContractState) EncodeBinary(bw *io.BinWriter) {
|
func (cs *Contract) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteVarBytes(cs.Script)
|
bw.WriteVarBytes(cs.Script)
|
||||||
bw.WriteArray(cs.ParamList)
|
bw.WriteArray(cs.ParamList)
|
||||||
bw.WriteLE(cs.ReturnType)
|
bw.WriteLE(cs.ReturnType)
|
||||||
|
@ -63,25 +49,8 @@ func (cs *ContractState) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteString(cs.Description)
|
bw.WriteString(cs.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
// putContractStateIntoStore puts given contract state into the given store.
|
|
||||||
func putContractStateIntoStore(s storage.Store, cs *ContractState) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
cs.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE())
|
|
||||||
return s.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteContractStateInStore deletes given contract state in the given store.
|
|
||||||
func deleteContractStateInStore(s storage.Store, hash util.Uint160) error {
|
|
||||||
key := storage.AppendPrefix(storage.STContract, hash.BytesBE())
|
|
||||||
return s.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScriptHash returns a contract script hash.
|
// ScriptHash returns a contract script hash.
|
||||||
func (cs *ContractState) ScriptHash() util.Uint160 {
|
func (cs *Contract) ScriptHash() util.Uint160 {
|
||||||
if cs.scriptHash.Equals(util.Uint160{}) {
|
if cs.scriptHash.Equals(util.Uint160{}) {
|
||||||
cs.createHash()
|
cs.createHash()
|
||||||
}
|
}
|
||||||
|
@ -89,21 +58,21 @@ func (cs *ContractState) ScriptHash() util.Uint160 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// createHash creates contract script hash.
|
// createHash creates contract script hash.
|
||||||
func (cs *ContractState) createHash() {
|
func (cs *Contract) createHash() {
|
||||||
cs.scriptHash = hash.Hash160(cs.Script)
|
cs.scriptHash = hash.Hash160(cs.Script)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasStorage checks whether the contract has storage property set.
|
// 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
|
return (cs.Properties & smartcontract.HasStorage) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasDynamicInvoke checks whether the contract has dynamic invoke property set.
|
// 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
|
return (cs.Properties & smartcontract.HasDynamicInvoke) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPayable checks whether the contract has payable property set.
|
// 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
|
return (cs.Properties & smartcontract.IsPayable) != 0
|
||||||
}
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
package core
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
@ -13,7 +12,7 @@ import (
|
||||||
func TestEncodeDecodeContractState(t *testing.T) {
|
func TestEncodeDecodeContractState(t *testing.T) {
|
||||||
script := []byte("testscript")
|
script := []byte("testscript")
|
||||||
|
|
||||||
contract := &ContractState{
|
contract := &Contract{
|
||||||
Script: script,
|
Script: script,
|
||||||
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
||||||
ReturnType: smartcontract.BoolType,
|
ReturnType: smartcontract.BoolType,
|
||||||
|
@ -29,7 +28,7 @@ func TestEncodeDecodeContractState(t *testing.T) {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
contract.EncodeBinary(buf.BinWriter)
|
contract.EncodeBinary(buf.BinWriter)
|
||||||
assert.Nil(t, buf.Err)
|
assert.Nil(t, buf.Err)
|
||||||
contractDecoded := &ContractState{}
|
contractDecoded := &Contract{}
|
||||||
r := io.NewBinReaderFromBuf(buf.Bytes())
|
r := io.NewBinReaderFromBuf(buf.Bytes())
|
||||||
contractDecoded.DecodeBinary(r)
|
contractDecoded.DecodeBinary(r)
|
||||||
assert.Nil(t, r.Err)
|
assert.Nil(t, r.Err)
|
||||||
|
@ -38,10 +37,10 @@ func TestEncodeDecodeContractState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContractStateProperties(t *testing.T) {
|
func TestContractStateProperties(t *testing.T) {
|
||||||
flaggedContract := ContractState{
|
flaggedContract := Contract{
|
||||||
Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable,
|
Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable,
|
||||||
}
|
}
|
||||||
nonFlaggedContract := ContractState{
|
nonFlaggedContract := Contract{
|
||||||
ReturnType: smartcontract.BoolType,
|
ReturnType: smartcontract.BoolType,
|
||||||
}
|
}
|
||||||
assert.Equal(t, true, flaggedContract.HasStorage())
|
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.HasDynamicInvoke())
|
||||||
assert.Equal(t, false, nonFlaggedContract.IsPayable())
|
assert.Equal(t, false, nonFlaggedContract.IsPayable())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPutGetDeleteContractState(t *testing.T) {
|
|
||||||
s := storage.NewMemoryStore()
|
|
||||||
script := []byte("testscript")
|
|
||||||
|
|
||||||
contract := &ContractState{
|
|
||||||
Script: script,
|
|
||||||
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
|
||||||
ReturnType: smartcontract.BoolType,
|
|
||||||
Properties: smartcontract.HasStorage,
|
|
||||||
Name: "Contrato",
|
|
||||||
CodeVersion: "1.0.0",
|
|
||||||
Author: "Joe Random",
|
|
||||||
Email: "joe@example.com",
|
|
||||||
Description: "Test contract",
|
|
||||||
}
|
|
||||||
assert.NoError(t, putContractStateIntoStore(s, contract))
|
|
||||||
csRead := getContractStateFromStore(s, contract.ScriptHash())
|
|
||||||
assert.NotNil(t, csRead)
|
|
||||||
assert.Equal(t, contract, csRead)
|
|
||||||
assert.NoError(t, deleteContractStateInStore(s, contract.ScriptHash()))
|
|
||||||
csRead2 := getContractStateFromStore(s, contract.ScriptHash())
|
|
||||||
assert.Nil(t, csRead2)
|
|
||||||
}
|
|
|
@ -1,11 +1,9 @@
|
||||||
package core
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotificationEvent is a tuple of scripthash that emitted the StackItem as a
|
// NotificationEvent is a tuple of scripthash that emitted the StackItem as a
|
||||||
|
@ -26,35 +24,6 @@ type AppExecResult struct {
|
||||||
Events []NotificationEvent
|
Events []NotificationEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// putAppExecResultIntoStore puts given application execution result into the
|
|
||||||
// given store.
|
|
||||||
func putAppExecResultIntoStore(s storage.Store, aer *AppExecResult) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
aer.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
key := storage.AppendPrefix(storage.STNotification, aer.TxHash.BytesBE())
|
|
||||||
return s.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAppExecResultFromStore gets application execution result from the
|
|
||||||
// given store.
|
|
||||||
func getAppExecResultFromStore(s storage.Store, hash util.Uint256) (*AppExecResult, error) {
|
|
||||||
aer := &AppExecResult{}
|
|
||||||
key := storage.AppendPrefix(storage.STNotification, hash.BytesBE())
|
|
||||||
if b, err := s.Get(key); err == nil {
|
|
||||||
r := io.NewBinReaderFromBuf(b)
|
|
||||||
aer.DecodeBinary(r)
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, errors.Wrap(r.Err, "decoding failure:")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return aer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary implements the Serializable interface.
|
// EncodeBinary implements the Serializable interface.
|
||||||
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
|
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteBytes(ne.ScriptHash[:])
|
w.WriteBytes(ne.ScriptHash[:])
|
||||||
|
@ -70,11 +39,19 @@ func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
|
||||||
// EncodeBinary implements the Serializable interface.
|
// EncodeBinary implements the Serializable interface.
|
||||||
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteBytes(aer.TxHash[:])
|
w.WriteBytes(aer.TxHash[:])
|
||||||
|
w.WriteLE(aer.Trigger)
|
||||||
|
w.WriteString(aer.VMState)
|
||||||
|
w.WriteLE(aer.GasConsumed)
|
||||||
|
w.WriteString(aer.Stack)
|
||||||
w.WriteArray(aer.Events)
|
w.WriteArray(aer.Events)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements the Serializable interface.
|
// DecodeBinary implements the Serializable interface.
|
||||||
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
||||||
r.ReadBytes(aer.TxHash[:])
|
r.ReadBytes(aer.TxHash[:])
|
||||||
|
r.ReadLE(&aer.Trigger)
|
||||||
|
aer.VMState = r.ReadString()
|
||||||
|
r.ReadLE(&aer.GasConsumed)
|
||||||
|
aer.Stack = r.ReadString()
|
||||||
r.ReadArray(&aer.Events)
|
r.ReadArray(&aer.Events)
|
||||||
}
|
}
|
44
pkg/core/state/notification_event_test.go
Normal file
44
pkg/core/state/notification_event_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
23
pkg/core/state/storage_item.go
Normal file
23
pkg/core/state/storage_item.go
Normal file
|
@ -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)
|
||||||
|
}
|
26
pkg/core/state/storage_item_test.go
Normal file
26
pkg/core/state/storage_item_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
99
pkg/core/state/validator.go
Normal file
99
pkg/core/state/validator.go
Normal file
|
@ -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
|
||||||
|
}
|
64
pkg/core/state/validator_test.go
Normal file
64
pkg/core/state/validator_test.go
Normal file
|
@ -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())
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
9
pkg/core/uint32.go
Normal file
9
pkg/core/uint32.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
// slice attaches the methods of Interface to []int, sorting in increasing order.
|
||||||
|
type slice []uint32
|
||||||
|
|
||||||
|
func (p slice) Len() int { return len(p) }
|
||||||
|
func (p slice) Less(i, j int) bool { return p[i] < p[j] }
|
||||||
|
func (p slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
|
@ -1,93 +1,26 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnspentCoins is mapping between transactions and their unspent
|
|
||||||
// coin state.
|
|
||||||
type UnspentCoins map[util.Uint256]*UnspentCoinState
|
|
||||||
|
|
||||||
// getAndUpdate retreives UnspentCoinState from temporary or persistent Store
|
|
||||||
// and return it. If it's not present in both stores, returns a new
|
|
||||||
// UnspentCoinState.
|
|
||||||
func (u UnspentCoins) getAndUpdate(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
|
|
||||||
if unspent, ok := u[hash]; ok {
|
|
||||||
return unspent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
unspent, err := getUnspentCoinStateFromStore(s, hash)
|
|
||||||
if err != nil {
|
|
||||||
if err != storage.ErrKeyNotFound {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
unspent = &UnspentCoinState{
|
|
||||||
states: []CoinState{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u[hash] = unspent
|
|
||||||
return unspent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUnspentCoinStateFromStore retrieves UnspentCoinState from the given store
|
|
||||||
func getUnspentCoinStateFromStore(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
|
|
||||||
unspent := &UnspentCoinState{}
|
|
||||||
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
|
||||||
if b, err := s.Get(key); err == nil {
|
|
||||||
r := io.NewBinReaderFromBuf(b)
|
|
||||||
unspent.DecodeBinary(r)
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode (UnspentCoinState): %s", r.Err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return unspent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// putUnspentCoinStateIntoStore puts given UnspentCoinState into the given store.
|
|
||||||
func putUnspentCoinStateIntoStore(store storage.Store, hash util.Uint256, ucs *UnspentCoinState) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
ucs.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
|
|
||||||
return store.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnspentCoinState hold the state of a unspent coin.
|
// UnspentCoinState hold the state of a unspent coin.
|
||||||
type UnspentCoinState struct {
|
type UnspentCoinState struct {
|
||||||
states []CoinState
|
states []state.Coin
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUnspentCoinState returns a new unspent coin state with N confirmed states.
|
// NewUnspentCoinState returns a new unspent coin state with N confirmed states.
|
||||||
func NewUnspentCoinState(n int) *UnspentCoinState {
|
func NewUnspentCoinState(n int) *UnspentCoinState {
|
||||||
u := &UnspentCoinState{
|
u := &UnspentCoinState{
|
||||||
states: make([]CoinState, n),
|
states: make([]state.Coin, n),
|
||||||
}
|
}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
u.states[i] = CoinStateConfirmed
|
u.states[i] = state.CoinConfirmed
|
||||||
}
|
}
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit writes all unspent coin states to the given Batch.
|
|
||||||
func (u UnspentCoins) commit(store storage.Store) error {
|
|
||||||
for hash, state := range u {
|
|
||||||
if err := putUnspentCoinStateIntoStore(store, hash, state); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary encodes UnspentCoinState to the given BinWriter.
|
// EncodeBinary encodes UnspentCoinState to the given BinWriter.
|
||||||
func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
|
func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteVarUint(uint64(len(s.states)))
|
bw.WriteVarUint(uint64(len(s.states)))
|
||||||
|
@ -99,40 +32,10 @@ func (s *UnspentCoinState) EncodeBinary(bw *io.BinWriter) {
|
||||||
// DecodeBinary decodes UnspentCoinState from the given BinReader.
|
// DecodeBinary decodes UnspentCoinState from the given BinReader.
|
||||||
func (s *UnspentCoinState) DecodeBinary(br *io.BinReader) {
|
func (s *UnspentCoinState) DecodeBinary(br *io.BinReader) {
|
||||||
lenStates := br.ReadVarUint()
|
lenStates := br.ReadVarUint()
|
||||||
s.states = make([]CoinState, lenStates)
|
s.states = make([]state.Coin, lenStates)
|
||||||
for i := 0; i < int(lenStates); i++ {
|
for i := 0; i < int(lenStates); i++ {
|
||||||
var state uint8
|
var coinState uint8
|
||||||
br.ReadLE(&state)
|
br.ReadLE(&coinState)
|
||||||
s.states[i] = CoinState(state)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,19 +3,19 @@ package core
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecodeEncodeUnspentCoinState(t *testing.T) {
|
func TestDecodeEncodeUnspentCoinState(t *testing.T) {
|
||||||
unspent := &UnspentCoinState{
|
unspent := &UnspentCoinState{
|
||||||
states: []CoinState{
|
states: []state.Coin{
|
||||||
CoinStateConfirmed,
|
state.CoinConfirmed,
|
||||||
CoinStateSpent,
|
state.CoinSpent,
|
||||||
CoinStateSpent,
|
state.CoinSpent,
|
||||||
CoinStateSpent,
|
state.CoinSpent,
|
||||||
CoinStateConfirmed,
|
state.CoinConfirmed,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,33 +27,3 @@ func TestDecodeEncodeUnspentCoinState(t *testing.T) {
|
||||||
unspentDecode.DecodeBinary(r)
|
unspentDecode.DecodeBinary(r)
|
||||||
assert.Nil(t, r.Err)
|
assert.Nil(t, r.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCommitUnspentCoins(t *testing.T) {
|
|
||||||
var (
|
|
||||||
store = storage.NewMemoryStore()
|
|
||||||
unspentCoins = make(UnspentCoins)
|
|
||||||
)
|
|
||||||
|
|
||||||
txA := randomUint256()
|
|
||||||
txB := randomUint256()
|
|
||||||
txC := randomUint256()
|
|
||||||
|
|
||||||
unspentCoins[txA] = &UnspentCoinState{
|
|
||||||
states: []CoinState{CoinStateConfirmed},
|
|
||||||
}
|
|
||||||
unspentCoins[txB] = &UnspentCoinState{
|
|
||||||
states: []CoinState{
|
|
||||||
CoinStateConfirmed,
|
|
||||||
CoinStateConfirmed,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
unspentCoins[txC] = &UnspentCoinState{
|
|
||||||
states: []CoinState{
|
|
||||||
CoinStateConfirmed,
|
|
||||||
CoinStateConfirmed,
|
|
||||||
CoinStateConfirmed,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Nil(t, unspentCoins.commit(store))
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
40
pkg/internal/random/random_util.go
Normal file
40
pkg/internal/random/random_util.go
Normal file
|
@ -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())
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
@ -62,7 +63,7 @@ func (chain testChain) HeaderHeight() uint32 {
|
||||||
func (chain testChain) GetBlock(hash util.Uint256) (*core.Block, error) {
|
func (chain testChain) GetBlock(hash util.Uint256) (*core.Block, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetContractState(hash util.Uint160) *core.ContractState {
|
func (chain testChain) GetContractState(hash util.Uint160) *state.Contract {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetHeaderHash(int) util.Uint256 {
|
func (chain testChain) GetHeaderHash(int) util.Uint256 {
|
||||||
|
@ -72,10 +73,10 @@ func (chain testChain) GetHeader(hash util.Uint256) (*core.Header, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain testChain) GetAssetState(util.Uint256) *core.AssetState {
|
func (chain testChain) GetAssetState(util.Uint256) *state.Asset {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState {
|
func (chain testChain) GetAccountState(util.Uint160) *state.Account {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
||||||
|
@ -84,13 +85,13 @@ func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.Publi
|
||||||
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
|
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *core.StorageItem {
|
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetTestVM() (*vm.VM, storage.Store) {
|
func (chain testChain) GetTestVM() (*vm.VM, storage.Store) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*core.StorageItem, error) {
|
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) CurrentHeaderHash() util.Uint256 {
|
func (chain testChain) CurrentHeaderHash() util.Uint256 {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
@ -158,7 +158,7 @@ func (c *Client) SetClient(cli *http.Client) {
|
||||||
// asset belonging to specified address. This implementation uses GetUnspents
|
// asset belonging to specified address. This implementation uses GetUnspents
|
||||||
// JSON-RPC call internally, so make sure your RPC server suppors that.
|
// JSON-RPC call internally, so make sure your RPC server suppors that.
|
||||||
func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
||||||
var utxos core.UnspentBalances
|
var utxos state.UnspentBalances
|
||||||
|
|
||||||
resp, err := c.GetUnspents(address)
|
resp, err := c.GetUnspents(address)
|
||||||
if err != nil || resp.Error != nil {
|
if err != nil || resp.Error != nil {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
|
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
@ -68,7 +68,7 @@ func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256,
|
||||||
|
|
||||||
// unspentsToInputs uses UnspentBalances to create a slice of inputs for a new
|
// unspentsToInputs uses UnspentBalances to create a slice of inputs for a new
|
||||||
// transcation containing the required amount of asset.
|
// transcation containing the required amount of asset.
|
||||||
func unspentsToInputs(utxos core.UnspentBalances, required util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
func unspentsToInputs(utxos state.UnspentBalances, required util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
|
||||||
var (
|
var (
|
||||||
num, i uint16
|
num, i uint16
|
||||||
selected = util.Fixed8(0)
|
selected = util.Fixed8(0)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ type (
|
||||||
|
|
||||||
// Unspent stores Unspents per asset
|
// Unspent stores Unspents per asset
|
||||||
Unspent struct {
|
Unspent struct {
|
||||||
Unspent core.UnspentBalances
|
Unspent state.UnspentBalances
|
||||||
Asset string // "NEO" / "GAS"
|
Asset string // "NEO" / "GAS"
|
||||||
Amount util.Fixed8 // total unspent of this asset
|
Amount util.Fixed8 // total unspent of this asset
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,15 @@ package wrappers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccountState wrapper used for the representation of
|
// AccountState wrapper used for the representation of
|
||||||
// core.AccountState on the RPC Server.
|
// state.Account on the RPC Server.
|
||||||
type AccountState struct {
|
type AccountState struct {
|
||||||
Version uint8 `json:"version"`
|
Version uint8 `json:"version"`
|
||||||
ScriptHash util.Uint160 `json:"script_hash"`
|
ScriptHash util.Uint160 `json:"script_hash"`
|
||||||
|
@ -32,8 +32,8 @@ type Balance struct {
|
||||||
Value util.Fixed8 `json:"value"`
|
Value util.Fixed8 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountState creates a new AccountState wrapper.
|
// NewAccountState creates a new Account wrapper.
|
||||||
func NewAccountState(a *core.AccountState) AccountState {
|
func NewAccountState(a *state.Account) AccountState {
|
||||||
balances := make(Balances, 0, len(a.Balances))
|
balances := make(Balances, 0, len(a.Balances))
|
||||||
for k, v := range a.GetBalanceValues() {
|
for k, v := range a.GetBalanceValues() {
|
||||||
balances = append(balances, Balance{
|
balances = append(balances, Balance{
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package wrappers
|
package wrappers
|
||||||
|
|
||||||
import (
|
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/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssetState wrapper used for the representation of
|
// AssetState wrapper used for the representation of
|
||||||
// core.AssetState on the RPC Server.
|
// state.Asset on the RPC Server.
|
||||||
type AssetState struct {
|
type AssetState struct {
|
||||||
ID util.Uint256 `json:"assetID"`
|
ID util.Uint256 `json:"assetID"`
|
||||||
AssetType transaction.AssetType `json:"assetType"`
|
AssetType transaction.AssetType `json:"assetType"`
|
||||||
|
@ -25,8 +25,8 @@ type AssetState struct {
|
||||||
IsFrozen bool `json:"is_frozen"`
|
IsFrozen bool `json:"is_frozen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAssetState creates a new AssetState wrapper.
|
// NewAssetState creates a new Asset wrapper.
|
||||||
func NewAssetState(a *core.AssetState) AssetState {
|
func NewAssetState(a *state.Asset) AssetState {
|
||||||
return AssetState{
|
return AssetState{
|
||||||
ID: a.ID,
|
ID: a.ID,
|
||||||
AssetType: a.AssetType,
|
AssetType: a.AssetType,
|
||||||
|
|
|
@ -2,17 +2,18 @@ package wrappers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnspentBalanceInfo wrapper is used to represent single unspent asset entry
|
// UnspentBalanceInfo wrapper is used to represent single unspent asset entry
|
||||||
// in `getunspents` output.
|
// in `getunspents` output.
|
||||||
type UnspentBalanceInfo struct {
|
type UnspentBalanceInfo struct {
|
||||||
Unspents []core.UnspentBalance `json:"unspent"`
|
Unspents []state.UnspentBalance `json:"unspent"`
|
||||||
AssetHash util.Uint256 `json:"asset_hash"`
|
AssetHash util.Uint256 `json:"asset_hash"`
|
||||||
Asset string `json:"asset"`
|
Asset string `json:"asset"`
|
||||||
AssetSymbol string `json:"asset_symbol"`
|
AssetSymbol string `json:"asset_symbol"`
|
||||||
Amount util.Fixed8 `json:"amount"`
|
Amount util.Fixed8 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unspents wrapper is used to represent getunspents return result.
|
// Unspents wrapper is used to represent getunspents return result.
|
||||||
|
@ -27,8 +28,8 @@ var GlobalAssets = map[string]string{
|
||||||
"602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "GAS",
|
"602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "GAS",
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUnspents creates a new AccountState wrapper using given Blockchainer.
|
// NewUnspents creates a new Account wrapper using given Blockchainer.
|
||||||
func NewUnspents(a *core.AccountState, chain core.Blockchainer, addr string) Unspents {
|
func NewUnspents(a *state.Account, chain core.Blockchainer, addr string) Unspents {
|
||||||
res := Unspents{
|
res := Unspents{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Balance: make([]UnspentBalanceInfo, 0, len(a.Balances)),
|
Balance: make([]UnspentBalanceInfo, 0, len(a.Balances)),
|
||||||
|
|
Loading…
Reference in a new issue