Persist transactions (#51)
* added account_state + changed ECPoint to PublicKey * account state persist * in depth test for existing accounts. * implemented GetTransaction. * added enrollment TX * added persist of accounts and unspent coins * bumped version -> 0.32.0
This commit is contained in:
parent
a67728628e
commit
52fa41a12a
26 changed files with 930 additions and 66 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.31.0
|
||||
0.32.0
|
||||
|
|
137
pkg/core/account_state.go
Normal file
137
pkg/core/account_state.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Accounts is mapping between a account address and AccountState.
|
||||
type Accounts map[util.Uint160]*AccountState
|
||||
|
||||
func (a Accounts) getAndChange(s storage.Store, hash util.Uint160) (*AccountState, error) {
|
||||
if account, ok := a[hash]; ok {
|
||||
return account, nil
|
||||
}
|
||||
|
||||
var 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
|
||||
}
|
||||
} else {
|
||||
account = NewAccountState(hash)
|
||||
}
|
||||
|
||||
a[hash] = account
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// commit writes all account states to the given Batch.
|
||||
func (a Accounts) commit(b storage.Batch) error {
|
||||
buf := new(bytes.Buffer)
|
||||
for hash, state := range a {
|
||||
if err := state.EncodeBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
key := storage.AppendPrefix(storage.STAccount, hash.Bytes())
|
||||
b.Put(key, buf.Bytes())
|
||||
buf.Reset()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AccountState represents the state of a NEO account.
|
||||
type AccountState struct {
|
||||
ScriptHash util.Uint160
|
||||
IsFrozen bool
|
||||
Votes []*crypto.PublicKey
|
||||
Balances map[util.Uint256]util.Fixed8
|
||||
}
|
||||
|
||||
// NewAccountState returns a new AccountState object.
|
||||
func NewAccountState(scriptHash util.Uint160) *AccountState {
|
||||
return &AccountState{
|
||||
ScriptHash: scriptHash,
|
||||
IsFrozen: false,
|
||||
Votes: []*crypto.PublicKey{},
|
||||
Balances: make(map[util.Uint256]util.Fixed8),
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeBinary decodes AccountState from the given io.Reader.
|
||||
func (s *AccountState) DecodeBinary(r io.Reader) error {
|
||||
if err := binary.Read(r, binary.LittleEndian, &s.ScriptHash); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Read(r, binary.LittleEndian, &s.IsFrozen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lenVotes := util.ReadVarUint(r)
|
||||
s.Votes = make([]*crypto.PublicKey, lenVotes)
|
||||
for i := 0; i < int(lenVotes); i++ {
|
||||
s.Votes[i] = &crypto.PublicKey{}
|
||||
if err := s.Votes[i].DecodeBinary(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.Balances = make(map[util.Uint256]util.Fixed8)
|
||||
lenBalances := util.ReadVarUint(r)
|
||||
for i := 0; i < int(lenBalances); i++ {
|
||||
key := util.Uint256{}
|
||||
if err := binary.Read(r, binary.LittleEndian, &key); err != nil {
|
||||
return err
|
||||
}
|
||||
var val util.Fixed8
|
||||
if err := binary.Read(r, binary.LittleEndian, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Balances[key] = val
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary encode AccountState to the given io.Writer.
|
||||
func (s *AccountState) EncodeBinary(w io.Writer) error {
|
||||
if err := binary.Write(w, binary.LittleEndian, s.ScriptHash); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(w, binary.LittleEndian, s.IsFrozen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range s.Balances {
|
||||
if v > 0 {
|
||||
if err := binary.Write(w, binary.LittleEndian, k); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
47
pkg/core/account_state_test.go
Normal file
47
pkg/core/account_state_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeEncodeAccountState(t *testing.T) {
|
||||
var (
|
||||
n = 10
|
||||
balances = make(map[util.Uint256]util.Fixed8)
|
||||
votes = make([]*crypto.PublicKey, n)
|
||||
)
|
||||
for i := 0; i < n; i++ {
|
||||
balances[util.RandomUint256()] = util.Fixed8(int64(util.RandomInt(1, 10000)))
|
||||
votes[i] = &crypto.PublicKey{crypto.RandomECPoint()}
|
||||
}
|
||||
|
||||
a := &AccountState{
|
||||
ScriptHash: util.RandomUint160(),
|
||||
IsFrozen: true,
|
||||
Votes: votes,
|
||||
Balances: balances,
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := a.EncodeBinary(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aDecode := &AccountState{}
|
||||
if err := aDecode.DecodeBinary(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, a.ScriptHash, aDecode.ScriptHash)
|
||||
assert.Equal(t, a.IsFrozen, aDecode.IsFrozen)
|
||||
|
||||
for i, vote := range a.Votes {
|
||||
assert.Equal(t, vote.X, aDecode.Votes[i].X)
|
||||
}
|
||||
assert.Equal(t, a.Balances, aDecode.Balances)
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -154,6 +155,13 @@ func (bc *Blockchain) run() {
|
|||
}
|
||||
}
|
||||
|
||||
// For now this will return a hardcoded hash of the NEO governing token.
|
||||
func (bc *Blockchain) governingToken() util.Uint256 {
|
||||
neoNativeAsset := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
|
||||
val, _ := util.Uint256DecodeString(neoNativeAsset)
|
||||
return val
|
||||
}
|
||||
|
||||
// AddBlock processes the given block and will add it to the cache so it
|
||||
// can be persisted.
|
||||
func (bc *Blockchain) AddBlock(block *Block) error {
|
||||
|
@ -246,15 +254,99 @@ func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList *
|
|||
return nil
|
||||
}
|
||||
|
||||
// TODO: persistBlock needs some more love, its implemented as in the original
|
||||
// project. This for the sake of development speed and understanding of what
|
||||
// is happening here, quite allot as you can see :). If things are wired together
|
||||
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
||||
func (bc *Blockchain) persistBlock(block *Block) error {
|
||||
batch := bc.Batch()
|
||||
var (
|
||||
batch = bc.Batch()
|
||||
unspentCoins = make(UnspentCoins)
|
||||
accounts = make(Accounts)
|
||||
)
|
||||
|
||||
storeAsBlock(batch, block, 0)
|
||||
storeAsCurrentBlock(batch, block)
|
||||
|
||||
for _, tx := range block.Transactions {
|
||||
storeAsTransaction(batch, tx, block.Index)
|
||||
|
||||
// Add CoinStateConfirmed for each tx output.
|
||||
unspent := make([]CoinState, len(tx.Outputs))
|
||||
for i := 0; i < len(tx.Outputs); i++ {
|
||||
unspent[i] = CoinStateConfirmed
|
||||
}
|
||||
unspentCoins[tx.Hash()] = &UnspentCoinState{unspent}
|
||||
|
||||
// Process TX outputs.
|
||||
for _, output := range tx.Outputs {
|
||||
account, err := accounts.getAndChange(bc.Store, output.ScriptHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := account.Balances[output.AssetID]; ok {
|
||||
account.Balances[output.AssetID] += output.Amount
|
||||
} else {
|
||||
account.Balances[output.AssetID] = output.Amount
|
||||
}
|
||||
|
||||
if output.AssetID.Equals(bc.governingToken()) && len(account.Votes) > 0 {
|
||||
log.Warnf("governing token detected in TX output need to update validators!")
|
||||
}
|
||||
}
|
||||
|
||||
// Process TX inputs that are grouped by previous hash.
|
||||
for prevHash, inputs := range tx.GroupInputsByPrevHash() {
|
||||
prevTX, _, err := bc.GetTransaction(prevHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, input := range inputs {
|
||||
unspent, err := unspentCoins.getAndChange(bc.Store, input.PrevHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unspent.states[input.PrevIndex] = CoinStateSpent
|
||||
|
||||
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
||||
account, err := accounts.getAndChange(bc.Store, prevTXOutput.ScriptHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if prevTXOutput.AssetID.Equals(bc.governingToken()) {
|
||||
log.Warnf("governing token detected in TX input need to update validators!")
|
||||
}
|
||||
|
||||
account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount
|
||||
}
|
||||
}
|
||||
|
||||
// Process the underlying type of the TX.
|
||||
switch tx.Data.(type) {
|
||||
case *transaction.RegisterTX:
|
||||
case *transaction.IssueTX:
|
||||
case *transaction.ClaimTX:
|
||||
case *transaction.EnrollmentTX:
|
||||
case *transaction.StateTX:
|
||||
case *transaction.PublishTX:
|
||||
case *transaction.InvocationTX:
|
||||
log.Warn("invocation TX but we have no VM, o noo :(")
|
||||
}
|
||||
}
|
||||
|
||||
// Persist all to storage.
|
||||
if err := accounts.commit(batch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := unspentCoins.commit(batch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bc.PutBatch(batch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.AddUint32(&bc.blockHeight, 1)
|
||||
return nil
|
||||
}
|
||||
|
@ -302,6 +394,27 @@ func (bc *Blockchain) headerListLen() (n int) {
|
|||
return
|
||||
}
|
||||
|
||||
// GetTransaction returns a TX and its height by the given hash.
|
||||
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesReverse())
|
||||
b, err := bc.Get(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
r := bytes.NewReader(b)
|
||||
|
||||
var height uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &height); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
tx := &transaction.Transaction{}
|
||||
if err := tx.DecodeBinary(r); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return tx, height, nil
|
||||
}
|
||||
|
||||
// GetBlock returns a Block by the given hash.
|
||||
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
||||
key := storage.AppendPrefix(storage.DataBlock, hash.BytesReverse())
|
||||
|
|
|
@ -122,6 +122,21 @@ func TestHasBlock(t *testing.T) {
|
|||
assert.False(t, bc.HasBlock(newBlock.Hash()))
|
||||
}
|
||||
|
||||
func TestGetTransaction(t *testing.T) {
|
||||
block := getDecodedBlock(t, 1)
|
||||
bc := newTestChain(t)
|
||||
|
||||
assert.Nil(t, bc.AddBlock(block))
|
||||
assert.Nil(t, bc.persistBlock(block))
|
||||
|
||||
tx, height, err := bc.GetTransaction(block.Transactions[0].Hash())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, block.Index, height)
|
||||
assert.Equal(t, block.Transactions[0], tx)
|
||||
}
|
||||
|
||||
func newTestChain(t *testing.T) *Blockchain {
|
||||
startHash, _ := util.Uint256DecodeString("a")
|
||||
chain, err := NewBlockchain(storage.NewMemoryStore(), startHash)
|
||||
|
|
12
pkg/core/coin_state.go
Normal file
12
pkg/core/coin_state.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package core
|
||||
|
||||
// CoinState represents the state of a coin.
|
||||
type CoinState uint8
|
||||
|
||||
// Viable CoinState constants.
|
||||
const (
|
||||
CoinStateConfirmed CoinState = 0
|
||||
CoinStateSpent CoinState = 1 << 1
|
||||
CoinStateClaimed CoinState = 1 << 2
|
||||
CoinStateFrozen CoinState = 1 << 5
|
||||
)
|
|
@ -51,7 +51,8 @@ func (s *LevelDBStore) Seek(key []byte, f func(k, v []byte)) {
|
|||
iter.Release()
|
||||
}
|
||||
|
||||
// Batch implements the Batch interface and returns a compatible Batch.
|
||||
// Batch implements the Batch interface and returns a leveldb
|
||||
// compatible Batch.
|
||||
func (s *LevelDBStore) Batch() Batch {
|
||||
return new(leveldb.Batch)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// MemoryStore is an in-memory implementation of a Store, mainly
|
||||
// used for testing. Do not use MemoryStore in production.
|
||||
type MemoryStore struct {
|
||||
|
@ -31,7 +35,7 @@ func NewMemoryStore() *MemoryStore {
|
|||
|
||||
// Get implements the Store interface.
|
||||
func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
||||
if val, ok := s.mem[string(key)]; ok {
|
||||
if val, ok := s.mem[makeKey(key)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return nil, ErrKeyNotFound
|
||||
|
@ -39,7 +43,7 @@ func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
|||
|
||||
// Put implementes the Store interface.
|
||||
func (s *MemoryStore) Put(key, value []byte) error {
|
||||
s.mem[string(key)] = value
|
||||
s.mem[makeKey(key)] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -62,3 +66,7 @@ func (s *MemoryStore) Batch() Batch {
|
|||
m: make(map[*[]byte][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
func makeKey(k []byte) string {
|
||||
return hex.EncodeToString(k)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,17 @@ func TestGetPut(t *testing.T) {
|
|||
assert.Equal(t, value, newVal)
|
||||
}
|
||||
|
||||
func TestKeyNotExist(t *testing.T) {
|
||||
var (
|
||||
s = NewMemoryStore()
|
||||
key = []byte("sparse")
|
||||
)
|
||||
|
||||
_, err := s.Get(key)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, err.Error(), "key not found")
|
||||
}
|
||||
|
||||
func TestPutBatch(t *testing.T) {
|
||||
var (
|
||||
s = NewMemoryStore()
|
||||
|
|
|
@ -11,6 +11,7 @@ const (
|
|||
DataTransaction KeyPrefix = 0x02
|
||||
STAccount KeyPrefix = 0x40
|
||||
STCoin KeyPrefix = 0x44
|
||||
STSpentCoin KeyPrefix = 0x45
|
||||
STValidator KeyPrefix = 0x48
|
||||
STAsset KeyPrefix = 0x4c
|
||||
STContract KeyPrefix = 0x50
|
||||
|
|
28
pkg/core/transaction/enrollment.go
Normal file
28
pkg/core/transaction/enrollment.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
)
|
||||
|
||||
// A Enrollment transaction represents an enrollment form, which indicates
|
||||
// that the sponsor of the transaction would like to sign up as a validator.
|
||||
// The way to sign up is: To construct an EnrollmentTransaction type of transaction,
|
||||
// and send a deposit to the address of the PublicKey.
|
||||
// The way to cancel the registration is: Spend the deposit on the address of the PublicKey.
|
||||
type EnrollmentTX struct {
|
||||
// PublicKey of the validator
|
||||
PublicKey *crypto.PublicKey
|
||||
}
|
||||
|
||||
// DecodeBinary implements the Payload interface.
|
||||
func (tx *EnrollmentTX) DecodeBinary(r io.Reader) error {
|
||||
tx.PublicKey = &crypto.PublicKey{}
|
||||
return tx.PublicKey.DecodeBinary(r)
|
||||
}
|
||||
|
||||
// EncodeBinary implements the Payload interface.
|
||||
func (tx *EnrollmentTX) EncodeBinary(w io.Writer) error {
|
||||
return tx.PublicKey.EncodeBinary(w)
|
||||
}
|
6
pkg/core/transaction/enrollment_test.go
Normal file
6
pkg/core/transaction/enrollment_test.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package transaction
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDecodeEncode(t *testing.T) {
|
||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/anthdm/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Input represents a Transaction input.
|
||||
|
|
30
pkg/core/transaction/publish.go
Normal file
30
pkg/core/transaction/publish.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
)
|
||||
|
||||
// PublishTX represents a publish transaction.
|
||||
// This is deprecated and should no longer be used.
|
||||
type PublishTX struct {
|
||||
Script []byte
|
||||
ParamList []smartcontract.ParamType
|
||||
ReturnType smartcontract.ParamType
|
||||
NeedStorage bool
|
||||
Name string
|
||||
Author string
|
||||
Email string
|
||||
Description string
|
||||
}
|
||||
|
||||
// DecodeBinary implements the Payload interface.
|
||||
func (tx *PublishTX) DecodeBinary(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary implements the Payload interface.
|
||||
func (tx *PublishTX) EncodeBinary(w io.Writer) error {
|
||||
return nil
|
||||
}
|
|
@ -29,7 +29,7 @@ import (
|
|||
// # 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.
|
||||
type RegisterTX struct {
|
||||
// The type of the asset being registered.
|
||||
AssetType AssetType
|
||||
|
@ -44,7 +44,9 @@ type RegisterTX struct {
|
|||
// Decimals
|
||||
Precision uint8
|
||||
|
||||
Owner crypto.EllipticCurvePoint
|
||||
// Public key of the owner
|
||||
Owner *crypto.PublicKey
|
||||
|
||||
Admin util.Uint160
|
||||
}
|
||||
|
||||
|
@ -65,11 +67,10 @@ func (tx *RegisterTX) DecodeBinary(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
point, err := crypto.NewEllipticCurvePointFromReader(r)
|
||||
if err != nil {
|
||||
tx.Owner = &crypto.PublicKey{}
|
||||
if err := tx.Owner.DecodeBinary(r); err != nil {
|
||||
return err
|
||||
}
|
||||
tx.Owner = point
|
||||
|
||||
return binary.Read(r, binary.LittleEndian, &tx.Admin)
|
||||
}
|
||||
|
|
29
pkg/core/transaction/state.go
Normal file
29
pkg/core/transaction/state.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// StateTX represents a state transaction.
|
||||
type StateTX struct {
|
||||
Descriptors []*StateDescriptor
|
||||
}
|
||||
|
||||
// DecodeBinary implements the Payload interface.
|
||||
func (tx *StateTX) DecodeBinary(r io.Reader) error {
|
||||
lenDesc := util.ReadVarUint(r)
|
||||
for i := 0; i < int(lenDesc); i++ {
|
||||
tx.Descriptors[i] = &StateDescriptor{}
|
||||
if err := tx.Descriptors[i].DecodeBinary(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary implements the Payload interface.
|
||||
func (tx *StateTX) EncodeBinary(w io.Writer) error {
|
||||
return nil
|
||||
}
|
58
pkg/core/transaction/state_descriptor.go
Normal file
58
pkg/core/transaction/state_descriptor.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// DescStateType represents the type of StateDescriptor.
|
||||
type DescStateType uint8
|
||||
|
||||
// Valid DescStateType constants.
|
||||
const (
|
||||
Account DescStateType = 0x40
|
||||
Validator DescStateType = 0x48
|
||||
)
|
||||
|
||||
// StateDescriptor ..
|
||||
type StateDescriptor struct {
|
||||
Type DescStateType
|
||||
Key []byte
|
||||
Value []byte
|
||||
Field string
|
||||
}
|
||||
|
||||
// DecodeBinary implements the Payload interface.
|
||||
func (s *StateDescriptor) DecodeBinary(r io.Reader) error {
|
||||
if err := binary.Read(r, binary.LittleEndian, &s.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyLen := util.ReadVarUint(r)
|
||||
s.Key = make([]byte, keyLen)
|
||||
if err := binary.Read(r, binary.LittleEndian, s.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valLen := util.ReadVarUint(r)
|
||||
s.Value = make([]byte, valLen)
|
||||
if err := binary.Read(r, binary.LittleEndian, s.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldLen := util.ReadVarUint(r)
|
||||
field := make([]byte, fieldLen)
|
||||
if err := binary.Read(r, binary.LittleEndian, field); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Field = string(field)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary implements the Payload interface.
|
||||
func (s *StateDescriptor) EncodeBinary(w io.Writer) error {
|
||||
return nil
|
||||
}
|
|
@ -147,6 +147,10 @@ func (t *Transaction) decodeData(r io.Reader) error {
|
|||
case IssueType:
|
||||
t.Data = &IssueTX{}
|
||||
return t.Data.(*IssueTX).DecodeBinary(r)
|
||||
case EnrollmentType:
|
||||
t.Data = &EnrollmentTX{}
|
||||
return t.Data.(*EnrollmentTX).DecodeBinary(r)
|
||||
|
||||
default:
|
||||
log.Warnf("invalid TX type %s", t.Type)
|
||||
}
|
||||
|
@ -180,8 +184,10 @@ func (t *Transaction) encodeHashableFields(w io.Writer) error {
|
|||
}
|
||||
|
||||
// Underlying TXer.
|
||||
if err := t.Data.EncodeBinary(w); err != nil {
|
||||
return err
|
||||
if t.Data != nil {
|
||||
if err := t.Data.EncodeBinary(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes
|
||||
|
@ -230,3 +236,12 @@ func (t *Transaction) createHash() (hash util.Uint256, err error) {
|
|||
b = sha.Sum(nil)
|
||||
return util.Uint256DecodeBytes(util.ArrayReverse(b))
|
||||
}
|
||||
|
||||
// GroupTXInputsByPrevHash groups all TX inputs by their previous hash.
|
||||
func (t *Transaction) GroupInputsByPrevHash() map[util.Uint256][]*Input {
|
||||
m := make(map[util.Uint256][]*Input)
|
||||
for _, in := range t.Inputs {
|
||||
m[in.PrevHash] = append(m[in.PrevHash], in)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
81
pkg/core/unspent_coin_state.go
Normal file
81
pkg/core/unspent_coin_state.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// UnspentCoins is mapping between transactions and their unspent
|
||||
// coin state.
|
||||
type UnspentCoins map[util.Uint256]*UnspentCoinState
|
||||
|
||||
func (u UnspentCoins) getAndChange(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
|
||||
if unspent, ok := u[hash]; ok {
|
||||
return unspent, nil
|
||||
}
|
||||
|
||||
var 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
|
||||
}
|
||||
} else {
|
||||
unspent = &UnspentCoinState{
|
||||
states: []CoinState{},
|
||||
}
|
||||
}
|
||||
|
||||
u[hash] = unspent
|
||||
return unspent, nil
|
||||
}
|
||||
|
||||
// UnspentCoinState hold the state of a unspent coin.
|
||||
type UnspentCoinState struct {
|
||||
states []CoinState
|
||||
}
|
||||
|
||||
// commit writes all unspent coin states to the given Batch.
|
||||
func (s UnspentCoins) commit(b storage.Batch) error {
|
||||
buf := new(bytes.Buffer)
|
||||
for hash, state := range s {
|
||||
if err := state.EncodeBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
key := storage.AppendPrefix(storage.STCoin, hash.BytesReverse())
|
||||
b.Put(key, buf.Bytes())
|
||||
buf.Reset()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary encodes UnspentCoinState to the given io.Writer.
|
||||
func (s *UnspentCoinState) EncodeBinary(w io.Writer) error {
|
||||
if err := util.WriteVarUint(w, uint64(len(s.states))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, state := range s.states {
|
||||
if err := binary.Write(w, binary.LittleEndian, byte(state)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodBinary decodes UnspentCoinState from the given io.Reader.
|
||||
func (s *UnspentCoinState) DecodeBinary(r io.Reader) error {
|
||||
lenStates := util.ReadVarUint(r)
|
||||
s.states = make([]CoinState, lenStates)
|
||||
for i := 0; i < int(lenStates); i++ {
|
||||
var state uint8
|
||||
if err := binary.Read(r, binary.LittleEndian, &state); err != nil {
|
||||
return err
|
||||
}
|
||||
s.states[i] = CoinState(state)
|
||||
}
|
||||
return nil
|
||||
}
|
59
pkg/core/unspent_coint_state_test.go
Normal file
59
pkg/core/unspent_coint_state_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeEncode(t *testing.T) {
|
||||
unspent := &UnspentCoinState{
|
||||
states: []CoinState{
|
||||
CoinStateConfirmed,
|
||||
CoinStateSpent,
|
||||
CoinStateSpent,
|
||||
CoinStateSpent,
|
||||
CoinStateConfirmed,
|
||||
},
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
assert.Nil(t, unspent.EncodeBinary(buf))
|
||||
unspentDecode := &UnspentCoinState{}
|
||||
assert.Nil(t, unspentDecode.DecodeBinary(buf))
|
||||
}
|
||||
|
||||
func TestCommit(t *testing.T) {
|
||||
var (
|
||||
store = storage.NewMemoryStore()
|
||||
batch = store.Batch()
|
||||
unspentCoins = make(UnspentCoins)
|
||||
)
|
||||
|
||||
txA := util.RandomUint256()
|
||||
txB := util.RandomUint256()
|
||||
txC := util.RandomUint256()
|
||||
|
||||
unspentCoins[txA] = &UnspentCoinState{
|
||||
states: []CoinState{CoinStateConfirmed},
|
||||
}
|
||||
unspentCoins[txB] = &UnspentCoinState{
|
||||
states: []CoinState{
|
||||
CoinStateConfirmed,
|
||||
CoinStateConfirmed,
|
||||
},
|
||||
}
|
||||
unspentCoins[txC] = &UnspentCoinState{
|
||||
states: []CoinState{
|
||||
CoinStateConfirmed,
|
||||
CoinStateConfirmed,
|
||||
CoinStateConfirmed,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Nil(t, unspentCoins.commit(batch))
|
||||
assert.Nil(t, store.PutBatch(batch))
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
|
@ -65,6 +66,22 @@ func storeAsBlock(batch storage.Batch, block *Block, sysFee uint32) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// storeAsTransaction stores the given TX as DataTransaction.
|
||||
func storeAsTransaction(batch storage.Batch, tx *transaction.Transaction, index uint32) error {
|
||||
key := storage.AppendPrefix(storage.DataTransaction, tx.Hash().BytesReverse())
|
||||
buf := new(bytes.Buffer)
|
||||
if err := tx.EncodeBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest := make([]byte, buf.Len()+4)
|
||||
binary.LittleEndian.PutUint32(dest[:4], index)
|
||||
copy(dest[4:], buf.Bytes())
|
||||
batch.Put(key, dest)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readStoredHeaderHashes returns a sorted list of header hashes
|
||||
// retrieved from the given Store.
|
||||
func readStoredHeaderHashes(store storage.Store) ([]util.Uint256, error) {
|
||||
|
|
16
pkg/core/validator_state.go
Normal file
16
pkg/core/validator_state.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Validators is a mapping between public keys and ValidatorState.
|
||||
type Validators map[*crypto.PublicKey]*ValidatorState
|
||||
|
||||
// ValidatorState holds the state of a validator.
|
||||
type ValidatorState struct {
|
||||
PublicKey *crypto.PublicKey
|
||||
Registered bool
|
||||
Votes util.Fixed8
|
||||
}
|
|
@ -4,6 +4,8 @@ package crypto
|
|||
// Expanded and tweaked upon here under MIT license.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
@ -21,51 +23,18 @@ type (
|
|||
A *big.Int
|
||||
B *big.Int
|
||||
P *big.Int
|
||||
G EllipticCurvePoint
|
||||
G ECPoint
|
||||
N *big.Int
|
||||
H *big.Int
|
||||
}
|
||||
|
||||
// EllipticCurveEllipticCurvePoint represents a point on the EllipticCurve.
|
||||
EllipticCurvePoint struct {
|
||||
// ECPoint represents a point on the EllipticCurve.
|
||||
ECPoint struct {
|
||||
X *big.Int
|
||||
Y *big.Int
|
||||
}
|
||||
)
|
||||
|
||||
// NewEllipticCurvePointFromReader return a new point from the given reader.
|
||||
// f == 4, 6 or 7 are not implemented.
|
||||
func NewEllipticCurvePointFromReader(r io.Reader) (point EllipticCurvePoint, err error) {
|
||||
var f uint8
|
||||
if err = binary.Read(r, binary.LittleEndian, &f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Infinity
|
||||
if f == 0 {
|
||||
return EllipticCurvePoint{
|
||||
X: new(big.Int),
|
||||
Y: new(big.Int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if f == 2 || f == 3 {
|
||||
y := new(big.Int).SetBytes([]byte{f & 1})
|
||||
data := make([]byte, 32)
|
||||
if err = binary.Read(r, binary.LittleEndian, data); err != nil {
|
||||
return
|
||||
}
|
||||
data = util.ArrayReverse(data)
|
||||
data = append(data, byte(0x00))
|
||||
|
||||
return EllipticCurvePoint{
|
||||
X: new(big.Int).SetBytes(data),
|
||||
Y: y,
|
||||
}, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewEllipticCurve returns a ready to use EllipticCurve with preconfigured
|
||||
// fields for the NEO protocol.
|
||||
func NewEllipticCurve() EllipticCurve {
|
||||
|
@ -94,9 +63,84 @@ func NewEllipticCurve() EllipticCurve {
|
|||
return c
|
||||
}
|
||||
|
||||
func (p *EllipticCurvePoint) format() string {
|
||||
if p.X == nil && p.Y == nil {
|
||||
return "(inf,inf)"
|
||||
// RandomECPoint returns a random generated ECPoint, mostly used
|
||||
// for testing.
|
||||
func RandomECPoint() ECPoint {
|
||||
c := NewEllipticCurve()
|
||||
b := make([]byte, c.N.BitLen()/8+8)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return ECPoint{}
|
||||
}
|
||||
|
||||
d := new(big.Int).SetBytes(b)
|
||||
d.Mod(d, new(big.Int).Sub(c.N, big.NewInt(1)))
|
||||
d.Add(d, big.NewInt(1))
|
||||
|
||||
q := new(big.Int).SetBytes(d.Bytes())
|
||||
return c.ScalarBaseMult(q)
|
||||
}
|
||||
|
||||
// ECPointFromReader return a new point from the given reader.
|
||||
// f == 4, 6 or 7 are not implemented.
|
||||
func ECPointFromReader(r io.Reader) (point ECPoint, err error) {
|
||||
var f uint8
|
||||
if err = binary.Read(r, binary.LittleEndian, &f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(f)
|
||||
|
||||
// Infinity
|
||||
if f == 0 {
|
||||
return ECPoint{
|
||||
X: new(big.Int),
|
||||
Y: new(big.Int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if f == 2 || f == 3 {
|
||||
y := new(big.Int).SetBytes([]byte{f & 1})
|
||||
data := make([]byte, 32)
|
||||
if err = binary.Read(r, binary.LittleEndian, data); err != nil {
|
||||
return
|
||||
}
|
||||
data = util.ArrayReverse(data)
|
||||
data = append(data, byte(0x00))
|
||||
|
||||
return ECPoint{
|
||||
X: new(big.Int).SetBytes(data),
|
||||
Y: y,
|
||||
}, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeBinary encodes the point to the given io.Writer.
|
||||
func (p ECPoint) EncodeBinary(w io.Writer) error {
|
||||
bx := p.X.Bytes()
|
||||
padded := append(
|
||||
bytes.Repeat(
|
||||
[]byte{0x00},
|
||||
32-len(bx),
|
||||
),
|
||||
bx...,
|
||||
)
|
||||
|
||||
prefix := byte(0x03)
|
||||
if p.Y.Bit(0) == 0 {
|
||||
prefix = byte(0x02)
|
||||
}
|
||||
buf := make([]byte, len(padded)+1)
|
||||
buf[0] = prefix
|
||||
copy(buf[1:], padded)
|
||||
|
||||
return binary.Write(w, binary.LittleEndian, buf)
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (p *ECPoint) String() string {
|
||||
if p.IsInfinity() {
|
||||
return "(inf, inf)"
|
||||
}
|
||||
bx := hex.EncodeToString(p.X.Bytes())
|
||||
by := hex.EncodeToString(p.Y.Bytes())
|
||||
|
@ -104,15 +148,17 @@ func (p *EllipticCurvePoint) format() string {
|
|||
}
|
||||
|
||||
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
||||
func (c *EllipticCurve) IsInfinity(P EllipticCurvePoint) bool {
|
||||
if P.X == nil && P.Y == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
func (p *ECPoint) IsInfinity() bool {
|
||||
return p.X == nil && p.Y == nil
|
||||
}
|
||||
|
||||
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
||||
func (c *EllipticCurve) IsInfinity(P ECPoint) bool {
|
||||
return P.X == nil && P.Y == nil
|
||||
}
|
||||
|
||||
// IsOnCurve checks if point P is on EllipticCurve ec.
|
||||
func (c *EllipticCurve) IsOnCurve(P EllipticCurvePoint) bool {
|
||||
func (c *EllipticCurve) IsOnCurve(P ECPoint) bool {
|
||||
if c.IsInfinity(P) {
|
||||
return false
|
||||
}
|
||||
|
@ -130,7 +176,7 @@ func (c *EllipticCurve) IsOnCurve(P EllipticCurvePoint) bool {
|
|||
}
|
||||
|
||||
// Add computes R = P + Q on EllipticCurve ec.
|
||||
func (c *EllipticCurve) Add(P, Q EllipticCurvePoint) (R EllipticCurvePoint) {
|
||||
func (c *EllipticCurve) Add(P, Q ECPoint) (R ECPoint) {
|
||||
// See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf
|
||||
if c.IsInfinity(P) && c.IsInfinity(Q) {
|
||||
R.X = nil
|
||||
|
@ -172,18 +218,18 @@ func (c *EllipticCurve) Add(P, Q EllipticCurvePoint) (R EllipticCurvePoint) {
|
|||
subMod(P.X, R.X, c.P), c.P),
|
||||
P.Y, c.P)
|
||||
} else {
|
||||
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P.format(), Q.format()))
|
||||
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P, Q))
|
||||
}
|
||||
|
||||
return R
|
||||
}
|
||||
|
||||
// ScalarMult computes Q = k * P on EllipticCurve ec.
|
||||
func (c *EllipticCurve) ScalarMult(k *big.Int, P EllipticCurvePoint) (Q EllipticCurvePoint) {
|
||||
func (c *EllipticCurve) ScalarMult(k *big.Int, P ECPoint) (Q ECPoint) {
|
||||
// Implementation based on pseudocode here:
|
||||
// https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
|
||||
var R0 EllipticCurvePoint
|
||||
var R1 EllipticCurvePoint
|
||||
var R0 ECPoint
|
||||
var R1 ECPoint
|
||||
|
||||
R0.X = nil
|
||||
R0.Y = nil
|
||||
|
@ -203,12 +249,12 @@ func (c *EllipticCurve) ScalarMult(k *big.Int, P EllipticCurvePoint) (Q Elliptic
|
|||
}
|
||||
|
||||
// ScalarBaseMult computes Q = k * G on EllipticCurve ec.
|
||||
func (c *EllipticCurve) ScalarBaseMult(k *big.Int) (Q EllipticCurvePoint) {
|
||||
func (c *EllipticCurve) ScalarBaseMult(k *big.Int) (Q ECPoint) {
|
||||
return c.ScalarMult(k, c.G)
|
||||
}
|
||||
|
||||
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a EllipticCurvePoint P on EllipticCurve ec.
|
||||
func (c *EllipticCurve) Decompress(x *big.Int, ylsb uint) (P EllipticCurvePoint, err error) {
|
||||
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a ECPoint P on EllipticCurve ec.
|
||||
func (c *EllipticCurve) Decompress(x *big.Int, ylsb uint) (P ECPoint, err error) {
|
||||
/* y**2 = x**3 + a*x + b % p */
|
||||
rhs := addMod(
|
||||
addMod(
|
||||
|
|
68
pkg/crypto/public_key.go
Normal file
68
pkg/crypto/public_key.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// PublicKey represents a public key.
|
||||
type PublicKey struct {
|
||||
ECPoint
|
||||
}
|
||||
|
||||
// Bytes returns the byte array representation of the public key.
|
||||
func (p *PublicKey) Bytes() []byte {
|
||||
var (
|
||||
x = p.X.Bytes()
|
||||
paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...)
|
||||
prefix = byte(0x03)
|
||||
)
|
||||
|
||||
if p.Y.Bit(0) == 0 {
|
||||
prefix = byte(0x02)
|
||||
}
|
||||
|
||||
return append([]byte{prefix}, paddedX...)
|
||||
}
|
||||
|
||||
// DecodeBinary decodes a PublicKey from the given io.Reader.
|
||||
func (p *PublicKey) DecodeBinary(r io.Reader) error {
|
||||
var prefix uint8
|
||||
if err := binary.Read(r, binary.LittleEndian, &prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compressed public keys.
|
||||
if prefix == 0x02 || prefix == 0x03 {
|
||||
c := NewEllipticCurve()
|
||||
b := make([]byte, 32)
|
||||
if err := binary.Read(r, binary.LittleEndian, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
p.ECPoint, err = c.Decompress(new(big.Int).SetBytes(b), uint(prefix&0x1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if prefix == 0x04 {
|
||||
buf := make([]byte, 65)
|
||||
if err := binary.Read(r, binary.LittleEndian, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
p.X = new(big.Int).SetBytes(buf[1:33])
|
||||
p.Y = new(big.Int).SetBytes(buf[33:65])
|
||||
} else {
|
||||
return fmt.Errorf("invalid prefix %d", prefix)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary encodes a PublicKey to the given io.Writer.
|
||||
func (p *PublicKey) EncodeBinary(w io.Writer) error {
|
||||
return binary.Write(w, binary.LittleEndian, p.Bytes())
|
||||
}
|
20
pkg/crypto/public_key_test.go
Normal file
20
pkg/crypto/public_key_test.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeDecodePublicKey(t *testing.T) {
|
||||
for i := 0; i < 4; i++ {
|
||||
p := &PublicKey{RandomECPoint()}
|
||||
buf := new(bytes.Buffer)
|
||||
assert.Nil(t, p.EncodeBinary(buf))
|
||||
|
||||
pDecode := &PublicKey{}
|
||||
assert.Nil(t, pDecode.DecodeBinary(buf))
|
||||
assert.Equal(t, p.X, pDecode.X)
|
||||
}
|
||||
}
|
45
pkg/util/test_util.go
Normal file
45
pkg/util/test_util.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
// RandomString returns a random string with the n as its length.
|
||||
func RandomString(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = byte(RandomInt(65, 90))
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// RamdomInt returns a ramdom integer betweeen min and max.
|
||||
func RandomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// RandomUint256 returns a random Uint256.
|
||||
func RandomUint256() Uint256 {
|
||||
str := RandomString(20)
|
||||
h := sha256.Sum256([]byte(str))
|
||||
return Uint256(h)
|
||||
}
|
||||
|
||||
// RandomUint160 returns a random Uint160.
|
||||
func RandomUint160() Uint160 {
|
||||
str := RandomString(20)
|
||||
ripemd := ripemd160.New()
|
||||
ripemd.Write([]byte(str))
|
||||
h := ripemd.Sum(nil)
|
||||
v, _ := Uint160DecodeBytes(h)
|
||||
return v
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
Loading…
Reference in a new issue