Merge branch 'logjson' into develop

poc8
obscuren 10 years ago
commit 75d164037f
  1. 2
      cmd/ethereum/flags.go
  2. 1
      cmd/ethereum/main.go
  3. 65
      cmd/mist/assets/examples/coin.js
  4. 11
      eth/backend.go
  5. 10
      logger/log.go
  6. 142
      logger/loggers.go
  7. 63
      logger/logsystem.go
  8. 112
      logger/sys.go
  9. 360
      logger/types.go
  10. 5
      p2p/server.go

@ -58,6 +58,7 @@ var (
ConfigFile string
DebugFile string
LogLevel int
LogFormat string
Dump bool
DumpHash string
DumpNumber int
@ -112,6 +113,7 @@ func Init() {
flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file")
flag.StringVar(&DebugFile, "debug", "", "debug file (no debugging if not set)")
flag.IntVar(&LogLevel, "loglevel", int(logger.InfoLevel), "loglevel: 0-5: silent,error,warn,info,debug,debug detail)")
flag.StringVar(&LogFormat, "logformat", "std", "logformat: std,raw)")
flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0")
flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false")
flag.BoolVar(&ShowGenesis, "genesis", false, "Dump the genesis block")

@ -67,6 +67,7 @@ func main() {
DataDir: Datadir,
LogFile: LogFile,
LogLevel: LogLevel,
LogFormat: LogFormat,
Identifier: Identifier,
MaxPeers: MaxPeer,
Port: OutboundPort,

@ -1 +1,64 @@
var contract = web3.eth.contractFromAbi([{"constant":false,"inputs":[{"name":"_h","type":"hash256"}],"name":"confirm","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"hash256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"CashIn","type":"event"},{"inputs":[{"indexed":true,"name":"out","type":"string32"},{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"}],"name":"SingleTransact","type":"event"},{"inputs":[{"indexed":true,"name":"out","type":"string32"},{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"hash256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"}],"name":"MultiTransact","type":"event"}]);
var contract = web3.eth.contractFromAbi([
{
"constant":false,
"inputs":[
{"name":"_h","type":"hash256"}
],
"name":"confirm",
"outputs":[],
"type":"function"
},{
"constant":false,
"inputs":[
{"name":_to,"type":"address"},
{"name":"_value","type":"uint256"},
{"name":"_data","type":"bytes"}
],
"name":"execute",
"outputs":[
{"name":"_r","type":"hash256"}
],
"type":"function"
},{
"constant":false,
"inputs":[
{"name":"_to","type":"address"}
],"name":"kill",
"outputs":[],
"type":"function"
},{
"constant":false,
"inputs":[
{"name":"_from","type":"address"},
{"name":"_to","type":"address"}
],
"name":"changeOwner",
"outputs":[],
"type":"function"
},{
"inputs":[
{"indexed":false,"name":"value","type":"uint256"}
],
"name":"CashIn",
"type":"event"
},{
"inputs":[
{"indexed":true,"name":"out","type":"string32"},
{"indexed":false,"name":"owner","type":"address"},
{"indexed":false,"name":"value","type":"uint256"},
{"indexed":false,"name":"to","type":"address"}
],
"name":"SingleTransact",
"type":"event"
},{
"inputs":[
{"indexed":true,"name":"out","type":"string32"},
{"indexed":false,"name":"owner","type":"address"},
{"indexed":false,"name":"operation","type":"hash256"},
{"indexed":false,"name":"value","type":"uint256"},
{"indexed":false,"name":"to","type":"address"}
],
"name":"MultiTransact",
"type":"event"
}
]);

@ -25,6 +25,7 @@ type Config struct {
DataDir string
LogFile string
LogLevel int
LogFormat string
KeyRing string
MaxPeers int
@ -39,6 +40,7 @@ type Config struct {
}
var logger = ethlogger.NewLogger("SERV")
var jsonlogger = ethlogger.NewJsonLogger()
type Ethereum struct {
// Channel for shutting down the ethereum
@ -77,7 +79,7 @@ type Ethereum struct {
func New(config *Config) (*Ethereum, error) {
// Boostrap database
logger := ethlogger.New(config.DataDir, config.LogFile, config.LogLevel)
logger := ethlogger.New(config.DataDir, config.LogFile, config.LogLevel, config.LogFormat)
db, err := ethdb.NewLDBDatabase("blockchain")
if err != nil {
return nil, err
@ -220,6 +222,13 @@ func (s *Ethereum) Coinbase() []byte {
// Start the ethereum
func (s *Ethereum) Start(seedNode string) error {
jsonlogger.LogJson(&ethlogger.LogStarting{
ClientString: s.ClientIdentity().String(),
Coinbase: ethutil.Bytes2Hex(s.KeyManager().Address()),
ProtocolVersion: ProtocolVersion,
LogEvent: ethlogger.LogEvent{Guid: ethutil.Bytes2Hex(s.ClientIdentity().Pubkey())},
})
err := s.net.Start()
if err != nil {
return err

@ -18,7 +18,7 @@ func openLogFile(datadir string, filename string) *os.File {
return file
}
func New(datadir string, logFile string, logLevel int) LogSystem {
func New(datadir string, logFile string, logLevel int, logFormat string) LogSystem {
var writer io.Writer
if logFile == "" {
writer = os.Stdout
@ -26,7 +26,13 @@ func New(datadir string, logFile string, logLevel int) LogSystem {
writer = openLogFile(datadir, logFile)
}
sys := NewStdLogSystem(writer, log.LstdFlags, LogLevel(logLevel))
var sys LogSystem
switch logFormat {
case "raw":
sys = NewRawLogSystem(writer, 0, LogLevel(logLevel))
default:
sys = NewStdLogSystem(writer, log.LstdFlags, LogLevel(logLevel))
}
AddLogSystem(sys)
return sys

@ -13,28 +13,12 @@ logging of mutable state.
package logger
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"sync"
"sync/atomic"
)
// LogSystem is implemented by log output devices.
// All methods can be called concurrently from multiple goroutines.
type LogSystem interface {
GetLogLevel() LogLevel
SetLogLevel(i LogLevel)
LogPrint(LogLevel, string)
}
type message struct {
level LogLevel
msg string
}
type LogLevel uint8
type LogLevel uint32
const (
// Standard log levels
@ -44,102 +28,9 @@ const (
InfoLevel
DebugLevel
DebugDetailLevel
JsonLevel = 1000
)
var (
logMessageC = make(chan message)
addSystemC = make(chan LogSystem)
flushC = make(chan chan struct{})
resetC = make(chan chan struct{})
)
func init() {
go dispatchLoop()
}
// each system can buffer this many messages before
// blocking incoming log messages.
const sysBufferSize = 500
func dispatchLoop() {
var (
systems []LogSystem
systemIn []chan message
systemWG sync.WaitGroup
)
bootSystem := func(sys LogSystem) {
in := make(chan message, sysBufferSize)
systemIn = append(systemIn, in)
systemWG.Add(1)
go sysLoop(sys, in, &systemWG)
}
for {
select {
case msg := <-logMessageC:
for _, c := range systemIn {
c <- msg
}
case sys := <-addSystemC:
systems = append(systems, sys)
bootSystem(sys)
case waiter := <-resetC:
// reset means terminate all systems
for _, c := range systemIn {
close(c)
}
systems = nil
systemIn = nil
systemWG.Wait()
close(waiter)
case waiter := <-flushC:
// flush means reboot all systems
for _, c := range systemIn {
close(c)
}
systemIn = nil
systemWG.Wait()
for _, sys := range systems {
bootSystem(sys)
}
close(waiter)
}
}
}
func sysLoop(sys LogSystem, in <-chan message, wg *sync.WaitGroup) {
for msg := range in {
if sys.GetLogLevel() >= msg.level {
sys.LogPrint(msg.level, msg.msg)
}
}
wg.Done()
}
// Reset removes all active log systems.
// It blocks until all current messages have been delivered.
func Reset() {
waiter := make(chan struct{})
resetC <- waiter
<-waiter
}
// Flush waits until all current log messages have been dispatched to
// the active log systems.
func Flush() {
waiter := make(chan struct{})
flushC <- waiter
<-waiter
}
// AddLogSystem starts printing messages to the given LogSystem.
func AddLogSystem(sys LogSystem) {
addSystemC <- sys
}
// A Logger prints messages prefixed by a given tag. It provides named
// Printf and Println style methods for all loglevels. Each ethereum
// component should have its own logger with a unique prefix.
@ -223,26 +114,21 @@ func (logger *Logger) Fatalf(format string, v ...interface{}) {
os.Exit(0)
}
// NewStdLogSystem creates a LogSystem that prints to the given writer.
// The flag values are defined package log.
func NewStdLogSystem(writer io.Writer, flags int, level LogLevel) LogSystem {
logger := log.New(writer, "", flags)
return &stdLogSystem{logger, uint32(level)}
type JsonLogger struct {
Coinbase string
}
type stdLogSystem struct {
logger *log.Logger
level uint32
func NewJsonLogger() *JsonLogger {
return &JsonLogger{}
}
func (t *stdLogSystem) LogPrint(level LogLevel, msg string) {
t.logger.Print(msg)
}
func (logger *JsonLogger) LogJson(v JsonLog) {
msgname := v.EventName()
obj := map[string]interface{}{
msgname: v,
}
func (t *stdLogSystem) SetLogLevel(i LogLevel) {
atomic.StoreUint32(&t.level, uint32(i))
}
jsontxt, _ := json.Marshal(obj)
logMessageC <- message{JsonLevel, string(jsontxt)}
func (t *stdLogSystem) GetLogLevel() LogLevel {
return LogLevel(atomic.LoadUint32(&t.level))
}

@ -0,0 +1,63 @@
package logger
import (
"io"
"log"
"sync/atomic"
)
// LogSystem is implemented by log output devices.
// All methods can be called concurrently from multiple goroutines.
type LogSystem interface {
GetLogLevel() LogLevel
SetLogLevel(i LogLevel)
LogPrint(LogLevel, string)
}
// NewStdLogSystem creates a LogSystem that prints to the given writer.
// The flag values are defined package log.
func NewStdLogSystem(writer io.Writer, flags int, level LogLevel) LogSystem {
logger := log.New(writer, "", flags)
return &stdLogSystem{logger, uint32(level)}
}
type stdLogSystem struct {
logger *log.Logger
level uint32
}
func (t *stdLogSystem) LogPrint(level LogLevel, msg string) {
t.logger.Print(msg)
}
func (t *stdLogSystem) SetLogLevel(i LogLevel) {
atomic.StoreUint32(&t.level, uint32(i))
}
func (t *stdLogSystem) GetLogLevel() LogLevel {
return LogLevel(atomic.LoadUint32(&t.level))
}
// NewRawLogSystem creates a LogSystem that prints to the given writer without
// adding extra information. Suitable for preformatted output
func NewRawLogSystem(writer io.Writer, flags int, level LogLevel) LogSystem {
logger := log.New(writer, "", 0)
return &rawLogSystem{logger, uint32(level)}
}
type rawLogSystem struct {
logger *log.Logger
level uint32
}
func (t *rawLogSystem) LogPrint(level LogLevel, msg string) {
t.logger.Print(msg)
}
func (t *rawLogSystem) SetLogLevel(i LogLevel) {
atomic.StoreUint32(&t.level, uint32(i))
}
func (t *rawLogSystem) GetLogLevel() LogLevel {
return LogLevel(atomic.LoadUint32(&t.level))
}

@ -0,0 +1,112 @@
package logger
import (
"sync"
)
type message struct {
level LogLevel
msg string
}
var (
logMessageC = make(chan message)
addSystemC = make(chan LogSystem)
flushC = make(chan chan struct{})
resetC = make(chan chan struct{})
)
func init() {
go dispatchLoop()
}
// each system can buffer this many messages before
// blocking incoming log messages.
const sysBufferSize = 500
func dispatchLoop() {
var (
systems []LogSystem
systemIn []chan message
systemWG sync.WaitGroup
)
bootSystem := func(sys LogSystem) {
in := make(chan message, sysBufferSize)
systemIn = append(systemIn, in)
systemWG.Add(1)
go sysLoop(sys, in, &systemWG)
}
for {
select {
case msg := <-logMessageC:
for _, c := range systemIn {
c <- msg
}
case sys := <-addSystemC:
systems = append(systems, sys)
bootSystem(sys)
case waiter := <-resetC:
// reset means terminate all systems
for _, c := range systemIn {
close(c)
}
systems = nil
systemIn = nil
systemWG.Wait()
close(waiter)
case waiter := <-flushC:
// flush means reboot all systems
for _, c := range systemIn {
close(c)
}
systemIn = nil
systemWG.Wait()
for _, sys := range systems {
bootSystem(sys)
}
close(waiter)
}
}
}
func sysLoop(sys LogSystem, in <-chan message, wg *sync.WaitGroup) {
for msg := range in {
switch sys.(type) {
case *rawLogSystem:
// This is a semantic hack since rawLogSystem has little to do with JsonLevel
if msg.level == JsonLevel {
sys.LogPrint(msg.level, msg.msg)
}
default:
if sys.GetLogLevel() >= msg.level {
sys.LogPrint(msg.level, msg.msg)
}
}
}
wg.Done()
}
// Reset removes all active log systems.
// It blocks until all current messages have been delivered.
func Reset() {
waiter := make(chan struct{})
resetC <- waiter
<-waiter
}
// Flush waits until all current log messages have been dispatched to
// the active log systems.
func Flush() {
waiter := make(chan struct{})
flushC <- waiter
<-waiter
}
// AddLogSystem starts printing messages to the given LogSystem.
func AddLogSystem(sys LogSystem) {
addSystemC <- sys
}

@ -0,0 +1,360 @@
package logger
import (
"time"
)
type utctime8601 struct{}
func (utctime8601) MarshalJSON() ([]byte, error) {
// FIX This should be re-formated for proper ISO 8601
return []byte(`"` + time.Now().UTC().Format(time.RFC3339Nano)[:26] + `Z"`), nil
}
type JsonLog interface {
EventName() string
}
type LogEvent struct {
Guid string `json:"guid"`
Ts utctime8601 `json:"ts"`
// Level string `json:"level"`
}
type LogStarting struct {
ClientString string `json:"version_string"`
Coinbase string `json:"coinbase"`
ProtocolVersion int `json:"eth_version"`
LogEvent
}
func (l *LogStarting) EventName() string {
return "starting"
}
type P2PConnecting struct {
RemoteId string `json:"remote_id"`
RemoteEndpoint string `json:"remote_endpoint"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PConnecting) EventName() string {
return "p2p.connecting"
}
type P2PConnected struct {
NumConnections int `json:"num_connections"`
RemoteId string `json:"remote_id"`
LogEvent
}
func (l *P2PConnected) EventName() string {
return "p2p.connected"
}
type P2PHandshaked struct {
RemoteCapabilities []string `json:"remote_capabilities"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PHandshaked) EventName() string {
return "p2p.handshaked"
}
type P2PDisconnected struct {
NumConnections int `json:"num_connections"`
RemoteId string `json:"remote_id"`
LogEvent
}
func (l *P2PDisconnected) EventName() string {
return "p2p.disconnected"
}
type P2PDisconnecting struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PDisconnecting) EventName() string {
return "p2p.disconnecting"
}
type P2PDisconnectingBadHandshake struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PDisconnectingBadHandshake) EventName() string {
return "p2p.disconnecting.bad_handshake"
}
type P2PDisconnectingBadProtocol struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PDisconnectingBadProtocol) EventName() string {
return "p2p.disconnecting.bad_protocol"
}
type P2PDisconnectingReputation struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PDisconnectingReputation) EventName() string {
return "p2p.disconnecting.reputation"
}
type P2PDisconnectingDHT struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PDisconnectingDHT) EventName() string {
return "p2p.disconnecting.dht"
}
type P2PEthDisconnectingBadBlock struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PEthDisconnectingBadBlock) EventName() string {
return "p2p.eth.disconnecting.bad_block"
}
type P2PEthDisconnectingBadTx struct {
Reason string `json:"reason"`
RemoteId string `json:"remote_id"`
NumConnections int `json:"num_connections"`
LogEvent
}
func (l *P2PEthDisconnectingBadTx) EventName() string {
return "p2p.eth.disconnecting.bad_tx"
}
type EthNewBlockMined struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockHexRlp string `json:"block_hexrlp"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockMined) EventName() string {
return "eth.newblock.mined"
}
type EthNewBlockBroadcasted struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockBroadcasted) EventName() string {
return "eth.newblock.broadcasted"
}
type EthNewBlockReceived struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockReceived) EventName() string {
return "eth.newblock.received"
}
type EthNewBlockIsKnown struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockIsKnown) EventName() string {
return "eth.newblock.is_known"
}
type EthNewBlockIsNew struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockIsNew) EventName() string {
return "eth.newblock.is_new"
}
type EthNewBlockMissingParent struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockMissingParent) EventName() string {
return "eth.newblock.missing_parent"
}
type EthNewBlockIsInvalid struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockIsInvalid) EventName() string {
return "eth.newblock.is_invalid"
}
type EthNewBlockChainIsOlder struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockChainIsOlder) EventName() string {
return "eth.newblock.chain.is_older"
}
type EthNewBlockChainIsCanonical struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockChainIsCanonical) EventName() string {
return "eth.newblock.chain.is_cannonical"
}
type EthNewBlockChainNotCanonical struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockChainNotCanonical) EventName() string {
return "eth.newblock.chain.not_cannonical"
}
type EthNewBlockChainSwitched struct {
BlockNumber int `json:"block_number"`
HeadHash string `json:"head_hash"`
OldHeadHash string `json:"old_head_hash"`
BlockHash string `json:"block_hash"`
BlockDifficulty int `json:"block_difficulty"`
BlockPrevHash string `json:"block_prev_hash"`
LogEvent
}
func (l *EthNewBlockChainSwitched) EventName() string {
return "eth.newblock.chain.switched"
}
type EthTxCreated struct {
TxHash string `json:"tx_hash"`
TxSender string `json:"tx_sender"`
TxAddress string `json:"tx_address"`
TxHexRLP string `json:"tx_hexrlp"`
TxNonce int `json:"tx_nonce"`
LogEvent
}
func (l *EthTxCreated) EventName() string {
return "eth.tx.created"
}
type EthTxReceived struct {
TxHash string `json:"tx_hash"`
TxAddress string `json:"tx_address"`
TxHexRLP string `json:"tx_hexrlp"`
RemoteId string `json:"remote_id"`
TxNonce int `json:"tx_nonce"`
LogEvent
}
func (l *EthTxReceived) EventName() string {
return "eth.tx.received"
}
type EthTxBroadcasted struct {
TxHash string `json:"tx_hash"`
TxSender string `json:"tx_sender"`
TxAddress string `json:"tx_address"`
TxNonce int `json:"tx_nonce"`
LogEvent
}
func (l *EthTxBroadcasted) EventName() string {
return "eth.tx.broadcasted"
}
type EthTxValidated struct {
TxHash string `json:"tx_hash"`
TxSender string `json:"tx_sender"`
TxAddress string `json:"tx_address"`
TxNonce int `json:"tx_nonce"`
LogEvent
}
func (l *EthTxValidated) EventName() string {
return "eth.tx.validated"
}
type EthTxIsInvalid struct {
TxHash string `json:"tx_hash"`
TxSender string `json:"tx_sender"`
TxAddress string `json:"tx_address"`
Reason string `json:"reason"`
TxNonce int `json:"tx_nonce"`
LogEvent
}
func (l *EthTxIsInvalid) EventName() string {
return "eth.tx.is_invalid"
}

@ -375,7 +375,10 @@ func (srv *Server) addPeer(conn net.Conn, desc *peerAddr, slot int) *Peer {
peer.slot = slot
srv.peers[slot] = peer
srv.peerCount++
go func() { peer.loop(); srv.peerDisconnect <- peer }()
go func() {
peer.loop()
srv.peerDisconnect <- peer
}()
return peer
}

Loading…
Cancel
Save