Fixed header sync issue (#17)

* headers can now sync till infinity

* fixed empty hashStop getBlock payload + test

* added more test + more binary decoding/encoding

* bump version
This commit is contained in:
Anthony De Meulemeester 2018-02-07 15:16:50 +01:00 committed by GitHub
parent 046494dd68
commit b6d8271b8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 290 additions and 106 deletions

View file

@ -1 +1 @@
0.7.0
0.8.0

View file

@ -36,16 +36,32 @@ type BlockBase struct {
// DecodeBinary implements the payload interface.
func (b *BlockBase) DecodeBinary(r io.Reader) error {
binary.Read(r, binary.LittleEndian, &b.Version)
binary.Read(r, binary.LittleEndian, &b.PrevHash)
binary.Read(r, binary.LittleEndian, &b.MerkleRoot)
binary.Read(r, binary.LittleEndian, &b.Timestamp)
binary.Read(r, binary.LittleEndian, &b.Index)
binary.Read(r, binary.LittleEndian, &b.ConsensusData)
binary.Read(r, binary.LittleEndian, &b.NextConsensus)
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
binary.Read(r, binary.LittleEndian, &padding)
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)
}
@ -86,6 +102,21 @@ func (b *BlockBase) encodeHashableFields(w io.Writer) error {
return err
}
// EncodeBinary implements the Payload interface
func (b *BlockBase) EncodeBinary(w io.Writer) error {
if err := b.encodeHashableFields(w); err != nil {
return err
}
// padding
if err := binary.Write(w, binary.LittleEndian, uint8(1)); err != nil {
return err
}
// script
return b.Script.EncodeBinary(w)
}
// Header holds the head info of a block
type Header struct {
BlockBase
@ -115,7 +146,12 @@ func (h *Header) DecodeBinary(r io.Reader) error {
// EncodeBinary impelements the Payload interface.
func (h *Header) EncodeBinary(w io.Writer) error {
return nil
if err := h.BlockBase.EncodeBinary(w); err != nil {
return err
}
// padding
return binary.Write(w, binary.LittleEndian, uint8(0))
}
// Block represents one block in the chain.

View file

@ -82,7 +82,10 @@ func newBlockBase() BlockBase {
Index: 1,
ConsensusData: 1111,
NextConsensus: util.Uint160{},
Script: &Witness{},
Script: &Witness{
VerificationScript: []byte{0x0},
InvocationScript: []byte{0x1},
},
}
}

View file

@ -191,6 +191,11 @@ func (bc *Blockchain) CurrentBlockHash() (hash util.Uint256) {
return bc.headerIndex[bc.currentBlockHeight]
}
// CurrentHeaderHash returns the hash of the latest known header.
func (bc *Blockchain) CurrentHeaderHash() (hash util.Uint256) {
return bc.headerIndex[len(bc.headerIndex)-1]
}
// BlockHeight return the height/index of the latest block this node has.
func (bc *Blockchain) BlockHeight() uint32 {
return bc.currentBlockHeight

View file

@ -31,9 +31,9 @@ func TestAddHeaders(t *testing.T) {
startHash, _ := util.Uint256DecodeFromString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
bc := NewBlockchain(NewMemoryStore(), log.New(os.Stdout, "", 0), startHash)
h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1}}
h2 := &Header{BlockBase: BlockBase{Version: 0, Index: 2}}
h3 := &Header{BlockBase: BlockBase{Version: 0, Index: 3}}
h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1, Script: &Witness{}}}
h2 := &Header{BlockBase: BlockBase{Version: 0, Index: 2, Script: &Witness{}}}
h3 := &Header{BlockBase: BlockBase{Version: 0, Index: 3, Script: &Witness{}}}
if err := bc.AddHeaders(h1, h2, h3); err != nil {
t.Fatal(err)

61
pkg/core/header_test.go Normal file
View file

@ -0,0 +1,61 @@
package core
import (
"bytes"
"crypto/sha256"
"testing"
"time"
"github.com/CityOfZion/neo-go/pkg/util"
)
func TestHeaderEncodeDecode(t *testing.T) {
header := Header{BlockBase: BlockBase{
Version: 0,
PrevHash: sha256.Sum256([]byte("prevhash")),
MerkleRoot: sha256.Sum256([]byte("merkleroot")),
Timestamp: uint32(time.Now().UTC().Unix()),
Index: 3445,
ConsensusData: 394949,
NextConsensus: util.Uint160{},
Script: &Witness{
InvocationScript: []byte{0x10},
VerificationScript: []byte{0x11},
},
}}
buf := new(bytes.Buffer)
if err := header.EncodeBinary(buf); err != nil {
t.Fatal(err)
}
headerDecode := &Header{}
if err := headerDecode.DecodeBinary(buf); err != nil {
t.Fatal(err)
}
if header.Version != headerDecode.Version {
t.Fatal("expected both versions to be equal")
}
if !header.PrevHash.Equals(headerDecode.PrevHash) {
t.Fatal("expected both prev hashes to be equal")
}
if !header.MerkleRoot.Equals(headerDecode.MerkleRoot) {
t.Fatal("expected both merkle roots to be equal")
}
if header.Index != headerDecode.Index {
t.Fatal("expected both indexes to be equal")
}
if header.ConsensusData != headerDecode.ConsensusData {
t.Fatal("expected both consensus data fields to be equal")
}
if !header.NextConsensus.Equals(headerDecode.NextConsensus) {
t.Fatalf("expected both next consensus fields to be equal")
}
if bytes.Compare(header.Script.InvocationScript, headerDecode.Script.InvocationScript) != 0 {
t.Fatalf("expected equal invocation scripts %v and %v", header.Script.InvocationScript, headerDecode.Script.InvocationScript)
}
if bytes.Compare(header.Script.VerificationScript, headerDecode.Script.VerificationScript) != 0 {
t.Fatalf("expected equal verification scripts %v and %v", header.Script.VerificationScript, headerDecode.Script.VerificationScript)
}
}

View file

@ -32,5 +32,10 @@ func (wit *Witness) DecodeBinary(r io.Reader) error {
// EncodeBinary implements the payload interface.
func (wit *Witness) EncodeBinary(w io.Writer) error {
return nil
util.WriteVarUint(w, uint64(len(wit.InvocationScript)))
if err := binary.Write(w, binary.LittleEndian, wit.InvocationScript); err != nil {
return err
}
util.WriteVarUint(w, uint64(len(wit.VerificationScript)))
return binary.Write(w, binary.LittleEndian, wit.VerificationScript)
}

View file

@ -49,7 +49,7 @@ func (p *AddrWithTime) EncodeBinary(w io.Writer) error {
return err
}
// AddressList holds a slice of AddrWithTime.
// AddressList is a list with AddrWithTime.
type AddressList struct {
Addrs []*AddrWithTime
}

View file

@ -33,14 +33,13 @@ func TestEncodeDecodeAddr(t *testing.T) {
func TestEncodeDecodeAddressList(t *testing.T) {
var lenList uint8 = 4
addrs := make([]*AddrWithTime, lenList)
addrList := &AddressList{make([]*AddrWithTime, lenList)}
for i := 0; i < int(lenList); i++ {
e, _ := util.EndpointFromString(fmt.Sprintf("127.0.0.1:200%d", i))
addrs[i] = NewAddrWithTime(e)
addrList.Addrs[i] = NewAddrWithTime(e)
}
buf := new(bytes.Buffer)
addrList := &AddressList{addrs}
if err := addrList.EncodeBinary(buf); err != nil {
t.Fatal(err)
}

View file

@ -2,7 +2,6 @@ package payload
import (
"encoding/binary"
"fmt"
"io"
"github.com/CityOfZion/neo-go/pkg/util"
@ -18,21 +17,23 @@ type GetBlocks struct {
// NewGetBlocks return a pointer to a GetBlocks object.
func NewGetBlocks(start []util.Uint256, stop util.Uint256) *GetBlocks {
p := &GetBlocks{}
p.HashStart = start
p.HashStop = stop
return p
return &GetBlocks{
HashStart: start,
HashStop: stop,
}
}
// DecodeBinary implements the payload interface.
func (p *GetBlocks) DecodeBinary(r io.Reader) error {
lenStart := util.ReadVarUint(r)
fmt.Println(lenStart)
p.HashStart = make([]util.Uint256, lenStart)
err := binary.Read(r, binary.LittleEndian, &p.HashStart)
err = binary.Read(r, binary.LittleEndian, &p.HashStop)
fmt.Println(p)
if err := binary.Read(r, binary.LittleEndian, &p.HashStart); err != nil {
return err
}
// If the reader returns EOF we know the hashStop is not encoded.
err := binary.Read(r, binary.LittleEndian, &p.HashStop)
if err == io.EOF {
return nil
}
@ -42,11 +43,20 @@ func (p *GetBlocks) DecodeBinary(r io.Reader) error {
// EncodeBinary implements the payload interface.
func (p *GetBlocks) EncodeBinary(w io.Writer) error {
err := util.WriteVarUint(w, uint64(len(p.HashStart)))
err = binary.Write(w, binary.LittleEndian, p.HashStart)
//err = binary.Write(w, binary.LittleEndian, p.HashStop)
if err := util.WriteVarUint(w, uint64(len(p.HashStart))); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, p.HashStart); err != nil {
return err
}
// Only write hashStop if its not filled with zero bytes.
var emtpy util.Uint256
if p.HashStop != emtpy {
return binary.Write(w, binary.LittleEndian, p.HashStop)
}
return nil
}
// Size implements the payload interface.

View file

@ -1,58 +1,60 @@
package payload
// TODO: Currently the hashstop is not encoded, therefore this test will fail.
// Need to figure some stuff how to handle this properly.
// - anthdm 04/02/2018
import (
"bytes"
"crypto/sha256"
"reflect"
"testing"
// func TestGetBlocksEncodeDecode(t *testing.T) {
// hash, _ := util.Uint256DecodeFromString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf")
"github.com/CityOfZion/neo-go/pkg/util"
)
// start := []util.Uint256{
// hash,
// sha256.Sum256([]byte("a")),
// sha256.Sum256([]byte("b")),
// sha256.Sum256([]byte("c")),
// }
// stop := sha256.Sum256([]byte("d"))
func TestGetBlockEncodeDecode(t *testing.T) {
start := []util.Uint256{
sha256.Sum256([]byte("a")),
sha256.Sum256([]byte("b")),
sha256.Sum256([]byte("c")),
sha256.Sum256([]byte("d")),
}
// p := NewGetBlocks(start, stop)
// buf := new(bytes.Buffer)
// if err := p.EncodeBinary(buf); err != nil {
// t.Fatal(err)
// }
p := NewGetBlocks(start, util.Uint256{})
buf := new(bytes.Buffer)
if err := p.EncodeBinary(buf); err != nil {
t.Fatal(err)
}
// pDecode := &GetBlocks{}
// if err := pDecode.DecodeBinary(buf); err != nil {
// t.Fatal(err)
// }
pDecode := &GetBlocks{}
if err := pDecode.DecodeBinary(buf); err != nil {
t.Fatal(err)
}
// if !reflect.DeepEqual(p, pDecode) {
// t.Fatalf("expecting both getblocks payloads to be equal %v and %v", p, pDecode)
// }
// }
if !reflect.DeepEqual(p, pDecode) {
t.Fatalf("expected to have equal block payload %v and %v", p, pDecode)
}
}
// TODO: Currently the hashstop is not encoded, therefore this test will fail.
// Need to figure some stuff how to handle this properly.
// - anthdm 04/02/2018
//
// func TestGetBlocksWithEmptyHashStop(t *testing.T) {
// start := []util.Uint256{
// sha256.Sum256([]byte("a")),
// }
// stop := util.Uint256{}
func TestGetBlockEncodeDecodeWithHashStop(t *testing.T) {
var (
start = []util.Uint256{
sha256.Sum256([]byte("a")),
sha256.Sum256([]byte("b")),
sha256.Sum256([]byte("c")),
sha256.Sum256([]byte("d")),
}
stop = sha256.Sum256([]byte("e"))
)
p := NewGetBlocks(start, stop)
buf := new(bytes.Buffer)
if err := p.EncodeBinary(buf); err != nil {
t.Fatal(err)
}
// buf := new(bytes.Buffer)
// p := NewGetBlocks(start, stop)
// if err := p.EncodeBinary(buf); err != nil {
// t.Fatal(err)
// }
pDecode := &GetBlocks{}
if err := pDecode.DecodeBinary(buf); err != nil {
t.Fatal(err)
}
// pDecode := &GetBlocks{}
// if err := pDecode.DecodeBinary(buf); err != nil {
// t.Fatal(err)
// }
// if !reflect.DeepEqual(p, pDecode) {
// t.Fatalf("expecting both getblocks payloads to be equal %v and %v", p, pDecode)
// }
// }
if !reflect.DeepEqual(p, pDecode) {
t.Fatalf("expected to have equal block payload %v and %v", p, pDecode)
}
}

View file

@ -30,6 +30,13 @@ func (p *Headers) DecodeBinary(r io.Reader) error {
}
// EncodeBinary implements the Payload interface.
func (h *Headers) EncodeBinary(w io.Writer) error {
func (p *Headers) EncodeBinary(w io.Writer) error {
util.WriteVarUint(w, uint64(len(p.Hdrs)))
for _, header := range p.Hdrs {
if err := header.EncodeBinary(w); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,55 @@
package payload
import (
"bytes"
"reflect"
"testing"
"github.com/CityOfZion/neo-go/pkg/core"
)
func TestHeadersEncodeDecode(t *testing.T) {
headers := &Headers{[]*core.Header{
&core.Header{
BlockBase: core.BlockBase{
Version: 0,
Index: 1,
Script: &core.Witness{
InvocationScript: []byte{0x0},
VerificationScript: []byte{0x1},
},
}},
&core.Header{
BlockBase: core.BlockBase{
Version: 0,
Index: 2,
Script: &core.Witness{
InvocationScript: []byte{0x0},
VerificationScript: []byte{0x1},
},
}},
&core.Header{
BlockBase: core.BlockBase{
Version: 0,
Index: 3,
Script: &core.Witness{
InvocationScript: []byte{0x0},
VerificationScript: []byte{0x1},
},
}},
}}
buf := new(bytes.Buffer)
if err := headers.EncodeBinary(buf); err != nil {
t.Fatal(err)
}
headersDecode := &Headers{}
if err := headersDecode.DecodeBinary(buf); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(headers, headersDecode) {
t.Fatalf("expected both header payload to be equal %+v and %+v", headers, headersDecode)
}
}

View file

@ -41,7 +41,8 @@ func (p *LocalPeer) version() *payload.Version {
}
func (p *LocalPeer) callVersion(msg *Message) error {
return p.s.handleVersionCmd(msg, p)
version := msg.Payload.(*payload.Version)
return p.s.handleVersionCmd(version, p)
}
func (p *LocalPeer) callVerack(msg *Message) error {

View file

@ -212,8 +212,7 @@ func (s *Server) handlePeerConnected(p Peer) error {
return p.callVersion(msg)
}
func (s *Server) handleVersionCmd(msg *Message, p Peer) error {
version := msg.Payload.(*payload.Version)
func (s *Server) handleVersionCmd(version *payload.Version, p Peer) error {
if s.id == version.Nonce {
return errors.New("identical nonce")
}
@ -230,8 +229,7 @@ func (s *Server) handleGetaddrCmd(msg *Message, p Peer) error {
// The node can broadcast the object information it owns by this message.
// The message can be sent automatically or can be used to answer getbloks messages.
func (s *Server) handleInvCmd(msg *Message, p Peer) error {
inv := msg.Payload.(*payload.Inventory)
func (s *Server) handleInvCmd(inv *payload.Inventory, p Peer) error {
if !inv.Type.Valid() {
return fmt.Errorf("invalid inventory type %s", inv.Type)
}
@ -248,8 +246,7 @@ func (s *Server) handleInvCmd(msg *Message, p Peer) error {
}
// handleBlockCmd processes the received block.
func (s *Server) handleBlockCmd(msg *Message, p Peer) error {
block := msg.Payload.(*core.Block)
func (s *Server) handleBlockCmd(block *core.Block, p Peer) error {
hash, err := block.Hash()
if err != nil {
return err
@ -262,8 +259,7 @@ func (s *Server) handleBlockCmd(msg *Message, p Peer) error {
// After receiving the getaddr message, the node returns an addr message as response
// and provides information about the known nodes on the network.
func (s *Server) handleAddrCmd(msg *Message, p Peer) error {
addrList := msg.Payload.(*payload.AddressList)
func (s *Server) handleAddrCmd(addrList *payload.AddressList, p Peer) error {
for _, addr := range addrList.Addrs {
if !s.peerAlreadyConnected(addr.Addr) {
// TODO: this is not transport abstracted.
@ -285,13 +281,11 @@ func (s *Server) handleHeadersCmd(headers *payload.Headers, p Peer) error {
// Ask more headers if we are not in sync with the peer.
if s.bc.HeaderHeight() < p.version().StartHeight {
s.logger.Printf("header height %d peer height %d", s.bc.HeaderHeight(), p.version().StartHeight)
if err := s.askMoreHeaders(p); err != nil {
s.logger.Printf("getheaders RPC failed: %s", err)
return
}
}
}(context.TODO(), headers.Hdrs)
return nil
@ -299,7 +293,7 @@ func (s *Server) handleHeadersCmd(headers *payload.Headers, p Peer) error {
// Ask the peer for more headers We use the current block hash as start.
func (s *Server) askMoreHeaders(p Peer) error {
start := []util.Uint256{s.bc.CurrentBlockHash()}
start := []util.Uint256{s.bc.CurrentHeaderHash()}
payload := payload.NewGetBlocks(start, util.Uint256{})
msg := newMessage(s.net, cmdGetHeaders, payload)

View file

@ -15,8 +15,7 @@ func TestHandleVersionFailWrongPort(t *testing.T) {
p := NewLocalPeer(s)
version := payload.NewVersion(1337, 1, "/NEO:0.0.0/", 0, true)
msg := newMessage(ModeDevNet, cmdVersion, version)
if err := s.handleVersionCmd(msg, p); err == nil {
if err := s.handleVersionCmd(version, p); err == nil {
t.Fatal("expected error got nil")
}
}
@ -28,8 +27,7 @@ func TestHandleVersionFailIdenticalNonce(t *testing.T) {
p := NewLocalPeer(s)
version := payload.NewVersion(s.id, 1, "/NEO:0.0.0/", 0, true)
msg := newMessage(ModeDevNet, cmdVersion, version)
if err := s.handleVersionCmd(msg, p); err == nil {
if err := s.handleVersionCmd(version, p); err == nil {
t.Fatal("expected error got nil")
}
}
@ -41,9 +39,7 @@ func TestHandleVersion(t *testing.T) {
p := NewLocalPeer(s)
version := payload.NewVersion(1337, p.addr().Port, "/NEO:0.0.0/", 0, true)
msg := newMessage(ModeDevNet, cmdVersion, version)
if err := s.handleVersionCmd(msg, p); err != nil {
if err := s.handleVersionCmd(version, p); err != nil {
t.Fatal(err)
}
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"net"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/network/payload"
"github.com/CityOfZion/neo-go/pkg/util"
)
@ -84,10 +85,10 @@ func handleMessage(s *Server, p *TCPPeer) {
switch command {
case cmdVersion:
if err = s.handleVersionCmd(msg, p); err != nil {
version := msg.Payload.(*payload.Version)
if err = s.handleVersionCmd(version, p); err != nil {
break
}
version := msg.Payload.(*payload.Version)
p.nonce = version.Nonce
p.pVersion = version
@ -106,19 +107,22 @@ func handleMessage(s *Server, p *TCPPeer) {
// start the protocol
go s.startProtocol(p)
case cmdAddr:
err = s.handleAddrCmd(msg, p)
addrList := msg.Payload.(*payload.AddressList)
err = s.handleAddrCmd(addrList, p)
case cmdGetAddr:
err = s.handleGetaddrCmd(msg, p)
case cmdInv:
err = s.handleInvCmd(msg, p)
inv := msg.Payload.(*payload.Inventory)
err = s.handleInvCmd(inv, p)
case cmdBlock:
err = s.handleBlockCmd(msg, p)
block := msg.Payload.(*core.Block)
err = s.handleBlockCmd(block, p)
case cmdConsensus:
case cmdTX:
case cmdVerack:
// If we receive a verack here we disconnect. We already handled the verack
// when we sended our version.
err = errors.New("received verack twice")
err = errors.New("verack already received")
case cmdGetHeaders:
case cmdGetBlocks:
case cmdGetData:
@ -284,6 +288,7 @@ func (p *TCPPeer) writeLoop() {
p.disconnect()
}()
// resuse this buffer
buf := new(bytes.Buffer)
for {
t := <-p.send

View file

@ -22,3 +22,8 @@ func (u Uint160) ToSlice() []byte {
func (u Uint160) String() string {
return hex.EncodeToString(u.ToSlice())
}
// Equals returns true if both Uint256 values are the same.
func (u Uint160) Equals(other Uint160) bool {
return u.String() == other.String()
}