Persistance (#53)

* added publish TX for backwards compat.

* lowered the prototick for faster block syncing

* print useragent on startup

* added createMultiRedeemScript for genesis block generation.

* building genesis block from scratch.

* implemented merkle tree.

* starting blockhain with generated genesis hash

* Fixed bug in unspent coin state.

* fixed broken tests after genesis block.

* removed log line.

* bumped version -> 0.34.0
This commit is contained in:
Anthony De Meulemeester 2018-03-25 12:45:54 +02:00 committed by GitHub
parent ad9333c74c
commit 94672cb9cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 955 additions and 187 deletions

View file

@ -1 +1 @@
0.33.1 0.34.0

View file

@ -5,11 +5,11 @@ import (
"os" "os"
"os/signal" "os/signal"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/storage" "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/rpc" "github.com/CityOfZion/neo-go/pkg/rpc"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -32,17 +32,17 @@ func NewCommand() cli.Command {
} }
func startServer(ctx *cli.Context) error { func startServer(ctx *cli.Context) error {
net := network.ModePrivNet net := config.ModePrivNet
if ctx.Bool("testnet") { if ctx.Bool("testnet") {
net = network.ModeTestNet net = config.ModeTestNet
} }
if ctx.Bool("mainnet") { if ctx.Bool("mainnet") {
net = network.ModeMainNet net = config.ModeMainNet
} }
configPath := "./config" configPath := "./config"
configPath = ctx.String("config-path") configPath = ctx.String("config-path")
config, err := network.LoadConfig(configPath, net) cfg, err := config.Load(configPath, net)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -50,10 +50,10 @@ func startServer(ctx *cli.Context) error {
interruptChan := make(chan os.Signal, 1) interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt) signal.Notify(interruptChan, os.Interrupt)
serverConfig := network.NewServerConfig(config) serverConfig := network.NewServerConfig(cfg)
chain, err := newBlockchain(net, config.ApplicationConfiguration.DataDirectoryPath) chain, err := newBlockchain(cfg)
if err != nil { if err != nil {
err = fmt.Errorf("could not initialize blockhain: %s", err) err = fmt.Errorf("could not initialize blockchain: %s", err)
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -61,15 +61,18 @@ func startServer(ctx *cli.Context) error {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} }
fmt.Println(logo())
server := network.NewServer(serverConfig, chain) server := network.NewServer(serverConfig, chain)
rpcServer := rpc.NewServer(chain, config.ApplicationConfiguration.RPCPort, server) rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server)
errChan := make(chan error) errChan := make(chan error)
go server.Start(errChan) go server.Start(errChan)
go rpcServer.Start(errChan) go rpcServer.Start(errChan)
var shutdownErr error
fmt.Println(logo())
fmt.Println(server.UserAgent)
fmt.Println()
var shutdownErr error
Main: Main:
for { for {
select { select {
@ -93,25 +96,17 @@ Main:
return nil return nil
} }
func newBlockchain(net network.NetMode, path string) (*core.Blockchain, error) { func newBlockchain(cfg config.Config) (*core.Blockchain, error) {
var startHash util.Uint256
if net == network.ModePrivNet {
startHash = core.GenesisHashPrivNet()
}
if net == network.ModeTestNet {
startHash = core.GenesisHashTestNet()
}
if net == network.ModeMainNet {
startHash = core.GenesisHashMainNet()
}
// Hardcoded for now. // Hardcoded for now.
store, err := storage.NewLevelDBStore(path, nil) store, err := storage.NewLevelDBStore(
cfg.ApplicationConfiguration.DataDirectoryPath,
nil,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return core.NewBlockchain(store, startHash) return core.NewBlockchain(store, cfg.ProtocolConfiguration)
} }
func logo() string { func logo() string {

View file

@ -1,4 +1,4 @@
package network package config
import ( import (
"fmt" "fmt"
@ -12,6 +12,11 @@ import (
const ( const (
userAgentFormat = "/NEO-GO:%s/" userAgentFormat = "/NEO-GO:%s/"
// Valid NetMode constants.
ModeMainNet NetMode = 0x00746e41 // 7630401
ModeTestNet NetMode = 0x74746e41 // 1953787457
ModePrivNet NetMode = 56753 // docker privnet
) )
var ( var (
@ -59,16 +64,33 @@ type (
ProtoTickInterval time.Duration `yaml:"ProtoTickInterval"` ProtoTickInterval time.Duration `yaml:"ProtoTickInterval"`
MaxPeers int `yaml:"MaxPeers"` MaxPeers int `yaml:"MaxPeers"`
} }
// NetMode describes the mode the blockchain will operate on.
NetMode uint32
) )
// String implements the stringer interface.
func (n NetMode) String() string {
switch n {
case ModePrivNet:
return "privnet"
case ModeTestNet:
return "testnet"
case ModeMainNet:
return "mainnet"
default:
return "net unknown"
}
}
// GenerateUserAgent creates user agent string based on build time environment. // GenerateUserAgent creates user agent string based on build time environment.
func (c Config) GenerateUserAgent() string { func (c Config) GenerateUserAgent() string {
return fmt.Sprintf(userAgentFormat, Version) return fmt.Sprintf(userAgentFormat, Version)
} }
// LoadConfig attempts to load the config from the give // Loadattempts to load the config from the give
// path and netMode. // path and netMode.
func LoadConfig(path string, netMode NetMode) (Config, error) { func Load(path string, netMode NetMode) (Config, error) {
configPath := fmt.Sprintf("%s/protocol.%s.yml", path, netMode) configPath := fmt.Sprintf("%s/protocol.%s.yml", path, netMode)
if _, err := os.Stat(configPath); os.IsNotExist(err) { if _, err := os.Stat(configPath); os.IsNotExist(err) {
return Config{}, errors.Wrap(err, "Unable to load config") return Config{}, errors.Wrap(err, "Unable to load config")

View file

@ -27,6 +27,6 @@ ApplicationConfiguration:
NodePort: 20333 NodePort: 20333
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 10 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50

View file

@ -23,5 +23,5 @@ ApplicationConfiguration:
NodePort: 20333 NodePort: 20333
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 10 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50

View file

@ -27,5 +27,5 @@ ApplicationConfiguration:
NodePort: 20333 NodePort: 20333
Relay: true Relay: true
DialTimeout: 3 DialTimeout: 3
ProtoTickInterval: 10 ProtoTickInterval: 2
MaxPeers: 50 MaxPeers: 50

View file

@ -3,6 +3,7 @@ package core
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
@ -18,11 +19,11 @@ func (a Accounts) getAndChange(s storage.Store, hash util.Uint160) (*AccountStat
return account, nil return account, nil
} }
var account *AccountState account := &AccountState{}
key := storage.AppendPrefix(storage.STAccount, hash.Bytes()) key := storage.AppendPrefix(storage.STAccount, hash.Bytes())
if b, err := s.Get(key); err == nil { if b, err := s.Get(key); err == nil {
if err := account.DecodeBinary(bytes.NewReader(b)); err != nil { if err := account.DecodeBinary(bytes.NewReader(b)); err != nil {
return nil, err return nil, fmt.Errorf("failed to decode (AccountState): %s", err)
} }
} else { } else {
account = NewAccountState(hash) account = NewAccountState(hash)
@ -48,6 +49,7 @@ func (a Accounts) commit(b storage.Batch) error {
// AccountState represents the state of a NEO account. // AccountState represents the state of a NEO account.
type AccountState struct { type AccountState struct {
Version uint8
ScriptHash util.Uint160 ScriptHash util.Uint160
IsFrozen bool IsFrozen bool
Votes []*crypto.PublicKey Votes []*crypto.PublicKey
@ -57,6 +59,7 @@ type AccountState struct {
// NewAccountState returns a new AccountState object. // NewAccountState returns a new AccountState object.
func NewAccountState(scriptHash util.Uint160) *AccountState { func NewAccountState(scriptHash util.Uint160) *AccountState {
return &AccountState{ return &AccountState{
Version: 0,
ScriptHash: scriptHash, ScriptHash: scriptHash,
IsFrozen: false, IsFrozen: false,
Votes: []*crypto.PublicKey{}, Votes: []*crypto.PublicKey{},
@ -66,6 +69,9 @@ func NewAccountState(scriptHash util.Uint160) *AccountState {
// DecodeBinary decodes AccountState from the given io.Reader. // DecodeBinary decodes AccountState from the given io.Reader.
func (s *AccountState) DecodeBinary(r io.Reader) error { func (s *AccountState) DecodeBinary(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &s.Version); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &s.ScriptHash); err != nil { if err := binary.Read(r, binary.LittleEndian, &s.ScriptHash); err != nil {
return err return err
} }
@ -101,6 +107,9 @@ func (s *AccountState) DecodeBinary(r io.Reader) error {
// EncodeBinary encode AccountState to the given io.Writer. // EncodeBinary encode AccountState to the given io.Writer.
func (s *AccountState) EncodeBinary(w io.Writer) error { func (s *AccountState) EncodeBinary(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, s.Version); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, s.ScriptHash); err != nil { if err := binary.Write(w, binary.LittleEndian, s.ScriptHash); err != nil {
return err return err
} }
@ -111,27 +120,35 @@ func (s *AccountState) EncodeBinary(w io.Writer) error {
if err := util.WriteVarUint(w, uint64(len(s.Votes))); err != nil { if err := util.WriteVarUint(w, uint64(len(s.Votes))); err != nil {
return err return err
} }
for _, point := range s.Votes { for _, point := range s.Votes {
if err := point.EncodeBinary(w); err != nil { if err := point.EncodeBinary(w); err != nil {
return err return err
} }
} }
if err := util.WriteVarUint(w, uint64(len(s.Balances))); err != nil { balances := s.nonZeroBalances()
if err := util.WriteVarUint(w, uint64(len(balances))); err != nil {
return err return err
} }
for k, v := range balances {
for k, v := range s.Balances { if err := binary.Write(w, binary.LittleEndian, k); err != nil {
if v > 0 { return err
if err := binary.Write(w, binary.LittleEndian, k); err != nil { }
return err if err := binary.Write(w, binary.LittleEndian, v); err != nil {
} return err
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
return err
}
} }
} }
return nil return nil
} }
// Returns only the non-zero balances for the account.
func (s *AccountState) nonZeroBalances() map[util.Uint256]util.Fixed8 {
b := make(map[util.Uint256]util.Fixed8)
for k, v := range s.Balances {
if v > 0 {
b[k] = v
}
}
return b
}

View file

@ -21,6 +21,7 @@ func TestDecodeEncodeAccountState(t *testing.T) {
} }
a := &AccountState{ a := &AccountState{
Version: 0,
ScriptHash: util.RandomUint160(), ScriptHash: util.RandomUint160(),
IsFrozen: true, IsFrozen: true,
Votes: votes, Votes: votes,
@ -37,6 +38,7 @@ func TestDecodeEncodeAccountState(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, a.Version, aDecode.Version)
assert.Equal(t, a.ScriptHash, aDecode.ScriptHash) assert.Equal(t, a.ScriptHash, aDecode.ScriptHash)
assert.Equal(t, a.IsFrozen, aDecode.IsFrozen) assert.Equal(t, a.IsFrozen, aDecode.IsFrozen)

View file

@ -6,6 +6,7 @@ import (
"io" "io"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -29,6 +30,22 @@ func (b *Block) Header() *Header {
} }
} }
// rebuildMerkleRoot rebuild the merkleroot of the block.
func (b *Block) rebuildMerkleRoot() error {
hashes := make([]util.Uint256, len(b.Transactions))
for i, tx := range b.Transactions {
hashes[i] = tx.Hash()
}
merkle, err := crypto.NewMerkleTree(hashes)
if err != nil {
return err
}
b.MerkleRoot = merkle.Root()
return nil
}
// Verify the integrity of the block. // Verify the integrity of the block.
func (b *Block) Verify(full bool) bool { func (b *Block) Verify(full bool) bool {
// The first TX has to be a miner transaction. // The first TX has to be a miner transaction.

View file

@ -54,6 +54,9 @@ func (b *BlockBase) Verify() bool {
// Hash return the hash of the block. // Hash return the hash of the block.
func (b *BlockBase) Hash() util.Uint256 { func (b *BlockBase) Hash() util.Uint256 {
if b.hash.Equals(util.Uint256{}) {
b.createHash()
}
return b.hash return b.hash
} }
@ -92,16 +95,19 @@ func (b *BlockBase) EncodeBinary(w io.Writer) error {
// version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner. // version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner.
// Since MerkleRoot already contains the hash value of all transactions, // Since MerkleRoot already contains the hash value of all transactions,
// the modification of transaction will influence the hash value of the block. // the modification of transaction will influence the hash value of the block.
func (b *BlockBase) createHash() (hash util.Uint256, err error) { func (b *BlockBase) createHash() error {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err = b.encodeHashableFields(buf); err != nil { if err := b.encodeHashableFields(buf); err != nil {
return hash, err return err
} }
// Double hash the encoded fields. // Double hash the encoded fields.
var hash util.Uint256
hash = sha256.Sum256(buf.Bytes()) hash = sha256.Sum256(buf.Bytes())
hash = sha256.Sum256(hash.Bytes()) hash = sha256.Sum256(hash.Bytes())
return hash, nil b.hash = hash
return nil
} }
// encodeHashableFields will only encode the fields used for hashing. // encodeHashableFields will only encode the fields used for hashing.
@ -155,10 +161,5 @@ func (b *BlockBase) decodeHashableFields(r io.Reader) error {
// Make the hash of the block here so we dont need to do this // Make the hash of the block here so we dont need to do this
// again. // again.
hash, err := b.createHash() return b.createHash()
if err != nil {
return err
}
b.hash = hash
return nil
} }

View file

@ -4,10 +4,10 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
@ -18,15 +18,19 @@ import (
const ( const (
secondsPerBlock = 15 secondsPerBlock = 15
headerBatchCount = 2000 headerBatchCount = 2000
version = "0.0.1"
) )
var ( var (
genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
persistInterval = 5 * time.Second decrementInterval = 2000000
persistInterval = 5 * time.Second
) )
// Blockchain holds the chain. // Blockchain represents the blockchain.
type Blockchain struct { type Blockchain struct {
config config.ProtocolConfiguration
// Any object that satisfies the BlockchainStorer interface. // Any object that satisfies the BlockchainStorer interface.
storage.Store storage.Store
@ -40,8 +44,6 @@ type Blockchain struct {
blockCache *Cache blockCache *Cache
startHash util.Uint256
// All operation on headerList must be called from an // All operation on headerList must be called from an
// headersOp to be routine safe. // headersOp to be routine safe.
headerList *HeaderHashList headerList *HeaderHashList
@ -58,12 +60,12 @@ type headersOpFunc func(headerList *HeaderHashList)
// NewBlockchain return a new blockchain object the will use the // NewBlockchain return a new blockchain object the will use the
// given Store as its underlying storage. // given Store as its underlying storage.
func NewBlockchain(s storage.Store, startHash util.Uint256) (*Blockchain, error) { func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockchain, error) {
bc := &Blockchain{ bc := &Blockchain{
config: cfg,
Store: s, Store: s,
headersOp: make(chan headersOpFunc), headersOp: make(chan headersOpFunc),
headersOpDone: make(chan struct{}), headersOpDone: make(chan struct{}),
startHash: startHash,
blockCache: NewCache(), blockCache: NewCache(),
verifyBlocks: false, verifyBlocks: false,
} }
@ -77,19 +79,30 @@ func NewBlockchain(s storage.Store, startHash util.Uint256) (*Blockchain, error)
} }
func (bc *Blockchain) init() error { func (bc *Blockchain) init() error {
// TODO: This should be the persistance of the genisis block. genesisBlock, err := createGenesisBlock(bc.config)
// for now we just add the genisis block start hash. if err != nil {
bc.headerList = NewHeaderHashList(bc.startHash) return err
bc.storedHeaderCount = 1 // genisis hash }
bc.headerList = NewHeaderHashList(genesisBlock.Hash())
// Look in the storage for a version. If we could not the version key
// there is nothing stored.
if version, err := bc.Get(storage.SYSVersion.Bytes()); err != nil {
bc.Put(storage.SYSVersion.Bytes(), []byte(version))
if err := bc.persistBlock(genesisBlock); err != nil {
return err
}
return nil
}
// At this point there was no version found in the storage which
// implies a creating fresh storage with the version specified
// and the genesis block as first block.
log.Infof("restoring blockchain with storage version: %s", version)
// 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()) currBlockBytes, err := bc.Get(storage.SYSCurrentBlock.Bytes())
if err != nil { if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil
}
return err return err
} }
@ -98,8 +111,9 @@ func (bc *Blockchain) init() error {
if err != nil { if err != nil {
return err return err
} }
for _, hash := range hashes { for _, hash := range hashes {
if !bc.startHash.Equals(hash) { if !genesisBlock.Hash().Equals(hash) {
bc.headerList.Add(hash) bc.headerList.Add(hash)
bc.storedHeaderCount++ bc.storedHeaderCount++
} }
@ -292,7 +306,7 @@ func (bc *Blockchain) persistBlock(block *Block) error {
} }
if output.AssetID.Equals(bc.governingToken()) && len(account.Votes) > 0 { if output.AssetID.Equals(bc.governingToken()) && len(account.Votes) > 0 {
log.Warnf("governing token detected in TX output need to update validators!") // TODO
} }
} }
@ -300,7 +314,7 @@ func (bc *Blockchain) persistBlock(block *Block) error {
for prevHash, inputs := range tx.GroupInputsByPrevHash() { for prevHash, inputs := range tx.GroupInputsByPrevHash() {
prevTX, _, err := bc.GetTransaction(prevHash) prevTX, _, err := bc.GetTransaction(prevHash)
if err != nil { if err != nil {
return err return fmt.Errorf("could not find previous TX: %s", prevHash)
} }
for _, input := range inputs { for _, input := range inputs {
unspent, err := unspentCoins.getAndChange(bc.Store, input.PrevHash) unspent, err := unspentCoins.getAndChange(bc.Store, input.PrevHash)
@ -316,7 +330,7 @@ func (bc *Blockchain) persistBlock(block *Block) error {
} }
if prevTXOutput.AssetID.Equals(bc.governingToken()) { if prevTXOutput.AssetID.Equals(bc.governingToken()) {
log.Warnf("governing token detected in TX input need to update validators!") // TODO
} }
account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount
@ -347,7 +361,7 @@ func (bc *Blockchain) persistBlock(block *Block) error {
return err return err
} }
atomic.AddUint32(&bc.blockHeight, 1) atomic.StoreUint32(&bc.blockHeight, block.Index)
return nil return nil
} }
@ -366,6 +380,7 @@ func (bc *Blockchain) persist() (err error) {
hash := headerList.Get(int(bc.BlockHeight() + 1)) hash := headerList.Get(int(bc.BlockHeight() + 1))
if block, ok := bc.blockCache.GetBlock(hash); ok { if block, ok := bc.blockCache.GetBlock(hash); ok {
if err = bc.persistBlock(block); err != nil { if err = bc.persistBlock(block); err != nil {
log.Warnf("failed to persist blocks: %s", err)
return return
} }
bc.blockCache.Delete(hash) bc.blockCache.Delete(hash)

View file

@ -3,8 +3,8 @@ package core
import ( import (
"testing" "testing"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -20,7 +20,6 @@ func TestAddHeaders(t *testing.T) {
assert.Equal(t, 0, bc.blockCache.Len()) assert.Equal(t, 0, bc.blockCache.Len())
assert.Equal(t, h3.Index, bc.HeaderHeight()) assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(1), bc.storedHeaderCount)
assert.Equal(t, uint32(0), bc.BlockHeight()) assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
@ -30,7 +29,6 @@ func TestAddHeaders(t *testing.T) {
} }
assert.Equal(t, h3.Index, bc.HeaderHeight()) assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(1), bc.storedHeaderCount)
assert.Equal(t, uint32(0), bc.BlockHeight()) assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
} }
@ -53,12 +51,20 @@ func TestAddBlock(t *testing.T) {
assert.Equal(t, 3, bc.blockCache.Len()) assert.Equal(t, 3, bc.blockCache.Len())
assert.Equal(t, lastBlock.Index, bc.HeaderHeight()) assert.Equal(t, lastBlock.Index, bc.HeaderHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash()) assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
assert.Equal(t, uint32(1), bc.storedHeaderCount)
t.Log(bc.blockCache)
if err := bc.persist(); err != nil { if err := bc.persist(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, block := range blocks {
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesReverse())
if _, err := bc.Get(key); err != nil {
t.Fatalf("block %s not persisted", block.Hash())
}
}
assert.Equal(t, lastBlock.Index, bc.BlockHeight()) assert.Equal(t, lastBlock.Index, bc.BlockHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash()) assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
assert.Equal(t, 0, bc.blockCache.Len()) assert.Equal(t, 0, bc.blockCache.Len())
@ -138,8 +144,11 @@ func TestGetTransaction(t *testing.T) {
} }
func newTestChain(t *testing.T) *Blockchain { func newTestChain(t *testing.T) *Blockchain {
startHash, _ := util.Uint256DecodeString("a") cfg, err := config.Load("../../config", config.ModePrivNet)
chain, err := NewBlockchain(storage.NewMemoryStore(), startHash) if err != nil {
t.Fatal(err)
}
chain, err := NewBlockchain(storage.NewMemoryStore(), cfg.ProtocolConfiguration)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -31,11 +31,9 @@ func newBlock(index uint32, txs ...*transaction.Transaction) *Block {
}, },
Transactions: txs, Transactions: txs,
} }
hash, err := b.createHash()
if err != nil { b.createHash()
panic(err)
}
b.hash = hash
return b return b
} }

View file

@ -7,7 +7,7 @@ import (
// MinerTX represents a miner transaction. // MinerTX represents a miner transaction.
type MinerTX struct { type MinerTX struct {
// Random number/identifier // Random number to avoid hash collision.
Nonce uint32 Nonce uint32
} }

View file

@ -1,19 +1,22 @@
package transaction package transaction
import ( import (
"encoding/binary"
"io" "io"
"github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
) )
// PublishTX represents a publish transaction. // PublishTX represents a publish transaction.
// This is deprecated and should no longer be used. // NOTE: This is deprecated and should no longer be used.
type PublishTX struct { type PublishTX struct {
Script []byte Script []byte
ParamList []smartcontract.ParamType ParamList []smartcontract.ParamType
ReturnType smartcontract.ParamType ReturnType smartcontract.ParamType
NeedStorage bool NeedStorage bool
Name string Name string
CodeVersion string
Author string Author string
Email string Email string
Description string Description string
@ -21,6 +24,54 @@ type PublishTX struct {
// DecodeBinary implements the Payload interface. // DecodeBinary implements the Payload interface.
func (tx *PublishTX) DecodeBinary(r io.Reader) error { func (tx *PublishTX) DecodeBinary(r io.Reader) error {
var err error
tx.Script, err = util.ReadVarBytes(r)
if err != nil {
return err
}
lenParams := util.ReadVarUint(r)
tx.ParamList = make([]smartcontract.ParamType, lenParams)
for i := 0; i < int(lenParams); i++ {
var ptype uint8
if err := binary.Read(r, binary.LittleEndian, &ptype); err != nil {
return err
}
tx.ParamList[i] = smartcontract.ParamType(ptype)
}
var rtype uint8
if err := binary.Read(r, binary.LittleEndian, &rtype); err != nil {
return err
}
tx.ReturnType = smartcontract.ParamType(rtype)
if err := binary.Read(r, binary.LittleEndian, &tx.NeedStorage); err != nil {
return err
}
tx.Name, err = util.ReadVarString(r)
if err != nil {
return err
}
tx.CodeVersion, err = util.ReadVarString(r)
if err != nil {
return err
}
tx.Author, err = util.ReadVarString(r)
if err != nil {
return err
}
tx.Email, err = util.ReadVarString(r)
if err != nil {
return err
}
tx.Description, err = util.ReadVarString(r)
if err != nil {
return err
}
return nil return nil
} }

View file

@ -8,34 +8,14 @@ import (
"github.com/CityOfZion/neo-go/pkg/util" "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. // RegisterTX represents a register transaction.
// NOTE: This is deprecated.
type RegisterTX struct { type RegisterTX struct {
// The type of the asset being registered. // The type of the asset being registered.
AssetType AssetType AssetType AssetType
// Name of the asset being registered. // Name of the asset being registered.
Name []byte Name string
// Amount registered // Amount registered
// Unlimited mode -0.00000001 // Unlimited mode -0.00000001
@ -55,14 +35,17 @@ func (tx *RegisterTX) DecodeBinary(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &tx.AssetType); err != nil { if err := binary.Read(r, binary.LittleEndian, &tx.AssetType); err != nil {
return err return err
} }
lenName := util.ReadVarUint(r)
tx.Name = make([]byte, lenName) var err error
if err := binary.Read(r, binary.LittleEndian, &tx.Name); err != nil { tx.Name, err = util.ReadVarString(r)
if err != nil {
return err return err
} }
if err := binary.Read(r, binary.LittleEndian, &tx.Amount); err != nil { if err := binary.Read(r, binary.LittleEndian, &tx.Amount); err != nil {
return err return err
} }
if err := binary.Read(r, binary.LittleEndian, &tx.Precision); err != nil { if err := binary.Read(r, binary.LittleEndian, &tx.Precision); err != nil {
return err return err
} }
@ -77,5 +60,20 @@ func (tx *RegisterTX) DecodeBinary(r io.Reader) error {
// EncodeBinary implements the Payload interface. // EncodeBinary implements the Payload interface.
func (tx *RegisterTX) EncodeBinary(w io.Writer) error { func (tx *RegisterTX) EncodeBinary(w io.Writer) error {
return nil if err := binary.Write(w, binary.LittleEndian, tx.AssetType); err != nil {
return err
}
if err := util.WriteVarString(w, tx.Name); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, tx.Amount); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, tx.Precision); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, tx.Owner.Bytes()); err != nil {
return err
}
return binary.Write(w, binary.LittleEndian, tx.Admin)
} }

View file

@ -0,0 +1,63 @@
package transaction
import (
"bytes"
"encoding/hex"
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestRegisterTX(t *testing.T) {
tx := &Transaction{
Type: RegisterType,
Version: 0,
Data: &RegisterTX{
AssetType: UtilityToken,
Name: "this is some token I created",
Amount: util.NewFixed8(1000000),
Precision: 8,
Owner: &crypto.PublicKey{},
Admin: util.RandomUint160(),
},
}
buf := new(bytes.Buffer)
assert.Nil(t, tx.EncodeBinary(buf))
txDecode := &Transaction{}
assert.Nil(t, txDecode.DecodeBinary(buf))
txData := tx.Data.(*RegisterTX)
txDecodeData := txDecode.Data.(*RegisterTX)
assert.Equal(t, txData, txDecodeData)
assert.Equal(t, tx.Hash(), txDecode.Hash())
}
func TestDecodeRegisterTXFromRawString(t *testing.T) {
rawTX := "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000"
b, err := hex.DecodeString(rawTX)
if err != nil {
t.Fatal(err)
}
tx := &Transaction{}
assert.Nil(t, tx.DecodeBinary(bytes.NewReader(b)))
assert.Equal(t, RegisterType, tx.Type)
txData := tx.Data.(*RegisterTX)
assert.Equal(t, GoverningToken, txData.AssetType)
assert.Equal(t, "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", txData.Name)
assert.Equal(t, util.NewFixed8(100000000), txData.Amount)
assert.Equal(t, uint8(0), txData.Precision)
assert.Equal(t, &crypto.PublicKey{}, txData.Owner)
assert.Equal(t, "Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt", crypto.AddressFromUint160(txData.Admin))
assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", tx.Hash().String())
buf := new(bytes.Buffer)
assert.Nil(t, tx.EncodeBinary(buf))
txDecode := &Transaction{}
assert.Nil(t, txDecode.DecodeBinary(buf))
assert.Equal(t, tx, txDecode)
}

View file

@ -55,6 +55,9 @@ func NewTrimmedTX(hash util.Uint256) *Transaction {
// Hash return the hash of the transaction. // Hash return the hash of the transaction.
func (t *Transaction) Hash() util.Uint256 { func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) {
t.createHash()
}
return t.hash return t.hash
} }
@ -118,13 +121,7 @@ func (t *Transaction) DecodeBinary(r io.Reader) error {
// Create the hash of the transaction at decode, so we dont need // Create the hash of the transaction at decode, so we dont need
// to do it anymore. // to do it anymore.
hash, err := t.createHash() return t.createHash()
if err != nil {
return err
}
t.hash = hash
return nil
} }
func (t *Transaction) decodeData(r io.Reader) error { func (t *Transaction) decodeData(r io.Reader) error {
@ -150,7 +147,12 @@ func (t *Transaction) decodeData(r io.Reader) error {
case EnrollmentType: case EnrollmentType:
t.Data = &EnrollmentTX{} t.Data = &EnrollmentTX{}
return t.Data.(*EnrollmentTX).DecodeBinary(r) return t.Data.(*EnrollmentTX).DecodeBinary(r)
case PublishType:
t.Data = &PublishTX{}
return t.Data.(*PublishTX).DecodeBinary(r)
case StateType:
t.Data = &StateTX{}
return t.Data.(*StateTX).DecodeBinary(r)
default: default:
log.Warnf("invalid TX type %s", t.Type) log.Warnf("invalid TX type %s", t.Type)
} }
@ -223,18 +225,19 @@ func (t *Transaction) encodeHashableFields(w io.Writer) error {
return nil return nil
} }
func (t *Transaction) createHash() (hash util.Uint256, err error) { // createHash creates the hash of the transaction.
func (t *Transaction) createHash() error {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err = t.encodeHashableFields(buf); err != nil { if err := t.encodeHashableFields(buf); err != nil {
return return err
} }
sha := sha256.New()
sha.Write(buf.Bytes()) var hash util.Uint256
b := sha.Sum(nil) hash = sha256.Sum256(buf.Bytes())
sha.Reset() hash = sha256.Sum256(hash.Bytes())
sha.Write(b) t.hash = hash
b = sha.Sum(nil)
return util.Uint256DecodeBytes(util.ArrayReverse(b)) return nil
} }
// GroupTXInputsByPrevHash groups all TX inputs by their previous hash. // GroupTXInputsByPrevHash groups all TX inputs by their previous hash.

View file

@ -3,6 +3,7 @@ package core
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
@ -18,11 +19,11 @@ func (u UnspentCoins) getAndChange(s storage.Store, hash util.Uint256) (*Unspent
return unspent, nil return unspent, nil
} }
var unspent *UnspentCoinState unspent := &UnspentCoinState{}
key := storage.AppendPrefix(storage.STCoin, hash.BytesReverse()) key := storage.AppendPrefix(storage.STCoin, hash.BytesReverse())
if b, err := s.Get(key); err == nil { if b, err := s.Get(key); err == nil {
if err := unspent.DecodeBinary(bytes.NewReader(b)); err != nil { if err := unspent.DecodeBinary(bytes.NewReader(b)); err != nil {
return nil, err return nil, fmt.Errorf("failed to decode (UnspentCoinState): %s", err)
} }
} else { } else {
unspent = &UnspentCoinState{ unspent = &UnspentCoinState{

View file

@ -4,29 +4,176 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"sort" "sort"
"time"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
) )
// Utilities for quick bootstrapping blockchains. Normally we should // Creates a genesis block based on the given configuration.
// create the genisis block. For now (to speed up development) we will add func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) {
// The hashes manually. validators, err := getValidators(cfg)
if err != nil {
return nil, err
}
func GenesisHashPrivNet() util.Uint256 { nextConsensus, err := getNextConsensusAddress(validators)
hash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099") if err != nil {
return hash return nil, err
}
base := BlockBase{
Version: 0,
PrevHash: util.Uint256{},
Timestamp: uint32(time.Date(2016, 7, 15, 15, 8, 21, 0, time.UTC).Unix()),
Index: 0,
ConsensusData: 2083236893,
NextConsensus: nextConsensus,
Script: &transaction.Witness{
InvocationScript: []byte{},
VerificationScript: []byte{byte(vm.Opusht)},
},
}
governingTX := governingTokenTX()
utilityTX := utilityTokenTX()
rawScript, err := smartcontract.CreateMultiSigRedeemScript(
len(cfg.StandbyValidators)/2+1,
validators,
)
if err != nil {
return nil, err
}
scriptOut, err := util.Uint160FromScript(rawScript)
if err != nil {
return nil, err
}
block := &Block{
BlockBase: base,
Transactions: []*transaction.Transaction{
{
Type: transaction.MinerType,
Data: &transaction.MinerTX{
Nonce: 2083236893,
},
Attributes: []*transaction.Attribute{},
Inputs: []*transaction.Input{},
Outputs: []*transaction.Output{},
Scripts: []*transaction.Witness{},
},
governingTX,
utilityTX,
{
Type: transaction.IssueType,
Data: &transaction.IssueTX{}, // no fields.
Inputs: []*transaction.Input{},
Outputs: []*transaction.Output{
{
AssetID: governingTX.Hash(),
Amount: governingTX.Data.(*transaction.RegisterTX).Amount,
ScriptHash: scriptOut,
},
},
Scripts: []*transaction.Witness{
{
InvocationScript: []byte{},
VerificationScript: []byte{byte(vm.Opusht)},
},
},
},
},
}
block.rebuildMerkleRoot()
return block, nil
} }
func GenesisHashTestNet() util.Uint256 { func governingTokenTX() *transaction.Transaction {
hash, _ := util.Uint256DecodeString("b3181718ef6167105b70920e4a8fbbd0a0a56aacf460d70e10ba6fa1668f1fef") admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)})
return hash adminB, _ := crypto.Uint160DecodeAddress("Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt")
if !admin.Equals(adminB) {
panic("kdjdkljfkdjfkdjf")
}
registerTX := &transaction.RegisterTX{
AssetType: transaction.GoverningToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",
Amount: util.NewFixed8(100000000),
Precision: 0,
Owner: &crypto.PublicKey{},
Admin: admin,
}
tx := &transaction.Transaction{
Type: transaction.RegisterType,
Data: registerTX,
Attributes: []*transaction.Attribute{},
Inputs: []*transaction.Input{},
Outputs: []*transaction.Output{},
Scripts: []*transaction.Witness{},
}
return tx
} }
func GenesisHashMainNet() util.Uint256 { func utilityTokenTX() *transaction.Transaction {
hash, _ := util.Uint256DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf") admin, _ := util.Uint160FromScript([]byte{byte(vm.Opushf)})
return hash registerTX := &transaction.RegisterTX{
AssetType: transaction.UtilityToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",
Amount: calculateUtilityAmount(),
Precision: 8,
Owner: &crypto.PublicKey{},
Admin: admin,
}
tx := &transaction.Transaction{
Type: transaction.RegisterType,
Data: registerTX,
Attributes: []*transaction.Attribute{},
Inputs: []*transaction.Input{},
Outputs: []*transaction.Output{},
Scripts: []*transaction.Witness{},
}
return tx
}
func getValidators(cfg config.ProtocolConfiguration) ([]*crypto.PublicKey, error) {
validators := make([]*crypto.PublicKey, len(cfg.StandbyValidators))
for i, pubKeyStr := range cfg.StandbyValidators {
pubKey, err := crypto.NewPublicKeyFromString(pubKeyStr)
if err != nil {
return nil, err
}
validators[i] = pubKey
}
return validators, nil
}
func getNextConsensusAddress(validators []*crypto.PublicKey) (val util.Uint160, err error) {
vlen := len(validators)
raw, err := smartcontract.CreateMultiSigRedeemScript(
vlen-(vlen-1)/3,
validators,
)
if err != nil {
return val, err
}
return util.Uint160FromScript(raw)
}
func calculateUtilityAmount() util.Fixed8 {
sum := 0
for i := 0; i < len(genAmount); i++ {
sum += genAmount[i]
}
return util.NewFixed8(sum * decrementInterval)
} }
// headerSliceReverse reverses the given slice of *Header. // headerSliceReverse reverses the given slice of *Header.

61
pkg/core/util_test.go Normal file
View file

@ -0,0 +1,61 @@
package core
import (
"testing"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/stretchr/testify/assert"
)
func TestGenesisBlockMainNet(t *testing.T) {
cfg, err := config.Load("../../config", config.ModeMainNet)
if err != nil {
t.Fatal(err)
}
block, err := createGenesisBlock(cfg.ProtocolConfiguration)
if err != nil {
t.Fatal(err)
}
expect := "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
assert.Equal(t, expect, block.Hash().String())
}
func TestGetConsensusAddressMainNet(t *testing.T) {
var (
consensusAddr = "APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR"
consensusScript = "59e75d652b5d3827bf04c165bbe9ef95cca4bf55"
)
cfg, err := config.Load("../../config", config.ModeMainNet)
if err != nil {
t.Fatal(err)
}
validators, err := getValidators(cfg.ProtocolConfiguration)
if err != nil {
t.Fatal(err)
}
script, err := getNextConsensusAddress(validators)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, consensusScript, script.String())
assert.Equal(t, consensusAddr, crypto.AddressFromUint160(script))
}
func TestUtilityTokenTX(t *testing.T) {
expect := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
tx := utilityTokenTX()
assert.Equal(t, expect, tx.Hash().String())
}
func TestGoverningTokenTX(t *testing.T) {
expect := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
tx := governingTokenTX()
assert.Equal(t, expect, tx.Hash().String())
}

View file

@ -88,8 +88,6 @@ func ECPointFromReader(r io.Reader) (point ECPoint, err error) {
return return
} }
fmt.Println(f)
// Infinity // Infinity
if f == 0 { if f == 0 {
return ECPoint{ return ECPoint{

99
pkg/crypto/merkle_tree.go Normal file
View file

@ -0,0 +1,99 @@
package crypto
import (
"crypto/sha256"
"errors"
"github.com/CityOfZion/neo-go/pkg/util"
)
// MerkleTree implementation.
type MerkleTree struct {
root *MerkleTreeNode
depth int
}
// NewMerkleTree returns new MerkleTree object.
func NewMerkleTree(hashes []util.Uint256) (*MerkleTree, error) {
if len(hashes) == 0 {
return nil, errors.New("length of the hashes cannot be zero")
}
nodes := make([]*MerkleTreeNode, len(hashes))
for i := 0; i < len(hashes); i++ {
nodes[i] = &MerkleTreeNode{
hash: hashes[i],
}
}
root, err := buildMerkleTree(nodes)
if err != nil {
return nil, err
}
return &MerkleTree{
root: root,
depth: 1,
}, nil
}
// Root return the computed root hash of the MerkleTree.
func (t *MerkleTree) Root() util.Uint256 {
return t.root.hash
}
func buildMerkleTree(leaves []*MerkleTreeNode) (*MerkleTreeNode, error) {
if len(leaves) == 0 {
return nil, errors.New("length of the leaves cannot be zero")
}
if len(leaves) == 1 {
return leaves[0], nil
}
parents := make([]*MerkleTreeNode, (len(leaves)+1)/2)
for i := 0; i < len(parents); i++ {
parents[i] = &MerkleTreeNode{}
parents[i].leftChild = leaves[i*2]
leaves[i*2].parent = parents[i]
if i*2+1 == len(leaves) {
parents[i].rightChild = parents[i].leftChild
} else {
parents[i].rightChild = leaves[i*2+1]
leaves[i*2+1].parent = parents[i]
}
b1 := parents[i].leftChild.hash.Bytes()
b2 := parents[i].rightChild.hash.Bytes()
b1 = append(b1, b2...)
parents[i].hash = hash256(b1)
}
return buildMerkleTree(parents)
}
// MerkleTreeNode represents a node in the MerkleTree.
type MerkleTreeNode struct {
hash util.Uint256
parent *MerkleTreeNode
leftChild *MerkleTreeNode
rightChild *MerkleTreeNode
}
// IsLeaf returns whether this node is a leaf node or not.
func (n *MerkleTreeNode) IsLeaf() bool {
return n.leftChild == nil && n.rightChild == nil
}
// IsRoot returns whether this node is a root node or not.
func (n *MerkleTreeNode) IsRoot() bool {
return n.parent == nil
}
func hash256(b []byte) util.Uint256 {
var hash util.Uint256
hash = sha256.Sum256(b)
hash = sha256.Sum256(hash.Bytes())
return hash
}

View file

@ -0,0 +1,29 @@
package crypto
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestComputeMerkleTree(t *testing.T) {
rawHashes := []string{
"fb5bd72b2d6792d75dc2f1084ffa9e9f70ca85543c717a6b13d9959b452a57d6",
"c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
"602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
"3631f66024ca6f5b033d7e0809eb993443374830025af904fb51b0334f127cda",
}
hashes := make([]util.Uint256, len(rawHashes))
for i, str := range rawHashes {
hash, _ := util.Uint256DecodeString(str)
hashes[i] = hash
}
merkle, err := NewMerkleTree(hashes)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "803ff4abe3ea6533bcc0be574efa02f83ae8fdc651c879056b0d9be336c01bf4", merkle.Root().String())
}

View file

@ -3,18 +3,59 @@ package crypto
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
) )
// PublicKey represents a public key. // PublicKeys is a list of public keys.
type PublicKeys []*PublicKey
func (keys PublicKeys) Len() int { return len(keys) }
func (keys PublicKeys) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] }
func (keys PublicKeys) Less(i, j int) bool {
if keys[i].X.Cmp(keys[j].X) == -1 {
return true
}
if keys[i].X.Cmp(keys[j].X) == 1 {
return false
}
if keys[i].X.Cmp(keys[j].X) == 0 {
return false
}
return keys[i].Y.Cmp(keys[j].Y) == -1
}
// PublicKey represents a public key and provides a high level
// API around the ECPoint.
type PublicKey struct { type PublicKey struct {
ECPoint ECPoint
} }
// NewPublicKeyFromString return a public key created from the
// given hex string.
func NewPublicKeyFromString(s string) (*PublicKey, error) {
b, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
pubKey := &PublicKey{}
if err := pubKey.DecodeBinary(bytes.NewReader(b)); err != nil {
return nil, err
}
return pubKey, nil
}
// Bytes returns the byte array representation of the public key. // Bytes returns the byte array representation of the public key.
func (p *PublicKey) Bytes() []byte { func (p *PublicKey) Bytes() []byte {
if p.IsInfinity() {
return []byte{0x00}
}
var ( var (
x = p.X.Bytes() x = p.X.Bytes()
paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...) paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...)
@ -35,6 +76,12 @@ func (p *PublicKey) DecodeBinary(r io.Reader) error {
return err return err
} }
// Infinity
if prefix == 0x00 {
p.ECPoint = ECPoint{}
return nil
}
// Compressed public keys. // Compressed public keys.
if prefix == 0x02 || prefix == 0x03 { if prefix == 0x02 || prefix == 0x03 {
c := NewEllipticCurve() c := NewEllipticCurve()

View file

@ -2,11 +2,23 @@ package crypto
import ( import (
"bytes" "bytes"
"encoding/hex"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEncodeDecodeInfinity(t *testing.T) {
key := &PublicKey{ECPoint{}}
buf := new(bytes.Buffer)
assert.Nil(t, key.EncodeBinary(buf))
assert.Equal(t, 1, buf.Len())
keyDecode := &PublicKey{}
assert.Nil(t, keyDecode.DecodeBinary(buf))
assert.Equal(t, []byte{0x00}, keyDecode.Bytes())
}
func TestEncodeDecodePublicKey(t *testing.T) { func TestEncodeDecodePublicKey(t *testing.T) {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
p := &PublicKey{RandomECPoint()} p := &PublicKey{RandomECPoint()}
@ -18,3 +30,12 @@ func TestEncodeDecodePublicKey(t *testing.T) {
assert.Equal(t, p.X, pDecode.X) assert.Equal(t, p.X, pDecode.X)
} }
} }
func TestDecodeFromString(t *testing.T) {
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
pubKey, err := NewPublicKeyFromString(str)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, str, hex.EncodeToString(pubKey.Bytes()))
}

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/network/payload"
@ -23,34 +24,10 @@ var (
errChecksumMismatch = errors.New("checksum mismatch") errChecksumMismatch = errors.New("checksum mismatch")
) )
// NetMode type that is compatible with netModes below.
type NetMode uint32
// String implements the stringer interface.
func (n NetMode) String() string {
switch n {
case ModePrivNet:
return "privnet"
case ModeTestNet:
return "testnet"
case ModeMainNet:
return "mainnet"
default:
return "net unknown"
}
}
// Values used for the magic field, according to the docs.
const (
ModeMainNet NetMode = 0x00746e41 // 7630401
ModeTestNet NetMode = 0x74746e41 // 1953787457
ModePrivNet NetMode = 56753 // docker privnet
)
// Message is the complete message send between nodes. // Message is the complete message send between nodes.
type Message struct { type Message struct {
// NetMode of the node that sends this message. // NetMode of the node that sends this message.
Magic NetMode Magic config.NetMode
// Command is utf8 code, of which the length is 12 bytes, // Command is utf8 code, of which the length is 12 bytes,
// the extra part is filled with 0. // the extra part is filled with 0.
@ -88,7 +65,7 @@ const (
) )
// NewMessage returns a new message with the given payload. // NewMessage returns a new message with the given payload.
func NewMessage(magic NetMode, cmd CommandType, p payload.Payload) *Message { func NewMessage(magic config.NetMode, cmd CommandType, p payload.Payload) *Message {
var ( var (
size uint32 size uint32
checksum []byte checksum []byte

View file

@ -3,6 +3,7 @@ package network
import ( import (
"time" "time"
"github.com/CityOfZion/neo-go/config"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -23,7 +24,7 @@ type (
// ModePrivNet docker private network. // ModePrivNet docker private network.
// ModeTestNet NEO test network. // ModeTestNet NEO test network.
// ModeMainNet NEO main network. // ModeMainNet NEO main network.
Net NetMode Net config.NetMode
// Relay determins whether the server is forwarding its inventory. // Relay determins whether the server is forwarding its inventory.
Relay bool Relay bool
@ -45,12 +46,12 @@ type (
// NewServerConfig creates a new ServerConfig struct // NewServerConfig creates a new ServerConfig struct
// using the main applications config. // using the main applications config.
func NewServerConfig(config Config) ServerConfig { func NewServerConfig(cfg config.Config) ServerConfig {
appConfig := config.ApplicationConfiguration appConfig := cfg.ApplicationConfiguration
protoConfig := config.ProtocolConfiguration protoConfig := cfg.ProtocolConfiguration
return ServerConfig{ return ServerConfig{
UserAgent: config.GenerateUserAgent(), UserAgent: cfg.GenerateUserAgent(),
ListenTCP: appConfig.NodePort, ListenTCP: appConfig.NodePort,
Net: protoConfig.Magic, Net: protoConfig.Magic,
Relay: appConfig.Relay, Relay: appConfig.Relay,

View file

@ -1,4 +1,34 @@
package smartcontract package smartcontract
// Contract represents a NEO smartcontract. import (
type Contract struct{} "bytes"
"fmt"
"sort"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/vm"
)
// CreateMultiSigRedeemScript will create a script runnable by the VM.
func CreateMultiSigRedeemScript(m int, publicKeys crypto.PublicKeys) ([]byte, error) {
if m <= 1 {
return nil, fmt.Errorf("param m cannot be smaller or equal to 1 got %d", m)
}
if m > len(publicKeys) {
return nil, fmt.Errorf("length of the signatures (%d) is higher then the number of public keys", m)
}
if m > 1024 {
return nil, fmt.Errorf("public key count %d exceeds maximum of length 1024", len(publicKeys))
}
buf := new(bytes.Buffer)
vm.EmitInt(buf, int64(m))
sort.Sort(publicKeys)
for _, pubKey := range publicKeys {
vm.EmitBytes(buf, pubKey.Bytes())
}
vm.EmitInt(buf, int64(len(publicKeys)))
vm.EmitOpcode(buf, vm.Ocheckmultisig)
return buf.Bytes(), nil
}

View file

@ -0,0 +1,41 @@
package smartcontract
import (
"bytes"
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
"github.com/stretchr/testify/assert"
)
func TestCreateMultiSigRedeemScript(t *testing.T) {
val1, _ := crypto.NewPublicKeyFromString("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c")
val2, _ := crypto.NewPublicKeyFromString("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093")
val3, _ := crypto.NewPublicKeyFromString("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a")
validators := []*crypto.PublicKey{val1, val2, val3}
out, err := CreateMultiSigRedeemScript(3, validators)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(out)
b, _ := buf.ReadByte()
assert.Equal(t, vm.Opush3, vm.Opcode(b))
for i := 0; i < len(validators); i++ {
b, err := util.ReadVarBytes(buf)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, validators[i].Bytes(), b)
}
b, _ = buf.ReadByte()
assert.Equal(t, vm.Opush3, vm.Opcode(b))
b, _ = buf.ReadByte()
assert.Equal(t, vm.Ocheckmultisig, vm.Opcode(b))
}

View file

@ -5,6 +5,8 @@ import (
"strconv" "strconv"
) )
const decimals = 100000000
// Fixed8 represents a fixed-point number with precision 10^-8. // Fixed8 represents a fixed-point number with precision 10^-8.
type Fixed8 int64 type Fixed8 int64
@ -16,9 +18,9 @@ func (f Fixed8) String() string {
buf.WriteRune('-') buf.WriteRune('-')
val = -val val = -val
} }
str := strconv.FormatInt(val/100000000, 10) str := strconv.FormatInt(val/decimals, 10)
buf.WriteString(str) buf.WriteString(str)
val %= 100000000 val %= decimals
if val > 0 { if val > 0 {
buf.WriteRune('.') buf.WriteRune('.')
str = strconv.FormatInt(val, 10) str = strconv.FormatInt(val, 10)
@ -29,3 +31,13 @@ func (f Fixed8) String() string {
} }
return buf.String() return buf.String()
} }
// Value returns the original value representing the Fixed8.
func (f Fixed8) Value() int64 {
return int64(f) / int64(decimals)
}
// NewFixed8 return a new Fixed8 type multiplied by decimals.
func NewFixed8(val int) Fixed8 {
return Fixed8(decimals * val)
}

16
pkg/util/fixed8_test.go Normal file
View file

@ -0,0 +1,16 @@
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFixed8(t *testing.T) {
values := []int{9000, 100000000, 5, 10945}
for _, val := range values {
assert.Equal(t, Fixed8(val*decimals), NewFixed8(val))
assert.Equal(t, int64(val), NewFixed8(val).Value())
}
}

View file

@ -65,6 +65,35 @@ func WriteVarUint(w io.Writer, val uint64) error {
return nil return nil
} }
// ReadVarBytes reads a variable length byte array.
func ReadVarBytes(r io.Reader) ([]byte, error) {
n := ReadVarUint(r)
b := make([]byte, n)
if err := binary.Read(r, binary.LittleEndian, b); err != nil {
return nil, err
}
return b, nil
}
// ReadVarString reads a variable length string.
func ReadVarString(r io.Reader) (string, error) {
b, err := ReadVarBytes(r)
return string(b), err
}
// WriteVarString writes a variable length string.
func WriteVarString(w io.Writer, s string) error {
return WriteVarBytes(w, []byte(s))
}
// WriteVarBytes writes a variable length byte array.
func WriteVarBytes(w io.Writer, b []byte) error {
if err := WriteVarUint(w, uint64(len(b))); err != nil {
return err
}
return binary.Write(w, binary.LittleEndian, b)
}
// Read2000Uint256Hashes attempt to read 2000 Uint256 hashes from // Read2000Uint256Hashes attempt to read 2000 Uint256 hashes from
// the given byte array. // the given byte array.
func Read2000Uint256Hashes(b []byte) ([]Uint256, error) { func Read2000Uint256Hashes(b []byte) ([]Uint256, error) {

View file

@ -1,9 +1,12 @@
package util package util
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"golang.org/x/crypto/ripemd160"
) )
const uint160Size = 20 const uint160Size = 20
@ -34,6 +37,17 @@ func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
return return
} }
// Uint160FromScript returns a Uint160 type from a raw script.
func Uint160FromScript(script []byte) (u Uint160, err error) {
sha := sha256.New()
sha.Write(script)
b := sha.Sum(nil)
ripemd := ripemd160.New()
ripemd.Write(b)
b = ripemd.Sum(nil)
return Uint160DecodeBytes(b)
}
// Bytes returns the byte slice representation of u. // Bytes returns the byte slice representation of u.
func (u Uint160) Bytes() []byte { func (u Uint160) Bytes() []byte {
b := make([]byte, uint160Size) b := make([]byte, uint160Size)
@ -43,6 +57,11 @@ func (u Uint160) Bytes() []byte {
return b return b
} }
// BytesReverse return a reversed byte representation of u.
func (u Uint160) BytesReverse() []byte {
return ArrayReverse(u.Bytes())
}
// 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())

View file

@ -1 +1,50 @@
package util package util
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUInt160DecodeString(t *testing.T) {
hexStr := "2d3b96ae1bcc5a585e075e3b81920210dec16302"
val, err := Uint160DecodeString(hexStr)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, hexStr, val.String())
}
func TestUint160DecodeBytes(t *testing.T) {
hexStr := "2d3b96ae1bcc5a585e075e3b81920210dec16302"
b, err := hex.DecodeString(hexStr)
if err != nil {
t.Fatal(err)
}
val, err := Uint160DecodeBytes(b)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, hexStr, val.String())
}
func TestUInt160Equals(t *testing.T) {
a := "2d3b96ae1bcc5a585e075e3b81920210dec16302"
b := "4d3b96ae1bcc5a585e075e3b81920210dec16302"
ua, err := Uint160DecodeString(a)
if err != nil {
t.Fatal(err)
}
ub, err := Uint160DecodeString(b)
if err != nil {
t.Fatal(err)
}
if ua.Equals(ub) {
t.Fatalf("%s and %s cannot be equal", ua, ub)
}
if !ua.Equals(ua) {
t.Fatalf("%s and %s must be equal", ua, ua)
}
}