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

View file

@ -1,4 +1,4 @@
package network
package config
import (
"fmt"
@ -12,6 +12,11 @@ import (
const (
userAgentFormat = "/NEO-GO:%s/"
// Valid NetMode constants.
ModeMainNet NetMode = 0x00746e41 // 7630401
ModeTestNet NetMode = 0x74746e41 // 1953787457
ModePrivNet NetMode = 56753 // docker privnet
)
var (
@ -59,16 +64,33 @@ type (
ProtoTickInterval time.Duration `yaml:"ProtoTickInterval"`
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.
func (c Config) GenerateUserAgent() string {
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.
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)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return Config{}, errors.Wrap(err, "Unable to load config")

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package core
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"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
}
var account *AccountState
account := &AccountState{}
key := storage.AppendPrefix(storage.STAccount, hash.Bytes())
if b, err := s.Get(key); 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 {
account = NewAccountState(hash)
@ -48,6 +49,7 @@ func (a Accounts) commit(b storage.Batch) error {
// AccountState represents the state of a NEO account.
type AccountState struct {
Version uint8
ScriptHash util.Uint160
IsFrozen bool
Votes []*crypto.PublicKey
@ -57,6 +59,7 @@ type AccountState struct {
// NewAccountState returns a new AccountState object.
func NewAccountState(scriptHash util.Uint160) *AccountState {
return &AccountState{
Version: 0,
ScriptHash: scriptHash,
IsFrozen: false,
Votes: []*crypto.PublicKey{},
@ -66,6 +69,9 @@ func NewAccountState(scriptHash util.Uint160) *AccountState {
// DecodeBinary decodes AccountState from the given io.Reader.
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 {
return err
}
@ -101,6 +107,9 @@ func (s *AccountState) DecodeBinary(r io.Reader) error {
// EncodeBinary encode AccountState to the given io.Writer.
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 {
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 {
return err
}
for _, point := range s.Votes {
if err := point.EncodeBinary(w); err != nil {
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
}
for k, v := range s.Balances {
if v > 0 {
if err := binary.Write(w, binary.LittleEndian, k); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
return err
}
for k, v := range balances {
if err := binary.Write(w, binary.LittleEndian, k); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
return err
}
}
return nil
}
// 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{
Version: 0,
ScriptHash: util.RandomUint160(),
IsFrozen: true,
Votes: votes,
@ -37,6 +38,7 @@ func TestDecodeEncodeAccountState(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, a.Version, aDecode.Version)
assert.Equal(t, a.ScriptHash, aDecode.ScriptHash)
assert.Equal(t, a.IsFrozen, aDecode.IsFrozen)

View file

@ -6,6 +6,7 @@ import (
"io"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
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.
func (b *Block) Verify(full bool) bool {
// 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.
func (b *BlockBase) Hash() util.Uint256 {
if b.hash.Equals(util.Uint256{}) {
b.createHash()
}
return b.hash
}
@ -92,16 +95,19 @@ func (b *BlockBase) EncodeBinary(w io.Writer) error {
// 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) {
func (b *BlockBase) createHash() error {
buf := new(bytes.Buffer)
if err = b.encodeHashableFields(buf); err != nil {
return hash, err
if err := b.encodeHashableFields(buf); err != nil {
return err
}
// Double hash the encoded fields.
var hash util.Uint256
hash = sha256.Sum256(buf.Bytes())
hash = sha256.Sum256(hash.Bytes())
return hash, nil
b.hash = hash
return nil
}
// 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
// again.
hash, err := b.createHash()
if err != nil {
return err
}
b.hash = hash
return nil
return b.createHash()
}

View file

@ -4,10 +4,10 @@ import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util"
@ -18,15 +18,19 @@ import (
const (
secondsPerBlock = 15
headerBatchCount = 2000
version = "0.0.1"
)
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}
persistInterval = 5 * time.Second
genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
decrementInterval = 2000000
persistInterval = 5 * time.Second
)
// Blockchain holds the chain.
// Blockchain represents the blockchain.
type Blockchain struct {
config config.ProtocolConfiguration
// Any object that satisfies the BlockchainStorer interface.
storage.Store
@ -40,8 +44,6 @@ type Blockchain struct {
blockCache *Cache
startHash util.Uint256
// All operation on headerList must be called from an
// headersOp to be routine safe.
headerList *HeaderHashList
@ -58,12 +60,12 @@ type headersOpFunc func(headerList *HeaderHashList)
// 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) {
func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockchain, error) {
bc := &Blockchain{
config: cfg,
Store: s,
headersOp: make(chan headersOpFunc),
headersOpDone: make(chan struct{}),
startHash: startHash,
blockCache: NewCache(),
verifyBlocks: false,
}
@ -77,19 +79,30 @@ func NewBlockchain(s storage.Store, startHash util.Uint256) (*Blockchain, error)
}
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
genesisBlock, err := createGenesisBlock(bc.config)
if err != nil {
return err
}
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())
if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil
}
return err
}
@ -98,8 +111,9 @@ func (bc *Blockchain) init() error {
if err != nil {
return err
}
for _, hash := range hashes {
if !bc.startHash.Equals(hash) {
if !genesisBlock.Hash().Equals(hash) {
bc.headerList.Add(hash)
bc.storedHeaderCount++
}
@ -292,7 +306,7 @@ func (bc *Blockchain) persistBlock(block *Block) error {
}
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() {
prevTX, _, err := bc.GetTransaction(prevHash)
if err != nil {
return err
return fmt.Errorf("could not find previous TX: %s", prevHash)
}
for _, input := range inputs {
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()) {
log.Warnf("governing token detected in TX input need to update validators!")
// TODO
}
account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount
@ -347,7 +361,7 @@ func (bc *Blockchain) persistBlock(block *Block) error {
return err
}
atomic.AddUint32(&bc.blockHeight, 1)
atomic.StoreUint32(&bc.blockHeight, block.Index)
return nil
}
@ -366,6 +380,7 @@ func (bc *Blockchain) persist() (err error) {
hash := headerList.Get(int(bc.BlockHeight() + 1))
if block, ok := bc.blockCache.GetBlock(hash); ok {
if err = bc.persistBlock(block); err != nil {
log.Warnf("failed to persist blocks: %s", err)
return
}
bc.blockCache.Delete(hash)

View file

@ -3,8 +3,8 @@ package core
import (
"testing"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
@ -20,7 +20,6 @@ func TestAddHeaders(t *testing.T) {
assert.Equal(t, 0, bc.blockCache.Len())
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(1), bc.storedHeaderCount)
assert.Equal(t, uint32(0), bc.BlockHeight())
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, uint32(1), bc.storedHeaderCount)
assert.Equal(t, uint32(0), bc.BlockHeight())
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, lastBlock.Index, bc.HeaderHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
assert.Equal(t, uint32(1), bc.storedHeaderCount)
t.Log(bc.blockCache)
if err := bc.persist(); err != nil {
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.Hash(), bc.CurrentHeaderHash())
assert.Equal(t, 0, bc.blockCache.Len())
@ -138,8 +144,11 @@ func TestGetTransaction(t *testing.T) {
}
func newTestChain(t *testing.T) *Blockchain {
startHash, _ := util.Uint256DecodeString("a")
chain, err := NewBlockchain(storage.NewMemoryStore(), startHash)
cfg, err := config.Load("../../config", config.ModePrivNet)
if err != nil {
t.Fatal(err)
}
chain, err := NewBlockchain(storage.NewMemoryStore(), cfg.ProtocolConfiguration)
if err != nil {
t.Fatal(err)
}

View file

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

View file

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

View file

@ -1,19 +1,22 @@
package transaction
import (
"encoding/binary"
"io"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
)
// 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 {
Script []byte
ParamList []smartcontract.ParamType
ReturnType smartcontract.ParamType
NeedStorage bool
Name string
CodeVersion string
Author string
Email string
Description string
@ -21,6 +24,54 @@ type PublishTX struct {
// DecodeBinary implements the Payload interface.
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
}

View file

@ -8,34 +8,14 @@ import (
"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.
// NOTE: This is deprecated.
type RegisterTX struct {
// The type of the asset being registered.
AssetType AssetType
// Name of the asset being registered.
Name []byte
Name string
// Amount registered
// 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 {
return err
}
lenName := util.ReadVarUint(r)
tx.Name = make([]byte, lenName)
if err := binary.Read(r, binary.LittleEndian, &tx.Name); err != nil {
var err error
tx.Name, err = util.ReadVarString(r)
if 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
}
@ -77,5 +60,20 @@ func (tx *RegisterTX) DecodeBinary(r io.Reader) error {
// EncodeBinary implements the Payload interface.
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.
func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) {
t.createHash()
}
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
// to do it anymore.
hash, err := t.createHash()
if err != nil {
return err
}
t.hash = hash
return nil
return t.createHash()
}
func (t *Transaction) decodeData(r io.Reader) error {
@ -150,7 +147,12 @@ func (t *Transaction) decodeData(r io.Reader) error {
case EnrollmentType:
t.Data = &EnrollmentTX{}
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:
log.Warnf("invalid TX type %s", t.Type)
}
@ -223,18 +225,19 @@ func (t *Transaction) encodeHashableFields(w io.Writer) error {
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)
if err = t.encodeHashableFields(buf); err != nil {
return
if err := t.encodeHashableFields(buf); err != nil {
return err
}
sha := sha256.New()
sha.Write(buf.Bytes())
b := sha.Sum(nil)
sha.Reset()
sha.Write(b)
b = sha.Sum(nil)
return util.Uint256DecodeBytes(util.ArrayReverse(b))
var hash util.Uint256
hash = sha256.Sum256(buf.Bytes())
hash = sha256.Sum256(hash.Bytes())
t.hash = hash
return nil
}
// GroupTXInputsByPrevHash groups all TX inputs by their previous hash.

View file

@ -3,6 +3,7 @@ package core
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"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
}
var unspent *UnspentCoinState
unspent := &UnspentCoinState{}
key := storage.AppendPrefix(storage.STCoin, hash.BytesReverse())
if b, err := s.Get(key); 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 {
unspent = &UnspentCoinState{

View file

@ -4,29 +4,176 @@ import (
"bytes"
"encoding/binary"
"sort"
"time"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
)
// Utilities for quick bootstrapping blockchains. Normally we should
// create the genisis block. For now (to speed up development) we will add
// The hashes manually.
// Creates a genesis block based on the given configuration.
func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) {
validators, err := getValidators(cfg)
if err != nil {
return nil, err
}
func GenesisHashPrivNet() util.Uint256 {
hash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
return hash
nextConsensus, err := getNextConsensusAddress(validators)
if err != nil {
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 {
hash, _ := util.Uint256DecodeString("b3181718ef6167105b70920e4a8fbbd0a0a56aacf460d70e10ba6fa1668f1fef")
return hash
func governingTokenTX() *transaction.Transaction {
admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)})
adminB, _ := crypto.Uint160DecodeAddress("Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt")
if !admin.Equals(adminB) {
panic("kdjdkljfkdjfkdjf")
}
registerTX := &transaction.RegisterTX{
AssetType: transaction.GoverningToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",
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 {
hash, _ := util.Uint256DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf")
return hash
func utilityTokenTX() *transaction.Transaction {
admin, _ := util.Uint160FromScript([]byte{byte(vm.Opushf)})
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.

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
}
fmt.Println(f)
// Infinity
if f == 0 {
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 (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"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 {
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.
func (p *PublicKey) Bytes() []byte {
if p.IsInfinity() {
return []byte{0x00}
}
var (
x = p.X.Bytes()
paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...)
@ -35,6 +76,12 @@ func (p *PublicKey) DecodeBinary(r io.Reader) error {
return err
}
// Infinity
if prefix == 0x00 {
p.ECPoint = ECPoint{}
return nil
}
// Compressed public keys.
if prefix == 0x02 || prefix == 0x03 {
c := NewEllipticCurve()

View file

@ -2,11 +2,23 @@ package crypto
import (
"bytes"
"encoding/hex"
"testing"
"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) {
for i := 0; i < 4; i++ {
p := &PublicKey{RandomECPoint()}
@ -18,3 +30,12 @@ func TestEncodeDecodePublicKey(t *testing.T) {
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"
"io"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/network/payload"
@ -23,34 +24,10 @@ var (
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.
type Message struct {
// NetMode of the node that sends this message.
Magic NetMode
Magic config.NetMode
// Command is utf8 code, of which the length is 12 bytes,
// the extra part is filled with 0.
@ -88,7 +65,7 @@ const (
)
// 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 (
size uint32
checksum []byte

View file

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

View file

@ -1,4 +1,34 @@
package smartcontract
// Contract represents a NEO smartcontract.
type Contract struct{}
import (
"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"
)
const decimals = 100000000
// Fixed8 represents a fixed-point number with precision 10^-8.
type Fixed8 int64
@ -16,9 +18,9 @@ func (f Fixed8) String() string {
buf.WriteRune('-')
val = -val
}
str := strconv.FormatInt(val/100000000, 10)
str := strconv.FormatInt(val/decimals, 10)
buf.WriteString(str)
val %= 100000000
val %= decimals
if val > 0 {
buf.WriteRune('.')
str = strconv.FormatInt(val, 10)
@ -29,3 +31,13 @@ func (f Fixed8) String() 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
}
// 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
// the given byte array.
func Read2000Uint256Hashes(b []byte) ([]Uint256, error) {

View file

@ -1,9 +1,12 @@
package util
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"golang.org/x/crypto/ripemd160"
)
const uint160Size = 20
@ -34,6 +37,17 @@ func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
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.
func (u Uint160) Bytes() []byte {
b := make([]byte, uint160Size)
@ -43,6 +57,11 @@ func (u Uint160) Bytes() []byte {
return b
}
// BytesReverse return a reversed byte representation of u.
func (u Uint160) BytesReverse() []byte {
return ArrayReverse(u.Bytes())
}
// String implements the stringer interface.
func (u Uint160) String() string {
return hex.EncodeToString(u.Bytes())

View file

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