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/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 {
|
||||
|
|
|
@ -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")
|
|
@ -27,6 +27,6 @@ ApplicationConfiguration:
|
|||
NodePort: 20333
|
||||
Relay: true
|
||||
DialTimeout: 3
|
||||
ProtoTickInterval: 10
|
||||
ProtoTickInterval: 2
|
||||
MaxPeers: 50
|
||||
|
||||
|
|
|
@ -23,5 +23,5 @@ ApplicationConfiguration:
|
|||
NodePort: 20333
|
||||
Relay: true
|
||||
DialTimeout: 3
|
||||
ProtoTickInterval: 10
|
||||
ProtoTickInterval: 2
|
||||
MaxPeers: 50
|
||||
|
|
|
@ -27,5 +27,5 @@ ApplicationConfiguration:
|
|||
NodePort: 20333
|
||||
Relay: true
|
||||
DialTimeout: 3
|
||||
ProtoTickInterval: 10
|
||||
ProtoTickInterval: 2
|
||||
MaxPeers: 50
|
||||
|
|
|
@ -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,19 +120,17 @@ 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 {
|
||||
for k, v := range balances {
|
||||
if err := binary.Write(w, binary.LittleEndian, k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -131,7 +138,17 @@ func (s *AccountState) EncodeBinary(w io.Writer) error {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
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
|
||||
|
||||
// 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())
|
||||
genesisBlock, err := createGenesisBlock(bc.config)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
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)
|
||||
|
||||
currBlockBytes, err := bc.Get(storage.SYSCurrentBlock.Bytes())
|
||||
if err != 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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
// MinerTX represents a miner transaction.
|
||||
type MinerTX struct {
|
||||
// Random number/identifier
|
||||
// Random number to avoid hash collision.
|
||||
Nonce uint32
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
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.
|
||||
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.
|
||||
|
|
|
@ -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{
|
||||
|
|
171
pkg/core/util.go
171
pkg/core/util.go
|
@ -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
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
|
||||
}
|
||||
|
||||
fmt.Println(f)
|
||||
|
||||
// Infinity
|
||||
if f == 0 {
|
||||
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 (
|
||||
"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()
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
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"
|
||||
)
|
||||
|
||||
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
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue