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:
Anthony De Meulemeester 2018-03-17 12:53:21 +01:00 committed by GitHub
parent b41e14e0f0
commit a67728628e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1419 additions and 530 deletions

2
Gopkg.lock generated
View file

@ -126,6 +126,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "7e6d4161196284974f07ba9655d21ee9b4fe1478ef0e054e159df73aeec1343a"
inputs-digest = "83630d732c34b1ddf24cd34025fd9fdd982a5e5075ec93a450e7edada658c6c9"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -19,7 +19,7 @@ push-tag:
git push origin ${VERSION}
run: build
./bin/neo-go node -config-path ./config -${NETMODE}
./bin/neo-go node -config-path ./config -${NETMODE} --debug
test:
@go test ./... -cover

View file

@ -1 +1 @@
0.30.0
0.31.0

View file

@ -4,8 +4,10 @@ import (
"fmt"
"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/util"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@ -20,6 +22,7 @@ func NewCommand() cli.Command {
cli.BoolFlag{Name: "privnet, p"},
cli.BoolFlag{Name: "mainnet, m"},
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")
config, err := network.LoadConfig(configPath, net)
if err != nil {
return err
return cli.NewExitError(err, 1)
}
serverConfig := network.NewServerConfig(config)
@ -47,8 +50,12 @@ func startServer(ctx *cli.Context) error {
return cli.NewExitError(err, 1)
}
s := network.NewServer(serverConfig, chain)
s.Start()
if ctx.Bool("debug") {
log.SetLevel(log.DebugLevel)
}
fmt.Println(logo())
network.NewServer(serverConfig, chain).Start()
return nil
}
@ -65,13 +72,20 @@ func newBlockchain(net network.NetMode, path string) (*core.Blockchain, error) {
}
// Hardcoded for now.
store, err := core.NewLevelDBStore(path, nil)
store, err := storage.NewLevelDBStore(path, nil)
if err != nil {
return nil, err
}
return core.NewBlockchain(
store,
startHash,
), nil
return core.NewBlockchain(store, startHash)
}
func logo() string {
return `
_ ____________ __________
/ | / / ____/ __ \ / ____/ __ \
/ |/ / __/ / / / /_____/ / __/ / / /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
/_/ |_/_____/\____/ \____/\____/
`
}

View file

@ -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
)

View file

@ -2,188 +2,27 @@ package core
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"log"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"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.
type Block struct {
// The base of the block.
BlockBase
// Transaction list.
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 {
return &Header{
BlockBase: b.BlockBase,
@ -196,25 +35,82 @@ func (b *Block) Verify(full bool) bool {
if b.Transactions[0].Type != transaction.MinerType {
return false
}
// If the first TX is a minerTX then all others cant.
for _, tx := range b.Transactions[1:] {
if tx.Type == transaction.MinerType {
return false
}
}
// TODO: When full is true, do a full verification.
if full {
log.Println("full verification of blocks is not yet implemented")
log.Warn("full verification of blocks is not yet implemented")
}
return true
}
// EncodeBinary encodes the block to the given writer.
func (b *Block) EncodeBinary(w io.Writer) error {
return nil
// NewBlockFromTrimmedBytes returns a new block from trimmed data.
// This is commonly used to create a block from stored data.
// 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.
@ -234,3 +130,8 @@ func (b *Block) DecodeBinary(r io.Reader) error {
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
View 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
}

View file

@ -2,39 +2,78 @@ package core
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"testing"
"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"
)
func TestDecodeBlock(t *testing.T) {
var (
rawBlock = "00000000b7def681f0080262aa293071c53b41fc3146b196067243700b68acd059734fd19543108bf9ddc738cbee2ed1160f153aa0d057f062de0aa3cbb64ba88735c23d43667e59543f050095df82b02e324c5ff3812db982f3b0089a21a278988efeec6a027b2501fd450140113ac66657c2f544e8ad13905fcb2ebaadfef9502cbefb07960fbe56df098814c223dcdd3d0efa0b43a9459e654d948516dcbd8b370f50fbecfb8b411d48051a408500ce85591e516525db24065411f6a88f43de90fa9c167c2e6f5af43bc84e65e5a4bb174bc83a19b6965ff10f476b1b151ae15439a985f33916abc6822b0bb140f4aae522ffaea229987a10d01beec826c3b9a189fe02aa82680581b78f3df0ea4d3f93ca8ea35ffc90f15f7db9017f92fafd9380d9ba3237973cf4313cf626fc40e30e50e3588bd047b39f478b59323868cd50c7ab54355d8245bf0f1988d37528f9bbfc68110cf917debbdbf1f4bdd02cdcccdc3269fdf18a6c727ee54b6934d840e43918dd1ec6123550ec37a513e72b34b2c2a3baa510dec3037cbef2fa9f6ed1e7ccd1f3f6e19d4ce2c0919af55249a970c2685217f75a5589cf9e54dff8449af155210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41acf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dddc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae01000095df82b000000000"
rawBlockHash = "922ba0c0d06afbeec4c50b0541a29153feaa46c5d7304e7bf7f40870d9f3aeb0"
rawBlockPrevHash = "d14f7359d0ac680b7043720696b14631fc413bc5713029aa620208f081f6deb7"
rawBlockIndex = 343892
rawBlockTimestamp = 1501455939
rawBlockConsensusData = 6866918707944415125
)
// Test blocks are blocks from mainnet with their corresponding index.
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 {
t.Fatal(err)
}
block := &Block{}
if err := block.DecodeBinary(bytes.NewReader(rawBlockBytes)); err != nil {
if err := block.DecodeBinary(bytes.NewReader(b)); err != nil {
t.Fatal(err)
}
assert.Equal(t, uint32(rawBlockIndex), block.Index)
assert.Equal(t, uint32(rawBlockTimestamp), block.Timestamp)
assert.Equal(t, uint64(rawBlockConsensusData), block.ConsensusData)
assert.Equal(t, rawBlockPrevHash, block.PrevHash.String())
assert.Equal(t, rawBlockHash, block.Hash().String())
assert.Equal(t, uint32(data["index"].(float64)), block.Index)
assert.Equal(t, uint32(data["version"].(float64)), block.Version)
assert.Equal(t, data["hash"].(string), 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) {
@ -48,42 +87,17 @@ func TestBlockVerify(t *testing.T) {
newTX(transaction.MinerType),
newTX(transaction.IssueType),
)
if !block.Verify(false) {
t.Fatal("block should be verified")
}
assert.True(t, block.Verify(false))
block.Transactions = []*transaction.Transaction{
{Type: transaction.IssueType},
{Type: transaction.MinerType},
}
if block.Verify(false) {
t.Fatal("block should not by verified")
}
assert.False(t, block.Verify(false))
block.Transactions = []*transaction.Transaction{
{Type: transaction.MinerType},
{Type: transaction.MinerType},
}
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},
},
}
assert.False(t, block.Verify(false))
}

View file

@ -4,12 +4,13 @@ import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/util"
log "github.com/sirupsen/logrus"
"github.com/syndtr/goleveldb/leveldb"
)
// tuning parameters
@ -26,20 +27,24 @@ var (
// Blockchain holds the chain.
type Blockchain struct {
// Any object that satisfies the BlockchainStorer interface.
Store
storage.Store
// Current index/height of the highest block.
// Read access should always be called by BlockHeight().
// Writes access should only happen in persist().
// Write access should only happen in persist().
blockHeight uint32
// Number of headers stored.
// Number of headers stored in the chain file.
storedHeaderCount uint32
blockCache *Cache
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.
headersOp chan headersOpFunc
headersOpDone chan struct{}
@ -50,8 +55,9 @@ type Blockchain struct {
type headersOpFunc func(headerList *HeaderHashList)
// NewBlockchain creates a new Blockchain object.
func NewBlockchain(s Store, startHash util.Uint256) *Blockchain {
// NewBlockchain return a new blockchain object the will use the
// given Store as its underlying storage.
func NewBlockchain(s storage.Store, startHash util.Uint256) (*Blockchain, error) {
bc := &Blockchain{
Store: s,
headersOp: make(chan headersOpFunc),
@ -61,25 +67,85 @@ func NewBlockchain(s Store, startHash util.Uint256) *Blockchain {
verifyBlocks: false,
}
go bc.run()
bc.init()
return bc
if err := bc.init(); err != nil {
return nil, err
}
return bc, nil
}
func (bc *Blockchain) init() {
// for the initial header, for now
bc.storedHeaderCount = 1
func (bc *Blockchain) init() error {
// TODO: This should be the persistance of the genisis block.
// 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() {
var (
headerList = NewHeaderHashList(bc.startHash)
persistTimer = time.NewTimer(persistInterval)
)
persistTimer := time.NewTimer(persistInterval)
for {
select {
case op := <-bc.headersOp:
op(headerList)
op(bc.headerList)
bc.headersOpDone <- struct{}{}
case <-persistTimer.C:
go bc.persist()
@ -113,14 +179,14 @@ func (bc *Blockchain) AddBlock(block *Block) error {
func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
var (
start = time.Now()
batch = new(leveldb.Batch)
batch = bc.Batch()
)
bc.headersOp <- func(headerList *HeaderHashList) {
for _, h := range headers {
if int(h.Index-1) >= headerList.Len() {
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(),
)
return
@ -138,7 +204,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
}
if batch.Len() > 0 {
if err = bc.writeBatch(batch); err != nil {
if err = bc.PutBatch(batch); err != nil {
return
}
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
// 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())
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 {
return err
}
key := makeEntryPrefixInt(preIXHeaderHashList, int(bc.storedHeaderCount))
key := storage.AppendPrefixInt(storage.IXHeaderHashList, int(bc.storedHeaderCount))
batch.Put(key, buf.Bytes())
bc.storedHeaderCount += headerBatchCount
buf.Reset()
@ -173,25 +239,22 @@ func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList
return err
}
key := makeEntryPrefix(preDataBlock, h.Hash().BytesReverse())
key := storage.AppendPrefix(storage.DataBlock, h.Hash().BytesReverse())
batch.Put(key, buf.Bytes())
key = preSYSCurrentHeader.bytes()
batch.Put(key, hashAndIndexToBytes(h.Hash(), h.Index))
batch.Put(storage.SYSCurrentHeader.Bytes(), hashAndIndexToBytes(h.Hash(), h.Index))
return nil
}
func (bc *Blockchain) persistBlock(block *Block) error {
batch := new(leveldb.Batch)
batch := bc.Batch()
// Store the block.
key := preSYSCurrentBlock.bytes()
batch.Put(key, hashAndIndexToBytes(block.Hash(), block.Index))
storeAsBlock(batch, block, 0)
storeAsCurrentBlock(batch, block)
if err := bc.Store.writeBatch(batch); err != nil {
if err := bc.PutBatch(batch); err != nil {
return err
}
atomic.AddUint32(&bc.blockHeight, 1)
return nil
}
@ -241,7 +304,32 @@ func (bc *Blockchain) headerListLen() (n int) {
// GetBlock returns a Block by the given hash.
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
@ -253,6 +341,9 @@ func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
// HasBlock return true if the blockchain contains the given
// block hash.
func (bc *Blockchain) HasBlock(hash util.Uint256) bool {
if header, err := bc.getHeader(hash); err == nil {
return header.Index <= bc.BlockHeight()
}
return false
}

View file

@ -3,22 +3,13 @@ package core
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func 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) {
bc := newTestBC()
bc := newTestChain(t)
h1 := newBlock(1).Header()
h2 := newBlock(2).Header()
h3 := newBlock(3).Header()
@ -45,7 +36,7 @@ func TestAddHeaders(t *testing.T) {
}
func TestAddBlock(t *testing.T) {
bc := newTestBC()
bc := newTestChain(t)
blocks := []*Block{
newBlock(1),
newBlock(2),
@ -73,8 +64,69 @@ func TestAddBlock(t *testing.T) {
assert.Equal(t, 0, bc.blockCache.Len())
}
func newTestBC() *Blockchain {
startHash, _ := util.Uint256DecodeString("a")
bc := NewBlockchain(NewMemoryStore(), startHash)
return bc
func TestGetHeader(t *testing.T) {
bc := newTestChain(t)
block := newBlock(1)
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
View 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))
}

View file

@ -14,6 +14,11 @@ type HeaderHashList struct {
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.
func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList {
return &HeaderHashList{
@ -22,8 +27,8 @@ func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList {
}
// Add appends the given hash to the list of hashes.
func (l *HeaderHashList) Add(h util.Uint256) {
l.hashes = append(l.hashes, h)
func (l *HeaderHashList) Add(h ...util.Uint256) {
l.hashes = append(l.hashes, h...)
}
// 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.
// Subsliced from start to end.
// Example:
// headers := headerList.Slice(0, 2000)
// headers := headerList.Slice(0, 2000)
func (l *HeaderHashList) Slice(start, end int) []util.Uint256 {
return l.hashes[start:end]
}

View file

@ -1,7 +1,13 @@
package core
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"testing"
"time"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
@ -33,8 +39,47 @@ func newBlock(index uint32, txs ...*transaction.Transaction) *Block {
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 {
return &transaction.Transaction{
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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
package core
var (
rawBlock0 = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151"
rawBlock1 = "00000000bf4421c88776c53b43ce1dc45463bfd2028e322fdfb60064be150ed3e36125d418f98ec3ed2c2d1c9427385e7b85d0d1a366e29c4e399693a59718380f8bbad6d6d90358010000004490d0bb7170726c59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd4501404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae0100004490d0bb00000000"
)

View file

@ -0,0 +1,2 @@
# Storage

View 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)
}

View file

@ -0,0 +1 @@
package storage

View 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),
}
}

View 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
View 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)
}

View 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]))
}
}

View file

@ -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
}

View 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"
}

View 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"
}

View 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
)

View file

@ -2,7 +2,7 @@ package transaction
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/CityOfZion/neo-go/pkg/util"
@ -14,7 +14,7 @@ type Attribute struct {
Data []byte
}
// DecodeBinary implements the Payloader interface.
// DecodeBinary implements the Payload interface.
func (attr *Attribute) DecodeBinary(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &attr.Usage); err != nil {
return err
@ -43,7 +43,7 @@ func (attr *Attribute) DecodeBinary(r io.Reader) error {
attr.Data = make([]byte, lenData)
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.
@ -75,5 +75,5 @@ func (attr *Attribute) EncodeBinary(w io.Writer) error {
}
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)
}

View 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
}

View 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
}

View file

@ -7,6 +7,7 @@ import (
"io"
"github.com/CityOfZion/neo-go/pkg/util"
log "github.com/sirupsen/logrus"
)
// Transaction is a process recorded in the NEO blockchain.
@ -37,6 +38,19 @@ type Transaction struct {
// hash of the transaction
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.
@ -127,13 +141,21 @@ func (t *Transaction) decodeData(r io.Reader) error {
case ContractType:
t.Data = &ContractTX{}
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
}
// EncodeBinary implements the payload interface.
func (t *Transaction) EncodeBinary(w io.Writer) error {
if err := t.EncodeBinaryUnsigned(w); err != nil {
if err := t.encodeHashableFields(w); err != nil {
return err
}
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
}
// 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.
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 {
return err
}
@ -163,7 +185,8 @@ func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error {
}
// 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
}
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) {
buf := new(bytes.Buffer)
if err = t.EncodeBinaryUnsigned(buf); err != nil {
if err = t.encodeHashableFields(buf); err != nil {
return
}
sha := sha256.New()

View file

@ -5,10 +5,42 @@ import (
"encoding/hex"
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
"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) {
b, err := hex.DecodeString(rawClaimTX)
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.Inputs))
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, tx.Outputs[0].Amount.String(), "0.06247739")
invoc := "40456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb"

View file

@ -4,7 +4,7 @@ package transaction
type TXType uint8
// All processes in NEO system are recorded in transactions.
// There are several types of transactions.
// Valid transaction types.
const (
MinerType TXType = 0x00
IssueType TXType = 0x01
@ -23,28 +23,28 @@ const (
func (t TXType) String() string {
switch t {
case MinerType:
return "miner transaction"
return "MinerTransaction"
case IssueType:
return "issue transaction"
return "IssueTransaction"
case ClaimType:
return "claim transaction"
return "ClaimTransaction"
case EnrollmentType:
return "enrollment transaction"
return "EnrollmentTransaction"
case VotingType:
return "voting transaction"
return "VotingTransaction"
case RegisterType:
return "register transaction"
return "RegisterTransaction"
case ContractType:
return "contract transaction"
return "ContractTransaction"
case StateType:
return "state transaction"
return "StateTransaction"
case AgencyType:
return "agency transaction"
return "AgencyTransaction"
case PublishType:
return "publish transaction"
return "PublishTransaction"
case InvocationType:
return "invocation transaction"
return "InvocationTransaction"
default:
return ""
return "UnkownTransaction"
}
}

View file

@ -27,10 +27,14 @@ func (wit *Witness) DecodeBinary(r io.Reader) error {
// EncodeBinary implements the payload interface.
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 {
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)
}

View file

@ -1,6 +1,13 @@
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
// 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")
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
View 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])
}

View 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))
}
}

View file

@ -4,10 +4,14 @@ package crypto
// Expanded and tweaked upon here under MIT license.
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"github.com/CityOfZion/neo-go/pkg/util"
)
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
// fields for the NEO protocol.
func NewEllipticCurve() EllipticCurve {

View file

@ -86,6 +86,11 @@ func NewServer(config ServerConfig, chain *core.Blockchain) *Server {
// Start will start the server and its underlying transport.
func (s *Server) Start() {
log.WithFields(log.Fields{
"blockHeight": s.chain.BlockHeight(),
"headerHeight": s.chain.HeaderHeight(),
}).Info("node started")
go s.transport.Accept()
s.discovery.BackFill(s.Seeds...)
s.run()
@ -221,7 +226,10 @@ func (s *Server) handleHeadersCmd(p Peer, headers *payload.Headers) {
// handleBlockCmd processes the received block received from its peer.
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.

View file

@ -1,6 +1,7 @@
package util
import (
"bytes"
"encoding/binary"
"errors"
"io"
@ -48,18 +49,30 @@ func WriteVarUint(w io.Writer, val uint64) error {
return nil
}
if val < 0xFFFF {
binary.Write(w, binary.LittleEndian, 0xfd)
binary.Write(w, binary.LittleEndian, byte(0xfd))
binary.Write(w, binary.LittleEndian, uint16(val))
return nil
}
if val < 0xFFFFFFFF {
binary.Write(w, binary.LittleEndian, 0xfe)
binary.Write(w, binary.LittleEndian, byte(0xfe))
binary.Write(w, binary.LittleEndian, uint32(val))
return nil
}
binary.Write(w, binary.LittleEndian, 0xff)
binary.Write(w, binary.LittleEndian, byte(0xff))
binary.Write(w, binary.LittleEndian, val)
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
View 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)
}

View file

@ -3,8 +3,6 @@ package util
import (
"encoding/hex"
"fmt"
"github.com/CityOfZion/neo-go/pkg/crypto"
)
const uint160Size = 20
@ -24,16 +22,6 @@ func Uint160DecodeString(s string) (u Uint160, err error) {
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.
func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
if len(b) != uint160Size {
@ -54,13 +42,6 @@ func (u Uint160) Bytes() []byte {
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.
func (u Uint160) String() string {
return hex.EncodeToString(u.Bytes())

View file

@ -1,22 +1 @@
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())
}
}