From a67728628e5bfd728b4897f6ff94a7092a2f3b7b Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Sat, 17 Mar 2018 12:53:21 +0100 Subject: [PATCH] Persist blockchain with leveldb on disk (#48) * Created test_data folder with block json files for testing + create separate file for block base. * Fixed bug in WriteVarUint + Trim logic + unit tests * Refactored store and add more tests for it. * restore headerList from chain file * Fix tx decode bug + lots of housekeeping. * Implemented Node restore state from chain file. * Created standalone package for storage. Added couple more methods to Batch and Store interfaces. * Block persisting + tests * bumped version -> 0.31.0 --- Gopkg.lock | 2 +- Makefile | 2 +- VERSION | 2 +- cli/server/server.go | 30 ++- pkg/core/asset_type.go | 18 -- pkg/core/block.go | 253 ++++++------------ pkg/core/block_base.go | 164 ++++++++++++ pkg/core/block_test.go | 106 ++++---- pkg/core/blockchain.go | 153 ++++++++--- pkg/core/blockchain_test.go | 84 ++++-- pkg/core/header.go | 38 +++ pkg/core/header_hash_list.go | 11 +- pkg/core/helper_test.go | 45 ++++ pkg/core/leveldb_store.go | 41 --- pkg/core/leveldb_test.go | 31 --- pkg/core/memory_store.go | 27 -- pkg/core/raw_blocks_test.go | 7 - pkg/core/storage/README.md | 2 + pkg/core/storage/leveldb_store.go | 57 ++++ pkg/core/storage/leveldb_store_test.go | 1 + pkg/core/storage/memory_store.go | 64 +++++ pkg/core/storage/memory_store_test.go | 41 +++ pkg/core/storage/store.go | 73 +++++ pkg/core/storage/store_test.go | 57 ++++ pkg/core/store.go | 53 ---- pkg/core/test_data/block_1.json | 33 +++ pkg/core/test_data/block_2.json | 33 +++ pkg/core/transaction/asset_type.go | 16 ++ pkg/core/transaction/attribute.go | 8 +- pkg/core/transaction/issue.go | 19 ++ pkg/core/transaction/register.go | 80 ++++++ pkg/core/transaction/transaction.go | 33 ++- pkg/core/transaction/transaction_test.go | 35 ++- .../transaction/{transactioner.go => txer.go} | 0 pkg/core/transaction/type.go | 26 +- pkg/core/transaction/witness.go | 8 +- pkg/core/util.go | 81 +++++- pkg/crypto/address.go | 23 ++ pkg/crypto/address_test.go | 22 ++ pkg/crypto/elliptic_curve.go | 37 +++ pkg/network/server.go | 10 +- pkg/util/io.go | 19 +- pkg/util/io_test.go | 64 +++++ pkg/util/uint160.go | 19 -- pkg/util/uint160_test.go | 21 -- 45 files changed, 1419 insertions(+), 530 deletions(-) delete mode 100644 pkg/core/asset_type.go create mode 100644 pkg/core/block_base.go create mode 100644 pkg/core/header.go delete mode 100644 pkg/core/leveldb_store.go delete mode 100644 pkg/core/leveldb_test.go delete mode 100644 pkg/core/memory_store.go delete mode 100644 pkg/core/raw_blocks_test.go create mode 100644 pkg/core/storage/README.md create mode 100644 pkg/core/storage/leveldb_store.go create mode 100644 pkg/core/storage/leveldb_store_test.go create mode 100644 pkg/core/storage/memory_store.go create mode 100644 pkg/core/storage/memory_store_test.go create mode 100644 pkg/core/storage/store.go create mode 100644 pkg/core/storage/store_test.go delete mode 100644 pkg/core/store.go create mode 100644 pkg/core/test_data/block_1.json create mode 100644 pkg/core/test_data/block_2.json create mode 100644 pkg/core/transaction/asset_type.go create mode 100644 pkg/core/transaction/issue.go create mode 100644 pkg/core/transaction/register.go rename pkg/core/transaction/{transactioner.go => txer.go} (100%) create mode 100644 pkg/crypto/address.go create mode 100644 pkg/crypto/address_test.go create mode 100644 pkg/util/io_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 220a337ec..49ebbd2ec 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -126,6 +126,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "7e6d4161196284974f07ba9655d21ee9b4fe1478ef0e054e159df73aeec1343a" + inputs-digest = "83630d732c34b1ddf24cd34025fd9fdd982a5e5075ec93a450e7edada658c6c9" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile index 31e7a7429..11cb0b1b1 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ push-tag: git push origin ${VERSION} run: build - ./bin/neo-go node -config-path ./config -${NETMODE} + ./bin/neo-go node -config-path ./config -${NETMODE} --debug test: @go test ./... -cover diff --git a/VERSION b/VERSION index c25c8e5b7..26bea73e8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.30.0 +0.31.0 diff --git a/cli/server/server.go b/cli/server/server.go index a226e733d..b8be9eb6e 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -4,8 +4,10 @@ import ( "fmt" "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/util" + log "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -20,6 +22,7 @@ func NewCommand() cli.Command { cli.BoolFlag{Name: "privnet, p"}, cli.BoolFlag{Name: "mainnet, m"}, cli.BoolFlag{Name: "testnet, t"}, + cli.BoolFlag{Name: "debug, d"}, }, } } @@ -37,7 +40,7 @@ func startServer(ctx *cli.Context) error { configPath = ctx.String("config-path") config, err := network.LoadConfig(configPath, net) if err != nil { - return err + return cli.NewExitError(err, 1) } serverConfig := network.NewServerConfig(config) @@ -47,8 +50,12 @@ func startServer(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - s := network.NewServer(serverConfig, chain) - s.Start() + if ctx.Bool("debug") { + log.SetLevel(log.DebugLevel) + } + + fmt.Println(logo()) + network.NewServer(serverConfig, chain).Start() return nil } @@ -65,13 +72,20 @@ func newBlockchain(net network.NetMode, path string) (*core.Blockchain, error) { } // Hardcoded for now. - store, err := core.NewLevelDBStore(path, nil) + store, err := storage.NewLevelDBStore(path, nil) if err != nil { return nil, err } - return core.NewBlockchain( - store, - startHash, - ), nil + return core.NewBlockchain(store, startHash) +} + +func logo() string { + return ` + _ ____________ __________ + / | / / ____/ __ \ / ____/ __ \ + / |/ / __/ / / / /_____/ / __/ / / / + / /| / /___/ /_/ /_____/ /_/ / /_/ / +/_/ |_/_____/\____/ \____/\____/ +` } diff --git a/pkg/core/asset_type.go b/pkg/core/asset_type.go deleted file mode 100644 index 052930267..000000000 --- a/pkg/core/asset_type.go +++ /dev/null @@ -1,18 +0,0 @@ -package core - -// AssetType represent a NEO asset type -type AssetType uint8 - -// Valid asset types. -const ( - CreditFlag AssetType = 0x40 - DutyFlag AssetType = 0x80 - - GoverningToken AssetType = 0x00 - UtilityToken AssetType = 0x01 - Currency AssetType = 0x08 - - Share = DutyFlag | 0x10 - Invoice = DutyFlag | 0x18 - Token = CreditFlag | 0x20 -) diff --git a/pkg/core/block.go b/pkg/core/block.go index 843b62ce3..9be44871d 100644 --- a/pkg/core/block.go +++ b/pkg/core/block.go @@ -2,188 +2,27 @@ package core import ( "bytes" - "crypto/sha256" "encoding/binary" - "fmt" "io" - "log" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/util" + log "github.com/sirupsen/logrus" ) -// BlockBase holds the base info of a block -type BlockBase struct { - Version uint32 - // hash of the previous block. - PrevHash util.Uint256 - // Root hash of a transaction list. - MerkleRoot util.Uint256 - // The time stamp of each block must be later than previous block's time stamp. - // Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed. - // The height of the block must be exactly equal to the height of the previous block plus 1. - Timestamp uint32 - // index/height of the block - Index uint32 - // Random number also called nonce - ConsensusData uint64 - // Contract addresss of the next miner - NextConsensus util.Uint160 - // fixed to 1 - _ uint8 // padding - // Script used to validate the block - Script *transaction.Witness - - // hash of this block, created when binary encoded. - hash util.Uint256 -} - -// DecodeBinary implements the payload interface. -func (b *BlockBase) DecodeBinary(r io.Reader) error { - if err := binary.Read(r, binary.LittleEndian, &b.Version); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &b.PrevHash); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &b.MerkleRoot); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &b.Timestamp); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &b.Index); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &b.ConsensusData); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &b.NextConsensus); err != nil { - return err - } - - var padding uint8 - if err := binary.Read(r, binary.LittleEndian, &padding); err != nil { - return err - } - if padding != 1 { - return fmt.Errorf("format error: padding must equal 1 got %d", padding) - } - - b.Script = &transaction.Witness{} - if err := b.Script.DecodeBinary(r); err != nil { - return err - } - - // 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 -} - -// Hash return the hash of the block. -func (b *BlockBase) Hash() util.Uint256 { - return b.hash -} - -// createHash creates the hash of the block. -// When calculating the hash value of the block, instead of calculating the entire block, -// only first seven fields in the block head will be calculated, which are -// 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) { - buf := new(bytes.Buffer) - if err = b.encodeHashableFields(buf); err != nil { - return hash, err - } - - // Double hash the encoded fields. - hash = sha256.Sum256(buf.Bytes()) - hash = sha256.Sum256(hash.Bytes()) - return hash, nil -} - -// encodeHashableFields will only encode the fields used for hashing. -// see Hash() for more information about the fields. -func (b *BlockBase) encodeHashableFields(w io.Writer) error { - if err := binary.Write(w, binary.LittleEndian, &b.Version); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, &b.PrevHash); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, &b.MerkleRoot); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, &b.Timestamp); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, &b.Index); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, &b.ConsensusData); err != nil { - return err - } - return binary.Write(w, binary.LittleEndian, &b.NextConsensus) -} - -// EncodeBinary implements the Payload interface -func (b *BlockBase) EncodeBinary(w io.Writer) error { - if err := b.encodeHashableFields(w); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, uint8(1)); err != nil { - return err - } - return b.Script.EncodeBinary(w) -} - -// Header holds the head info of a block -type Header struct { - BlockBase - _ uint8 // padding fixed to 0 -} - -// Verify the integrity of the header -func (h *Header) Verify() bool { - return true -} - -// DecodeBinary impelements the Payload interface. -func (h *Header) DecodeBinary(r io.Reader) error { - if err := h.BlockBase.DecodeBinary(r); err != nil { - return err - } - - var padding uint8 - binary.Read(r, binary.LittleEndian, &padding) - if padding != 0 { - return fmt.Errorf("format error: padding must equal 0 got %d", padding) - } - - return nil -} - -// EncodeBinary impelements the Payload interface. -func (h *Header) EncodeBinary(w io.Writer) error { - if err := h.BlockBase.EncodeBinary(w); err != nil { - return err - } - return binary.Write(w, binary.LittleEndian, uint8(0)) -} - // Block represents one block in the chain. type Block struct { + // The base of the block. BlockBase + + // Transaction list. Transactions []*transaction.Transaction + + // True if this block is created from trimmed data. + Trimmed bool } -// Header returns a pointer to the head of the block (BlockHead). +// Header returns the Header of the Block. func (b *Block) Header() *Header { return &Header{ BlockBase: b.BlockBase, @@ -196,25 +35,82 @@ func (b *Block) Verify(full bool) bool { if b.Transactions[0].Type != transaction.MinerType { return false } - // If the first TX is a minerTX then all others cant. for _, tx := range b.Transactions[1:] { if tx.Type == transaction.MinerType { return false } } - // TODO: When full is true, do a full verification. if full { - log.Println("full verification of blocks is not yet implemented") + log.Warn("full verification of blocks is not yet implemented") } - return true } -// EncodeBinary encodes the block to the given writer. -func (b *Block) EncodeBinary(w io.Writer) error { - return nil +// NewBlockFromTrimmedBytes returns a new block from trimmed data. +// This is commonly used to create a block from stored data. +// Blocks created from trimmed data will have their Trimmed field +// set to true. +func NewBlockFromTrimmedBytes(b []byte) (*Block, error) { + block := &Block{ + Trimmed: true, + } + + r := bytes.NewReader(b) + if err := block.decodeHashableFields(r); err != nil { + return block, err + } + + var padding uint8 + if err := binary.Read(r, binary.LittleEndian, &padding); err != nil { + return block, err + } + + block.Script = &transaction.Witness{} + if err := block.Script.DecodeBinary(r); err != nil { + return block, err + } + + lenTX := util.ReadVarUint(r) + block.Transactions = make([]*transaction.Transaction, lenTX) + for i := 0; i < int(lenTX); i++ { + var hash util.Uint256 + if err := binary.Read(r, binary.LittleEndian, &hash); err != nil { + return block, err + } + block.Transactions[i] = transaction.NewTrimmedTX(hash) + } + + return block, nil +} + +// Trim returns a subset of the block data to save up space +// in storage. +// Notice that only the hashes of the transactions are stored. +func (b *Block) Trim() ([]byte, error) { + buf := new(bytes.Buffer) + if err := b.encodeHashableFields(buf); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.LittleEndian, uint8(1)); err != nil { + return nil, err + } + if err := b.Script.EncodeBinary(buf); err != nil { + return nil, err + } + + lenTX := uint64(len(b.Transactions)) + if err := util.WriteVarUint(buf, lenTX); err != nil { + return nil, err + } + for _, tx := range b.Transactions { + if err := binary.Write(buf, binary.LittleEndian, tx.Hash()); err != nil { + return nil, err + } + } + + return buf.Bytes(), nil } // DecodeBinary decodes the block from the given reader. @@ -234,3 +130,8 @@ func (b *Block) DecodeBinary(r io.Reader) error { return nil } + +// EncodeBinary encodes the block to the given writer. +func (b *Block) EncodeBinary(w io.Writer) error { + return nil +} diff --git a/pkg/core/block_base.go b/pkg/core/block_base.go new file mode 100644 index 000000000..cb9558c9b --- /dev/null +++ b/pkg/core/block_base.go @@ -0,0 +1,164 @@ +package core + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "fmt" + "io" + + "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/util" +) + +// BlockBase holds the base info of a block +type BlockBase struct { + // Version of the block. + Version uint32 + + // hash of the previous block. + PrevHash util.Uint256 + + // Root hash of a transaction list. + MerkleRoot util.Uint256 + + // The time stamp of each block must be later than previous block's time stamp. + // Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed. + // The height of the block must be exactly equal to the height of the previous block plus 1. + Timestamp uint32 + + // index/height of the block + Index uint32 + + // Random number also called nonce + ConsensusData uint64 + + // Contract addresss of the next miner + NextConsensus util.Uint160 + + // Padding that is fixed to 1 + _ uint8 + + // Script used to validate the block + Script *transaction.Witness + + // hash of this block, created when binary encoded. + hash util.Uint256 +} + +// Verify verifies the integrity of the BlockBase. +func (b *BlockBase) Verify() bool { + // TODO: Need a persisted blockchain for this. + return true +} + +// Hash return the hash of the block. +func (b *BlockBase) Hash() util.Uint256 { + return b.hash +} + +// DecodeBinary implements the payload interface. +func (b *BlockBase) DecodeBinary(r io.Reader) error { + if err := b.decodeHashableFields(r); err != nil { + return err + } + + var padding uint8 + if err := binary.Read(r, binary.LittleEndian, &padding); err != nil { + return err + } + if padding != 1 { + return fmt.Errorf("format error: padding must equal 1 got %d", padding) + } + + b.Script = &transaction.Witness{} + return b.Script.DecodeBinary(r) +} + +// EncodeBinary implements the Payload interface +func (b *BlockBase) EncodeBinary(w io.Writer) error { + if err := b.encodeHashableFields(w); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, uint8(1)); err != nil { + return err + } + return b.Script.EncodeBinary(w) +} + +// createHash creates the hash of the block. +// When calculating the hash value of the block, instead of calculating the entire block, +// only first seven fields in the block head will be calculated, which are +// 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) { + buf := new(bytes.Buffer) + if err = b.encodeHashableFields(buf); err != nil { + return hash, err + } + + // Double hash the encoded fields. + hash = sha256.Sum256(buf.Bytes()) + hash = sha256.Sum256(hash.Bytes()) + return hash, nil +} + +// encodeHashableFields will only encode the fields used for hashing. +// see Hash() for more information about the fields. +func (b *BlockBase) encodeHashableFields(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, b.Version); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, b.PrevHash); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, b.MerkleRoot); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, b.Timestamp); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, b.Index); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, b.ConsensusData); err != nil { + return err + } + return binary.Write(w, binary.LittleEndian, b.NextConsensus) +} + +// decodeHashableFields will only decode the fields used for hashing. +// see Hash() for more information about the fields. +func (b *BlockBase) decodeHashableFields(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &b.Version); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &b.PrevHash); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &b.MerkleRoot); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &b.Timestamp); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &b.Index); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &b.ConsensusData); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &b.NextConsensus); err != nil { + return err + } + + // 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 +} diff --git a/pkg/core/block_test.go b/pkg/core/block_test.go index 200b00589..9ae7f2a98 100644 --- a/pkg/core/block_test.go +++ b/pkg/core/block_test.go @@ -2,39 +2,78 @@ package core import ( "bytes" - "crypto/sha256" "encoding/hex" "testing" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/crypto" "github.com/stretchr/testify/assert" ) -func TestDecodeBlock(t *testing.T) { - var ( - rawBlock = "00000000b7def681f0080262aa293071c53b41fc3146b196067243700b68acd059734fd19543108bf9ddc738cbee2ed1160f153aa0d057f062de0aa3cbb64ba88735c23d43667e59543f050095df82b02e324c5ff3812db982f3b0089a21a278988efeec6a027b2501fd450140113ac66657c2f544e8ad13905fcb2ebaadfef9502cbefb07960fbe56df098814c223dcdd3d0efa0b43a9459e654d948516dcbd8b370f50fbecfb8b411d48051a408500ce85591e516525db24065411f6a88f43de90fa9c167c2e6f5af43bc84e65e5a4bb174bc83a19b6965ff10f476b1b151ae15439a985f33916abc6822b0bb140f4aae522ffaea229987a10d01beec826c3b9a189fe02aa82680581b78f3df0ea4d3f93ca8ea35ffc90f15f7db9017f92fafd9380d9ba3237973cf4313cf626fc40e30e50e3588bd047b39f478b59323868cd50c7ab54355d8245bf0f1988d37528f9bbfc68110cf917debbdbf1f4bdd02cdcccdc3269fdf18a6c727ee54b6934d840e43918dd1ec6123550ec37a513e72b34b2c2a3baa510dec3037cbef2fa9f6ed1e7ccd1f3f6e19d4ce2c0919af55249a970c2685217f75a5589cf9e54dff8449af155210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41acf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dddc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae01000095df82b000000000" - rawBlockHash = "922ba0c0d06afbeec4c50b0541a29153feaa46c5d7304e7bf7f40870d9f3aeb0" - rawBlockPrevHash = "d14f7359d0ac680b7043720696b14631fc413bc5713029aa620208f081f6deb7" - rawBlockIndex = 343892 - rawBlockTimestamp = 1501455939 - rawBlockConsensusData = 6866918707944415125 - ) +// Test blocks are blocks from mainnet with their corresponding index. - rawBlockBytes, err := hex.DecodeString(rawBlock) +func TestDecodeBlock1(t *testing.T) { + data, err := getBlockData(1) + if err != nil { + t.Fatal(err) + } + + b, err := hex.DecodeString(data["raw"].(string)) if err != nil { t.Fatal(err) } block := &Block{} - if err := block.DecodeBinary(bytes.NewReader(rawBlockBytes)); err != nil { + if err := block.DecodeBinary(bytes.NewReader(b)); err != nil { t.Fatal(err) } - assert.Equal(t, uint32(rawBlockIndex), block.Index) - assert.Equal(t, uint32(rawBlockTimestamp), block.Timestamp) - assert.Equal(t, uint64(rawBlockConsensusData), block.ConsensusData) - assert.Equal(t, rawBlockPrevHash, block.PrevHash.String()) - assert.Equal(t, rawBlockHash, block.Hash().String()) + + assert.Equal(t, uint32(data["index"].(float64)), block.Index) + assert.Equal(t, uint32(data["version"].(float64)), block.Version) + assert.Equal(t, data["hash"].(string), block.Hash().String()) + assert.Equal(t, data["previousblockhash"].(string), block.PrevHash.String()) + assert.Equal(t, data["merkleroot"].(string), block.MerkleRoot.String()) + assert.Equal(t, data["nextconsensus"].(string), crypto.AddressFromUint160(block.NextConsensus)) + + script := data["script"].(map[string]interface{}) + assert.Equal(t, script["invocation"].(string), hex.EncodeToString(block.Script.InvocationScript)) + assert.Equal(t, script["verification"].(string), hex.EncodeToString(block.Script.VerificationScript)) + + tx := data["tx"].([]interface{}) + minerTX := tx[0].(map[string]interface{}) + assert.Equal(t, len(tx), len(block.Transactions)) + assert.Equal(t, minerTX["type"].(string), block.Transactions[0].Type.String()) + assert.Equal(t, len(minerTX["attributes"].([]interface{})), len(block.Transactions[0].Attributes)) +} + +func TestTrimmedBlock(t *testing.T) { + block := getDecodedBlock(t, 1) + + b, err := block.Trim() + if err != nil { + t.Fatal(err) + } + + trimmedBlock, err := NewBlockFromTrimmedBytes(b) + if err != nil { + t.Fatal(err) + } + + assert.True(t, trimmedBlock.Trimmed) + assert.Equal(t, block.Version, trimmedBlock.Version) + assert.Equal(t, block.PrevHash, trimmedBlock.PrevHash) + assert.Equal(t, block.MerkleRoot, trimmedBlock.MerkleRoot) + assert.Equal(t, block.Timestamp, trimmedBlock.Timestamp) + assert.Equal(t, block.Index, trimmedBlock.Index) + assert.Equal(t, block.ConsensusData, trimmedBlock.ConsensusData) + assert.Equal(t, block.NextConsensus, trimmedBlock.NextConsensus) + + assert.Equal(t, block.Script, trimmedBlock.Script) + assert.Equal(t, len(block.Transactions), len(trimmedBlock.Transactions)) + for i := 0; i < len(block.Transactions); i++ { + assert.Equal(t, block.Transactions[i].Hash(), trimmedBlock.Transactions[i].Hash()) + assert.True(t, trimmedBlock.Transactions[i].Trimmed) + } } func TestHashBlockEqualsHashHeader(t *testing.T) { @@ -48,42 +87,17 @@ func TestBlockVerify(t *testing.T) { newTX(transaction.MinerType), newTX(transaction.IssueType), ) - - if !block.Verify(false) { - t.Fatal("block should be verified") - } + assert.True(t, block.Verify(false)) block.Transactions = []*transaction.Transaction{ {Type: transaction.IssueType}, {Type: transaction.MinerType}, } - - if block.Verify(false) { - t.Fatal("block should not by verified") - } + assert.False(t, block.Verify(false)) block.Transactions = []*transaction.Transaction{ {Type: transaction.MinerType}, {Type: transaction.MinerType}, } - - if block.Verify(false) { - t.Fatal("block should not by verified") - } -} - -func newBlockBase() BlockBase { - return BlockBase{ - Version: 0, - PrevHash: sha256.Sum256([]byte("a")), - MerkleRoot: sha256.Sum256([]byte("b")), - Timestamp: 999, - Index: 1, - ConsensusData: 1111, - NextConsensus: util.Uint160{}, - Script: &transaction.Witness{ - VerificationScript: []byte{0x0}, - InvocationScript: []byte{0x1}, - }, - } + assert.False(t, block.Verify(false)) } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 890095698..63580868d 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -4,12 +4,13 @@ import ( "bytes" "encoding/binary" "fmt" + "strings" "sync/atomic" "time" + "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/util" log "github.com/sirupsen/logrus" - "github.com/syndtr/goleveldb/leveldb" ) // tuning parameters @@ -26,20 +27,24 @@ var ( // Blockchain holds the chain. type Blockchain struct { // Any object that satisfies the BlockchainStorer interface. - Store + storage.Store // Current index/height of the highest block. // Read access should always be called by BlockHeight(). - // Writes access should only happen in persist(). + // Write access should only happen in persist(). blockHeight uint32 - // Number of headers stored. + // Number of headers stored in the chain file. storedHeaderCount uint32 blockCache *Cache startHash util.Uint256 + // All operation on headerList must be called from an + // headersOp to be routine safe. + headerList *HeaderHashList + // Only for operating on the headerList. headersOp chan headersOpFunc headersOpDone chan struct{} @@ -50,8 +55,9 @@ type Blockchain struct { type headersOpFunc func(headerList *HeaderHashList) -// NewBlockchain creates a new Blockchain object. -func NewBlockchain(s Store, startHash util.Uint256) *Blockchain { +// 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) { bc := &Blockchain{ Store: s, headersOp: make(chan headersOpFunc), @@ -61,25 +67,85 @@ func NewBlockchain(s Store, startHash util.Uint256) *Blockchain { verifyBlocks: false, } go bc.run() - bc.init() - return bc + if err := bc.init(); err != nil { + return nil, err + } + + return bc, nil } -func (bc *Blockchain) init() { - // for the initial header, for now - bc.storedHeaderCount = 1 +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()) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return nil + } + return err + } + + bc.blockHeight = binary.LittleEndian.Uint32(currBlockBytes[32:36]) + hashes, err := readStoredHeaderHashes(bc.Store) + if err != nil { + return err + } + for _, hash := range hashes { + if !bc.startHash.Equals(hash) { + bc.headerList.Add(hash) + bc.storedHeaderCount++ + } + } + + currHeaderBytes, err := bc.Get(storage.SYSCurrentHeader.Bytes()) + if err != nil { + return err + } + currHeaderHeight := binary.LittleEndian.Uint32(currHeaderBytes[32:36]) + currHeaderHash, err := util.Uint256DecodeBytes(currHeaderBytes[:32]) + if err != nil { + return err + } + + // Their is a high chance that the Node is stopped before the next + // batch of 2000 headers was stored. Via the currentHeaders stored we can sync + // that with stored blocks. + if currHeaderHeight > bc.storedHeaderCount { + hash := currHeaderHash + targetHash := bc.headerList.Get(bc.headerList.Len() - 1) + headers := []*Header{} + + for hash != targetHash { + header, err := bc.getHeader(hash) + if err != nil { + return fmt.Errorf("could not get header %s: %s", hash, err) + } + headers = append(headers, header) + hash = header.PrevHash + } + + headerSliceReverse(headers) + if err := bc.AddHeaders(headers...); err != nil { + return err + } + } + + return nil } func (bc *Blockchain) run() { - var ( - headerList = NewHeaderHashList(bc.startHash) - persistTimer = time.NewTimer(persistInterval) - ) + persistTimer := time.NewTimer(persistInterval) for { select { case op := <-bc.headersOp: - op(headerList) + op(bc.headerList) bc.headersOpDone <- struct{}{} case <-persistTimer.C: go bc.persist() @@ -113,14 +179,14 @@ func (bc *Blockchain) AddBlock(block *Block) error { func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) { var ( start = time.Now() - batch = new(leveldb.Batch) + batch = bc.Batch() ) bc.headersOp <- func(headerList *HeaderHashList) { for _, h := range headers { if int(h.Index-1) >= headerList.Len() { err = fmt.Errorf( - "height of block higher then current header height %d > %d\n", + "height of received header %d is higher then the current header %d", h.Index, headerList.Len(), ) return @@ -138,7 +204,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) { } if batch.Len() > 0 { - if err = bc.writeBatch(batch); err != nil { + if err = bc.PutBatch(batch); err != nil { return } log.WithFields(log.Fields{ @@ -154,7 +220,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) { // processHeader processes the given header. Note that this is only thread safe // if executed in headers operation. -func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList *HeaderHashList) error { +func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList *HeaderHashList) error { headerList.Add(h.Hash()) buf := new(bytes.Buffer) @@ -162,7 +228,7 @@ func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList if err := headerList.Write(buf, int(bc.storedHeaderCount), headerBatchCount); err != nil { return err } - key := makeEntryPrefixInt(preIXHeaderHashList, int(bc.storedHeaderCount)) + key := storage.AppendPrefixInt(storage.IXHeaderHashList, int(bc.storedHeaderCount)) batch.Put(key, buf.Bytes()) bc.storedHeaderCount += headerBatchCount buf.Reset() @@ -173,25 +239,22 @@ func (bc *Blockchain) processHeader(h *Header, batch *leveldb.Batch, headerList return err } - key := makeEntryPrefix(preDataBlock, h.Hash().BytesReverse()) + key := storage.AppendPrefix(storage.DataBlock, h.Hash().BytesReverse()) batch.Put(key, buf.Bytes()) - key = preSYSCurrentHeader.bytes() - batch.Put(key, hashAndIndexToBytes(h.Hash(), h.Index)) + batch.Put(storage.SYSCurrentHeader.Bytes(), hashAndIndexToBytes(h.Hash(), h.Index)) return nil } func (bc *Blockchain) persistBlock(block *Block) error { - batch := new(leveldb.Batch) + batch := bc.Batch() - // Store the block. - key := preSYSCurrentBlock.bytes() - batch.Put(key, hashAndIndexToBytes(block.Hash(), block.Index)) + storeAsBlock(batch, block, 0) + storeAsCurrentBlock(batch, block) - if err := bc.Store.writeBatch(batch); err != nil { + if err := bc.PutBatch(batch); err != nil { return err } - atomic.AddUint32(&bc.blockHeight, 1) return nil } @@ -241,7 +304,32 @@ func (bc *Blockchain) headerListLen() (n int) { // GetBlock returns a Block by the given hash. func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) { - return nil, nil + key := storage.AppendPrefix(storage.DataBlock, hash.BytesReverse()) + b, err := bc.Get(key) + if err != nil { + return nil, err + } + block, err := NewBlockFromTrimmedBytes(b) + if err != nil { + return nil, err + } + // TODO: persist TX first before we can handle this logic. + //if len(block.Transactions) == 0 { + // return nil, fmt.Errorf("block has no TX") + //} + return block, nil +} + +func (bc *Blockchain) getHeader(hash util.Uint256) (*Header, error) { + b, err := bc.Get(storage.AppendPrefix(storage.DataBlock, hash.BytesReverse())) + if err != nil { + return nil, err + } + block, err := NewBlockFromTrimmedBytes(b) + if err != nil { + return nil, err + } + return block.Header(), nil } // HasBlock return true if the blockchain contains he given @@ -253,6 +341,9 @@ func (bc *Blockchain) HasTransaction(hash util.Uint256) bool { // HasBlock return true if the blockchain contains the given // block hash. func (bc *Blockchain) HasBlock(hash util.Uint256) bool { + if header, err := bc.getHeader(hash); err == nil { + return header.Index <= bc.BlockHeight() + } return false } diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 8571d66bc..8d4e0c5e0 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -3,22 +3,13 @@ package core import ( "testing" + "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" ) -func TestNewBlockchain(t *testing.T) { - startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099") - bc := NewBlockchain(nil, startHash) - - assert.Equal(t, uint32(0), bc.BlockHeight()) - assert.Equal(t, uint32(0), bc.HeaderHeight()) - assert.Equal(t, uint32(1), bc.storedHeaderCount) - assert.Equal(t, startHash, bc.startHash) -} - func TestAddHeaders(t *testing.T) { - bc := newTestBC() + bc := newTestChain(t) h1 := newBlock(1).Header() h2 := newBlock(2).Header() h3 := newBlock(3).Header() @@ -45,7 +36,7 @@ func TestAddHeaders(t *testing.T) { } func TestAddBlock(t *testing.T) { - bc := newTestBC() + bc := newTestChain(t) blocks := []*Block{ newBlock(1), newBlock(2), @@ -73,8 +64,69 @@ func TestAddBlock(t *testing.T) { assert.Equal(t, 0, bc.blockCache.Len()) } -func newTestBC() *Blockchain { - startHash, _ := util.Uint256DecodeString("a") - bc := NewBlockchain(NewMemoryStore(), startHash) - return bc +func TestGetHeader(t *testing.T) { + bc := newTestChain(t) + block := newBlock(1) + err := bc.AddBlock(block) + assert.Nil(t, err) + + hash := block.Hash() + header, err := bc.getHeader(hash) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, block.Header(), header) + + block = newBlock(2) + hash = block.Hash() + _, err = bc.getHeader(block.Hash()) + assert.NotNil(t, err) +} + +func TestGetBlock(t *testing.T) { + bc := newTestChain(t) + blocks := makeBlocks(100) + + for i := 0; i < len(blocks); i++ { + if err := bc.AddBlock(blocks[i]); err != nil { + t.Fatal(err) + } + } + + for i := 0; i < len(blocks); i++ { + block, err := bc.GetBlock(blocks[i].Hash()) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, blocks[i].Index, block.Index) + assert.Equal(t, blocks[i].Hash(), block.Hash()) + } +} + +func TestHasBlock(t *testing.T) { + bc := newTestChain(t) + blocks := makeBlocks(50) + + for i := 0; i < len(blocks); i++ { + if err := bc.AddBlock(blocks[i]); err != nil { + t.Fatal(err) + } + } + assert.Nil(t, bc.persist()) + + for i := 0; i < len(blocks); i++ { + assert.True(t, bc.HasBlock(blocks[i].Hash())) + } + + newBlock := newBlock(51) + assert.False(t, bc.HasBlock(newBlock.Hash())) +} + +func newTestChain(t *testing.T) *Blockchain { + startHash, _ := util.Uint256DecodeString("a") + chain, err := NewBlockchain(storage.NewMemoryStore(), startHash) + if err != nil { + t.Fatal(err) + } + return chain } diff --git a/pkg/core/header.go b/pkg/core/header.go new file mode 100644 index 000000000..bb2837f62 --- /dev/null +++ b/pkg/core/header.go @@ -0,0 +1,38 @@ +package core + +import ( + "encoding/binary" + "fmt" + "io" +) + +// Header holds the head info of a block. +type Header struct { + // Base of the block. + BlockBase + // Padding that is fixed to 0 + _ uint8 +} + +// DecodeBinary impelements the Payload interface. +func (h *Header) DecodeBinary(r io.Reader) error { + if err := h.BlockBase.DecodeBinary(r); err != nil { + return err + } + + var padding uint8 + binary.Read(r, binary.LittleEndian, &padding) + if padding != 0 { + return fmt.Errorf("format error: padding must equal 0 got %d", padding) + } + + return nil +} + +// EncodeBinary impelements the Payload interface. +func (h *Header) EncodeBinary(w io.Writer) error { + if err := h.BlockBase.EncodeBinary(w); err != nil { + return err + } + return binary.Write(w, binary.LittleEndian, uint8(0)) +} diff --git a/pkg/core/header_hash_list.go b/pkg/core/header_hash_list.go index 9604f340c..ad97b526e 100644 --- a/pkg/core/header_hash_list.go +++ b/pkg/core/header_hash_list.go @@ -14,6 +14,11 @@ type HeaderHashList struct { hashes []util.Uint256 } +// NewHeaderHashListFromBytes return a new hash list from the given bytes. +func NewHeaderHashListFromBytes(b []byte) (*HeaderHashList, error) { + return nil, nil +} + // NewHeaderHashList return a new pointer to a HeaderHashList. func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList { return &HeaderHashList{ @@ -22,8 +27,8 @@ func NewHeaderHashList(hashes ...util.Uint256) *HeaderHashList { } // Add appends the given hash to the list of hashes. -func (l *HeaderHashList) Add(h util.Uint256) { - l.hashes = append(l.hashes, h) +func (l *HeaderHashList) Add(h ...util.Uint256) { + l.hashes = append(l.hashes, h...) } // Len return the length of the underlying hashes slice. @@ -47,7 +52,7 @@ func (l *HeaderHashList) Last() util.Uint256 { // Slice return a subslice of the underlying hashes. // Subsliced from start to end. // Example: -// headers := headerList.Slice(0, 2000) +// headers := headerList.Slice(0, 2000) func (l *HeaderHashList) Slice(start, end int) []util.Uint256 { return l.hashes[start:end] } diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 9933fffde..f6681cf79 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -1,7 +1,13 @@ package core import ( + "bytes" "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "testing" "time" "github.com/CityOfZion/neo-go/pkg/core/transaction" @@ -33,8 +39,47 @@ func newBlock(index uint32, txs ...*transaction.Transaction) *Block { return b } +func makeBlocks(n int) []*Block { + blocks := make([]*Block, n) + for i := 0; i < n; i++ { + blocks[i] = newBlock(uint32(i+1), newTX(transaction.MinerType)) + } + return blocks +} + func newTX(t transaction.TXType) *transaction.Transaction { return &transaction.Transaction{ Type: t, } } + +func getDecodedBlock(t *testing.T, i int) *Block { + data, err := getBlockData(i) + if err != nil { + t.Fatal(err) + } + + b, err := hex.DecodeString(data["raw"].(string)) + if err != nil { + t.Fatal(err) + } + + block := &Block{} + if err := block.DecodeBinary(bytes.NewReader(b)); err != nil { + t.Fatal(err) + } + + return block +} + +func getBlockData(i int) (map[string]interface{}, error) { + b, err := ioutil.ReadFile(fmt.Sprintf("test_data/block_%d.json", i)) + if err != nil { + return nil, err + } + var data map[string]interface{} + if err := json.Unmarshal(b, &data); err != nil { + return nil, err + } + return data, err +} diff --git a/pkg/core/leveldb_store.go b/pkg/core/leveldb_store.go deleted file mode 100644 index af5662c16..000000000 --- a/pkg/core/leveldb_store.go +++ /dev/null @@ -1,41 +0,0 @@ -package core - -import ( - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/opt" -) - -// LevelDBStore is the official storage implementation for storing and retreiving -// blockchain data. -type LevelDBStore struct { - db *leveldb.DB - path string -} - -// NewLevelDBStore return a new LevelDBStore object that will -// initialize the database found at the given path. -func NewLevelDBStore(path string, opts *opt.Options) (*LevelDBStore, error) { - db, err := leveldb.OpenFile(path, opts) - if err != nil { - return nil, err - } - return &LevelDBStore{ - path: path, - db: db, - }, nil -} - -// write implements the Store interface. -func (s *LevelDBStore) write(key, value []byte) error { - return s.db.Put(key, value, nil) -} - -//get implements the Store interface. -func (s *LevelDBStore) get(key []byte) ([]byte, error) { - return s.db.Get(key, nil) -} - -// writeBatch implements the Store interface. -func (s *LevelDBStore) writeBatch(batch *leveldb.Batch) error { - return s.db.Write(batch, nil) -} diff --git a/pkg/core/leveldb_test.go b/pkg/core/leveldb_test.go deleted file mode 100644 index f27c6aa67..000000000 --- a/pkg/core/leveldb_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package core - -import ( - "os" - "testing" - - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/syndtr/goleveldb/leveldb/opt" -) - -const ( - path = "test_chain" -) - -func TestPersistBlock(t *testing.T) { -} - -func newBlockchain() *Blockchain { - startHash, _ := util.Uint256DecodeString("a") - opts := &opt.Options{} - store, _ := NewLevelDBStore(path, opts) - chain := NewBlockchain( - store, - startHash, - ) - return chain -} - -func tearDown() error { - return os.RemoveAll(path) -} diff --git a/pkg/core/memory_store.go b/pkg/core/memory_store.go deleted file mode 100644 index 4696c1a3d..000000000 --- a/pkg/core/memory_store.go +++ /dev/null @@ -1,27 +0,0 @@ -package core - -import "github.com/syndtr/goleveldb/leveldb" - -// MemoryStore is an in memory implementation of a BlockChainStorer -// that should only be used for testing. -type MemoryStore struct{} - -// NewMemoryStore returns a pointer to a MemoryStore object. -func NewMemoryStore() *MemoryStore { - return &MemoryStore{} -} - -// get implementes the BlockchainStorer interface. -func (m *MemoryStore) get(key []byte) ([]byte, error) { - return nil, nil -} - -// write implementes the BlockchainStorer interface. -func (m *MemoryStore) write(key, value []byte) error { - return nil -} - -// writeBatch implementes the BlockchainStorer interface. -func (m *MemoryStore) writeBatch(batch *leveldb.Batch) error { - return nil -} diff --git a/pkg/core/raw_blocks_test.go b/pkg/core/raw_blocks_test.go deleted file mode 100644 index 7884468ff..000000000 --- a/pkg/core/raw_blocks_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package core - -var ( - rawBlock0 = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151" - - rawBlock1 = "00000000bf4421c88776c53b43ce1dc45463bfd2028e322fdfb60064be150ed3e36125d418f98ec3ed2c2d1c9427385e7b85d0d1a366e29c4e399693a59718380f8bbad6d6d90358010000004490d0bb7170726c59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd4501404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae0100004490d0bb00000000" -) diff --git a/pkg/core/storage/README.md b/pkg/core/storage/README.md new file mode 100644 index 000000000..171a62b56 --- /dev/null +++ b/pkg/core/storage/README.md @@ -0,0 +1,2 @@ +# Storage + diff --git a/pkg/core/storage/leveldb_store.go b/pkg/core/storage/leveldb_store.go new file mode 100644 index 000000000..71e83f11f --- /dev/null +++ b/pkg/core/storage/leveldb_store.go @@ -0,0 +1,57 @@ +package storage + +import ( + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/util" +) + +// LevelDBStore is the official storage implementation for storing and retreiving +// blockchain data. +type LevelDBStore struct { + db *leveldb.DB + path string +} + +// NewLevelDBStore return a new LevelDBStore object that will +// initialize the database found at the given path. +func NewLevelDBStore(path string, opts *opt.Options) (*LevelDBStore, error) { + db, err := leveldb.OpenFile(path, opts) + if err != nil { + return nil, err + } + return &LevelDBStore{ + path: path, + db: db, + }, nil +} + +// Put implements the Store interface. +func (s *LevelDBStore) Put(key, value []byte) error { + return s.db.Put(key, value, nil) +} + +// Get implements the Store interface. +func (s *LevelDBStore) Get(key []byte) ([]byte, error) { + return s.db.Get(key, nil) +} + +// PutBatch implements the Store interface. +func (s *LevelDBStore) PutBatch(batch Batch) error { + lvldbBatch := batch.(*leveldb.Batch) + return s.db.Write(lvldbBatch, nil) +} + +// Seek implements the Store interface. +func (s *LevelDBStore) Seek(key []byte, f func(k, v []byte)) { + iter := s.db.NewIterator(util.BytesPrefix(key), nil) + for iter.Next() { + f(iter.Key(), iter.Value()) + } + iter.Release() +} + +// Batch implements the Batch interface and returns a compatible Batch. +func (s *LevelDBStore) Batch() Batch { + return new(leveldb.Batch) +} diff --git a/pkg/core/storage/leveldb_store_test.go b/pkg/core/storage/leveldb_store_test.go new file mode 100644 index 000000000..82be0547e --- /dev/null +++ b/pkg/core/storage/leveldb_store_test.go @@ -0,0 +1 @@ +package storage diff --git a/pkg/core/storage/memory_store.go b/pkg/core/storage/memory_store.go new file mode 100644 index 000000000..a63813a92 --- /dev/null +++ b/pkg/core/storage/memory_store.go @@ -0,0 +1,64 @@ +package storage + +// MemoryStore is an in-memory implementation of a Store, mainly +// used for testing. Do not use MemoryStore in production. +type MemoryStore struct { + mem map[string][]byte +} + +// MemoryBatch a in-memory batch compatible with MemoryStore. +type MemoryBatch struct { + m map[*[]byte][]byte +} + +// Put implements the Batch interface. +func (b *MemoryBatch) Put(k, v []byte) { + key := &k + b.m[key] = v +} + +// Len implements the Batch interface. +func (b *MemoryBatch) Len() int { + return len(b.m) +} + +// NewMemoryStore creates a new MemoryStore object. +func NewMemoryStore() *MemoryStore { + return &MemoryStore{ + mem: make(map[string][]byte), + } +} + +// Get implements the Store interface. +func (s *MemoryStore) Get(key []byte) ([]byte, error) { + if val, ok := s.mem[string(key)]; ok { + return val, nil + } + return nil, ErrKeyNotFound +} + +// Put implementes the Store interface. +func (s *MemoryStore) Put(key, value []byte) error { + s.mem[string(key)] = value + return nil +} + +// PutBatch implementes the Store interface. +func (s *MemoryStore) PutBatch(batch Batch) error { + b := batch.(*MemoryBatch) + for k, v := range b.m { + s.Put(*k, v) + } + return nil +} + +// Seek implementes the Store interface. +func (s *MemoryStore) Seek(key []byte, f func(k, v []byte)) { +} + +// Batch implements the Batch interface and returns a compatible Batch. +func (s *MemoryStore) Batch() Batch { + return &MemoryBatch{ + m: make(map[*[]byte][]byte), + } +} diff --git a/pkg/core/storage/memory_store_test.go b/pkg/core/storage/memory_store_test.go new file mode 100644 index 000000000..883f14cfb --- /dev/null +++ b/pkg/core/storage/memory_store_test.go @@ -0,0 +1,41 @@ +package storage + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetPut(t *testing.T) { + var ( + s = NewMemoryStore() + key = []byte("sparse") + value = []byte("rocks") + ) + + s.Put(key, value) + + newVal, err := s.Get(key) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, value, newVal) +} + +func TestPutBatch(t *testing.T) { + var ( + s = NewMemoryStore() + key = []byte("sparse") + value = []byte("rocks") + batch = s.Batch() + ) + + batch.Put(key, value) + + s.PutBatch(batch) + newVal, err := s.Get(key) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, value, newVal) +} diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go new file mode 100644 index 000000000..96e126dca --- /dev/null +++ b/pkg/core/storage/store.go @@ -0,0 +1,73 @@ +package storage + +import ( + "encoding/binary" + "errors" +) + +// KeyPrefix constants. +const ( + DataBlock KeyPrefix = 0x01 + DataTransaction KeyPrefix = 0x02 + STAccount KeyPrefix = 0x40 + STCoin KeyPrefix = 0x44 + STValidator KeyPrefix = 0x48 + STAsset KeyPrefix = 0x4c + STContract KeyPrefix = 0x50 + STStorage KeyPrefix = 0x70 + IXHeaderHashList KeyPrefix = 0x80 + IXValidatorsCount KeyPrefix = 0x90 + SYSCurrentBlock KeyPrefix = 0xc0 + SYSCurrentHeader KeyPrefix = 0xc1 + SYSVersion KeyPrefix = 0xf0 +) + +// ErrKeyNotFound is an error returned by Store implementations +// when a certain key is not found. +var ErrKeyNotFound = errors.New("key not found") + +type ( + // Store is anything that can persist and retrieve the blockchain. + // information. + Store interface { + Batch() Batch + Get([]byte) ([]byte, error) + Put(k, v []byte) error + PutBatch(batch Batch) error + Seek(k []byte, f func(k, v []byte)) + } + + // Batch represents an abstraction on top of batch operations. + // Each Store implementation is responsible of casting a Batch + // to its appropriate type. + Batch interface { + Put(k, v []byte) + Len() int + } + + // KeyPrefix is a constant byte added as a prefix for each key + // stored. + KeyPrefix uint8 +) + +// Bytes returns the bytes representation of KeyPrefix. +func (k KeyPrefix) Bytes() []byte { + return []byte{byte(k)} +} + +// AppendPrefix append byteslice b to the given KeyPrefix. +// AppendKeyPrefix(SYSVersion, []byte{0x00, 0x01}) +func AppendPrefix(k KeyPrefix, b []byte) []byte { + dest := make([]byte, len(b)+1) + dest[0] = byte(k) + copy(dest[1:], b) + return dest +} + +// AppendPrefixInt append int n to the given KeyPrefix. +// AppendPrefixInt(SYSCurrentHeader, 10001) +func AppendPrefixInt(k KeyPrefix, n int) []byte { + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, uint32(n)) + return AppendPrefix(k, b) +} diff --git a/pkg/core/storage/store_test.go b/pkg/core/storage/store_test.go new file mode 100644 index 000000000..4c028fbe4 --- /dev/null +++ b/pkg/core/storage/store_test.go @@ -0,0 +1,57 @@ +package storage + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + prefixes = []KeyPrefix{ + DataBlock, + DataTransaction, + STAccount, + STCoin, + STValidator, + STAsset, + STContract, + STStorage, + IXHeaderHashList, + IXValidatorsCount, + SYSCurrentBlock, + SYSCurrentHeader, + SYSVersion, + } + + expected = []uint8{ + 0x01, + 0x02, + 0x40, + 0x44, + 0x48, + 0x4c, + 0x50, + 0x70, + 0x80, + 0x90, + 0xc0, + 0xc1, + 0xf0, + } +) + +func TestAppendPrefix(t *testing.T) { + for i := 0; i < len(expected); i++ { + value := []byte{0x01, 0x02} + prefix := AppendPrefix(prefixes[i], value) + assert.Equal(t, KeyPrefix(expected[i]), KeyPrefix(prefix[0])) + } +} + +func TestAppendPrefixInt(t *testing.T) { + for i := 0; i < len(expected); i++ { + value := 2000 + prefix := AppendPrefixInt(prefixes[i], value) + assert.Equal(t, KeyPrefix(expected[i]), KeyPrefix(prefix[0])) + } +} diff --git a/pkg/core/store.go b/pkg/core/store.go deleted file mode 100644 index 400746fda..000000000 --- a/pkg/core/store.go +++ /dev/null @@ -1,53 +0,0 @@ -package core - -import ( - "bytes" - "encoding/binary" - - "github.com/syndtr/goleveldb/leveldb" -) - -type dataEntry uint8 - -func (e dataEntry) bytes() []byte { - return []byte{byte(e)} -} - -// Storage data entry prefixes. -const ( - preDataBlock dataEntry = 0x01 - preDataTransaction dataEntry = 0x02 - preSTAccount dataEntry = 0x40 - preSTCoin dataEntry = 0x44 - preSTValidator dataEntry = 0x48 - preSTAsset dataEntry = 0x4c - preSTContract dataEntry = 0x50 - preSTStorage dataEntry = 0x70 - preIXHeaderHashList dataEntry = 0x80 - preIXValidatorsCount dataEntry = 0x90 - preSYSCurrentBlock dataEntry = 0xc0 - preSYSCurrentHeader dataEntry = 0xc1 - preSYSVersion dataEntry = 0xf0 -) - -func makeEntryPrefixInt(e dataEntry, n int) []byte { - buf := new(bytes.Buffer) - binary.Write(buf, binary.LittleEndian, n) - return makeEntryPrefix(e, buf.Bytes()) -} - -func makeEntryPrefix(e dataEntry, b []byte) []byte { - dest := make([]byte, len(b)+1) - dest[0] = byte(e) - for i := 1; i < len(b); i++ { - dest[i] = b[i] - } - return dest -} - -// Store is anything that can persist and retrieve the blockchain. -type Store interface { - get(k []byte) ([]byte, error) - write(k, v []byte) error - writeBatch(batch *leveldb.Batch) error -} diff --git a/pkg/core/test_data/block_1.json b/pkg/core/test_data/block_1.json new file mode 100644 index 000000000..a76d1a056 --- /dev/null +++ b/pkg/core/test_data/block_1.json @@ -0,0 +1,33 @@ +{ + "raw": "00000000bf4421c88776c53b43ce1dc45463bfd2028e322fdfb60064be150ed3e36125d418f98ec3ed2c2d1c9427385e7b85d0d1a366e29c4e399693a59718380f8bbad6d6d90358010000004490d0bb7170726c59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd4501404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae0100004490d0bb00000000", + "hash": "d782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9", + "size": 686, + "version": 0, + "previousblockhash": "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf", + "merkleroot": "d6ba8b0f381897a59396394e9ce266a3d1d0857b5e3827941c2d2cedc38ef918", + "time": 1476647382, + "index": 1, + "nonce": "6c727071bbd09044", + "nextconsensus": "APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR", + "script": { + "invocation": "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822", + "verification": "552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae" + }, + "tx": [ + { + "txid": "d6ba8b0f381897a59396394e9ce266a3d1d0857b5e3827941c2d2cedc38ef918", + "size": 10, + "type": "MinerTransaction", + "version": 0, + "attributes": [], + "vin": [], + "vout": [], + "sys_fee": "0", + "net_fee": "0", + "scripts": [], + "nonce": 3151007812 + } + ], + "confirmations": 2020977, + "nextblockhash": "bf638e92c85016df9bc3b62b33f3879fa22d49d5f55d822b423149a3bca9e574" +} diff --git a/pkg/core/test_data/block_2.json b/pkg/core/test_data/block_2.json new file mode 100644 index 000000000..73f69a84f --- /dev/null +++ b/pkg/core/test_data/block_2.json @@ -0,0 +1,33 @@ +{ + "raw": "00000000c96c94033911087c387cc77875869817c7617c000f4e39d7a0eeb0388adb82d74208df9542f56a42fb2764142d13274c951087c609565fd5c4ab9b57a183a1afead9035802000000b66fa838e89c9ab259e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd450140e8a85159d8655c7b5a66429831eb15dabefc0f27a22bef67febb9eccb6859cc4c5c6ae675175a0bbefeeeeff2a8e9f175aaaae0796f3b5f29cb93b5b50fbf270409270a02cbbcb99969d6dc8a85708d5609dc1bba9569c849b53db7896c7f1ffd3adc789c0fe8400fb665478567448b4c4bd9c1657432591e4de83df10348f865a40724a9cf9d43eda558bfa8755e7bd1c0e9282f96164f4ff0b7369fd80e878cf49f2e61ed0fdf8cf218e7fdd471be5f29ef1242c39f3695d5decb169667fe0d3d140860da333249f7c54db09b548ad5d5e45fb8787238d51b35a6d4759f7990f47f00ff102e7b88f45acce423dd9f4b87dbf85e7e2c5c7a6aace11e62267c0bbe16b4028d272a701c22c5f8aa3495fa22d7d5a583518ef552e73813ee369c6d51ad2f246a24eb0092ebe7e1550d7de2ee09abad4dae4f4c0277317f5b1190041b9c2c2f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae010000b66fa83800000000", + "hash": "0xbf638e92c85016df9bc3b62b33f3879fa22d49d5f55d822b423149a3bca9e574", + "size": 686, + "version": 0, + "previousblockhash": "0xd782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9", + "merkleroot": "0xafa183a1579babc4d55f5609c68710954c27132d146427fb426af54295df0842", + "time": 1476647402, + "index": 2, + "nonce": "b29a9ce838a86fb6", + "nextconsensus": "APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR", + "script": { + "invocation": "40e8a85159d8655c7b5a66429831eb15dabefc0f27a22bef67febb9eccb6859cc4c5c6ae675175a0bbefeeeeff2a8e9f175aaaae0796f3b5f29cb93b5b50fbf270409270a02cbbcb99969d6dc8a85708d5609dc1bba9569c849b53db7896c7f1ffd3adc789c0fe8400fb665478567448b4c4bd9c1657432591e4de83df10348f865a40724a9cf9d43eda558bfa8755e7bd1c0e9282f96164f4ff0b7369fd80e878cf49f2e61ed0fdf8cf218e7fdd471be5f29ef1242c39f3695d5decb169667fe0d3d140860da333249f7c54db09b548ad5d5e45fb8787238d51b35a6d4759f7990f47f00ff102e7b88f45acce423dd9f4b87dbf85e7e2c5c7a6aace11e62267c0bbe16b4028d272a701c22c5f8aa3495fa22d7d5a583518ef552e73813ee369c6d51ad2f246a24eb0092ebe7e1550d7de2ee09abad4dae4f4c0277317f5b1190041b9c2c2", + "verification": "552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae" + }, + "tx": [ + { + "txid": "0xafa183a1579babc4d55f5609c68710954c27132d146427fb426af54295df0842", + "size": 10, + "type": "MinerTransaction", + "version": 0, + "attributes": [], + "vin": [], + "vout": [], + "sys_fee": "0", + "net_fee": "0", + "scripts": [], + "nonce": 950562742 + } + ], + "confirmations": 2021026, + "nextblockhash": "0x1fca8800f1ffbc9fb08bcfee1269461161d58dcee0252cf4db13220ba8189c5d" +} diff --git a/pkg/core/transaction/asset_type.go b/pkg/core/transaction/asset_type.go new file mode 100644 index 000000000..82fc4913a --- /dev/null +++ b/pkg/core/transaction/asset_type.go @@ -0,0 +1,16 @@ +package transaction + +// AssetType represent a NEO asset type +type AssetType uint8 + +// Valid asset types. +const ( + CreditFlag AssetType = 0x40 + DutyFlag AssetType = 0x80 + GoverningToken AssetType = 0x00 + UtilityToken AssetType = 0x01 + Currency AssetType = 0x08 + Share AssetType = DutyFlag | 0x10 + Invoice AssetType = DutyFlag | 0x18 + Token AssetType = CreditFlag | 0x20 +) diff --git a/pkg/core/transaction/attribute.go b/pkg/core/transaction/attribute.go index d84e1bf4a..694f62a24 100644 --- a/pkg/core/transaction/attribute.go +++ b/pkg/core/transaction/attribute.go @@ -2,7 +2,7 @@ package transaction import ( "encoding/binary" - "errors" + "fmt" "io" "github.com/CityOfZion/neo-go/pkg/util" @@ -14,7 +14,7 @@ type Attribute struct { Data []byte } -// DecodeBinary implements the Payloader interface. +// DecodeBinary implements the Payload interface. func (attr *Attribute) DecodeBinary(r io.Reader) error { if err := binary.Read(r, binary.LittleEndian, &attr.Usage); err != nil { return err @@ -43,7 +43,7 @@ func (attr *Attribute) DecodeBinary(r io.Reader) error { attr.Data = make([]byte, lenData) return binary.Read(r, binary.LittleEndian, attr.Data) } - return errors.New("format error in decoding transaction attribute") + return fmt.Errorf("failed decoding TX attribute usage: 0x%2x", attr.Usage) } // EncodeBinary implements the Payload interface. @@ -75,5 +75,5 @@ func (attr *Attribute) EncodeBinary(w io.Writer) error { } return binary.Write(w, binary.LittleEndian, attr.Data) } - return errors.New("format error in encoding transaction attribute") + return fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Usage) } diff --git a/pkg/core/transaction/issue.go b/pkg/core/transaction/issue.go new file mode 100644 index 000000000..4ad8bdec5 --- /dev/null +++ b/pkg/core/transaction/issue.go @@ -0,0 +1,19 @@ +package transaction + +import ( + "io" +) + +// IssueTX represents a issue transaction. +// This TX has not special attributes. +type IssueTX struct{} + +// DecodeBinary implements the Payload interface. +func (tx *IssueTX) DecodeBinary(r io.Reader) error { + return nil +} + +// EncodeBinary implements the Payload interface. +func (tx *IssueTX) EncodeBinary(w io.Writer) error { + return nil +} diff --git a/pkg/core/transaction/register.go b/pkg/core/transaction/register.go new file mode 100644 index 000000000..def4d023d --- /dev/null +++ b/pkg/core/transaction/register.go @@ -0,0 +1,80 @@ +package transaction + +import ( + "encoding/binary" + "io" + + "github.com/CityOfZion/neo-go/pkg/crypto" + "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. +type RegisterTX struct { + // The type of the asset being registered. + AssetType AssetType + + // Name of the asset being registered. + Name []byte + + // Amount registered + // Unlimited mode -0.00000001 + Amount util.Fixed8 + + // Decimals + Precision uint8 + + Owner crypto.EllipticCurvePoint + Admin util.Uint160 +} + +// DecodeBinary implements the Payload interface. +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 { + 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 + } + + point, err := crypto.NewEllipticCurvePointFromReader(r) + if err != nil { + return err + } + tx.Owner = point + + return binary.Read(r, binary.LittleEndian, &tx.Admin) +} + +// EncodeBinary implements the Payload interface. +func (tx *RegisterTX) EncodeBinary(w io.Writer) error { + return nil +} diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index fc8b97e82..6f13d9f8b 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -7,6 +7,7 @@ import ( "io" "github.com/CityOfZion/neo-go/pkg/util" + log "github.com/sirupsen/logrus" ) // Transaction is a process recorded in the NEO blockchain. @@ -37,6 +38,19 @@ type Transaction struct { // hash of the transaction hash util.Uint256 + + // Trimmed indicates this is a transaction from trimmed + // data. + Trimmed bool +} + +// NewTrimmedTX returns a trimmed transaction with only its hash +// and Trimmed to true. +func NewTrimmedTX(hash util.Uint256) *Transaction { + return &Transaction{ + hash: hash, + Trimmed: true, + } } // Hash return the hash of the transaction. @@ -127,13 +141,21 @@ func (t *Transaction) decodeData(r io.Reader) error { case ContractType: t.Data = &ContractTX{} return t.Data.(*ContractTX).DecodeBinary(r) + case RegisterType: + t.Data = &RegisterTX{} + return t.Data.(*RegisterTX).DecodeBinary(r) + case IssueType: + t.Data = &IssueTX{} + return t.Data.(*IssueTX).DecodeBinary(r) + default: + log.Warnf("invalid TX type %s", t.Type) } return nil } // EncodeBinary implements the payload interface. func (t *Transaction) EncodeBinary(w io.Writer) error { - if err := t.EncodeBinaryUnsigned(w); err != nil { + if err := t.encodeHashableFields(w); err != nil { return err } if err := util.WriteVarUint(w, uint64(len(t.Scripts))); err != nil { @@ -147,9 +169,9 @@ func (t *Transaction) EncodeBinary(w io.Writer) error { return nil } -// EncodeBinaryUnsigned will only encode the fields that are not used for +// encodeHashableFields will only encode the fields that are not used for // signing the transaction, which are all fields except the scripts. -func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error { +func (t *Transaction) encodeHashableFields(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, t.Type); err != nil { return err } @@ -163,7 +185,8 @@ func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error { } // Attributes - if err := util.WriteVarUint(w, uint64(len(t.Attributes))); err != nil { + lenAttrs := uint64(len(t.Attributes)) + if err := util.WriteVarUint(w, lenAttrs); err != nil { return err } for _, attr := range t.Attributes { @@ -196,7 +219,7 @@ func (t *Transaction) EncodeBinaryUnsigned(w io.Writer) error { func (t *Transaction) createHash() (hash util.Uint256, err error) { buf := new(bytes.Buffer) - if err = t.EncodeBinaryUnsigned(buf); err != nil { + if err = t.encodeHashableFields(buf); err != nil { return } sha := sha256.New() diff --git a/pkg/core/transaction/transaction_test.go b/pkg/core/transaction/transaction_test.go index 54f2bbb87..c8761db10 100644 --- a/pkg/core/transaction/transaction_test.go +++ b/pkg/core/transaction/transaction_test.go @@ -5,10 +5,42 @@ import ( "encoding/hex" "testing" + "github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" ) +func TestWitnessEncodeDecode(t *testing.T) { + verif, err := hex.DecodeString("552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae") + assert.Nil(t, err) + invoc, err := hex.DecodeString("404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822") + assert.Nil(t, err) + + lenInvoc := len(invoc) + lenVerif := len(verif) + t.Log(lenInvoc) + t.Log(lenVerif) + + wit := &Witness{ + InvocationScript: invoc, + VerificationScript: verif, + } + + buf := new(bytes.Buffer) + if err := wit.EncodeBinary(buf); err != nil { + t.Fatal(err) + } + + witDecode := &Witness{} + if err := witDecode.DecodeBinary(buf); err != nil { + t.Fatal(err) + } + t.Log(len(witDecode.VerificationScript)) + t.Log(len(witDecode.InvocationScript)) + + assert.Equal(t, wit, witDecode) +} + func TestDecodeEncodeClaimTX(t *testing.T) { b, err := hex.DecodeString(rawClaimTX) if err != nil { @@ -25,7 +57,8 @@ func TestDecodeEncodeClaimTX(t *testing.T) { assert.Equal(t, 0, len(tx.Attributes)) assert.Equal(t, 0, len(tx.Inputs)) assert.Equal(t, 1, len(tx.Outputs)) - assert.Equal(t, "AQJseD8iBmCD4sgfHRhMahmoi9zvopG6yz", tx.Outputs[0].ScriptHash.Address()) + address := crypto.AddressFromUint160(tx.Outputs[0].ScriptHash) + assert.Equal(t, "AQJseD8iBmCD4sgfHRhMahmoi9zvopG6yz", address) assert.Equal(t, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", tx.Outputs[0].AssetID.String()) assert.Equal(t, tx.Outputs[0].Amount.String(), "0.06247739") invoc := "40456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb" diff --git a/pkg/core/transaction/transactioner.go b/pkg/core/transaction/txer.go similarity index 100% rename from pkg/core/transaction/transactioner.go rename to pkg/core/transaction/txer.go diff --git a/pkg/core/transaction/type.go b/pkg/core/transaction/type.go index 9b4cfb9e8..451032b69 100644 --- a/pkg/core/transaction/type.go +++ b/pkg/core/transaction/type.go @@ -4,7 +4,7 @@ package transaction type TXType uint8 // All processes in NEO system are recorded in transactions. -// There are several types of transactions. +// Valid transaction types. const ( MinerType TXType = 0x00 IssueType TXType = 0x01 @@ -23,28 +23,28 @@ const ( func (t TXType) String() string { switch t { case MinerType: - return "miner transaction" + return "MinerTransaction" case IssueType: - return "issue transaction" + return "IssueTransaction" case ClaimType: - return "claim transaction" + return "ClaimTransaction" case EnrollmentType: - return "enrollment transaction" + return "EnrollmentTransaction" case VotingType: - return "voting transaction" + return "VotingTransaction" case RegisterType: - return "register transaction" + return "RegisterTransaction" case ContractType: - return "contract transaction" + return "ContractTransaction" case StateType: - return "state transaction" + return "StateTransaction" case AgencyType: - return "agency transaction" + return "AgencyTransaction" case PublishType: - return "publish transaction" + return "PublishTransaction" case InvocationType: - return "invocation transaction" + return "InvocationTransaction" default: - return "" + return "UnkownTransaction" } } diff --git a/pkg/core/transaction/witness.go b/pkg/core/transaction/witness.go index eabedea32..4dc609754 100644 --- a/pkg/core/transaction/witness.go +++ b/pkg/core/transaction/witness.go @@ -27,10 +27,14 @@ func (wit *Witness) DecodeBinary(r io.Reader) error { // EncodeBinary implements the payload interface. func (wit *Witness) EncodeBinary(w io.Writer) error { - util.WriteVarUint(w, uint64(len(wit.InvocationScript))) + if err := util.WriteVarUint(w, uint64(len(wit.InvocationScript))); err != nil { + return err + } if err := binary.Write(w, binary.LittleEndian, wit.InvocationScript); err != nil { return err } - util.WriteVarUint(w, uint64(len(wit.VerificationScript))) + if err := util.WriteVarUint(w, uint64(len(wit.VerificationScript))); err != nil { + return err + } return binary.Write(w, binary.LittleEndian, wit.VerificationScript) } diff --git a/pkg/core/util.go b/pkg/core/util.go index e11b593e8..faff23cd6 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -1,6 +1,13 @@ package core -import "github.com/CityOfZion/neo-go/pkg/util" +import ( + "bytes" + "encoding/binary" + "sort" + + "github.com/CityOfZion/neo-go/pkg/core/storage" + "github.com/CityOfZion/neo-go/pkg/util" +) // Utilities for quick bootstrapping blockchains. Normally we should // create the genisis block. For now (to speed up development) we will add @@ -20,3 +27,75 @@ func GenesisHashMainNet() util.Uint256 { hash, _ := util.Uint256DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf") return hash } + +// headerSliceReverse reverses the given slice of *Header. +func headerSliceReverse(dest []*Header) { + for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 { + dest[i], dest[j] = dest[j], dest[i] + } +} + +// storeAsCurrentBlock stores the given block witch prefix +// SYSCurrentBlock. +func storeAsCurrentBlock(batch storage.Batch, block *Block) { + buf := new(bytes.Buffer) + buf.Write(block.Hash().BytesReverse()) + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, block.Index) + buf.Write(b) + batch.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes()) +} + +// storeAsBlock stores the given block as DataBlock. +func storeAsBlock(batch storage.Batch, block *Block, sysFee uint32) error { + var ( + key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesReverse()) + buf = new(bytes.Buffer) + ) + + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, sysFee) + + b, err := block.Trim() + if err != nil { + return err + } + buf.Write(b) + batch.Put(key, buf.Bytes()) + return nil +} + +// readStoredHeaderHashes returns a sorted list of header hashes +// retrieved from the given Store. +func readStoredHeaderHashes(store storage.Store) ([]util.Uint256, error) { + hashMap := make(map[uint32][]util.Uint256) + store.Seek(storage.IXHeaderHashList.Bytes(), func(k, v []byte) { + storedCount := binary.LittleEndian.Uint32(k[1:]) + hashes, err := util.Read2000Uint256Hashes(v) + if err != nil { + panic(err) + } + hashMap[storedCount] = hashes + }) + + var ( + i = 0 + sortedKeys = make([]int, len(hashMap)) + ) + + for k, _ := range hashMap { + sortedKeys[i] = int(k) + i++ + } + sort.Ints(sortedKeys) + + hashes := []util.Uint256{} + for _, key := range sortedKeys { + values := hashMap[uint32(key)] + for _, hash := range values { + hashes = append(hashes, hash) + } + } + + return hashes, nil +} diff --git a/pkg/crypto/address.go b/pkg/crypto/address.go new file mode 100644 index 000000000..ca2cbc55f --- /dev/null +++ b/pkg/crypto/address.go @@ -0,0 +1,23 @@ +package crypto + +import ( + "github.com/CityOfZion/neo-go/pkg/util" +) + +// AddressFromUint160 returns the "NEO address" from the given +// Uint160. +func AddressFromUint160(u util.Uint160) string { + // Dont forget to prepend the Address version 0x17 (23) A + b := append([]byte{0x17}, u.Bytes()...) + return Base58CheckEncode(b) +} + +// Uint160DecodeAddress attempts to decode the given NEO address string +// into an Uint160. +func Uint160DecodeAddress(s string) (u util.Uint160, err error) { + b, err := Base58CheckDecode(s) + if err != nil { + return u, err + } + return util.Uint160DecodeBytes(b[1:21]) +} diff --git a/pkg/crypto/address_test.go b/pkg/crypto/address_test.go new file mode 100644 index 000000000..b325adb43 --- /dev/null +++ b/pkg/crypto/address_test.go @@ -0,0 +1,22 @@ +package crypto + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUint160DecodeAddress(t *testing.T) { + addrs := []string{ + "AMLr1CpPQtbEdiJdriX1HpRNMZUwbU2Huj", + "AKtwd3DRXj3nL5kHMUoNsdnsCEVjnuuTFF", + "AMxkaxFVG8Q1BhnB4fjTA5ZmUTEnnTMJMa", + } + for _, addr := range addrs { + val, err := Uint160DecodeAddress(addr) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, addr, AddressFromUint160(val)) + } +} diff --git a/pkg/crypto/elliptic_curve.go b/pkg/crypto/elliptic_curve.go index 40a6938f6..eaf4d46ce 100644 --- a/pkg/crypto/elliptic_curve.go +++ b/pkg/crypto/elliptic_curve.go @@ -4,10 +4,14 @@ package crypto // Expanded and tweaked upon here under MIT license. import ( + "encoding/binary" "encoding/hex" "errors" "fmt" + "io" "math/big" + + "github.com/CityOfZion/neo-go/pkg/util" ) type ( @@ -29,6 +33,39 @@ type ( } ) +// 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 { diff --git a/pkg/network/server.go b/pkg/network/server.go index 004276a57..96134aff7 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -86,6 +86,11 @@ func NewServer(config ServerConfig, chain *core.Blockchain) *Server { // Start will start the server and its underlying transport. func (s *Server) Start() { + log.WithFields(log.Fields{ + "blockHeight": s.chain.BlockHeight(), + "headerHeight": s.chain.HeaderHeight(), + }).Info("node started") + go s.transport.Accept() s.discovery.BackFill(s.Seeds...) s.run() @@ -221,7 +226,10 @@ func (s *Server) handleHeadersCmd(p Peer, headers *payload.Headers) { // handleBlockCmd processes the received block received from its peer. func (s *Server) handleBlockCmd(p Peer, block *core.Block) error { - return s.chain.AddBlock(block) + if !s.chain.HasBlock(block.Hash()) { + return s.chain.AddBlock(block) + } + return nil } // handleInvCmd will process the received inventory. diff --git a/pkg/util/io.go b/pkg/util/io.go index 670296e43..3568d809a 100644 --- a/pkg/util/io.go +++ b/pkg/util/io.go @@ -1,6 +1,7 @@ package util import ( + "bytes" "encoding/binary" "errors" "io" @@ -48,18 +49,30 @@ func WriteVarUint(w io.Writer, val uint64) error { return nil } if val < 0xFFFF { - binary.Write(w, binary.LittleEndian, 0xfd) + binary.Write(w, binary.LittleEndian, byte(0xfd)) binary.Write(w, binary.LittleEndian, uint16(val)) return nil } if val < 0xFFFFFFFF { - binary.Write(w, binary.LittleEndian, 0xfe) + binary.Write(w, binary.LittleEndian, byte(0xfe)) binary.Write(w, binary.LittleEndian, uint32(val)) return nil } - binary.Write(w, binary.LittleEndian, 0xff) + binary.Write(w, binary.LittleEndian, byte(0xff)) binary.Write(w, binary.LittleEndian, val) return nil } + +// Read2000Uint256Hashes attempt to read 2000 Uint256 hashes from +// the given byte array. +func Read2000Uint256Hashes(b []byte) ([]Uint256, error) { + r := bytes.NewReader(b) + lenHashes := ReadVarUint(r) + hashes := make([]Uint256, lenHashes) + if err := binary.Read(r, binary.LittleEndian, hashes); err != nil { + return nil, err + } + return hashes, nil +} diff --git a/pkg/util/io_test.go b/pkg/util/io_test.go new file mode 100644 index 000000000..7d0258eda --- /dev/null +++ b/pkg/util/io_test.go @@ -0,0 +1,64 @@ +package util + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWriteVarUint1(t *testing.T) { + var ( + val = uint64(1) + buf = new(bytes.Buffer) + ) + if err := WriteVarUint(buf, val); err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, buf.Len()) +} + +func TestWriteVarUint1000(t *testing.T) { + var ( + val = uint64(1000) + buf = new(bytes.Buffer) + ) + + if err := WriteVarUint(buf, val); err != nil { + t.Fatal(err) + } + assert.Equal(t, 3, buf.Len()) + assert.Equal(t, byte(0xfd), buf.Bytes()[0]) + res := ReadVarUint(buf) + assert.Equal(t, val, res) +} + +func TestWriteVarUint100000(t *testing.T) { + var ( + val = uint64(100000) + buf = new(bytes.Buffer) + ) + + if err := WriteVarUint(buf, val); err != nil { + t.Fatal(err) + } + assert.Equal(t, 5, buf.Len()) + assert.Equal(t, byte(0xfe), buf.Bytes()[0]) + res := ReadVarUint(buf) + assert.Equal(t, val, res) +} + +func TestWriteVarUint100000000000(t *testing.T) { + var ( + val = uint64(1000000000000) + buf = new(bytes.Buffer) + ) + + if err := WriteVarUint(buf, val); err != nil { + t.Fatal(err) + } + assert.Equal(t, 9, buf.Len()) + assert.Equal(t, byte(0xff), buf.Bytes()[0]) + res := ReadVarUint(buf) + assert.Equal(t, val, res) +} diff --git a/pkg/util/uint160.go b/pkg/util/uint160.go index 800671825..f6bd0b9a5 100644 --- a/pkg/util/uint160.go +++ b/pkg/util/uint160.go @@ -3,8 +3,6 @@ package util import ( "encoding/hex" "fmt" - - "github.com/CityOfZion/neo-go/pkg/crypto" ) const uint160Size = 20 @@ -24,16 +22,6 @@ func Uint160DecodeString(s string) (u Uint160, err error) { return Uint160DecodeBytes(b) } -// Uint160DecodeAddress attempts to decode the given NEO address string -// into an Uint160. -func Uint160DecodeAddress(s string) (u Uint160, err error) { - b, err := crypto.Base58CheckDecode(s) - if err != nil { - return u, err - } - return Uint160DecodeBytes(b[1:21]) -} - // Uint160DecodeBytes attempts to decode the given bytes into an Uint160. func Uint160DecodeBytes(b []byte) (u Uint160, err error) { if len(b) != uint160Size { @@ -54,13 +42,6 @@ func (u Uint160) Bytes() []byte { return b } -// Address returns the NEO address representation of u. -func (u Uint160) Address() string { - // Dont forget to prepend the Address version 0x17 (23) A - b := append([]byte{0x17}, u.Bytes()...) - return crypto.Base58CheckEncode(b) -} - // String implements the stringer interface. func (u Uint160) String() string { return hex.EncodeToString(u.Bytes()) diff --git a/pkg/util/uint160_test.go b/pkg/util/uint160_test.go index 145533e7c..c7d868219 100644 --- a/pkg/util/uint160_test.go +++ b/pkg/util/uint160_test.go @@ -1,22 +1 @@ package util - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUint160FromToAddress(t *testing.T) { - addrs := []string{ - "AMLr1CpPQtbEdiJdriX1HpRNMZUwbU2Huj", - "AKtwd3DRXj3nL5kHMUoNsdnsCEVjnuuTFF", - "AMxkaxFVG8Q1BhnB4fjTA5ZmUTEnnTMJMa", - } - for _, addr := range addrs { - val, err := Uint160DecodeAddress(addr) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, addr, val.Address()) - } -}