mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 23:42:23 +00:00
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:
parent
ad9333c74c
commit
94672cb9cc
35 changed files with 955 additions and 187 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.33.1
|
0.34.0
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
63
pkg/core/transaction/register_test.go
Normal file
63
pkg/core/transaction/register_test.go
Normal 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)
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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{
|
||||||
|
|
171
pkg/core/util.go
171
pkg/core/util.go
|
@ -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
61
pkg/core/util_test.go
Normal 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())
|
||||||
|
}
|
|
@ -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
99
pkg/crypto/merkle_tree.go
Normal 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
|
||||||
|
}
|
29
pkg/crypto/merkle_tree_test.go
Normal file
29
pkg/crypto/merkle_tree_test.go
Normal 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())
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
41
pkg/smartcontract/contract_test.go
Normal file
41
pkg/smartcontract/contract_test.go
Normal 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))
|
||||||
|
}
|
|
@ -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
16
pkg/util/fixed8_test.go
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue