Persisting more states (#71)

* added persistence of assets and spentcoins.

* contract params

* bumped version
This commit is contained in:
Anthony De Meulemeester 2018-04-16 22:15:30 +02:00 committed by GitHub
parent 7883f305e7
commit 2cdfee211a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 414 additions and 23 deletions

View file

@ -1 +1 @@
0.41.0 0.41.1

View file

@ -14,7 +14,7 @@ import (
// Accounts is mapping between a account address and AccountState. // Accounts is mapping between a account address and AccountState.
type Accounts map[util.Uint160]*AccountState type Accounts map[util.Uint160]*AccountState
func (a Accounts) getAndChange(s storage.Store, hash util.Uint160) (*AccountState, error) { func (a Accounts) getAndUpdate(s storage.Store, hash util.Uint160) (*AccountState, error) {
if account, ok := a[hash]; ok { if account, ok := a[hash]; ok {
return account, nil return account, nil
} }

131
pkg/core/asset_state.go Normal file
View file

@ -0,0 +1,131 @@
package core
import (
"bytes"
"encoding/binary"
"io"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
)
const feeMode = 0x0
// Assets is mapping between AssetID and the AssetState.
type Assets map[util.Uint256]*AssetState
func (a Assets) commit(b storage.Batch) error {
buf := new(bytes.Buffer)
for hash, state := range a {
if err := state.EncodeBinary(buf); err != nil {
return err
}
key := storage.AppendPrefix(storage.STAsset, hash.Bytes())
b.Put(key, buf.Bytes())
buf.Reset()
}
return nil
}
// AssetState represents the state of an NEO registerd Asset.
type AssetState struct {
ID util.Uint256
AssetType transaction.AssetType
Name string
Amount util.Fixed8
Available util.Fixed8
Precision uint8
FeeMode uint8
Owner *crypto.PublicKey
Admin util.Uint160
Issuer util.Uint160
Expiration uint32
IsFrozen bool
}
// DecodeBinary implements the Payload interface.
func (a *AssetState) DecodeBinary(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &a.ID); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.AssetType); err != nil {
return err
}
var err error
a.Name, err = util.ReadVarString(r)
if err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.Amount); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.Available); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.Precision); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.FeeMode); err != nil {
return err
}
a.Owner = &crypto.PublicKey{}
if err := a.Owner.DecodeBinary(r); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.Admin); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.Issuer); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &a.Expiration); err != nil {
return err
}
return binary.Read(r, binary.LittleEndian, &a.IsFrozen)
}
// EncodeBinary implements the Payload interface.
func (a *AssetState) EncodeBinary(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, a.ID); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.AssetType); err != nil {
return err
}
if err := util.WriteVarUint(w, uint64(len(a.Name))); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, []byte(a.Name)); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.Amount); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.Available); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.Precision); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.FeeMode); err != nil {
return err
}
if err := a.Owner.EncodeBinary(w); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.Admin); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.Issuer); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, a.Expiration); err != nil {
return err
}
return binary.Write(w, binary.LittleEndian, a.IsFrozen)
}

View file

@ -0,0 +1,34 @@
package core
import (
"bytes"
"testing"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeAssetState(t *testing.T) {
asset := &AssetState{
ID: util.RandomUint256(),
AssetType: transaction.Token,
Name: "super cool token",
Amount: util.Fixed8(1000000),
Available: util.Fixed8(100),
Precision: 0,
FeeMode: feeMode,
Owner: &crypto.PublicKey{},
Admin: util.RandomUint160(),
Issuer: util.RandomUint160(),
Expiration: 10,
IsFrozen: false,
}
buf := new(bytes.Buffer)
assert.Nil(t, asset.EncodeBinary(buf))
assetDecode := &AssetState{}
assert.Nil(t, assetDecode.DecodeBinary(buf))
assert.Equal(t, asset, assetDecode)
}

View file

@ -264,7 +264,9 @@ func (bc *Blockchain) persistBlock(block *Block) error {
var ( var (
batch = bc.Batch() batch = bc.Batch()
unspentCoins = make(UnspentCoins) unspentCoins = make(UnspentCoins)
spentCoins = make(SpentCoins)
accounts = make(Accounts) accounts = make(Accounts)
assets = make(Assets)
) )
storeAsBlock(batch, block, 0) storeAsBlock(batch, block, 0)
@ -272,21 +274,14 @@ func (bc *Blockchain) persistBlock(block *Block) error {
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
storeAsTransaction(batch, tx, block.Index) storeAsTransaction(batch, tx, block.Index)
unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs))
// Add CoinStateConfirmed for each tx output.
unspent := make([]CoinState, len(tx.Outputs))
for i := 0; i < len(tx.Outputs); i++ {
unspent[i] = CoinStateConfirmed
}
unspentCoins[tx.Hash()] = &UnspentCoinState{unspent}
// Process TX outputs. // Process TX outputs.
for _, output := range tx.Outputs { for _, output := range tx.Outputs {
account, err := accounts.getAndChange(bc.Store, output.ScriptHash) account, err := accounts.getAndUpdate(bc.Store, output.ScriptHash)
if err != nil { if err != nil {
return err return err
} }
if _, ok := account.Balances[output.AssetID]; ok { if _, ok := account.Balances[output.AssetID]; ok {
account.Balances[output.AssetID] += output.Amount account.Balances[output.AssetID] += output.Amount
} else { } else {
@ -296,37 +291,65 @@ func (bc *Blockchain) persistBlock(block *Block) error {
// Process TX inputs that are grouped by previous hash. // Process TX inputs that are grouped by previous hash.
for prevHash, inputs := range tx.GroupInputsByPrevHash() { for prevHash, inputs := range tx.GroupInputsByPrevHash() {
prevTX, _, err := bc.GetTransaction(prevHash) prevTX, prevTXHeight, err := bc.GetTransaction(prevHash)
if err != nil { if err != nil {
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 := unspentCoins.getAndChange(bc.Store, input.PrevHash) unspent, err := unspentCoins.getAndUpdate(bc.Store, input.PrevHash)
if err != nil { if err != nil {
return err return err
} }
unspent.states[input.PrevIndex] = CoinStateSpent unspent.states[input.PrevIndex] = CoinStateSpent
prevTXOutput := prevTX.Outputs[input.PrevIndex] prevTXOutput := prevTX.Outputs[input.PrevIndex]
account, err := accounts.getAndChange(bc.Store, prevTXOutput.ScriptHash) account, err := accounts.getAndUpdate(bc.Store, prevTXOutput.ScriptHash)
if err != nil { if err != nil {
return err return err
} }
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
spentCoin.items[input.PrevIndex] = block.Index
spentCoins[input.PrevHash] = spentCoin
}
account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount
} }
} }
// Process the underlying type of the TX. // Process the underlying type of the TX.
switch tx.Data.(type) { switch t := tx.Data.(type) {
case *transaction.RegisterTX: case *transaction.RegisterTX:
assets[tx.Hash()] = &AssetState{
ID: tx.Hash(),
AssetType: t.AssetType,
Name: t.Name,
Amount: t.Amount,
Precision: t.Precision,
Owner: t.Owner,
Admin: t.Admin,
}
case *transaction.IssueTX: case *transaction.IssueTX:
case *transaction.ClaimTX: case *transaction.ClaimTX:
case *transaction.EnrollmentTX: case *transaction.EnrollmentTX:
case *transaction.StateTX: case *transaction.StateTX:
case *transaction.PublishTX: case *transaction.PublishTX:
contract := &ContractState{
Script: t.Script,
ParamList: t.ParamList,
ReturnType: t.ReturnType,
HasStorage: t.NeedStorage,
Name: t.Name,
CodeVersion: t.CodeVersion,
Author: t.Author,
Email: t.Email,
Description: t.Description,
}
fmt.Printf("%+v", contract)
case *transaction.InvocationTX: case *transaction.InvocationTX:
log.Warn("invocation TX but we have no VM, o noo :(")
} }
} }
@ -337,6 +360,12 @@ func (bc *Blockchain) persistBlock(block *Block) error {
if err := unspentCoins.commit(batch); err != nil { if err := unspentCoins.commit(batch); err != nil {
return err return err
} }
if err := spentCoins.commit(batch); err != nil {
return err
}
if err := assets.commit(batch); err != nil {
return err
}
if err := bc.PutBatch(batch); err != nil { if err := bc.PutBatch(batch); err != nil {
return err return err
} }

View file

@ -0,0 +1,23 @@
package core
import (
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
)
// ContractState holds information about a smart contract in the NEO blockchain.
type ContractState struct {
Script []byte
ParamList []smartcontract.ParamType
ReturnType smartcontract.ParamType
Properties []int
Name string
CodeVersion string
Author string
Email string
Description string
HasStorage bool
HasDynamicInvoke bool
scriptHash util.Uint160
}

View file

@ -0,0 +1,116 @@
package core
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"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.BytesReverse())
if b, err := store.Get(key); err == nil {
if err := spent.DecodeBinary(bytes.NewReader(b)); err != nil {
return nil, fmt.Errorf("failed to decode (UnspentCoinState): %s", err)
}
} else {
spent = &SpentCoinState{
items: make(map[uint16]uint32),
}
}
s[hash] = spent
return spent, nil
}
func (s SpentCoins) commit(b storage.Batch) error {
buf := new(bytes.Buffer)
for hash, state := range s {
if err := state.EncodeBinary(buf); err != nil {
return err
}
key := storage.AppendPrefix(storage.STSpentCoin, hash.BytesReverse())
b.Put(key, buf.Bytes())
buf.Reset()
}
return nil
}
// SpentCoinState represents the state of a spent coin.
type SpentCoinState struct {
txHash util.Uint256
txHeight uint32
// A mapping between the index of the prevIndex and block height.
items map[uint16]uint32
}
// NewSpentCoinState returns a new SpentCoinState object.
func NewSpentCoinState(hash util.Uint256, height uint32) *SpentCoinState {
return &SpentCoinState{
txHash: hash,
txHeight: height,
items: make(map[uint16]uint32),
}
}
// DecodeBinary implements the Payload interface.
func (s *SpentCoinState) DecodeBinary(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &s.txHash); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &s.txHeight); err != nil {
return err
}
s.items = make(map[uint16]uint32)
lenItems := util.ReadVarUint(r)
for i := 0; i < int(lenItems); i++ {
var (
key uint16
value uint32
)
if err := binary.Read(r, binary.LittleEndian, &key); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &value); err != nil {
return err
}
s.items[key] = value
}
return nil
}
// EncodeBinary implements the Payload interface.
func (s *SpentCoinState) EncodeBinary(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, s.txHash); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, s.txHeight); err != nil {
return err
}
if err := util.WriteVarUint(w, uint64(len(s.items))); err != nil {
return err
}
for k, v := range s.items {
if err := binary.Write(w, binary.LittleEndian, k); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,51 @@
package core
import (
"bytes"
"testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeSpentCoinState(t *testing.T) {
spent := &SpentCoinState{
txHash: util.RandomUint256(),
txHeight: 1001,
items: map[uint16]uint32{
1: 3,
2: 8,
4: 100,
},
}
buf := new(bytes.Buffer)
assert.Nil(t, spent.EncodeBinary(buf))
spentDecode := new(SpentCoinState)
assert.Nil(t, spentDecode.DecodeBinary(buf))
assert.Equal(t, spent, spentDecode)
}
func TestCommitSpentCoins(t *testing.T) {
var (
store = storage.NewMemoryStore()
batch = store.Batch()
spentCoins = make(SpentCoins)
)
txx := []util.Uint256{
util.RandomUint256(),
util.RandomUint256(),
util.RandomUint256(),
}
for i := 0; i < len(txx); i++ {
spentCoins[txx[i]] = &SpentCoinState{
txHash: txx[i],
txHeight: 1,
}
}
assert.Nil(t, spentCoins.commit(batch))
assert.Nil(t, store.PutBatch(batch))
}

View file

@ -14,7 +14,7 @@ import (
// coin state. // coin state.
type UnspentCoins map[util.Uint256]*UnspentCoinState type UnspentCoins map[util.Uint256]*UnspentCoinState
func (u UnspentCoins) getAndChange(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) { func (u UnspentCoins) getAndUpdate(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
if unspent, ok := u[hash]; ok { if unspent, ok := u[hash]; ok {
return unspent, nil return unspent, nil
} }
@ -40,6 +40,17 @@ type UnspentCoinState struct {
states []CoinState states []CoinState
} }
// NewUnspentCoinState returns a new unspent coin state with N confirmed states.
func NewUnspentCoinState(n int) *UnspentCoinState {
u := &UnspentCoinState{
states: make([]CoinState, n),
}
for i := 0; i < n; i++ {
u.states[i] = CoinStateConfirmed
}
return u
}
// commit writes all unspent coin states to the given Batch. // commit writes all unspent coin states to the given Batch.
func (s UnspentCoins) commit(b storage.Batch) error { func (s UnspentCoins) commit(b storage.Batch) error {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)

View file

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestDecodeEncode(t *testing.T) { func TestDecodeEncodeUnspentCoinState(t *testing.T) {
unspent := &UnspentCoinState{ unspent := &UnspentCoinState{
states: []CoinState{ states: []CoinState{
CoinStateConfirmed, CoinStateConfirmed,
@ -26,7 +26,7 @@ func TestDecodeEncode(t *testing.T) {
assert.Nil(t, unspentDecode.DecodeBinary(buf)) assert.Nil(t, unspentDecode.DecodeBinary(buf))
} }
func TestCommit(t *testing.T) { func TestCommitUnspentCoins(t *testing.T) {
var ( var (
store = storage.NewMemoryStore() store = storage.NewMemoryStore()
batch = store.Batch() batch = store.Batch()

View file

@ -96,10 +96,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) {
func governingTokenTX() *transaction.Transaction { func governingTokenTX() *transaction.Transaction {
admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)}) admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)})
adminB, _ := crypto.Uint160DecodeAddress("Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt")
if !admin.Equals(adminB) {
panic("kdjdkljfkdjfkdjf")
}
registerTX := &transaction.RegisterTX{ registerTX := &transaction.RegisterTX{
AssetType: transaction.GoverningToken, AssetType: transaction.GoverningToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",