Persisting more states (#71)
* added persistence of assets and spentcoins. * contract params * bumped version
This commit is contained in:
parent
7883f305e7
commit
2cdfee211a
11 changed files with 414 additions and 23 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.41.0
|
||||
0.41.1
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
// Accounts is mapping between a account address and 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 {
|
||||
return account, nil
|
||||
}
|
||||
|
|
131
pkg/core/asset_state.go
Normal file
131
pkg/core/asset_state.go
Normal 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)
|
||||
}
|
34
pkg/core/asset_state_test.go
Normal file
34
pkg/core/asset_state_test.go
Normal 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)
|
||||
}
|
|
@ -264,7 +264,9 @@ func (bc *Blockchain) persistBlock(block *Block) error {
|
|||
var (
|
||||
batch = bc.Batch()
|
||||
unspentCoins = make(UnspentCoins)
|
||||
spentCoins = make(SpentCoins)
|
||||
accounts = make(Accounts)
|
||||
assets = make(Assets)
|
||||
)
|
||||
|
||||
storeAsBlock(batch, block, 0)
|
||||
|
@ -272,21 +274,14 @@ func (bc *Blockchain) persistBlock(block *Block) error {
|
|||
|
||||
for _, tx := range block.Transactions {
|
||||
storeAsTransaction(batch, tx, block.Index)
|
||||
|
||||
// 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}
|
||||
unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs))
|
||||
|
||||
// Process 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := account.Balances[output.AssetID]; ok {
|
||||
account.Balances[output.AssetID] += output.Amount
|
||||
} else {
|
||||
|
@ -296,37 +291,65 @@ func (bc *Blockchain) persistBlock(block *Block) error {
|
|||
|
||||
// Process TX inputs that are grouped by previous hash.
|
||||
for prevHash, inputs := range tx.GroupInputsByPrevHash() {
|
||||
prevTX, _, err := bc.GetTransaction(prevHash)
|
||||
prevTX, prevTXHeight, err := bc.GetTransaction(prevHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find previous TX: %s", prevHash)
|
||||
}
|
||||
for _, input := range inputs {
|
||||
unspent, err := unspentCoins.getAndChange(bc.Store, input.PrevHash)
|
||||
unspent, err := unspentCoins.getAndUpdate(bc.Store, input.PrevHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unspent.states[input.PrevIndex] = CoinStateSpent
|
||||
|
||||
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
||||
account, err := accounts.getAndChange(bc.Store, prevTXOutput.ScriptHash)
|
||||
account, err := accounts.getAndUpdate(bc.Store, prevTXOutput.ScriptHash)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Process the underlying type of the TX.
|
||||
switch tx.Data.(type) {
|
||||
switch t := tx.Data.(type) {
|
||||
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.ClaimTX:
|
||||
case *transaction.EnrollmentTX:
|
||||
case *transaction.StateTX:
|
||||
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:
|
||||
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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
23
pkg/core/contract_state.go
Normal file
23
pkg/core/contract_state.go
Normal 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
|
||||
}
|
116
pkg/core/spent_coin_state.go
Normal file
116
pkg/core/spent_coin_state.go
Normal 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
|
||||
}
|
51
pkg/core/spent_coin_state_test.go
Normal file
51
pkg/core/spent_coin_state_test.go
Normal 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))
|
||||
}
|
|
@ -14,7 +14,7 @@ import (
|
|||
// coin state.
|
||||
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 {
|
||||
return unspent, nil
|
||||
}
|
||||
|
@ -40,6 +40,17 @@ type UnspentCoinState struct {
|
|||
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.
|
||||
func (s UnspentCoins) commit(b storage.Batch) error {
|
||||
buf := new(bytes.Buffer)
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeEncode(t *testing.T) {
|
||||
func TestDecodeEncodeUnspentCoinState(t *testing.T) {
|
||||
unspent := &UnspentCoinState{
|
||||
states: []CoinState{
|
||||
CoinStateConfirmed,
|
||||
|
@ -26,7 +26,7 @@ func TestDecodeEncode(t *testing.T) {
|
|||
assert.Nil(t, unspentDecode.DecodeBinary(buf))
|
||||
}
|
||||
|
||||
func TestCommit(t *testing.T) {
|
||||
func TestCommitUnspentCoins(t *testing.T) {
|
||||
var (
|
||||
store = storage.NewMemoryStore()
|
||||
batch = store.Batch()
|
||||
|
|
|
@ -96,10 +96,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) {
|
|||
|
||||
func governingTokenTX() *transaction.Transaction {
|
||||
admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)})
|
||||
adminB, _ := crypto.Uint160DecodeAddress("Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt")
|
||||
if !admin.Equals(adminB) {
|
||||
panic("kdjdkljfkdjfkdjf")
|
||||
}
|
||||
registerTX := &transaction.RegisterTX{
|
||||
AssetType: transaction.GoverningToken,
|
||||
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",
|
||||
|
|
Loading…
Reference in a new issue