mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 13:47:19 +00:00
Persist blockchain with leveldb on disk (#48)
* Created test_data folder with block json files for testing + create separate file for block base. * Fixed bug in WriteVarUint + Trim logic + unit tests * Refactored store and add more tests for it. * restore headerList from chain file * Fix tx decode bug + lots of housekeeping. * Implemented Node restore state from chain file. * Created standalone package for storage. Added couple more methods to Batch and Store interfaces. * Block persisting + tests * bumped version -> 0.31.0
This commit is contained in:
parent
b41e14e0f0
commit
a67728628e
45 changed files with 1419 additions and 530 deletions
2
Gopkg.lock
generated
2
Gopkg.lock
generated
|
@ -126,6 +126,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "7e6d4161196284974f07ba9655d21ee9b4fe1478ef0e054e159df73aeec1343a"
|
inputs-digest = "83630d732c34b1ddf24cd34025fd9fdd982a5e5075ec93a450e7edada658c6c9"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -19,7 +19,7 @@ push-tag:
|
||||||
git push origin ${VERSION}
|
git push origin ${VERSION}
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./bin/neo-go node -config-path ./config -${NETMODE}
|
./bin/neo-go node -config-path ./config -${NETMODE} --debug
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test ./... -cover
|
@go test ./... -cover
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.30.0
|
0.31.0
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@ func NewCommand() cli.Command {
|
||||||
cli.BoolFlag{Name: "privnet, p"},
|
cli.BoolFlag{Name: "privnet, p"},
|
||||||
cli.BoolFlag{Name: "mainnet, m"},
|
cli.BoolFlag{Name: "mainnet, m"},
|
||||||
cli.BoolFlag{Name: "testnet, t"},
|
cli.BoolFlag{Name: "testnet, t"},
|
||||||
|
cli.BoolFlag{Name: "debug, d"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +40,7 @@ func startServer(ctx *cli.Context) error {
|
||||||
configPath = ctx.String("config-path")
|
configPath = ctx.String("config-path")
|
||||||
config, err := network.LoadConfig(configPath, net)
|
config, err := network.LoadConfig(configPath, net)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverConfig := network.NewServerConfig(config)
|
serverConfig := network.NewServerConfig(config)
|
||||||
|
@ -47,8 +50,12 @@ func startServer(ctx *cli.Context) error {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := network.NewServer(serverConfig, chain)
|
if ctx.Bool("debug") {
|
||||||
s.Start()
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(logo())
|
||||||
|
network.NewServer(serverConfig, chain).Start()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,13 +72,20 @@ func newBlockchain(net network.NetMode, path string) (*core.Blockchain, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardcoded for now.
|
// Hardcoded for now.
|
||||||
store, err := core.NewLevelDBStore(path, nil)
|
store, err := storage.NewLevelDBStore(path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return core.NewBlockchain(
|
return core.NewBlockchain(store, startHash)
|
||||||
store,
|
}
|
||||||
startHash,
|
|
||||||
), nil
|
func logo() string {
|
||||||
|
return `
|
||||||
|
_ ____________ __________
|
||||||
|
/ | / / ____/ __ \ / ____/ __ \
|
||||||
|
/ |/ / __/ / / / /_____/ / __/ / / /
|
||||||
|
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
|
||||||
|
/_/ |_/_____/\____/ \____/\____/
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
// AssetType represent a NEO asset type
|
|
||||||
type AssetType uint8
|
|
||||||
|
|
||||||
// Valid asset types.
|
|
||||||
const (
|
|
||||||
CreditFlag AssetType = 0x40
|
|
||||||
DutyFlag AssetType = 0x80
|
|
||||||
|
|
||||||
GoverningToken AssetType = 0x00
|
|
||||||
UtilityToken AssetType = 0x01
|
|
||||||
Currency AssetType = 0x08
|
|
||||||
|
|
||||||
Share = DutyFlag | 0x10
|
|
||||||
Invoice = DutyFlag | 0x18
|
|
||||||
Token = CreditFlag | 0x20
|
|
||||||
)
|
|
|
@ -2,188 +2,27 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BlockBase holds the base info of a block
|
|
||||||
type BlockBase struct {
|
|
||||||
Version uint32
|
|
||||||
// hash of the previous block.
|
|
||||||
PrevHash util.Uint256
|
|
||||||
// Root hash of a transaction list.
|
|
||||||
MerkleRoot util.Uint256
|
|
||||||
// The time stamp of each block must be later than previous block's time stamp.
|
|
||||||
// Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed.
|
|
||||||
// The height of the block must be exactly equal to the height of the previous block plus 1.
|
|
||||||
Timestamp uint32
|
|
||||||
// index/height of the block
|
|
||||||
Index uint32
|
|
||||||
// Random number also called nonce
|
|
||||||
ConsensusData uint64
|
|
||||||
// Contract addresss of the next miner
|
|
||||||
NextConsensus util.Uint160
|
|
||||||
// fixed to 1
|
|
||||||
_ uint8 // padding
|
|
||||||
// Script used to validate the block
|
|
||||||
Script *transaction.Witness
|
|
||||||
|
|
||||||
// hash of this block, created when binary encoded.
|
|
||||||
hash util.Uint256
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeBinary implements the payload interface.
|
|
||||||
func (b *BlockBase) DecodeBinary(r io.Reader) error {
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.PrevHash); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.MerkleRoot); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.Timestamp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.Index); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.ConsensusData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &b.NextConsensus); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var padding uint8
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &padding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if padding != 1 {
|
|
||||||
return fmt.Errorf("format error: padding must equal 1 got %d", padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Script = &transaction.Witness{}
|
|
||||||
if err := b.Script.DecodeBinary(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the hash of the block here so we dont need to do this
|
|
||||||
// again.
|
|
||||||
hash, err := b.createHash()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.hash = hash
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash return the hash of the block.
|
|
||||||
func (b *BlockBase) Hash() util.Uint256 {
|
|
||||||
return b.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// createHash creates the hash of the block.
|
|
||||||
// When calculating the hash value of the block, instead of calculating the entire block,
|
|
||||||
// only first seven fields in the block head will be calculated, which are
|
|
||||||
// version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner.
|
|
||||||
// Since MerkleRoot already contains the hash value of all transactions,
|
|
||||||
// the modification of transaction will influence the hash value of the block.
|
|
||||||
func (b *BlockBase) createHash() (hash util.Uint256, err error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if err = b.encodeHashableFields(buf); err != nil {
|
|
||||||
return hash, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double hash the encoded fields.
|
|
||||||
hash = sha256.Sum256(buf.Bytes())
|
|
||||||
hash = sha256.Sum256(hash.Bytes())
|
|
||||||
return hash, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeHashableFields will only encode the fields used for hashing.
|
|
||||||
// see Hash() for more information about the fields.
|
|
||||||
func (b *BlockBase) encodeHashableFields(w io.Writer) error {
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, &b.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, &b.PrevHash); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, &b.MerkleRoot); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, &b.Timestamp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, &b.Index); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, &b.ConsensusData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return binary.Write(w, binary.LittleEndian, &b.NextConsensus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary implements the Payload interface
|
|
||||||
func (b *BlockBase) EncodeBinary(w io.Writer) error {
|
|
||||||
if err := b.encodeHashableFields(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, uint8(1)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return b.Script.EncodeBinary(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header holds the head info of a block
|
|
||||||
type Header struct {
|
|
||||||
BlockBase
|
|
||||||
_ uint8 // padding fixed to 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the integrity of the header
|
|
||||||
func (h *Header) Verify() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeBinary impelements the Payload interface.
|
|
||||||
func (h *Header) DecodeBinary(r io.Reader) error {
|
|
||||||
if err := h.BlockBase.DecodeBinary(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var padding uint8
|
|
||||||
binary.Read(r, binary.LittleEndian, &padding)
|
|
||||||
if padding != 0 {
|
|
||||||
return fmt.Errorf("format error: padding must equal 0 got %d", padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary impelements the Payload interface.
|
|
||||||
func (h *Header) EncodeBinary(w io.Writer) error {
|
|
||||||
if err := h.BlockBase.EncodeBinary(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return binary.Write(w, binary.LittleEndian, uint8(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block represents one block in the chain.
|
// Block represents one block in the chain.
|
||||||
type Block struct {
|
type Block struct {
|
||||||
|
// The base of the block.
|
||||||
BlockBase
|
BlockBase
|
||||||
|
|
||||||
|
// Transaction list.
|
||||||
Transactions []*transaction.Transaction
|
Transactions []*transaction.Transaction
|
||||||
|
|
||||||
|
// True if this block is created from trimmed data.
|
||||||
|
Trimmed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns a pointer to the head of the block (BlockHead).
|
// Header returns the Header of the Block.
|
||||||
func (b *Block) Header() *Header {
|
func (b *Block) Header() *Header {
|
||||||
return &Header{
|
return &Header{
|
||||||
BlockBase: b.BlockBase,
|
BlockBase: b.BlockBase,
|
||||||
|
@ -196,25 +35,82 @@ func (b *Block) Verify(full bool) bool {
|
||||||
if b.Transactions[0].Type != transaction.MinerType {
|
if b.Transactions[0].Type != transaction.MinerType {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the first TX is a minerTX then all others cant.
|
// If the first TX is a minerTX then all others cant.
|
||||||
for _, tx := range b.Transactions[1:] {
|
for _, tx := range b.Transactions[1:] {
|
||||||
if tx.Type == transaction.MinerType {
|
if tx.Type == transaction.MinerType {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When full is true, do a full verification.
|
// TODO: When full is true, do a full verification.
|
||||||
if full {
|
if full {
|
||||||
log.Println("full verification of blocks is not yet implemented")
|
log.Warn("full verification of blocks is not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary encodes the block to the given writer.
|
// NewBlockFromTrimmedBytes returns a new block from trimmed data.
|
||||||
func (b *Block) EncodeBinary(w io.Writer) error {
|
// This is commonly used to create a block from stored data.
|
||||||
return nil
|
// Blocks created from trimmed data will have their Trimmed field
|
||||||
|
// set to true.
|
||||||
|
func NewBlockFromTrimmedBytes(b []byte) (*Block, error) {
|
||||||
|
block := &Block{
|
||||||
|
Trimmed: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(b)
|
||||||
|
if err := block.decodeHashableFields(r); err != nil {
|
||||||
|
return block, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding uint8
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &padding); err != nil {
|
||||||
|
return block, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block.Script = &transaction.Witness{}
|
||||||
|
if err := block.Script.DecodeBinary(r); err != nil {
|
||||||
|
return block, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lenTX := util.ReadVarUint(r)
|
||||||
|
block.Transactions = make([]*transaction.Transaction, lenTX)
|
||||||
|
for i := 0; i < int(lenTX); i++ {
|
||||||
|
var hash util.Uint256
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &hash); err != nil {
|
||||||
|
return block, err
|
||||||
|
}
|
||||||
|
block.Transactions[i] = transaction.NewTrimmedTX(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim returns a subset of the block data to save up space
|
||||||
|
// in storage.
|
||||||
|
// Notice that only the hashes of the transactions are stored.
|
||||||
|
func (b *Block) Trim() ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := b.encodeHashableFields(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(buf, binary.LittleEndian, uint8(1)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := b.Script.EncodeBinary(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lenTX := uint64(len(b.Transactions))
|
||||||
|
if err := util.WriteVarUint(buf, lenTX); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, tx := range b.Transactions {
|
||||||
|
if err := binary.Write(buf, binary.LittleEndian, tx.Hash()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary decodes the block from the given reader.
|
// DecodeBinary decodes the block from the given reader.
|
||||||
|
@ -234,3 +130,8 @@ func (b *Block) DecodeBinary(r io.Reader) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeBinary encodes the block to the given writer.
|
||||||
|
func (b *Block) EncodeBinary(w io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
164
pkg/core/block_base.go
Normal file
164
pkg/core/block_base.go
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockBase holds the base info of a block
|
||||||
|
type BlockBase struct {
|
||||||
|
// Version of the block.
|
||||||
|
Version uint32
|
||||||
|
|
||||||
|
// hash of the previous block.
|
||||||
|
PrevHash util.Uint256
|
||||||
|
|
||||||
|
// Root hash of a transaction list.
|
||||||
|
MerkleRoot util.Uint256
|
||||||
|
|
||||||
|
// The time stamp of each block must be later than previous block's time stamp.
|
||||||
|
// Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed.
|
||||||
|
// The height of the block must be exactly equal to the height of the previous block plus 1.
|
||||||
|
Timestamp uint32
|
||||||
|
|
||||||
|
// index/height of the block
|
||||||
|
Index uint32
|
||||||
|
|
||||||
|
// Random number also called nonce
|
||||||
|
ConsensusData uint64
|
||||||
|
|
||||||
|
// Contract addresss of the next miner
|
||||||
|
NextConsensus util.Uint160
|
||||||
|
|
||||||
|
// Padding that is fixed to 1
|
||||||
|
_ uint8
|
||||||
|
|
||||||
|
// Script used to validate the block
|
||||||
|
Script *transaction.Witness
|
||||||
|
|
||||||
|
// hash of this block, created when binary encoded.
|
||||||
|
hash util.Uint256
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies the integrity of the BlockBase.
|
||||||
|
func (b *BlockBase) Verify() bool {
|
||||||
|
// TODO: Need a persisted blockchain for this.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash return the hash of the block.
|
||||||
|
func (b *BlockBase) Hash() util.Uint256 {
|
||||||
|
return b.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements the payload interface.
|
||||||
|
func (b *BlockBase) DecodeBinary(r io.Reader) error {
|
||||||
|
if err := b.decodeHashableFields(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding uint8
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &padding); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if padding != 1 {
|
||||||
|
return fmt.Errorf("format error: padding must equal 1 got %d", padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Script = &transaction.Witness{}
|
||||||
|
return b.Script.DecodeBinary(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface
|
||||||
|
func (b *BlockBase) EncodeBinary(w io.Writer) error {
|
||||||
|
if err := b.encodeHashableFields(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, uint8(1)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.Script.EncodeBinary(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createHash creates the hash of the block.
|
||||||
|
// When calculating the hash value of the block, instead of calculating the entire block,
|
||||||
|
// only first seven fields in the block head will be calculated, which are
|
||||||
|
// version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner.
|
||||||
|
// Since MerkleRoot already contains the hash value of all transactions,
|
||||||
|
// the modification of transaction will influence the hash value of the block.
|
||||||
|
func (b *BlockBase) createHash() (hash util.Uint256, err error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err = b.encodeHashableFields(buf); err != nil {
|
||||||
|
return hash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double hash the encoded fields.
|
||||||
|
hash = sha256.Sum256(buf.Bytes())
|
||||||
|
hash = sha256.Sum256(hash.Bytes())
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeHashableFields will only encode the fields used for hashing.
|
||||||
|
// see Hash() for more information about the fields.
|
||||||
|
func (b *BlockBase) encodeHashableFields(w io.Writer) error {
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, b.Version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, b.PrevHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, b.MerkleRoot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, b.Timestamp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, b.Index); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, b.ConsensusData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Write(w, binary.LittleEndian, b.NextConsensus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeHashableFields will only decode the fields used for hashing.
|
||||||
|
// see Hash() for more information about the fields.
|
||||||
|
func (b *BlockBase) decodeHashableFields(r io.Reader) error {
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.Version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.PrevHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.MerkleRoot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.Timestamp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.Index); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.ConsensusData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &b.NextConsensus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the hash of the block here so we dont need to do this
|
||||||
|
// again.
|
||||||
|
hash, err := b.createHash()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.hash = hash
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,39 +2,78 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecodeBlock(t *testing.T) {
|
// Test blocks are blocks from mainnet with their corresponding index.
|
||||||
var (
|
|
||||||
rawBlock = "00000000b7def681f0080262aa293071c53b41fc3146b196067243700b68acd059734fd19543108bf9ddc738cbee2ed1160f153aa0d057f062de0aa3cbb64ba88735c23d43667e59543f050095df82b02e324c5ff3812db982f3b0089a21a278988efeec6a027b2501fd450140113ac66657c2f544e8ad13905fcb2ebaadfef9502cbefb07960fbe56df098814c223dcdd3d0efa0b43a9459e654d948516dcbd8b370f50fbecfb8b411d48051a408500ce85591e516525db24065411f6a88f43de90fa9c167c2e6f5af43bc84e65e5a4bb174bc83a19b6965ff10f476b1b151ae15439a985f33916abc6822b0bb140f4aae522ffaea229987a10d01beec826c3b9a189fe02aa82680581b78f3df0ea4d3f93ca8ea35ffc90f15f7db9017f92fafd9380d9ba3237973cf4313cf626fc40e30e50e3588bd047b39f478b59323868cd50c7ab54355d8245bf0f1988d37528f9bbfc68110cf917debbdbf1f4bdd02cdcccdc3269fdf18a6c727ee54b6934d840e43918dd1ec6123550ec37a513e72b34b2c2a3baa510dec3037cbef2fa9f6ed1e7ccd1f3f6e19d4ce2c0919af55249a970c2685217f75a5589cf9e54dff8449af155210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41acf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dddc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae01000095df82b000000000"
|
|
||||||
rawBlockHash = "922ba0c0d06afbeec4c50b0541a29153feaa46c5d7304e7bf7f40870d9f3aeb0"
|
|
||||||
rawBlockPrevHash = "d14f7359d0ac680b7043720696b14631fc413bc5713029aa620208f081f6deb7"
|
|
||||||
rawBlockIndex = 343892
|
|
||||||
rawBlockTimestamp = 1501455939
|
|
||||||
rawBlockConsensusData = 6866918707944415125
|
|
||||||
)
|
|
||||||
|
|
||||||
rawBlockBytes, err := hex.DecodeString(rawBlock)
|
func TestDecodeBlock1(t *testing.T) {
|
||||||
|
data, err := getBlockData(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := hex.DecodeString(data["raw"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
block := &Block{}
|
block := &Block{}
|
||||||
if err := block.DecodeBinary(bytes.NewReader(rawBlockBytes)); err != nil {
|
if err := block.DecodeBinary(bytes.NewReader(b)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, uint32(rawBlockIndex), block.Index)
|
|
||||||
assert.Equal(t, uint32(rawBlockTimestamp), block.Timestamp)
|
assert.Equal(t, uint32(data["index"].(float64)), block.Index)
|
||||||
assert.Equal(t, uint64(rawBlockConsensusData), block.ConsensusData)
|
assert.Equal(t, uint32(data["version"].(float64)), block.Version)
|
||||||
assert.Equal(t, rawBlockPrevHash, block.PrevHash.String())
|
assert.Equal(t, data["hash"].(string), block.Hash().String())
|
||||||
assert.Equal(t, rawBlockHash, block.Hash().String())
|
assert.Equal(t, data["previousblockhash"].(string), block.PrevHash.String())
|
||||||
|
assert.Equal(t, data["merkleroot"].(string), block.MerkleRoot.String())
|
||||||
|
assert.Equal(t, data["nextconsensus"].(string), crypto.AddressFromUint160(block.NextConsensus))
|
||||||
|
|
||||||
|
script := data["script"].(map[string]interface{})
|
||||||
|
assert.Equal(t, script["invocation"].(string), hex.EncodeToString(block.Script.InvocationScript))
|
||||||
|
assert.Equal(t, script["verification"].(string), hex.EncodeToString(block.Script.VerificationScript))
|
||||||
|
|
||||||
|
tx := data["tx"].([]interface{})
|
||||||
|
minerTX := tx[0].(map[string]interface{})
|
||||||
|
assert.Equal(t, len(tx), len(block.Transactions))
|
||||||
|
assert.Equal(t, minerTX["type"].(string), block.Transactions[0].Type.String())
|
||||||
|
assert.Equal(t, len(minerTX["attributes"].([]interface{})), len(block.Transactions[0].Attributes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimmedBlock(t *testing.T) {
|
||||||
|
block := getDecodedBlock(t, 1)
|
||||||
|
|
||||||
|
b, err := block.Trim()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedBlock, err := NewBlockFromTrimmedBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, trimmedBlock.Trimmed)
|
||||||
|
assert.Equal(t, block.Version, trimmedBlock.Version)
|
||||||
|
assert.Equal(t, block.PrevHash, trimmedBlock.PrevHash)
|
||||||
|
assert.Equal(t, block.MerkleRoot, trimmedBlock.MerkleRoot)
|
||||||
|
assert.Equal(t, block.Timestamp, trimmedBlock.Timestamp)
|
||||||
|
assert.Equal(t, block.Index, trimmedBlock.Index)
|
||||||
|
assert.Equal(t, block.ConsensusData, trimmedBlock.ConsensusData)
|
||||||
|
assert.Equal(t, block.NextConsensus, trimmedBlock.NextConsensus)
|
||||||
|
|
||||||
|
assert.Equal(t, block.Script, trimmedBlock.Script)
|
||||||
|
assert.Equal(t, len(block.Transactions), len(trimmedBlock.Transactions))
|
||||||
|
for i := 0; i < len(block.Transactions); i++ {
|
||||||
|
assert.Equal(t, block.Transactions[i].Hash(), trimmedBlock.Transactions[i].Hash())
|
||||||
|
assert.True(t, trimmedBlock.Transactions[i].Trimmed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHashBlockEqualsHashHeader(t *testing.T) {
|
func TestHashBlockEqualsHashHeader(t *testing.T) {
|
||||||
|
@ -48,42 +87,17 @@ func TestBlockVerify(t *testing.T) {
|
||||||
newTX(transaction.MinerType),
|
newTX(transaction.MinerType),
|
||||||
newTX(transaction.IssueType),
|
newTX(transaction.IssueType),
|
||||||
)
|
)
|
||||||
|
assert.True(t, block.Verify(false))
|
||||||
if !block.Verify(false) {
|
|
||||||
t.Fatal("block should be verified")
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Transactions = []*transaction.Transaction{
|
block.Transactions = []*transaction.Transaction{
|
||||||
{Type: transaction.IssueType},
|
{Type: transaction.IssueType},
|
||||||
{Type: transaction.MinerType},
|
{Type: transaction.MinerType},
|
||||||
}
|
}
|
||||||
|
assert.False(t, block.Verify(false))
|
||||||
if block.Verify(false) {
|
|
||||||
t.Fatal("block should not by verified")
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Transactions = []*transaction.Transaction{
|
block.Transactions = []*transaction.Transaction{
|
||||||
{Type: transaction.MinerType},
|
{Type: transaction.MinerType},
|
||||||
{Type: transaction.MinerType},
|
{Type: transaction.MinerType},
|
||||||
}
|
}
|
||||||
|
assert.False(t, block.Verify(false))
|
||||||
if block.Verify(false) {
|
|
||||||
t.Fatal("block should not by verified")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBlockBase() BlockBase {
|
|
||||||
return BlockBase{
|
|
||||||
Version: 0,
|
|
||||||
PrevHash: sha256.Sum256([]byte("a")),
|
|
||||||
MerkleRoot: sha256.Sum256([]byte("b")),
|
|
||||||
Timestamp: 999,
|
|
||||||
Index: 1,
|
|
||||||
ConsensusData: 1111,
|
|
||||||
NextConsensus: util.Uint160{},
|
|
||||||
Script: &transaction.Witness{
|
|
||||||
VerificationScript: []byte{0x0},
|
|
||||||
InvocationScript: []byte{0x1},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,13 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// tuning parameters
|
// tuning parameters
|
||||||
|
@ -26,20 +27,24 @@ var (
|
||||||
// Blockchain holds the chain.
|
// Blockchain holds the chain.
|
||||||
type Blockchain struct {
|
type Blockchain struct {
|
||||||
// Any object that satisfies the BlockchainStorer interface.
|
// Any object that satisfies the BlockchainStorer interface.
|
||||||
Store
|
storage.Store
|
||||||
|
|
||||||
// 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().
|
||||||
// Writes access should only happen in persist().
|
// Write access should only happen in persist().
|
||||||
blockHeight uint32
|
blockHeight uint32
|
||||||
|
|
||||||
// Number of headers stored.
|
// Number of headers stored in the chain file.
|
||||||
storedHeaderCount uint32
|
storedHeaderCount uint32
|
||||||
|
|
||||||
blockCache *Cache
|
blockCache *Cache
|
||||||
|
|
||||||
startHash util.Uint256
|
startHash util.Uint256
|
||||||
|
|
||||||
|
// All operation on headerList must be called from an
|
||||||
|
// headersOp to be routine safe.
|
||||||
|
headerList *HeaderHashList
|
||||||
|
|
||||||
// Only for operating on the headerList.
|
// Only for operating on the headerList.
|
||||||
headersOp chan headersOpFunc
|
headersOp chan headersOpFunc
|
||||||
headersOpDone chan struct{}
|
headersOpDone chan struct{}
|
||||||
|
@ -50,8 +55,9 @@ type Blockchain struct {
|
||||||
|
|
||||||
type headersOpFunc func(headerList *HeaderHashList)
|
type headersOpFunc func(headerList *HeaderHashList)
|
||||||
|
|
||||||
// NewBlockchain creates a new Blockchain object.
|
// NewBlockchain return a new blockchain object the will use the
|
||||||
func NewBlockchain(s Store, startHash util.Uint256) *Blockchain {
|
// given Store as its underlying storage.
|
||||||
|
func NewBlockchain(s storage.Store, startHash util.Uint256) (*Blockchain, error) {
|
||||||
bc := &Blockchain{
|
bc := &Blockchain{
|
||||||
Store: s,
|
Store: s,
|
||||||
headersOp: make(chan headersOpFunc),
|
headersOp: make(chan headersOpFunc),
|
||||||
|
@ -61,25 +67,85 @@ func NewBlockchain(s Store, startHash util.Uint256) *Blockchain {
|
||||||
verifyBlocks: false,
|
verifyBlocks: false,
|
||||||
}
|
}
|
||||||
go bc.run()
|
go bc.run()
|
||||||
bc.init()
|
|
||||||
|
|
||||||
return bc
|
if err := bc.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) init() {
|
func (bc *Blockchain) init() error {
|
||||||
// for the initial header, for now
|
// TODO: This should be the persistance of the genisis block.
|
||||||
bc.storedHeaderCount = 1
|
// for now we just add the genisis block start hash.
|
||||||
|
bc.headerList = NewHeaderHashList(bc.startHash)
|
||||||
|
bc.storedHeaderCount = 1 // genisis hash
|
||||||
|
|
||||||
|
// If we get an "not found" error, the store could not find
|
||||||
|
// the current block, which indicates there is nothing stored
|
||||||
|
// in the chain file.
|
||||||
|
currBlockBytes, err := bc.Get(storage.SYSCurrentBlock.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "not found") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bc.blockHeight = binary.LittleEndian.Uint32(currBlockBytes[32:36])
|
||||||
|
hashes, err := readStoredHeaderHashes(bc.Store)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, hash := range hashes {
|
||||||
|
if !bc.startHash.Equals(hash) {
|
||||||
|
bc.headerList.Add(hash)
|
||||||
|
bc.storedHeaderCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currHeaderBytes, err := bc.Get(storage.SYSCurrentHeader.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
currHeaderHeight := binary.LittleEndian.Uint32(currHeaderBytes[32:36])
|
||||||
|
currHeaderHash, err := util.Uint256DecodeBytes(currHeaderBytes[:32])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Their is a high chance that the Node is stopped before the next
|
||||||
|
// batch of 2000 headers was stored. Via the currentHeaders stored we can sync
|
||||||
|
// that with stored blocks.
|
||||||
|
if currHeaderHeight > bc.storedHeaderCount {
|
||||||
|
hash := currHeaderHash
|
||||||
|
targetHash := bc.headerList.Get(bc.headerList.Len() - 1)
|
||||||
|
headers := []*Header{}
|
||||||
|
|
||||||
|
for hash != targetHash {
|
||||||
|
header, err := bc.getHeader(hash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get header %s: %s", hash, err)
|
||||||
|
}
|
||||||
|
headers = append(headers, header)
|
||||||
|
hash = header.PrevHash
|
||||||
|
}
|
||||||
|
|
||||||
|
headerSliceReverse(headers)
|
||||||
|
if err := bc.AddHeaders(headers...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) run() {
|
func (bc *Blockchain) run() {
|
||||||
var (
|
persistTimer := time.NewTimer(persistInterval)
|
||||||
headerList = NewHeaderHashList(bc.startHash)
|
|
||||||
persistTimer = time.NewTimer(persistInterval)
|
|
||||||
)
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case op := <-bc.headersOp:
|
case op := <-bc.headersOp:
|
||||||
op(headerList)
|
op(bc.headerList)
|
||||||
bc.headersOpDone <- struct{}{}
|
bc.headersOpDone <- struct{}{}
|
||||||
case <-persistTimer.C:
|
case <-persistTimer.C:
|
||||||
go bc.persist()
|
go bc.persist()
|
||||||
|
@ -113,14 +179,14 @@ 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 = new(leveldb.Batch)
|
batch = bc.Batch()
|
||||||
)
|
)
|
||||||
|
|
||||||
bc.headersOp <- func(headerList *HeaderHashList) {
|
bc.headersOp <- func(headerList *HeaderHashList) {
|
||||||
for _, h := range headers {
|
for _, h := range headers {
|
||||||
if int(h.Index-1) >= headerList.Len() {
|
if int(h.Index-1) >= headerList.Len() {
|
||||||
err = fmt.Errorf(
|
err = fmt.Errorf(
|
||||||
"height of block higher then current header height %d > %d\n",
|
"height of received header %d is higher then the current header %d",
|
||||||
h.Index, headerList.Len(),
|
h.Index, headerList.Len(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -138,7 +204,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if batch.Len() > 0 {
|
if batch.Len() > 0 {
|
||||||
if err = bc.writeBatch(batch); err != nil {
|
if err = bc.PutBatch(batch); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
@ -154,7 +220,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
||||||
|
|
||||||
// processHeader processes the given header. Note that this is only thread safe
|
// processHeader processes the given header. Note that this is only thread safe
|
||||||
// if executed in headers operation.
|
// if executed in headers operation.
|
||||||
func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList *HeaderHashList) error {
|
func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList *HeaderHashList) error {
|
||||||
headerList.Add(h.Hash())
|
headerList.Add(h.Hash())
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
@ -162,7 +228,7 @@ func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList
|
||||||
if err := headerList.Write(buf, int(bc.storedHeaderCount), headerBatchCount); err != nil {
|
if err := headerList.Write(buf, int(bc.storedHeaderCount), headerBatchCount); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
key := makeEntryPrefixInt(preIXHeaderHashList, int(bc.storedHeaderCount))
|
key := storage.AppendPrefixInt(storage.IXHeaderHashList, int(bc.storedHeaderCount))
|
||||||
batch.Put(key, buf.Bytes())
|
batch.Put(key, buf.Bytes())
|
||||||
bc.storedHeaderCount += headerBatchCount
|
bc.storedHeaderCount += headerBatchCount
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
@ -173,25 +239,22 @@ func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
key := makeEntryPrefix(preDataBlock, h.Hash().BytesReverse())
|
key := storage.AppendPrefix(storage.DataBlock, h.Hash().BytesReverse())
|
||||||
batch.Put(key, buf.Bytes())
|
batch.Put(key, buf.Bytes())
|
||||||
key = preSYSCurrentHeader.bytes()
|
batch.Put(storage.SYSCurrentHeader.Bytes(), hashAndIndexToBytes(h.Hash(), h.Index))
|
||||||
batch.Put(key, hashAndIndexToBytes(h.Hash(), h.Index))
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) persistBlock(block *Block) error {
|
func (bc *Blockchain) persistBlock(block *Block) error {
|
||||||
batch := new(leveldb.Batch)
|
batch := bc.Batch()
|
||||||
|
|
||||||
// Store the block.
|
storeAsBlock(batch, block, 0)
|
||||||
key := preSYSCurrentBlock.bytes()
|
storeAsCurrentBlock(batch, block)
|
||||||
batch.Put(key, hashAndIndexToBytes(block.Hash(), block.Index))
|
|
||||||
|
|
||||||
if err := bc.Store.writeBatch(batch); err != nil {
|
if err := bc.PutBatch(batch); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddUint32(&bc.blockHeight, 1)
|
atomic.AddUint32(&bc.blockHeight, 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -241,7 +304,32 @@ func (bc *Blockchain) headerListLen() (n int) {
|
||||||
|
|
||||||
// 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) {
|
||||||
return nil, nil
|
key := storage.AppendPrefix(storage.DataBlock, hash.BytesReverse())
|
||||||
|
b, err := bc.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
block, err := NewBlockFromTrimmedBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO: persist TX first before we can handle this logic.
|
||||||
|
//if len(block.Transactions) == 0 {
|
||||||
|
// return nil, fmt.Errorf("block has no TX")
|
||||||
|
//}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) getHeader(hash util.Uint256) (*Header, error) {
|
||||||
|
b, err := bc.Get(storage.AppendPrefix(storage.DataBlock, hash.BytesReverse()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
block, err := NewBlockFromTrimmedBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return block.Header(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasBlock return true if the blockchain contains he given
|
// HasBlock return true if the blockchain contains he given
|
||||||
|
@ -253,6 +341,9 @@ func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
|
||||||
// HasBlock return true if the blockchain contains the given
|
// HasBlock return true if the blockchain contains the given
|
||||||
// block hash.
|
// block hash.
|
||||||
func (bc *Blockchain) HasBlock(hash util.Uint256) bool {
|
func (bc *Blockchain) HasBlock(hash util.Uint256) bool {
|
||||||
|
if header, err := bc.getHeader(hash); err == nil {
|
||||||
|
return header.Index <= bc.BlockHeight()
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,22 +3,13 @@ package core
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewBlockchain(t *testing.T) {
|
|
||||||
startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
|
||||||
bc := NewBlockchain(nil, startHash)
|
|
||||||
|
|
||||||
assert.Equal(t, uint32(0), bc.BlockHeight())
|
|
||||||
assert.Equal(t, uint32(0), bc.HeaderHeight())
|
|
||||||
assert.Equal(t, uint32(1), bc.storedHeaderCount)
|
|
||||||
assert.Equal(t, startHash, bc.startHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddHeaders(t *testing.T) {
|
func TestAddHeaders(t *testing.T) {
|
||||||
bc := newTestBC()
|
bc := newTestChain(t)
|
||||||
h1 := newBlock(1).Header()
|
h1 := newBlock(1).Header()
|
||||||
h2 := newBlock(2).Header()
|
h2 := newBlock(2).Header()
|
||||||
h3 := newBlock(3).Header()
|
h3 := newBlock(3).Header()
|
||||||
|
@ -45,7 +36,7 @@ func TestAddHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddBlock(t *testing.T) {
|
func TestAddBlock(t *testing.T) {
|
||||||
bc := newTestBC()
|
bc := newTestChain(t)
|
||||||
blocks := []*Block{
|
blocks := []*Block{
|
||||||
newBlock(1),
|
newBlock(1),
|
||||||
newBlock(2),
|
newBlock(2),
|
||||||
|
@ -73,8 +64,69 @@ func TestAddBlock(t *testing.T) {
|
||||||
assert.Equal(t, 0, bc.blockCache.Len())
|
assert.Equal(t, 0, bc.blockCache.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestBC() *Blockchain {
|
func TestGetHeader(t *testing.T) {
|
||||||
startHash, _ := util.Uint256DecodeString("a")
|
bc := newTestChain(t)
|
||||||
bc := NewBlockchain(NewMemoryStore(), startHash)
|
block := newBlock(1)
|
||||||
return bc
|
err := bc.AddBlock(block)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
hash := block.Hash()
|
||||||
|
header, err := bc.getHeader(hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, block.Header(), header)
|
||||||
|
|
||||||
|
block = newBlock(2)
|
||||||
|
hash = block.Hash()
|
||||||
|
_, err = bc.getHeader(block.Hash())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlock(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
blocks := makeBlocks(100)
|
||||||
|
|
||||||
|
for i := 0; i < len(blocks); i++ {
|
||||||
|
if err := bc.AddBlock(blocks[i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(blocks); i++ {
|
||||||
|
block, err := bc.GetBlock(blocks[i].Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, blocks[i].Index, block.Index)
|
||||||
|
assert.Equal(t, blocks[i].Hash(), block.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasBlock(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
blocks := makeBlocks(50)
|
||||||
|
|
||||||
|
for i := 0; i < len(blocks); i++ {
|
||||||
|
if err := bc.AddBlock(blocks[i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Nil(t, bc.persist())
|
||||||
|
|
||||||
|
for i := 0; i < len(blocks); i++ {
|
||||||
|
assert.True(t, bc.HasBlock(blocks[i].Hash()))
|
||||||
|
}
|
||||||
|
|
||||||
|
newBlock := newBlock(51)
|
||||||
|
assert.False(t, bc.HasBlock(newBlock.Hash()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestChain(t *testing.T) *Blockchain {
|
||||||
|
startHash, _ := util.Uint256DecodeString("a")
|
||||||
|
chain, err := NewBlockchain(storage.NewMemoryStore(), startHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return chain
|
||||||
}
|
}
|
||||||
|
|
38
pkg/core/header.go
Normal file
38
pkg/core/header.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Header holds the head info of a block.
|
||||||
|
type Header struct {
|
||||||
|
// Base of the block.
|
||||||
|
BlockBase
|
||||||
|
// Padding that is fixed to 0
|
||||||
|
_ uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary impelements the Payload interface.
|
||||||
|
func (h *Header) DecodeBinary(r io.Reader) error {
|
||||||
|
if err := h.BlockBase.DecodeBinary(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding uint8
|
||||||
|
binary.Read(r, binary.LittleEndian, &padding)
|
||||||
|
if padding != 0 {
|
||||||
|
return fmt.Errorf("format error: padding must equal 0 got %d", padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary impelements the Payload interface.
|
||||||
|
func (h *Header) EncodeBinary(w io.Writer) error {
|
||||||
|
if err := h.BlockBase.EncodeBinary(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Write(w, binary.LittleEndian, uint8(0))
|
||||||
|
}
|
|
@ -14,6 +14,11 @@ type HeaderHashList struct {
|
||||||
hashes []util.Uint256
|
hashes []util.Uint256
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewHeaderHashListFromBytes return a new hash list from the given bytes.
|
||||||
|
func NewHeaderHashListFromBytes(b []byte) (*HeaderHashList, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewHeaderHashList return a new pointer to a HeaderHashList.
|
// NewHeaderHashList return a new pointer to a HeaderHashList.
|
||||||
func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList {
|
func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList {
|
||||||
return &HeaderHashList{
|
return &HeaderHashList{
|
||||||
|
@ -22,8 +27,8 @@ func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends the given hash to the list of hashes.
|
// Add appends the given hash to the list of hashes.
|
||||||
func (l *HeaderHashList) Add(h util.Uint256) {
|
func (l *HeaderHashList) Add(h ...util.Uint256) {
|
||||||
l.hashes = append(l.hashes, h)
|
l.hashes = append(l.hashes, h...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len return the length of the underlying hashes slice.
|
// Len return the length of the underlying hashes slice.
|
||||||
|
@ -47,7 +52,7 @@ func (l *HeaderHashList) Last() util.Uint256 {
|
||||||
// Slice return a subslice of the underlying hashes.
|
// Slice return a subslice of the underlying hashes.
|
||||||
// Subsliced from start to end.
|
// Subsliced from start to end.
|
||||||
// Example:
|
// Example:
|
||||||
// headers := headerList.Slice(0, 2000)
|
// headers := headerList.Slice(0, 2000)
|
||||||
func (l *HeaderHashList) Slice(start, end int) []util.Uint256 {
|
func (l *HeaderHashList) Slice(start, end int) []util.Uint256 {
|
||||||
return l.hashes[start:end]
|
return l.hashes[start:end]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
@ -33,8 +39,47 @@ func newBlock(index uint32, txs ...*transaction.Transaction) *Block {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeBlocks(n int) []*Block {
|
||||||
|
blocks := make([]*Block, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
blocks[i] = newBlock(uint32(i+1), newTX(transaction.MinerType))
|
||||||
|
}
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
|
|
||||||
func newTX(t transaction.TXType) *transaction.Transaction {
|
func newTX(t transaction.TXType) *transaction.Transaction {
|
||||||
return &transaction.Transaction{
|
return &transaction.Transaction{
|
||||||
Type: t,
|
Type: t,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDecodedBlock(t *testing.T, i int) *Block {
|
||||||
|
data, err := getBlockData(i)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := hex.DecodeString(data["raw"].(string))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block := &Block{}
|
||||||
|
if err := block.DecodeBinary(bytes.NewReader(b)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlockData(i int) (map[string]interface{}, error) {
|
||||||
|
b, err := ioutil.ReadFile(fmt.Sprintf("test_data/block_%d.json", i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data map[string]interface{}
|
||||||
|
if err := json.Unmarshal(b, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LevelDBStore is the official storage implementation for storing and retreiving
|
|
||||||
// blockchain data.
|
|
||||||
type LevelDBStore struct {
|
|
||||||
db *leveldb.DB
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLevelDBStore return a new LevelDBStore object that will
|
|
||||||
// initialize the database found at the given path.
|
|
||||||
func NewLevelDBStore(path string, opts *opt.Options) (*LevelDBStore, error) {
|
|
||||||
db, err := leveldb.OpenFile(path, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &LevelDBStore{
|
|
||||||
path: path,
|
|
||||||
db: db,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// write implements the Store interface.
|
|
||||||
func (s *LevelDBStore) write(key, value []byte) error {
|
|
||||||
return s.db.Put(key, value, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
//get implements the Store interface.
|
|
||||||
func (s *LevelDBStore) get(key []byte) ([]byte, error) {
|
|
||||||
return s.db.Get(key, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeBatch implements the Store interface.
|
|
||||||
func (s *LevelDBStore) writeBatch(batch *leveldb.Batch) error {
|
|
||||||
return s.db.Write(batch, nil)
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
path = "test_chain"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPersistBlock(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBlockchain() *Blockchain {
|
|
||||||
startHash, _ := util.Uint256DecodeString("a")
|
|
||||||
opts := &opt.Options{}
|
|
||||||
store, _ := NewLevelDBStore(path, opts)
|
|
||||||
chain := NewBlockchain(
|
|
||||||
store,
|
|
||||||
startHash,
|
|
||||||
)
|
|
||||||
return chain
|
|
||||||
}
|
|
||||||
|
|
||||||
func tearDown() error {
|
|
||||||
return os.RemoveAll(path)
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import "github.com/syndtr/goleveldb/leveldb"
|
|
||||||
|
|
||||||
// MemoryStore is an in memory implementation of a BlockChainStorer
|
|
||||||
// that should only be used for testing.
|
|
||||||
type MemoryStore struct{}
|
|
||||||
|
|
||||||
// NewMemoryStore returns a pointer to a MemoryStore object.
|
|
||||||
func NewMemoryStore() *MemoryStore {
|
|
||||||
return &MemoryStore{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get implementes the BlockchainStorer interface.
|
|
||||||
func (m *MemoryStore) get(key []byte) ([]byte, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// write implementes the BlockchainStorer interface.
|
|
||||||
func (m *MemoryStore) write(key, value []byte) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeBatch implementes the BlockchainStorer interface.
|
|
||||||
func (m *MemoryStore) writeBatch(batch *leveldb.Batch) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
var (
|
|
||||||
rawBlock0 = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151"
|
|
||||||
|
|
||||||
rawBlock1 = "00000000bf4421c88776c53b43ce1dc45463bfd2028e322fdfb60064be150ed3e36125d418f98ec3ed2c2d1c9427385e7b85d0d1a366e29c4e399693a59718380f8bbad6d6d90358010000004490d0bb7170726c59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd4501404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae0100004490d0bb00000000"
|
|
||||||
)
|
|
2
pkg/core/storage/README.md
Normal file
2
pkg/core/storage/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Storage
|
||||||
|
|
57
pkg/core/storage/leveldb_store.go
Normal file
57
pkg/core/storage/leveldb_store.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LevelDBStore is the official storage implementation for storing and retreiving
|
||||||
|
// blockchain data.
|
||||||
|
type LevelDBStore struct {
|
||||||
|
db *leveldb.DB
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLevelDBStore return a new LevelDBStore object that will
|
||||||
|
// initialize the database found at the given path.
|
||||||
|
func NewLevelDBStore(path string, opts *opt.Options) (*LevelDBStore, error) {
|
||||||
|
db, err := leveldb.OpenFile(path, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &LevelDBStore{
|
||||||
|
path: path,
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Store interface.
|
||||||
|
func (s *LevelDBStore) Put(key, value []byte) error {
|
||||||
|
return s.db.Put(key, value, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Store interface.
|
||||||
|
func (s *LevelDBStore) Get(key []byte) ([]byte, error) {
|
||||||
|
return s.db.Get(key, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutBatch implements the Store interface.
|
||||||
|
func (s *LevelDBStore) PutBatch(batch Batch) error {
|
||||||
|
lvldbBatch := batch.(*leveldb.Batch)
|
||||||
|
return s.db.Write(lvldbBatch, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek implements the Store interface.
|
||||||
|
func (s *LevelDBStore) Seek(key []byte, f func(k, v []byte)) {
|
||||||
|
iter := s.db.NewIterator(util.BytesPrefix(key), nil)
|
||||||
|
for iter.Next() {
|
||||||
|
f(iter.Key(), iter.Value())
|
||||||
|
}
|
||||||
|
iter.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch implements the Batch interface and returns a compatible Batch.
|
||||||
|
func (s *LevelDBStore) Batch() Batch {
|
||||||
|
return new(leveldb.Batch)
|
||||||
|
}
|
1
pkg/core/storage/leveldb_store_test.go
Normal file
1
pkg/core/storage/leveldb_store_test.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package storage
|
64
pkg/core/storage/memory_store.go
Normal file
64
pkg/core/storage/memory_store.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
// MemoryStore is an in-memory implementation of a Store, mainly
|
||||||
|
// used for testing. Do not use MemoryStore in production.
|
||||||
|
type MemoryStore struct {
|
||||||
|
mem map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryBatch a in-memory batch compatible with MemoryStore.
|
||||||
|
type MemoryBatch struct {
|
||||||
|
m map[*[]byte][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Batch interface.
|
||||||
|
func (b *MemoryBatch) Put(k, v []byte) {
|
||||||
|
key := &k
|
||||||
|
b.m[key] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Batch interface.
|
||||||
|
func (b *MemoryBatch) Len() int {
|
||||||
|
return len(b.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemoryStore creates a new MemoryStore object.
|
||||||
|
func NewMemoryStore() *MemoryStore {
|
||||||
|
return &MemoryStore{
|
||||||
|
mem: make(map[string][]byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Store interface.
|
||||||
|
func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
||||||
|
if val, ok := s.mem[string(key)]; ok {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return nil, ErrKeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implementes the Store interface.
|
||||||
|
func (s *MemoryStore) Put(key, value []byte) error {
|
||||||
|
s.mem[string(key)] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutBatch implementes the Store interface.
|
||||||
|
func (s *MemoryStore) PutBatch(batch Batch) error {
|
||||||
|
b := batch.(*MemoryBatch)
|
||||||
|
for k, v := range b.m {
|
||||||
|
s.Put(*k, v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek implementes the Store interface.
|
||||||
|
func (s *MemoryStore) Seek(key []byte, f func(k, v []byte)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch implements the Batch interface and returns a compatible Batch.
|
||||||
|
func (s *MemoryStore) Batch() Batch {
|
||||||
|
return &MemoryBatch{
|
||||||
|
m: make(map[*[]byte][]byte),
|
||||||
|
}
|
||||||
|
}
|
41
pkg/core/storage/memory_store_test.go
Normal file
41
pkg/core/storage/memory_store_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetPut(t *testing.T) {
|
||||||
|
var (
|
||||||
|
s = NewMemoryStore()
|
||||||
|
key = []byte("sparse")
|
||||||
|
value = []byte("rocks")
|
||||||
|
)
|
||||||
|
|
||||||
|
s.Put(key, value)
|
||||||
|
|
||||||
|
newVal, err := s.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, value, newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutBatch(t *testing.T) {
|
||||||
|
var (
|
||||||
|
s = NewMemoryStore()
|
||||||
|
key = []byte("sparse")
|
||||||
|
value = []byte("rocks")
|
||||||
|
batch = s.Batch()
|
||||||
|
)
|
||||||
|
|
||||||
|
batch.Put(key, value)
|
||||||
|
|
||||||
|
s.PutBatch(batch)
|
||||||
|
newVal, err := s.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, value, newVal)
|
||||||
|
}
|
73
pkg/core/storage/store.go
Normal file
73
pkg/core/storage/store.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyPrefix constants.
|
||||||
|
const (
|
||||||
|
DataBlock KeyPrefix = 0x01
|
||||||
|
DataTransaction KeyPrefix = 0x02
|
||||||
|
STAccount KeyPrefix = 0x40
|
||||||
|
STCoin KeyPrefix = 0x44
|
||||||
|
STValidator KeyPrefix = 0x48
|
||||||
|
STAsset KeyPrefix = 0x4c
|
||||||
|
STContract KeyPrefix = 0x50
|
||||||
|
STStorage KeyPrefix = 0x70
|
||||||
|
IXHeaderHashList KeyPrefix = 0x80
|
||||||
|
IXValidatorsCount KeyPrefix = 0x90
|
||||||
|
SYSCurrentBlock KeyPrefix = 0xc0
|
||||||
|
SYSCurrentHeader KeyPrefix = 0xc1
|
||||||
|
SYSVersion KeyPrefix = 0xf0
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrKeyNotFound is an error returned by Store implementations
|
||||||
|
// when a certain key is not found.
|
||||||
|
var ErrKeyNotFound = errors.New("key not found")
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Store is anything that can persist and retrieve the blockchain.
|
||||||
|
// information.
|
||||||
|
Store interface {
|
||||||
|
Batch() Batch
|
||||||
|
Get([]byte) ([]byte, error)
|
||||||
|
Put(k, v []byte) error
|
||||||
|
PutBatch(batch Batch) error
|
||||||
|
Seek(k []byte, f func(k, v []byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch represents an abstraction on top of batch operations.
|
||||||
|
// Each Store implementation is responsible of casting a Batch
|
||||||
|
// to its appropriate type.
|
||||||
|
Batch interface {
|
||||||
|
Put(k, v []byte)
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyPrefix is a constant byte added as a prefix for each key
|
||||||
|
// stored.
|
||||||
|
KeyPrefix uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bytes returns the bytes representation of KeyPrefix.
|
||||||
|
func (k KeyPrefix) Bytes() []byte {
|
||||||
|
return []byte{byte(k)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendPrefix append byteslice b to the given KeyPrefix.
|
||||||
|
// AppendKeyPrefix(SYSVersion, []byte{0x00, 0x01})
|
||||||
|
func AppendPrefix(k KeyPrefix, b []byte) []byte {
|
||||||
|
dest := make([]byte, len(b)+1)
|
||||||
|
dest[0] = byte(k)
|
||||||
|
copy(dest[1:], b)
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendPrefixInt append int n to the given KeyPrefix.
|
||||||
|
// AppendPrefixInt(SYSCurrentHeader, 10001)
|
||||||
|
func AppendPrefixInt(k KeyPrefix, n int) []byte {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(b, uint32(n))
|
||||||
|
return AppendPrefix(k, b)
|
||||||
|
}
|
57
pkg/core/storage/store_test.go
Normal file
57
pkg/core/storage/store_test.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
prefixes = []KeyPrefix{
|
||||||
|
DataBlock,
|
||||||
|
DataTransaction,
|
||||||
|
STAccount,
|
||||||
|
STCoin,
|
||||||
|
STValidator,
|
||||||
|
STAsset,
|
||||||
|
STContract,
|
||||||
|
STStorage,
|
||||||
|
IXHeaderHashList,
|
||||||
|
IXValidatorsCount,
|
||||||
|
SYSCurrentBlock,
|
||||||
|
SYSCurrentHeader,
|
||||||
|
SYSVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = []uint8{
|
||||||
|
0x01,
|
||||||
|
0x02,
|
||||||
|
0x40,
|
||||||
|
0x44,
|
||||||
|
0x48,
|
||||||
|
0x4c,
|
||||||
|
0x50,
|
||||||
|
0x70,
|
||||||
|
0x80,
|
||||||
|
0x90,
|
||||||
|
0xc0,
|
||||||
|
0xc1,
|
||||||
|
0xf0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppendPrefix(t *testing.T) {
|
||||||
|
for i := 0; i < len(expected); i++ {
|
||||||
|
value := []byte{0x01, 0x02}
|
||||||
|
prefix := AppendPrefix(prefixes[i], value)
|
||||||
|
assert.Equal(t, KeyPrefix(expected[i]), KeyPrefix(prefix[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendPrefixInt(t *testing.T) {
|
||||||
|
for i := 0; i < len(expected); i++ {
|
||||||
|
value := 2000
|
||||||
|
prefix := AppendPrefixInt(prefixes[i], value)
|
||||||
|
assert.Equal(t, KeyPrefix(expected[i]), KeyPrefix(prefix[0]))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dataEntry uint8
|
|
||||||
|
|
||||||
func (e dataEntry) bytes() []byte {
|
|
||||||
return []byte{byte(e)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage data entry prefixes.
|
|
||||||
const (
|
|
||||||
preDataBlock dataEntry = 0x01
|
|
||||||
preDataTransaction dataEntry = 0x02
|
|
||||||
preSTAccount dataEntry = 0x40
|
|
||||||
preSTCoin dataEntry = 0x44
|
|
||||||
preSTValidator dataEntry = 0x48
|
|
||||||
preSTAsset dataEntry = 0x4c
|
|
||||||
preSTContract dataEntry = 0x50
|
|
||||||
preSTStorage dataEntry = 0x70
|
|
||||||
preIXHeaderHashList dataEntry = 0x80
|
|
||||||
preIXValidatorsCount dataEntry = 0x90
|
|
||||||
preSYSCurrentBlock dataEntry = 0xc0
|
|
||||||
preSYSCurrentHeader dataEntry = 0xc1
|
|
||||||
preSYSVersion dataEntry = 0xf0
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeEntryPrefixInt(e dataEntry, n int) []byte {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
binary.Write(buf, binary.LittleEndian, n)
|
|
||||||
return makeEntryPrefix(e, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeEntryPrefix(e dataEntry, b []byte) []byte {
|
|
||||||
dest := make([]byte, len(b)+1)
|
|
||||||
dest[0] = byte(e)
|
|
||||||
for i := 1; i < len(b); i++ {
|
|
||||||
dest[i] = b[i]
|
|
||||||
}
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store is anything that can persist and retrieve the blockchain.
|
|
||||||
type Store interface {
|
|
||||||
get(k []byte) ([]byte, error)
|
|
||||||
write(k, v []byte) error
|
|
||||||
writeBatch(batch *leveldb.Batch) error
|
|
||||||
}
|
|
33
pkg/core/test_data/block_1.json
Normal file
33
pkg/core/test_data/block_1.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"raw": "00000000bf4421c88776c53b43ce1dc45463bfd2028e322fdfb60064be150ed3e36125d418f98ec3ed2c2d1c9427385e7b85d0d1a366e29c4e399693a59718380f8bbad6d6d90358010000004490d0bb7170726c59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd4501404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae0100004490d0bb00000000",
|
||||||
|
"hash": "d782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9",
|
||||||
|
"size": 686,
|
||||||
|
"version": 0,
|
||||||
|
"previousblockhash": "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf",
|
||||||
|
"merkleroot": "d6ba8b0f381897a59396394e9ce266a3d1d0857b5e3827941c2d2cedc38ef918",
|
||||||
|
"time": 1476647382,
|
||||||
|
"index": 1,
|
||||||
|
"nonce": "6c727071bbd09044",
|
||||||
|
"nextconsensus": "APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR",
|
||||||
|
"script": {
|
||||||
|
"invocation": "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822",
|
||||||
|
"verification": "552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae"
|
||||||
|
},
|
||||||
|
"tx": [
|
||||||
|
{
|
||||||
|
"txid": "d6ba8b0f381897a59396394e9ce266a3d1d0857b5e3827941c2d2cedc38ef918",
|
||||||
|
"size": 10,
|
||||||
|
"type": "MinerTransaction",
|
||||||
|
"version": 0,
|
||||||
|
"attributes": [],
|
||||||
|
"vin": [],
|
||||||
|
"vout": [],
|
||||||
|
"sys_fee": "0",
|
||||||
|
"net_fee": "0",
|
||||||
|
"scripts": [],
|
||||||
|
"nonce": 3151007812
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"confirmations": 2020977,
|
||||||
|
"nextblockhash": "bf638e92c85016df9bc3b62b33f3879fa22d49d5f55d822b423149a3bca9e574"
|
||||||
|
}
|
33
pkg/core/test_data/block_2.json
Normal file
33
pkg/core/test_data/block_2.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"raw": "00000000c96c94033911087c387cc77875869817c7617c000f4e39d7a0eeb0388adb82d74208df9542f56a42fb2764142d13274c951087c609565fd5c4ab9b57a183a1afead9035802000000b66fa838e89c9ab259e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd450140e8a85159d8655c7b5a66429831eb15dabefc0f27a22bef67febb9eccb6859cc4c5c6ae675175a0bbefeeeeff2a8e9f175aaaae0796f3b5f29cb93b5b50fbf270409270a02cbbcb99969d6dc8a85708d5609dc1bba9569c849b53db7896c7f1ffd3adc789c0fe8400fb665478567448b4c4bd9c1657432591e4de83df10348f865a40724a9cf9d43eda558bfa8755e7bd1c0e9282f96164f4ff0b7369fd80e878cf49f2e61ed0fdf8cf218e7fdd471be5f29ef1242c39f3695d5decb169667fe0d3d140860da333249f7c54db09b548ad5d5e45fb8787238d51b35a6d4759f7990f47f00ff102e7b88f45acce423dd9f4b87dbf85e7e2c5c7a6aace11e62267c0bbe16b4028d272a701c22c5f8aa3495fa22d7d5a583518ef552e73813ee369c6d51ad2f246a24eb0092ebe7e1550d7de2ee09abad4dae4f4c0277317f5b1190041b9c2c2f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae010000b66fa83800000000",
|
||||||
|
"hash": "0xbf638e92c85016df9bc3b62b33f3879fa22d49d5f55d822b423149a3bca9e574",
|
||||||
|
"size": 686,
|
||||||
|
"version": 0,
|
||||||
|
"previousblockhash": "0xd782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9",
|
||||||
|
"merkleroot": "0xafa183a1579babc4d55f5609c68710954c27132d146427fb426af54295df0842",
|
||||||
|
"time": 1476647402,
|
||||||
|
"index": 2,
|
||||||
|
"nonce": "b29a9ce838a86fb6",
|
||||||
|
"nextconsensus": "APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR",
|
||||||
|
"script": {
|
||||||
|
"invocation": "40e8a85159d8655c7b5a66429831eb15dabefc0f27a22bef67febb9eccb6859cc4c5c6ae675175a0bbefeeeeff2a8e9f175aaaae0796f3b5f29cb93b5b50fbf270409270a02cbbcb99969d6dc8a85708d5609dc1bba9569c849b53db7896c7f1ffd3adc789c0fe8400fb665478567448b4c4bd9c1657432591e4de83df10348f865a40724a9cf9d43eda558bfa8755e7bd1c0e9282f96164f4ff0b7369fd80e878cf49f2e61ed0fdf8cf218e7fdd471be5f29ef1242c39f3695d5decb169667fe0d3d140860da333249f7c54db09b548ad5d5e45fb8787238d51b35a6d4759f7990f47f00ff102e7b88f45acce423dd9f4b87dbf85e7e2c5c7a6aace11e62267c0bbe16b4028d272a701c22c5f8aa3495fa22d7d5a583518ef552e73813ee369c6d51ad2f246a24eb0092ebe7e1550d7de2ee09abad4dae4f4c0277317f5b1190041b9c2c2",
|
||||||
|
"verification": "552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae"
|
||||||
|
},
|
||||||
|
"tx": [
|
||||||
|
{
|
||||||
|
"txid": "0xafa183a1579babc4d55f5609c68710954c27132d146427fb426af54295df0842",
|
||||||
|
"size": 10,
|
||||||
|
"type": "MinerTransaction",
|
||||||
|
"version": 0,
|
||||||
|
"attributes": [],
|
||||||
|
"vin": [],
|
||||||
|
"vout": [],
|
||||||
|
"sys_fee": "0",
|
||||||
|
"net_fee": "0",
|
||||||
|
"scripts": [],
|
||||||
|
"nonce": 950562742
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"confirmations": 2021026,
|
||||||
|
"nextblockhash": "0x1fca8800f1ffbc9fb08bcfee1269461161d58dcee0252cf4db13220ba8189c5d"
|
||||||
|
}
|
16
pkg/core/transaction/asset_type.go
Normal file
16
pkg/core/transaction/asset_type.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
// AssetType represent a NEO asset type
|
||||||
|
type AssetType uint8
|
||||||
|
|
||||||
|
// Valid asset types.
|
||||||
|
const (
|
||||||
|
CreditFlag AssetType = 0x40
|
||||||
|
DutyFlag AssetType = 0x80
|
||||||
|
GoverningToken AssetType = 0x00
|
||||||
|
UtilityToken AssetType = 0x01
|
||||||
|
Currency AssetType = 0x08
|
||||||
|
Share AssetType = DutyFlag | 0x10
|
||||||
|
Invoice AssetType = DutyFlag | 0x18
|
||||||
|
Token AssetType = CreditFlag | 0x20
|
||||||
|
)
|
|
@ -2,7 +2,7 @@ package transaction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
@ -14,7 +14,7 @@ type Attribute struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements the Payloader interface.
|
// DecodeBinary implements the Payload interface.
|
||||||
func (attr *Attribute) DecodeBinary(r io.Reader) error {
|
func (attr *Attribute) DecodeBinary(r io.Reader) error {
|
||||||
if err := binary.Read(r, binary.LittleEndian, &attr.Usage); err != nil {
|
if err := binary.Read(r, binary.LittleEndian, &attr.Usage); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -43,7 +43,7 @@ func (attr *Attribute) DecodeBinary(r io.Reader) error {
|
||||||
attr.Data = make([]byte, lenData)
|
attr.Data = make([]byte, lenData)
|
||||||
return binary.Read(r, binary.LittleEndian, attr.Data)
|
return binary.Read(r, binary.LittleEndian, attr.Data)
|
||||||
}
|
}
|
||||||
return errors.New("format error in decoding transaction attribute")
|
return fmt.Errorf("failed decoding TX attribute usage: 0x%2x", attr.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements the Payload interface.
|
// EncodeBinary implements the Payload interface.
|
||||||
|
@ -75,5 +75,5 @@ func (attr *Attribute) EncodeBinary(w io.Writer) error {
|
||||||
}
|
}
|
||||||
return binary.Write(w, binary.LittleEndian, attr.Data)
|
return binary.Write(w, binary.LittleEndian, attr.Data)
|
||||||
}
|
}
|
||||||
return errors.New("format error in encoding transaction attribute")
|
return fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Usage)
|
||||||
}
|
}
|
||||||
|
|
19
pkg/core/transaction/issue.go
Normal file
19
pkg/core/transaction/issue.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IssueTX represents a issue transaction.
|
||||||
|
// This TX has not special attributes.
|
||||||
|
type IssueTX struct{}
|
||||||
|
|
||||||
|
// DecodeBinary implements the Payload interface.
|
||||||
|
func (tx *IssueTX) DecodeBinary(r io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface.
|
||||||
|
func (tx *IssueTX) EncodeBinary(w io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
80
pkg/core/transaction/register.go
Normal file
80
pkg/core/transaction/register.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// # 发行总量,共有2种模式:
|
||||||
|
// # 1. 限量模式:当Amount为正数时,表示当前资产的最大总量为Amount,且不可修改(股权在未来可能会支持扩股或增发,会考虑需要公司签名或一定比例的股东签名认可)。
|
||||||
|
// # 2. 不限量模式:当Amount等于-1时,表示当前资产可以由创建者无限量发行。这种模式的自由度最大,但是公信力最低,不建议使用。
|
||||||
|
// # 在使用过程中,根据资产类型的不同,能够使用的总量模式也不同,具体规则如下:
|
||||||
|
// # 1. 对于股权,只能使用限量模式;
|
||||||
|
// # 2. 对于货币,只能使用不限量模式;
|
||||||
|
// # 3. 对于点券,可以使用任意模式;
|
||||||
|
//
|
||||||
|
// In English:
|
||||||
|
// # Total number of releases, there are 2 modes:
|
||||||
|
// # 1. Limited amount: When Amount is positive, it means that the maximum amount of current assets is Amount
|
||||||
|
// and can not be modified (the equity may support the expansion or issuance in the future, will consider the
|
||||||
|
// need for company signature or a certain percentage of shareholder signature recognition ).
|
||||||
|
// # 2. Unlimited mode: When Amount is equal to -1, it means that the current asset can be issued by the
|
||||||
|
// creator unlimited. This mode of freedom is the largest, but the credibility of the lowest, not recommended.
|
||||||
|
// # In the use of the process, according to the different types of assets, can use the total amount of
|
||||||
|
// different models, the specific rules are as follows:
|
||||||
|
// # 1. For equity, use only limited models;
|
||||||
|
// # 2. For currencies, use only unlimited models;
|
||||||
|
// # 3. For point coupons, you can use any pattern;
|
||||||
|
|
||||||
|
// RegisterTx represents a register transaction.
|
||||||
|
type RegisterTX struct {
|
||||||
|
// The type of the asset being registered.
|
||||||
|
AssetType AssetType
|
||||||
|
|
||||||
|
// Name of the asset being registered.
|
||||||
|
Name []byte
|
||||||
|
|
||||||
|
// Amount registered
|
||||||
|
// Unlimited mode -0.00000001
|
||||||
|
Amount util.Fixed8
|
||||||
|
|
||||||
|
// Decimals
|
||||||
|
Precision uint8
|
||||||
|
|
||||||
|
Owner crypto.EllipticCurvePoint
|
||||||
|
Admin util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements the Payload interface.
|
||||||
|
func (tx *RegisterTX) DecodeBinary(r io.Reader) error {
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &tx.AssetType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lenName := util.ReadVarUint(r)
|
||||||
|
tx.Name = make([]byte, lenName)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &tx.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &tx.Amount); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &tx.Precision); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
point, err := crypto.NewEllipticCurvePointFromReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.Owner = point
|
||||||
|
|
||||||
|
return binary.Read(r, binary.LittleEndian, &tx.Admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface.
|
||||||
|
func (tx *RegisterTX) EncodeBinary(w io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transaction is a process recorded in the NEO blockchain.
|
// Transaction is a process recorded in the NEO blockchain.
|
||||||
|
@ -37,6 +38,19 @@ type Transaction struct {
|
||||||
|
|
||||||
// hash of the transaction
|
// hash of the transaction
|
||||||
hash util.Uint256
|
hash util.Uint256
|
||||||
|
|
||||||
|
// Trimmed indicates this is a transaction from trimmed
|
||||||
|
// data.
|
||||||
|
Trimmed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTrimmedTX returns a trimmed transaction with only its hash
|
||||||
|
// and Trimmed to true.
|
||||||
|
func NewTrimmedTX(hash util.Uint256) *Transaction {
|
||||||
|
return &Transaction{
|
||||||
|
hash: hash,
|
||||||
|
Trimmed: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash return the hash of the transaction.
|
// Hash return the hash of the transaction.
|
||||||
|
@ -127,13 +141,21 @@ func (t *Transaction) decodeData(r io.Reader) error {
|
||||||
case ContractType:
|
case ContractType:
|
||||||
t.Data = &ContractTX{}
|
t.Data = &ContractTX{}
|
||||||
return t.Data.(*ContractTX).DecodeBinary(r)
|
return t.Data.(*ContractTX).DecodeBinary(r)
|
||||||
|
case RegisterType:
|
||||||
|
t.Data = &RegisterTX{}
|
||||||
|
return t.Data.(*RegisterTX).DecodeBinary(r)
|
||||||
|
case IssueType:
|
||||||
|
t.Data = &IssueTX{}
|
||||||
|
return t.Data.(*IssueTX).DecodeBinary(r)
|
||||||
|
default:
|
||||||
|
log.Warnf("invalid TX type %s", t.Type)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements the payload interface.
|
// EncodeBinary implements the payload interface.
|
||||||
func (t *Transaction) EncodeBinary(w io.Writer) error {
|
func (t *Transaction) EncodeBinary(w io.Writer) error {
|
||||||
if err := t.EncodeBinaryUnsigned(w); err != nil {
|
if err := t.encodeHashableFields(w); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := util.WriteVarUint(w, uint64(len(t.Scripts))); err != nil {
|
if err := util.WriteVarUint(w, uint64(len(t.Scripts))); err != nil {
|
||||||
|
@ -147,9 +169,9 @@ func (t *Transaction) EncodeBinary(w io.Writer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinaryUnsigned will only encode the fields that are not used for
|
// encodeHashableFields will only encode the fields that are not used for
|
||||||
// signing the transaction, which are all fields except the scripts.
|
// signing the transaction, which are all fields except the scripts.
|
||||||
func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error {
|
func (t *Transaction) encodeHashableFields(w io.Writer) error {
|
||||||
if err := binary.Write(w, binary.LittleEndian, t.Type); err != nil {
|
if err := binary.Write(w, binary.LittleEndian, t.Type); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -163,7 +185,8 @@ func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
if err := util.WriteVarUint(w, uint64(len(t.Attributes))); err != nil {
|
lenAttrs := uint64(len(t.Attributes))
|
||||||
|
if err := util.WriteVarUint(w, lenAttrs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, attr := range t.Attributes {
|
for _, attr := range t.Attributes {
|
||||||
|
@ -196,7 +219,7 @@ func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error {
|
||||||
|
|
||||||
func (t *Transaction) createHash() (hash util.Uint256, err error) {
|
func (t *Transaction) createHash() (hash util.Uint256, err error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
if err = t.EncodeBinaryUnsigned(buf); err != nil {
|
if err = t.encodeHashableFields(buf); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sha := sha256.New()
|
sha := sha256.New()
|
||||||
|
|
|
@ -5,10 +5,42 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestWitnessEncodeDecode(t *testing.T) {
|
||||||
|
verif, err := hex.DecodeString("552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
invoc, err := hex.DecodeString("404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
lenInvoc := len(invoc)
|
||||||
|
lenVerif := len(verif)
|
||||||
|
t.Log(lenInvoc)
|
||||||
|
t.Log(lenVerif)
|
||||||
|
|
||||||
|
wit := &Witness{
|
||||||
|
InvocationScript: invoc,
|
||||||
|
VerificationScript: verif,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := wit.EncodeBinary(buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
witDecode := &Witness{}
|
||||||
|
if err := witDecode.DecodeBinary(buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(len(witDecode.VerificationScript))
|
||||||
|
t.Log(len(witDecode.InvocationScript))
|
||||||
|
|
||||||
|
assert.Equal(t, wit, witDecode)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDecodeEncodeClaimTX(t *testing.T) {
|
func TestDecodeEncodeClaimTX(t *testing.T) {
|
||||||
b, err := hex.DecodeString(rawClaimTX)
|
b, err := hex.DecodeString(rawClaimTX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,7 +57,8 @@ func TestDecodeEncodeClaimTX(t *testing.T) {
|
||||||
assert.Equal(t, 0, len(tx.Attributes))
|
assert.Equal(t, 0, len(tx.Attributes))
|
||||||
assert.Equal(t, 0, len(tx.Inputs))
|
assert.Equal(t, 0, len(tx.Inputs))
|
||||||
assert.Equal(t, 1, len(tx.Outputs))
|
assert.Equal(t, 1, len(tx.Outputs))
|
||||||
assert.Equal(t, "AQJseD8iBmCD4sgfHRhMahmoi9zvopG6yz", tx.Outputs[0].ScriptHash.Address())
|
address := crypto.AddressFromUint160(tx.Outputs[0].ScriptHash)
|
||||||
|
assert.Equal(t, "AQJseD8iBmCD4sgfHRhMahmoi9zvopG6yz", address)
|
||||||
assert.Equal(t, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", tx.Outputs[0].AssetID.String())
|
assert.Equal(t, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", tx.Outputs[0].AssetID.String())
|
||||||
assert.Equal(t, tx.Outputs[0].Amount.String(), "0.06247739")
|
assert.Equal(t, tx.Outputs[0].Amount.String(), "0.06247739")
|
||||||
invoc := "40456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb"
|
invoc := "40456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb"
|
||||||
|
|
|
@ -4,7 +4,7 @@ package transaction
|
||||||
type TXType uint8
|
type TXType uint8
|
||||||
|
|
||||||
// All processes in NEO system are recorded in transactions.
|
// All processes in NEO system are recorded in transactions.
|
||||||
// There are several types of transactions.
|
// Valid transaction types.
|
||||||
const (
|
const (
|
||||||
MinerType TXType = 0x00
|
MinerType TXType = 0x00
|
||||||
IssueType TXType = 0x01
|
IssueType TXType = 0x01
|
||||||
|
@ -23,28 +23,28 @@ const (
|
||||||
func (t TXType) String() string {
|
func (t TXType) String() string {
|
||||||
switch t {
|
switch t {
|
||||||
case MinerType:
|
case MinerType:
|
||||||
return "miner transaction"
|
return "MinerTransaction"
|
||||||
case IssueType:
|
case IssueType:
|
||||||
return "issue transaction"
|
return "IssueTransaction"
|
||||||
case ClaimType:
|
case ClaimType:
|
||||||
return "claim transaction"
|
return "ClaimTransaction"
|
||||||
case EnrollmentType:
|
case EnrollmentType:
|
||||||
return "enrollment transaction"
|
return "EnrollmentTransaction"
|
||||||
case VotingType:
|
case VotingType:
|
||||||
return "voting transaction"
|
return "VotingTransaction"
|
||||||
case RegisterType:
|
case RegisterType:
|
||||||
return "register transaction"
|
return "RegisterTransaction"
|
||||||
case ContractType:
|
case ContractType:
|
||||||
return "contract transaction"
|
return "ContractTransaction"
|
||||||
case StateType:
|
case StateType:
|
||||||
return "state transaction"
|
return "StateTransaction"
|
||||||
case AgencyType:
|
case AgencyType:
|
||||||
return "agency transaction"
|
return "AgencyTransaction"
|
||||||
case PublishType:
|
case PublishType:
|
||||||
return "publish transaction"
|
return "PublishTransaction"
|
||||||
case InvocationType:
|
case InvocationType:
|
||||||
return "invocation transaction"
|
return "InvocationTransaction"
|
||||||
default:
|
default:
|
||||||
return ""
|
return "UnkownTransaction"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,14 @@ func (wit *Witness) DecodeBinary(r io.Reader) error {
|
||||||
|
|
||||||
// EncodeBinary implements the payload interface.
|
// EncodeBinary implements the payload interface.
|
||||||
func (wit *Witness) EncodeBinary(w io.Writer) error {
|
func (wit *Witness) EncodeBinary(w io.Writer) error {
|
||||||
util.WriteVarUint(w, uint64(len(wit.InvocationScript)))
|
if err := util.WriteVarUint(w, uint64(len(wit.InvocationScript))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := binary.Write(w, binary.LittleEndian, wit.InvocationScript); err != nil {
|
if err := binary.Write(w, binary.LittleEndian, wit.InvocationScript); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
util.WriteVarUint(w, uint64(len(wit.VerificationScript)))
|
if err := util.WriteVarUint(w, uint64(len(wit.VerificationScript))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return binary.Write(w, binary.LittleEndian, wit.VerificationScript)
|
return binary.Write(w, binary.LittleEndian, wit.VerificationScript)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import "github.com/CityOfZion/neo-go/pkg/util"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
// Utilities for quick bootstrapping blockchains. Normally we should
|
// Utilities for quick bootstrapping blockchains. Normally we should
|
||||||
// create the genisis block. For now (to speed up development) we will add
|
// create the genisis block. For now (to speed up development) we will add
|
||||||
|
@ -20,3 +27,75 @@ func GenesisHashMainNet() util.Uint256 {
|
||||||
hash, _ := util.Uint256DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf")
|
hash, _ := util.Uint256DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf")
|
||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// headerSliceReverse reverses the given slice of *Header.
|
||||||
|
func headerSliceReverse(dest []*Header) {
|
||||||
|
for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
dest[i], dest[j] = dest[j], dest[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeAsCurrentBlock stores the given block witch prefix
|
||||||
|
// SYSCurrentBlock.
|
||||||
|
func storeAsCurrentBlock(batch storage.Batch, block *Block) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.Write(block.Hash().BytesReverse())
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(b, block.Index)
|
||||||
|
buf.Write(b)
|
||||||
|
batch.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeAsBlock stores the given block as DataBlock.
|
||||||
|
func storeAsBlock(batch storage.Batch, block *Block, sysFee uint32) error {
|
||||||
|
var (
|
||||||
|
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesReverse())
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
)
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(b, sysFee)
|
||||||
|
|
||||||
|
b, err := block.Trim()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
batch.Put(key, buf.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readStoredHeaderHashes returns a sorted list of header hashes
|
||||||
|
// retrieved from the given Store.
|
||||||
|
func readStoredHeaderHashes(store storage.Store) ([]util.Uint256, error) {
|
||||||
|
hashMap := make(map[uint32][]util.Uint256)
|
||||||
|
store.Seek(storage.IXHeaderHashList.Bytes(), func(k, v []byte) {
|
||||||
|
storedCount := binary.LittleEndian.Uint32(k[1:])
|
||||||
|
hashes, err := util.Read2000Uint256Hashes(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
hashMap[storedCount] = hashes
|
||||||
|
})
|
||||||
|
|
||||||
|
var (
|
||||||
|
i = 0
|
||||||
|
sortedKeys = make([]int, len(hashMap))
|
||||||
|
)
|
||||||
|
|
||||||
|
for k, _ := range hashMap {
|
||||||
|
sortedKeys[i] = int(k)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Ints(sortedKeys)
|
||||||
|
|
||||||
|
hashes := []util.Uint256{}
|
||||||
|
for _, key := range sortedKeys {
|
||||||
|
values := hashMap[uint32(key)]
|
||||||
|
for _, hash := range values {
|
||||||
|
hashes = append(hashes, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashes, nil
|
||||||
|
}
|
||||||
|
|
23
pkg/crypto/address.go
Normal file
23
pkg/crypto/address.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddressFromUint160 returns the "NEO address" from the given
|
||||||
|
// Uint160.
|
||||||
|
func AddressFromUint160(u util.Uint160) string {
|
||||||
|
// Dont forget to prepend the Address version 0x17 (23) A
|
||||||
|
b := append([]byte{0x17}, u.Bytes()...)
|
||||||
|
return Base58CheckEncode(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint160DecodeAddress attempts to decode the given NEO address string
|
||||||
|
// into an Uint160.
|
||||||
|
func Uint160DecodeAddress(s string) (u util.Uint160, err error) {
|
||||||
|
b, err := Base58CheckDecode(s)
|
||||||
|
if err != nil {
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
|
return util.Uint160DecodeBytes(b[1:21])
|
||||||
|
}
|
22
pkg/crypto/address_test.go
Normal file
22
pkg/crypto/address_test.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUint160DecodeAddress(t *testing.T) {
|
||||||
|
addrs := []string{
|
||||||
|
"AMLr1CpPQtbEdiJdriX1HpRNMZUwbU2Huj",
|
||||||
|
"AKtwd3DRXj3nL5kHMUoNsdnsCEVjnuuTFF",
|
||||||
|
"AMxkaxFVG8Q1BhnB4fjTA5ZmUTEnnTMJMa",
|
||||||
|
}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
val, err := Uint160DecodeAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, addr, AddressFromUint160(val))
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,14 @@ package crypto
|
||||||
// Expanded and tweaked upon here under MIT license.
|
// Expanded and tweaked upon here under MIT license.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -29,6 +33,39 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewEllipticCurvePointFromReader return a new point from the given reader.
|
||||||
|
// f == 4, 6 or 7 are not implemented.
|
||||||
|
func NewEllipticCurvePointFromReader(r io.Reader) (point EllipticCurvePoint, err error) {
|
||||||
|
var f uint8
|
||||||
|
if err = binary.Read(r, binary.LittleEndian, &f); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infinity
|
||||||
|
if f == 0 {
|
||||||
|
return EllipticCurvePoint{
|
||||||
|
X: new(big.Int),
|
||||||
|
Y: new(big.Int),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f == 2 || f == 3 {
|
||||||
|
y := new(big.Int).SetBytes([]byte{f & 1})
|
||||||
|
data := make([]byte, 32)
|
||||||
|
if err = binary.Read(r, binary.LittleEndian, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = util.ArrayReverse(data)
|
||||||
|
data = append(data, byte(0x00))
|
||||||
|
|
||||||
|
return EllipticCurvePoint{
|
||||||
|
X: new(big.Int).SetBytes(data),
|
||||||
|
Y: y,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// NewEllipticCurve returns a ready to use EllipticCurve with preconfigured
|
// NewEllipticCurve returns a ready to use EllipticCurve with preconfigured
|
||||||
// fields for the NEO protocol.
|
// fields for the NEO protocol.
|
||||||
func NewEllipticCurve() EllipticCurve {
|
func NewEllipticCurve() EllipticCurve {
|
||||||
|
|
|
@ -86,6 +86,11 @@ func NewServer(config ServerConfig, chain *core.Blockchain) *Server {
|
||||||
|
|
||||||
// Start will start the server and its underlying transport.
|
// Start will start the server and its underlying transport.
|
||||||
func (s *Server) Start() {
|
func (s *Server) Start() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"blockHeight": s.chain.BlockHeight(),
|
||||||
|
"headerHeight": s.chain.HeaderHeight(),
|
||||||
|
}).Info("node started")
|
||||||
|
|
||||||
go s.transport.Accept()
|
go s.transport.Accept()
|
||||||
s.discovery.BackFill(s.Seeds...)
|
s.discovery.BackFill(s.Seeds...)
|
||||||
s.run()
|
s.run()
|
||||||
|
@ -221,7 +226,10 @@ func (s *Server) handleHeadersCmd(p Peer, headers *payload.Headers) {
|
||||||
|
|
||||||
// handleBlockCmd processes the received block received from its peer.
|
// handleBlockCmd processes the received block received from its peer.
|
||||||
func (s *Server) handleBlockCmd(p Peer, block *core.Block) error {
|
func (s *Server) handleBlockCmd(p Peer, block *core.Block) error {
|
||||||
return s.chain.AddBlock(block)
|
if !s.chain.HasBlock(block.Hash()) {
|
||||||
|
return s.chain.AddBlock(block)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleInvCmd will process the received inventory.
|
// handleInvCmd will process the received inventory.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
@ -48,18 +49,30 @@ func WriteVarUint(w io.Writer, val uint64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if val < 0xFFFF {
|
if val < 0xFFFF {
|
||||||
binary.Write(w, binary.LittleEndian, 0xfd)
|
binary.Write(w, binary.LittleEndian, byte(0xfd))
|
||||||
binary.Write(w, binary.LittleEndian, uint16(val))
|
binary.Write(w, binary.LittleEndian, uint16(val))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if val < 0xFFFFFFFF {
|
if val < 0xFFFFFFFF {
|
||||||
binary.Write(w, binary.LittleEndian, 0xfe)
|
binary.Write(w, binary.LittleEndian, byte(0xfe))
|
||||||
binary.Write(w, binary.LittleEndian, uint32(val))
|
binary.Write(w, binary.LittleEndian, uint32(val))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
binary.Write(w, binary.LittleEndian, 0xff)
|
binary.Write(w, binary.LittleEndian, byte(0xff))
|
||||||
binary.Write(w, binary.LittleEndian, val)
|
binary.Write(w, binary.LittleEndian, val)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read2000Uint256Hashes attempt to read 2000 Uint256 hashes from
|
||||||
|
// the given byte array.
|
||||||
|
func Read2000Uint256Hashes(b []byte) ([]Uint256, error) {
|
||||||
|
r := bytes.NewReader(b)
|
||||||
|
lenHashes := ReadVarUint(r)
|
||||||
|
hashes := make([]Uint256, lenHashes)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, hashes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hashes, nil
|
||||||
|
}
|
||||||
|
|
64
pkg/util/io_test.go
Normal file
64
pkg/util/io_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteVarUint1(t *testing.T) {
|
||||||
|
var (
|
||||||
|
val = uint64(1)
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
)
|
||||||
|
if err := WriteVarUint(buf, val); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 1, buf.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteVarUint1000(t *testing.T) {
|
||||||
|
var (
|
||||||
|
val = uint64(1000)
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := WriteVarUint(buf, val); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 3, buf.Len())
|
||||||
|
assert.Equal(t, byte(0xfd), buf.Bytes()[0])
|
||||||
|
res := ReadVarUint(buf)
|
||||||
|
assert.Equal(t, val, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteVarUint100000(t *testing.T) {
|
||||||
|
var (
|
||||||
|
val = uint64(100000)
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := WriteVarUint(buf, val); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 5, buf.Len())
|
||||||
|
assert.Equal(t, byte(0xfe), buf.Bytes()[0])
|
||||||
|
res := ReadVarUint(buf)
|
||||||
|
assert.Equal(t, val, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteVarUint100000000000(t *testing.T) {
|
||||||
|
var (
|
||||||
|
val = uint64(1000000000000)
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := WriteVarUint(buf, val); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 9, buf.Len())
|
||||||
|
assert.Equal(t, byte(0xff), buf.Bytes()[0])
|
||||||
|
res := ReadVarUint(buf)
|
||||||
|
assert.Equal(t, val, res)
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ package util
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const uint160Size = 20
|
const uint160Size = 20
|
||||||
|
@ -24,16 +22,6 @@ func Uint160DecodeString(s string) (u Uint160, err error) {
|
||||||
return Uint160DecodeBytes(b)
|
return Uint160DecodeBytes(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uint160DecodeAddress attempts to decode the given NEO address string
|
|
||||||
// into an Uint160.
|
|
||||||
func Uint160DecodeAddress(s string) (u Uint160, err error) {
|
|
||||||
b, err := crypto.Base58CheckDecode(s)
|
|
||||||
if err != nil {
|
|
||||||
return u, err
|
|
||||||
}
|
|
||||||
return Uint160DecodeBytes(b[1:21])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint160DecodeBytes attempts to decode the given bytes into an Uint160.
|
// Uint160DecodeBytes attempts to decode the given bytes into an Uint160.
|
||||||
func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
|
func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
|
||||||
if len(b) != uint160Size {
|
if len(b) != uint160Size {
|
||||||
|
@ -54,13 +42,6 @@ func (u Uint160) Bytes() []byte {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns the NEO address representation of u.
|
|
||||||
func (u Uint160) Address() string {
|
|
||||||
// Dont forget to prepend the Address version 0x17 (23) A
|
|
||||||
b := append([]byte{0x17}, u.Bytes()...)
|
|
||||||
return crypto.Base58CheckEncode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String implements the stringer interface.
|
// String implements the stringer interface.
|
||||||
func (u Uint160) String() string {
|
func (u Uint160) String() string {
|
||||||
return hex.EncodeToString(u.Bytes())
|
return hex.EncodeToString(u.Bytes())
|
||||||
|
|
|
@ -1,22 +1 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUint160FromToAddress(t *testing.T) {
|
|
||||||
addrs := []string{
|
|
||||||
"AMLr1CpPQtbEdiJdriX1HpRNMZUwbU2Huj",
|
|
||||||
"AKtwd3DRXj3nL5kHMUoNsdnsCEVjnuuTFF",
|
|
||||||
"AMxkaxFVG8Q1BhnB4fjTA5ZmUTEnnTMJMa",
|
|
||||||
}
|
|
||||||
for _, addr := range addrs {
|
|
||||||
val, err := Uint160DecodeAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assert.Equal(t, addr, val.Address())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue