diff --git a/.docker/6000-privnet-blocks.acc.gz b/.docker/6000-privnet-blocks.acc.gz new file mode 100644 index 000000000..b4d0213bb Binary files /dev/null and b/.docker/6000-privnet-blocks.acc.gz differ diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 3da7a6135..c9dfb75bf 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -24,9 +24,9 @@ services: neo_go_network: ipv4_address: 172.200.0.1 ports: - - 20331:20331 - - 20341:20341 - - 20351:20351 + - 20333:20333 + - 30333:30333 + - 20001:20001 node_two: container_name: neo_go_node_two image: env_neo_go_image @@ -38,9 +38,9 @@ services: neo_go_network: ipv4_address: 172.200.0.2 ports: - - 20332:20332 - - 20342:20342 - - 20352:20352 + - 20334:20334 + - 30334:30334 + - 20002:20002 node_three: container_name: neo_go_node_three image: env_neo_go_image @@ -52,9 +52,9 @@ services: neo_go_network: ipv4_address: 172.200.0.3 ports: - - 20333:20333 - - 20343:20343 - - 20353:20353 + - 20335:20335 + - 30335:30335 + - 20003:20003 node_four: container_name: neo_go_node_four image: env_neo_go_image @@ -66,6 +66,6 @@ services: neo_go_network: ipv4_address: 172.200.0.4 ports: - - 20334:20334 - - 20344:20344 - - 20354:20354 + - 20336:20336 + - 30336:30336 + - 20004:20004 diff --git a/.docker/privnet-entrypoint.sh b/.docker/privnet-entrypoint.sh new file mode 100755 index 000000000..db3c609ed --- /dev/null +++ b/.docker/privnet-entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh +if test -f /6000-privnet-blocks.acc.gz; then + gunzip /6000-privnet-blocks.acc.gz + /usr/bin/neo-go db restore -i /6000-privnet-blocks.acc +fi +/usr/bin/neo-go "$@" diff --git a/Dockerfile b/Dockerfile index 2fc68c4e3..fea8deda5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN set -x \ && go build -v -mod=vendor -ldflags "${LDFLAGS}" -o /go/bin/neo-go ./cli # Executable image -FROM scratch +FROM alpine ARG VERSION LABEL version=$VERSION @@ -30,9 +30,11 @@ LABEL version=$VERSION WORKDIR / COPY --from=builder /neo-go/config /config +COPY --from=builder /neo-go/.docker/6000-privnet-blocks.acc.gz / +COPY --from=builder /neo-go/.docker/privnet-entrypoint.sh /usr/bin/privnet-entrypoint.sh COPY --from=builder /go/bin/neo-go /usr/bin/neo-go COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -ENTRYPOINT ["/usr/bin/neo-go"] +ENTRYPOINT ["/usr/bin/privnet-entrypoint.sh"] -CMD ["node", "--config-path", "/config", "--testnet"] +CMD ["node", "--config-path", "/config", "--privnet"] diff --git a/Makefile b/Makefile index ac9280471..ecdae7062 100644 --- a/Makefile +++ b/Makefile @@ -108,10 +108,13 @@ env_up: @docker-compose -f $(DC_FILE) up -d node_one node_two node_three node_four env_down: - @echo "=> Stop and cleanup environment" + @echo "=> Stop environment" @docker-compose -f $(DC_FILE) down env_restart: - @echo "=> Stop and cleanup environment" + @echo "=> Stop and start environment" @docker-compose -f $(DC_FILE) restart +env_clean: env_down + @echo "=> Cleanup environment" + @docker volume rm docker_volume_chain diff --git a/config/protocol.privnet.docker.four.yml b/config/protocol.privnet.docker.four.yml index 578125d50..73a9869ee 100644 --- a/config/protocol.privnet.docker.four.yml +++ b/config/protocol.privnet.docker.four.yml @@ -9,10 +9,10 @@ ProtocolConfiguration: - 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699 - 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62 SeedList: - - 172.200.0.1:20331 - - 172.200.0.2:20332 - - 172.200.0.3:20333 - - 172.200.0.4:20334 + - 172.200.0.1:20333 + - 172.200.0.2:20334 + - 172.200.0.3:20335 + - 172.200.0.4:20336 SystemFee: EnrollmentTransaction: 1000 IssueTransaction: 500 @@ -37,7 +37,7 @@ ApplicationConfiguration: # FilePath: "./chains/privnet.bolt" # Uncomment in order to set up custom address for node. # Address: 127.0.0.1 - NodePort: 20334 + NodePort: 20336 Relay: true DialTimeout: 3 ProtoTickInterval: 2 @@ -47,10 +47,10 @@ ApplicationConfiguration: RPC: Enabled: true EnableCORSWorkaround: false - Port: 20344 + Port: 30336 Monitoring: Enabled: true - Port: 20354 + Port: 20004 UnlockWallet: Path: "6PYRXVwHSqFSukL3CuXxdQ75VmsKpjeLgQLEjt83FrtHf1gCVphHzdD4nc" Password: "four" diff --git a/config/protocol.privnet.docker.one.yml b/config/protocol.privnet.docker.one.yml index 0a6d50d5b..ff939f0b7 100644 --- a/config/protocol.privnet.docker.one.yml +++ b/config/protocol.privnet.docker.one.yml @@ -9,10 +9,10 @@ ProtocolConfiguration: - 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699 - 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62 SeedList: - - 172.200.0.1:20331 - - 172.200.0.2:20332 - - 172.200.0.3:20333 - - 172.200.0.4:20334 + - 172.200.0.1:20333 + - 172.200.0.2:20334 + - 172.200.0.3:20335 + - 172.200.0.4:20336 SystemFee: EnrollmentTransaction: 1000 IssueTransaction: 500 @@ -37,7 +37,7 @@ ApplicationConfiguration: # FilePath: "./chains/privnet.bolt" # Uncomment in order to set up custom address for node. # Address: 127.0.0.1 - NodePort: 20331 + NodePort: 20333 Relay: true DialTimeout: 3 ProtoTickInterval: 2 @@ -47,10 +47,10 @@ ApplicationConfiguration: RPC: Enabled: true EnableCORSWorkaround: false - Port: 20341 + Port: 30333 Monitoring: Enabled: true - Port: 20351 + Port: 20001 UnlockWallet: Path: "6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y" Password: "one" diff --git a/config/protocol.privnet.docker.three.yml b/config/protocol.privnet.docker.three.yml index 135f83c4b..95749d4ae 100644 --- a/config/protocol.privnet.docker.three.yml +++ b/config/protocol.privnet.docker.three.yml @@ -9,10 +9,10 @@ ProtocolConfiguration: - 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699 - 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62 SeedList: - - 172.200.0.1:20331 - - 172.200.0.2:20332 - - 172.200.0.3:20333 - - 172.200.0.4:20334 + - 172.200.0.1:20333 + - 172.200.0.2:20334 + - 172.200.0.3:20335 + - 172.200.0.4:20336 SystemFee: EnrollmentTransaction: 1000 IssueTransaction: 500 @@ -37,7 +37,7 @@ ApplicationConfiguration: # FilePath: "./chains/privnet.bolt" # Uncomment in order to set up custom address for node. # Address: 127.0.0.1 - NodePort: 20333 + NodePort: 20335 Relay: true DialTimeout: 3 ProtoTickInterval: 2 @@ -47,10 +47,10 @@ ApplicationConfiguration: RPC: Enabled: true EnableCORSWorkaround: false - Port: 20343 + Port: 30335 Monitoring: Enabled: true - Port: 20353 + Port: 20003 UnlockWallet: Path: "6PYX86vYiHfUbpD95hfN1xgnvcSxy5skxfWYKu3ztjecxk6ikYs2kcWbeh" Password: "three" diff --git a/config/protocol.privnet.docker.two.yml b/config/protocol.privnet.docker.two.yml index eb4694d33..4f7801166 100644 --- a/config/protocol.privnet.docker.two.yml +++ b/config/protocol.privnet.docker.two.yml @@ -9,10 +9,10 @@ ProtocolConfiguration: - 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699 - 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62 SeedList: - - 172.200.0.1:20331 - - 172.200.0.2:20332 - - 172.200.0.3:20333 - - 172.200.0.4:20334 + - 172.200.0.1:20333 + - 172.200.0.2:20334 + - 172.200.0.3:20335 + - 172.200.0.4:20336 SystemFee: EnrollmentTransaction: 1000 IssueTransaction: 500 @@ -37,7 +37,7 @@ ApplicationConfiguration: # FilePath: "./chains/privnet.bolt" # Uncomment in order to set up custom address for node. # Address: 127.0.0.1 - NodePort: 20332 + NodePort: 20334 Relay: true DialTimeout: 3 ProtoTickInterval: 2 @@ -47,10 +47,10 @@ ApplicationConfiguration: RPC: Enabled: true EnableCORSWorkaround: false - Port: 20342 + Port: 30334 Monitoring: Enabled: true - Port: 20352 + Port: 20002 UnlockWallet: Path: "6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L" Password: "two" diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 45362b789..61f7fea11 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -62,6 +62,9 @@ type Config struct { // Broadcast is a callback which is called to notify server // about new consensus payload to sent. Broadcast func(p *Payload) + // RelayBlock is a callback that is called to notify server + // about the new block that needs to be broadcasted. + RelayBlock func(b *core.Block) // Chain is a core.Blockchainer instance. Chain core.Blockchainer // RequestTx is a callback to which will be called @@ -252,6 +255,8 @@ func (s *service) processBlock(b block.Block) { if err := s.Chain.AddBlock(bb); err != nil { s.log.Warnf("error on add block: %v", err) + } else { + s.Config.RelayBlock(bb) } } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 2e589ca29..21d5ebb5f 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -150,7 +150,7 @@ func (bc *Blockchain) init() error { // There 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 { + if currHeaderHeight >= bc.storedHeaderCount { hash := currHeaderHash var targetHash util.Uint256 if bc.headerList.Len() > 0 { diff --git a/pkg/network/payload/headers.go b/pkg/network/payload/headers.go index 8635cb1e6..935bd05de 100644 --- a/pkg/network/payload/headers.go +++ b/pkg/network/payload/headers.go @@ -13,7 +13,7 @@ type Headers struct { // Users can at most request 2k header. const ( - maxHeadersAllowed = 2000 + MaxHeadersAllowed = 2000 ) // DecodeBinary implements Serializable interface. @@ -21,9 +21,9 @@ func (p *Headers) DecodeBinary(br *io.BinReader) { lenHeaders := br.ReadVarUint() // C# node does it silently - if lenHeaders > maxHeadersAllowed { - log.Warnf("received %d headers, capping to %d", lenHeaders, maxHeadersAllowed) - lenHeaders = maxHeadersAllowed + if lenHeaders > MaxHeadersAllowed { + log.Warnf("received %d headers, capping to %d", lenHeaders, MaxHeadersAllowed) + lenHeaders = MaxHeadersAllowed } p.Hdrs = make([]*core.Header, lenHeaders) diff --git a/pkg/network/server.go b/pkg/network/server.go index 549605bc1..28e1c71a9 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -37,6 +37,7 @@ var ( errMaxPeers = errors.New("max peers reached") errServerShutdown = errors.New("server shutdown") errInvalidInvType = errors.New("invalid inventory type") + errInvalidHashStart = errors.New("invalid requested HashStart") ) type ( @@ -94,10 +95,11 @@ func NewServer(config ServerConfig, chain core.Blockchainer) *Server { } srv, err := consensus.NewService(consensus.Config{ - Broadcast: s.handleNewPayload, - Chain: chain, - RequestTx: s.requestTx, - Wallet: config.Wallet, + Broadcast: s.handleNewPayload, + RelayBlock: s.relayBlock, + Chain: chain, + RequestTx: s.requestTx, + Wallet: config.Wallet, }) if err != nil { return nil @@ -200,12 +202,6 @@ func (s *Server) run() { } return case p := <-s.register: - // When a new peer is connected we send out our version immediately. - if err := s.sendVersion(p); err != nil { - log.WithFields(log.Fields{ - "addr": p.RemoteAddr(), - }).Error(err) - } s.lock.Lock() s.peers[p] = true s.lock.Unlock() @@ -289,6 +285,8 @@ func (s *Server) PeerCount() int { // startProtocol starts a long running background loop that interacts // every ProtoTickInterval with the peer. func (s *Server) startProtocol(p Peer) { + var err error + log.WithFields(log.Fields{ "addr": p.RemoteAddr(), "userAgent": string(p.Version().UserAgent), @@ -297,10 +295,12 @@ func (s *Server) startProtocol(p Peer) { }).Info("started protocol") s.discovery.RegisterGoodAddr(p.PeerAddr().String()) - err := s.requestHeaders(p) - if err != nil { - p.Disconnect(err) - return + if s.chain.HeaderHeight() < p.Version().StartHeight { + err = s.requestHeaders(p) + if err != nil { + p.Disconnect(err) + return + } } timer := time.NewTimer(s.ProtoTickInterval) @@ -427,6 +427,35 @@ func (s *Server) handleGetDataCmd(p Peer, inv *payload.Inventory) error { return nil } +// handleGetHeadersCmd processes the getheaders request. +func (s *Server) handleGetHeadersCmd(p Peer, gh *payload.GetBlocks) error { + if len(gh.HashStart) < 1 { + return errInvalidHashStart + } + startHash := gh.HashStart[0] + start, err := s.chain.GetHeader(startHash) + if err != nil { + return err + } + resp := payload.Headers{} + resp.Hdrs = make([]*core.Header, 0, payload.MaxHeadersAllowed) + for i := start.Index + 1; i < start.Index+1+payload.MaxHeadersAllowed; i++ { + hash := s.chain.GetHeaderHash(int(i)) + if hash.Equals(util.Uint256{}) || hash.Equals(gh.HashStop) { + break + } + header, err := s.chain.GetHeader(hash) + if err != nil { + break + } + resp.Hdrs = append(resp.Hdrs, header) + } + if len(resp.Hdrs) == 0 { + return nil + } + return p.WriteMsg(NewMessage(s.Net, CMDHeaders, &resp)) +} + // handleConsensusCmd processes received consensus payload. // It never returns an error. func (s *Server) handleConsensusCmd(cp *consensus.Payload) error { @@ -438,6 +467,9 @@ func (s *Server) handleConsensusCmd(cp *consensus.Payload) error { // It never returns an error. func (s *Server) handleTxCmd(tx *transaction.Transaction) error { s.consensus.OnTransaction(tx) + // It's OK for it to fail for various reasons like tx already existing + // in the pool. + _ = s.RelayTxn(tx) return nil } @@ -520,6 +552,9 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error { case CMDGetData: inv := msg.Payload.(*payload.Inventory) return s.handleGetDataCmd(peer, inv) + case CMDGetHeaders: + gh := msg.Payload.(*payload.GetBlocks) + return s.handleGetHeadersCmd(peer, gh) case CMDHeaders: headers := msg.Payload.(*payload.Headers) go s.handleHeadersCmd(peer, headers) @@ -580,6 +615,11 @@ func (s *Server) relayInventory(t payload.InventoryType, hashes ...util.Uint256) } } +// relayBlock tells all the other connected nodes about the given block. +func (s *Server) relayBlock(b *core.Block) { + s.relayInventory(payload.BlockType, b.Hash()) +} + // RelayTxn a new transaction to the local node and the connected peers. // Reference: the method OnRelay in C#: https://github.com/neo-project/neo/blob/master/neo/Network/P2P/LocalNode.cs#L159 func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason { @@ -599,10 +639,7 @@ func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason { return RelayOutOfMemory } - for p := range s.Peers() { - payload := payload.NewInventory(payload.TXType, []util.Uint256{t.Hash()}) - s.RelayDirectly(p, payload) - } + s.relayInventory(payload.TXType, t.Hash()) return RelaySucceed } diff --git a/pkg/network/tcp_transport.go b/pkg/network/tcp_transport.go index 8dba0be40..654515758 100644 --- a/pkg/network/tcp_transport.go +++ b/pkg/network/tcp_transport.go @@ -78,6 +78,12 @@ func (t *TCPTransport) handleConn(conn net.Conn) { t.server.register <- p + // When a new peer is connected we send out our version immediately. + if err := t.server.sendVersion(p); err != nil { + log.WithFields(log.Fields{ + "addr": p.RemoteAddr(), + }).Error(err) + } r := io.NewBinReaderFromIO(p.conn) for { msg := &Message{}