diff --git a/Peer.go b/Peer.go index 79490c6..4de701f 100644 --- a/Peer.go +++ b/Peer.go @@ -2,6 +2,7 @@ package p2p import ( "bufio" + "context" "encoding/hex" "errors" "fmt" @@ -34,8 +35,9 @@ const ( sentMsg = "Sent" receivedMsg = "Recv" - retryReadWriteMessageInterval = 10 * time.Second + retryReadWriteMessageInterval = 1 * time.Second retryReadWriteMessageAttempts = 5 + reconnectInterval = 10 * time.Second pingInterval = 2 * time.Minute connectionHealthTickerDuration = 3 * time.Minute @@ -70,6 +72,8 @@ type Peer struct { dataBatcher *batcher.Batcher[chainhash.Hash] maximumMessageSize int64 isHealthy bool + cancelReadHandler context.CancelFunc + //cancelReadHandlerWaitGroup *sync.WaitGroup } // NewPeer returns a new bitcoin peer for the provided address and configuration. @@ -122,24 +126,28 @@ func (p *Peer) initialize() { } }() + p.invBatcher = batcher.New(500, p.batchDelay, p.sendInvBatch, true) + p.dataBatcher = batcher.New(500, p.batchDelay, p.sendDataBatch, true) + if p.incomingConn != nil { p.logger.Info("Incoming connection from peer") - } else { - // reconnect if disconnected, but only on outgoing connections - go func() { - for range time.NewTicker(10 * time.Second).C { - if !p.Connected() && !p.Connecting() { - err := p.connect() - if err != nil { - p.logger.Warn("Failed to connect to peer", slog.String(errKey, err.Error())) - } - } - } - }() + return } - p.invBatcher = batcher.New(500, p.batchDelay, p.sendInvBatch, true) - p.dataBatcher = batcher.New(500, p.batchDelay, p.sendDataBatch, true) + // reconnect if disconnected, but only on outgoing connections + go func() { + + for range time.NewTicker(reconnectInterval).C { + if p.Connected() || p.Connecting() { + continue + } + + err := p.connect() + if err != nil { + p.logger.Warn("Failed to connect to peer", slog.String(errKey, err.Error())) + } + } + }() } func (p *Peer) disconnect() { @@ -187,7 +195,7 @@ func (p *Peer) connect() error { p.readConn = conn } - go p.readHandler() + p.startReadHandler() // write version message to our peer directly and not through the write channel, // write channel is not ready to send message until the VERACK handshake is done @@ -247,8 +255,17 @@ func (p *Peer) String() string { return p.address } -func (p *Peer) readRetry(r io.Reader, pver uint32, bsvnet wire.BitcoinNet) (wire.Message, error) { +func (p *Peer) readRetry(ctx context.Context, r io.Reader, pver uint32, bsvnet wire.BitcoinNet) (wire.Message, error) { policy := backoff.WithMaxRetries(backoff.NewConstantBackOff(retryReadWriteMessageInterval), retryReadWriteMessageAttempts) + + //ctx, cancel := context.WithCancel(context.Background()) + //ctx := context.Background() + policyContext := backoff.WithContext(policy, ctx) + + //p.mu.Lock() + //p.cancelReadHandler = cancel + //p.mu.Unlock() + operation := func() (wire.Message, error) { msg, _, err := wire.ReadMessage(r, pver, bsvnet) if err != nil { @@ -263,14 +280,9 @@ func (p *Peer) readRetry(r io.Reader, pver uint32, bsvnet wire.BitcoinNet) (wire } else { p.logger.Error("Failed to read message", slog.String("next try", nextTry.String()), slog.String(errKey, err.Error())) } - - err = p.connect() - if err != nil { - p.logger.Error("Failed to reconnect", slog.String("next try", nextTry.String()), slog.String(errKey, err.Error())) - } } - msg, err := backoff.RetryNotifyWithData(operation, policy, notifyAndReconnect) + msg, err := backoff.RetryNotifyWithData(operation, policyContext, notifyAndReconnect) if err != nil { return nil, err } @@ -278,147 +290,186 @@ func (p *Peer) readRetry(r io.Reader, pver uint32, bsvnet wire.BitcoinNet) (wire return msg, nil } -func (p *Peer) readHandler() { - readConn := p.readConn +func (p *Peer) startReadHandler() { + ctx, cancel := context.WithCancel(context.Background()) + p.cancelReadHandler = cancel - if readConn == nil { - p.logger.Error("no connection") - return - } + p.logger.Info("Starting read handler") - reader := bufio.NewReader(&io.LimitedReader{R: readConn, N: p.maximumMessageSize}) - for { - msg, err := p.readRetry(reader, wire.ProtocolVersion, p.network) - if err != nil { - p.logger.Error("Failed to read", slog.String(errKey, err.Error())) - continue + //p.cancelReadHandlerWaitGroup = &sync.WaitGroup{} + + go func(cancelCtx context.Context) { + defer func() { + //p.mu.Lock() + //defer p.mu.Unlock() + p.logger.Info("Shutting down read handler") + //p.cancelReadHandlerWaitGroup.Done() + }() + + //p.mu.Lock() + //p.cancelReadHandlerWaitGroup.Add(1) + //p.mu.Unlock() + + readConn := p.readConn + var msg wire.Message + var err error + + if readConn == nil { + p.cancelReadHandler = nil + p.logger.Error("no connection") + return } - commandLogger := p.logger.With(slog.String(commandKey, strings.ToUpper(msg.Command()))) + reader := bufio.NewReader(&io.LimitedReader{R: readConn, N: p.maximumMessageSize}) + for { + select { + case <-cancelCtx.Done(): + return + default: + msg, err = p.readRetry(cancelCtx, reader, wire.ProtocolVersion, p.network) + if err != nil { + if errors.Is(err, context.Canceled) { + p.logger.Info("Retrying to read cancelled") + return + } - // we could check this based on type (switch msg.(type)) but that would not allow - // us to override the default behaviour for a specific message type - switch msg.Command() { - case wire.CmdVersion: - commandLogger.Debug(receivedMsg) - if p.sentVerAck.Load() { - commandLogger.Warn("Received version message after sending verack") - continue - } + p.logger.Error("Retrying to read failed", slog.String(errKey, err.Error())) - verackMsg := wire.NewMsgVerAck() - if err = wire.WriteMessage(readConn, verackMsg, wire.ProtocolVersion, p.network); err != nil { - commandLogger.Error("failed to write message", slog.String(errKey, err.Error())) - } - commandLogger.Debug(sentMsg, slog.String(commandKey, strings.ToUpper(verackMsg.Command()))) - p.sentVerAck.Store(true) + p.disconnect() - case wire.CmdPing: - commandLogger.Debug(receivedMsg, slog.String(commandKey, strings.ToUpper(wire.CmdPing))) - p.pingPongAlive <- struct{}{} + p.mu.Lock() + p.cancelReadHandler = nil + p.mu.Unlock() - pingMsg, ok := msg.(*wire.MsgPing) - if !ok { - continue - } - p.writeChan <- wire.NewMsgPong(pingMsg.Nonce) + return + } - case wire.CmdInv: - invMsg, ok := msg.(*wire.MsgInv) - if !ok { - continue - } - for _, inv := range invMsg.InvList { - commandLogger.Debug(receivedMsg, slog.String(hashKey, inv.Hash.String()), slog.String(typeKey, inv.Type.String())) - } + commandLogger := p.logger.With(slog.String(commandKey, strings.ToUpper(msg.Command()))) - go func(invList []*wire.InvVect, routineLogger *slog.Logger) { - for _, invVect := range invList { - switch invVect.Type { - case wire.InvTypeTx: - if err = p.peerHandler.HandleTransactionAnnouncement(invVect, p); err != nil { - commandLogger.Error("Unable to process tx", slog.String(hashKey, invVect.Hash.String()), slog.String(typeKey, invVect.Type.String()), slog.String(errKey, err.Error())) - } - case wire.InvTypeBlock: - if err = p.peerHandler.HandleBlockAnnouncement(invVect, p); err != nil { - commandLogger.Error("Unable to process block", slog.String(hashKey, invVect.Hash.String()), slog.String(typeKey, invVect.Type.String()), slog.String(errKey, err.Error())) - } + // we could check this based on type (switch msg.(type)) but that would not allow + // us to override the default behaviour for a specific message type + switch msg.Command() { + case wire.CmdVersion: + commandLogger.Debug(receivedMsg) + if p.sentVerAck.Load() { + commandLogger.Warn("Received version message after sending verack") + continue } - } - }(invMsg.InvList, commandLogger) - case wire.CmdGetData: - dataMsg, ok := msg.(*wire.MsgGetData) - if !ok { - continue - } - for _, inv := range dataMsg.InvList { - commandLogger.Debug(receivedMsg, slog.String(hashKey, inv.Hash.String()), slog.String(typeKey, inv.Type.String())) - } - p.handleGetDataMsg(dataMsg, commandLogger) + verackMsg := wire.NewMsgVerAck() + if err = wire.WriteMessage(readConn, verackMsg, wire.ProtocolVersion, p.network); err != nil { + commandLogger.Error("failed to write message", slog.String(errKey, err.Error())) + } + commandLogger.Debug(sentMsg, slog.String(commandKey, strings.ToUpper(verackMsg.Command()))) + p.sentVerAck.Store(true) - case wire.CmdTx: - txMsg, ok := msg.(*wire.MsgTx) - if !ok { - continue - } - commandLogger.Debug(receivedMsg, slog.String(hashKey, txMsg.TxHash().String()), slog.Int("size", txMsg.SerializeSize())) - if err = p.peerHandler.HandleTransaction(txMsg, p); err != nil { - commandLogger.Error("Unable to process tx", slog.String(hashKey, txMsg.TxHash().String()), slog.String(errKey, err.Error())) - } + case wire.CmdPing: + commandLogger.Debug(receivedMsg, slog.String(commandKey, strings.ToUpper(wire.CmdPing))) + p.pingPongAlive <- struct{}{} - case wire.CmdBlock: - msgBlock, ok := msg.(*wire.MsgBlock) - if ok { - commandLogger.Info(receivedMsg, slog.String(hashKey, msgBlock.Header.BlockHash().String())) + pingMsg, ok := msg.(*wire.MsgPing) + if !ok { + continue + } + p.writeChan <- wire.NewMsgPong(pingMsg.Nonce) - err = p.peerHandler.HandleBlock(msgBlock, p) - if err != nil { - commandLogger.Error("Unable to process block", slog.String(hashKey, msgBlock.Header.BlockHash().String()), slog.String(errKey, err.Error())) - } - continue - } + case wire.CmdInv: + invMsg, ok := msg.(*wire.MsgInv) + if !ok { + continue + } + for _, inv := range invMsg.InvList { + commandLogger.Debug(receivedMsg, slog.String(hashKey, inv.Hash.String()), slog.String(typeKey, inv.Type.String())) + } - // Please note that this is the BlockMessage, not the wire.MsgBlock - blockMsg, ok := msg.(*BlockMessage) - if !ok { - commandLogger.Error("Unable to cast block message, calling with generic wire.Message") - err = p.peerHandler.HandleBlock(msg, p) - if err != nil { - commandLogger.Error("Unable to process block message", slog.String(errKey, err.Error())) - } - continue - } + go func(invList []*wire.InvVect, routineLogger *slog.Logger) { + for _, invVect := range invList { + switch invVect.Type { + case wire.InvTypeTx: + if err = p.peerHandler.HandleTransactionAnnouncement(invVect, p); err != nil { + commandLogger.Error("Unable to process tx", slog.String(hashKey, invVect.Hash.String()), slog.String(typeKey, invVect.Type.String()), slog.String(errKey, err.Error())) + } + case wire.InvTypeBlock: + if err = p.peerHandler.HandleBlockAnnouncement(invVect, p); err != nil { + commandLogger.Error("Unable to process block", slog.String(hashKey, invVect.Hash.String()), slog.String(typeKey, invVect.Type.String()), slog.String(errKey, err.Error())) + } + } + } + }(invMsg.InvList, commandLogger) - commandLogger.Info(receivedMsg, slog.String(hashKey, blockMsg.Header.BlockHash().String())) + case wire.CmdGetData: + dataMsg, ok := msg.(*wire.MsgGetData) + if !ok { + continue + } + for _, inv := range dataMsg.InvList { + commandLogger.Debug(receivedMsg, slog.String(hashKey, inv.Hash.String()), slog.String(typeKey, inv.Type.String())) + } + p.handleGetDataMsg(dataMsg, commandLogger) - err = p.peerHandler.HandleBlock(blockMsg, p) - if err != nil { - commandLogger.Error("Unable to process block", slog.String(hashKey, blockMsg.Header.BlockHash().String()), slog.String(errKey, err.Error())) - } + case wire.CmdTx: + txMsg, ok := msg.(*wire.MsgTx) + if !ok { + continue + } + commandLogger.Debug(receivedMsg, slog.String(hashKey, txMsg.TxHash().String()), slog.Int("size", txMsg.SerializeSize())) + if err = p.peerHandler.HandleTransaction(txMsg, p); err != nil { + commandLogger.Error("Unable to process tx", slog.String(hashKey, txMsg.TxHash().String()), slog.String(errKey, err.Error())) + } - case wire.CmdReject: - rejMsg, ok := msg.(*wire.MsgReject) - if !ok { - continue - } - if err = p.peerHandler.HandleTransactionRejection(rejMsg, p); err != nil { - commandLogger.Error("Unable to process block", slog.String(hashKey, rejMsg.Hash.String()), slog.String(errKey, err.Error())) - } + case wire.CmdBlock: + msgBlock, ok := msg.(*wire.MsgBlock) + if ok { + commandLogger.Info(receivedMsg, slog.String(hashKey, msgBlock.Header.BlockHash().String())) - case wire.CmdVerAck: - commandLogger.Debug(receivedMsg) - p.receivedVerAck.Store(true) + err = p.peerHandler.HandleBlock(msgBlock, p) + if err != nil { + commandLogger.Error("Unable to process block", slog.String(hashKey, msgBlock.Header.BlockHash().String()), slog.String(errKey, err.Error())) + } + continue + } - case wire.CmdPong: - commandLogger.Debug(receivedMsg, slog.String(commandKey, strings.ToUpper(wire.CmdPong))) - p.pingPongAlive <- struct{}{} + // Please note that this is the BlockMessage, not the wire.MsgBlock + blockMsg, ok := msg.(*BlockMessage) + if !ok { + commandLogger.Error("Unable to cast block message, calling with generic wire.Message") + err = p.peerHandler.HandleBlock(msg, p) + if err != nil { + commandLogger.Error("Unable to process block message", slog.String(errKey, err.Error())) + } + continue + } - default: + commandLogger.Info(receivedMsg, slog.String(hashKey, blockMsg.Header.BlockHash().String())) + + err = p.peerHandler.HandleBlock(blockMsg, p) + if err != nil { + commandLogger.Error("Unable to process block", slog.String(hashKey, blockMsg.Header.BlockHash().String()), slog.String(errKey, err.Error())) + } + + case wire.CmdReject: + rejMsg, ok := msg.(*wire.MsgReject) + if !ok { + continue + } + if err = p.peerHandler.HandleTransactionRejection(rejMsg, p); err != nil { + commandLogger.Error("Unable to process block", slog.String(hashKey, rejMsg.Hash.String()), slog.String(errKey, err.Error())) + } + + case wire.CmdVerAck: + commandLogger.Debug(receivedMsg) + p.receivedVerAck.Store(true) + + case wire.CmdPong: + commandLogger.Debug(receivedMsg, slog.String(commandKey, strings.ToUpper(wire.CmdPong))) + p.pingPongAlive <- struct{}{} - commandLogger.Debug("command ignored") + default: + commandLogger.Debug("command ignored") + } + } } - } + }(ctx) } func (p *Peer) handleGetDataMsg(dataMsg *wire.MsgGetData, logger *slog.Logger) { @@ -531,12 +582,7 @@ func (p *Peer) writeRetry(msg wire.Message) error { } notifyAndReconnect := func(err error, nextTry time.Duration) { - p.logger.Error("Failed to write message", slog.String("next try", nextTry.String()), slog.String(errKey, err.Error())) - - err = p.connect() - if err != nil { - p.logger.Error("Failed to reconnect", slog.String("next try", nextTry.String()), slog.String(errKey, err.Error())) - } + p.logger.Error("Failed to write message", slog.Duration("next try", nextTry), slog.String(errKey, err.Error())) } return backoff.RetryNotify(operation, policy, notifyAndReconnect) @@ -667,3 +713,10 @@ func (p *Peer) IsHealthy() bool { return p.isHealthy } + +func (p *Peer) Shutdown() { + if p.cancelReadHandler != nil { + p.cancelReadHandler() + //p.cancelReadHandlerWaitGroup.Wait() + } +} diff --git a/PeerManager.go b/PeerManager.go index 524309a..d5cf082 100644 --- a/PeerManager.go +++ b/PeerManager.go @@ -85,7 +85,7 @@ func (pm *PeerManager) RequestTransaction(txHash *chainhash.Hash) PeerI { // send to the first found peer that is connected var sendToPeer PeerI for _, peer := range pm.GetAnnouncedPeers() { - if peer.Connected() { + if peer.Connected() && peer.IsHealthy() { sendToPeer = peer break } @@ -117,7 +117,7 @@ func (pm *PeerManager) RequestBlock(blockHash *chainhash.Hash) PeerI { // send to the first found peer that is connected var sendToPeer PeerI for _, peer := range pm.GetAnnouncedPeers() { - if peer.Connected() { + if peer.Connected() && peer.IsHealthy() { sendToPeer = peer break } @@ -140,7 +140,7 @@ func (pm *PeerManager) GetAnnouncedPeers() []PeerI { // Get a list of peers that are connected connectedPeers := make([]PeerI, 0, len(pm.peers)) for _, peer := range pm.peers { - if peer.Connected() { + if peer.Connected() && peer.IsHealthy() { connectedPeers = append(connectedPeers, peer) } } diff --git a/PeerManager_test.go b/PeerManager_test.go index 4e599a5..9922d61 100644 --- a/PeerManager_test.go +++ b/PeerManager_test.go @@ -1,7 +1,6 @@ package p2p import ( - "fmt" "log/slog" "os" "testing" @@ -39,10 +38,13 @@ func TestNewPeerManager(t *testing.T) { err = pm.AddPeer(peer) require.NoError(t, err) assert.Len(t, pm.GetPeers(), 1) + peer.Shutdown() + + time.Sleep(200 * time.Millisecond) }) t.Run("1 peer - de dup", func(t *testing.T) { - peers := []string{ + peerAddresses := []string{ "localhost:18333", "localhost:18333", "localhost:18333", @@ -54,74 +56,79 @@ func TestNewPeerManager(t *testing.T) { peerHandler := NewMockPeerHandler() - for _, peerAddress := range peers { + peers := make([]*Peer, len(peerAddresses)) + for i, peerAddress := range peerAddresses { peer, _ := NewPeer(logger, peerAddress, peerHandler, wire.TestNet) _ = pm.AddPeer(peer) + peers[i] = peer } assert.Len(t, pm.GetPeers(), 4) - }) -} - -func TestAnnounceNewTransaction(t *testing.T) { - t.Run("announce tx", func(t *testing.T) { - logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) - pm := NewPeerManager(logger, wire.TestNet, WithBatchDuration(1*time.Millisecond)) - require.NotNil(t, pm) - - peerHandler := NewMockPeerHandler() - - peer, _ := NewPeerMock("localhost:18333", peerHandler, wire.TestNet) - err := pm.AddPeer(peer) - require.NoError(t, err) - - pm.AnnounceTransaction(tx1Hash, nil) - - // we need to wait for the batcher to send the inv - time.Sleep(5 * time.Millisecond) - - announcements := peer.GetAnnouncements() - require.Len(t, announcements, 1) - assert.Equal(t, tx1Hash, announcements[0]) - }) - t.Run("announce tx - multiple peers", func(t *testing.T) { - logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) - pm := NewPeerManager(logger, wire.TestNet, WithBatchDuration(1*time.Millisecond)) - require.NotNil(t, pm) - - peerHandler := NewMockPeerHandler() - - numberOfPeers := 5 - peers := make([]*PeerMock, numberOfPeers) - for i := 0; i < numberOfPeers; i++ { - peers[i], _ = NewPeerMock(fmt.Sprintf("localhost:1833%d", i), peerHandler, wire.TestNet) - err := pm.AddPeer(peers[i]) - require.NoError(t, err) - } - - pm.AnnounceTransaction(tx1Hash, nil) - - // we need to wait for the batcher to send the inv - time.Sleep(5 * time.Millisecond) - - peersMessaged := 0 for _, peer := range peers { - announcements := peer.GetAnnouncements() - if len(announcements) == 0 { - continue - } - - require.Len(t, announcements, 1) - assert.Equal(t, tx1Hash, announcements[0]) - peersMessaged++ + peer.Shutdown() } - assert.GreaterOrEqual(t, peersMessaged, len(peers)/2) - }) -} -func TestPeerManager_addPeer(t *testing.T) { + time.Sleep(200 * time.Millisecond) + }) } -func TestPeerManager_sendInvBatch(t *testing.T) { -} +// +//func TestAnnounceNewTransaction(t *testing.T) { +// t.Run("announce tx", func(t *testing.T) { +// logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) +// pm := NewPeerManager(logger, wire.TestNet, WithBatchDuration(1*time.Millisecond)) +// require.NotNil(t, pm) +// +// peerHandler := NewMockPeerHandler() +// +// peer, _ := NewPeerMock("localhost:18333", peerHandler, wire.TestNet) +// err := pm.AddPeer(peer) +// +// require.NoError(t, err) +// +// pm.AnnounceTransaction(tx1Hash, nil) +// +// // we need to wait for the batcher to send the inv +// time.Sleep(5 * time.Millisecond) +// +// announcements := peer.GetAnnouncements() +// require.Len(t, announcements, 1) +// assert.Equal(t, tx1Hash, announcements[0]) +// +// }) +// +// t.Run("announce tx - multiple peers", func(t *testing.T) { +// logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) +// pm := NewPeerManager(logger, wire.TestNet, WithBatchDuration(1*time.Millisecond)) +// require.NotNil(t, pm) +// +// peerHandler := NewMockPeerHandler() +// +// numberOfPeers := 5 +// peers := make([]*PeerMock, numberOfPeers) +// for i := 0; i < numberOfPeers; i++ { +// peers[i], _ = NewPeerMock(fmt.Sprintf("localhost:1833%d", i), peerHandler, wire.TestNet) +// err := pm.AddPeer(peers[i]) +// require.NoError(t, err) +// } +// +// pm.AnnounceTransaction(tx1Hash, nil) +// +// // we need to wait for the batcher to send the inv +// time.Sleep(5 * time.Millisecond) +// +// peersMessaged := 0 +// for _, peer := range peers { +// announcements := peer.GetAnnouncements() +// if len(announcements) == 0 { +// continue +// } +// +// require.Len(t, announcements, 1) +// assert.Equal(t, tx1Hash, announcements[0]) +// peersMessaged++ +// } +// assert.GreaterOrEqual(t, peersMessaged, len(peers)/2) +// }) +//} diff --git a/Peer_Mock.go b/Peer_Mock.go index 53b54c2..dc2fa35 100644 --- a/Peer_Mock.go +++ b/Peer_Mock.go @@ -47,6 +47,8 @@ func (p *PeerMock) IsHealthy() bool { return true } +func (p *PeerMock) Shutdown() {} + func (p *PeerMock) Connected() bool { return true } diff --git a/go.mod b/go.mod index ea330ed..1ffeeea 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,39 @@ go 1.21.3 require ( github.com/cbeuw/connutil v0.0.0-20200411215123-966bfaa51ee3 + github.com/cenkalti/backoff/v4 v4.2.1 github.com/davecgh/go-spew v1.1.1 github.com/ordishs/go-utils v1.0.24 + github.com/ory/dockertest/v3 v3.10.0 github.com/stretchr/testify v1.8.1 ) require ( - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/lmittmann/tint v1.0.3 // indirect + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/containerd/continuity v0.3.0 // indirect + github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/docker v20.10.7+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/tools v0.7.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 927f7c6..f463d5a 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,166 @@ +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/cbeuw/connutil v0.0.0-20200411215123-966bfaa51ee3 h1:LRxW8pdmWmyhoNh+TxUjxsAinGtCsVGjsl3xg6zoRSs= github.com/cbeuw/connutil v0.0.0-20200411215123-966bfaa51ee3/go.mod h1:6jR2SzckGv8hIIS9zWJ160mzGVVOYp4AXZMDtacL6LE= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/lmittmann/tint v1.0.3 h1:W5PHeA2D8bBJVvabNfQD/XW9HPLZK1XoPZH0cq8NouQ= -github.com/lmittmann/tint v1.0.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/ordishs/go-utils v1.0.24 h1:QIVYaaN4LE5SnCRbLii7OOrkC2Qqvg4FVKe9zzA3Yd8= github.com/ordishs/go-utils v1.0.24/go.mod h1:k9G7Bbv2GwoOn9fwZx70yM5jwwIymkv+90FUKLudtyc= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= diff --git a/interface.go b/interface.go index 6d98c9d..0862a14 100644 --- a/interface.go +++ b/interface.go @@ -30,6 +30,7 @@ type PeerI interface { RequestBlock(blockHash *chainhash.Hash) Network() wire.BitcoinNet IsHealthy() bool + Shutdown() } type PeerHandlerI interface { diff --git a/peer_integration_test.go b/peer_integration_test.go new file mode 100644 index 0000000..30db107 --- /dev/null +++ b/peer_integration_test.go @@ -0,0 +1,115 @@ +package p2p + +import ( + "fmt" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "log" + "os" + "testing" +) + +var ( + pool *dockertest.Pool + resource *dockertest.Resource + pwd string +) + +func TestMain(m *testing.M) { + var err error + + pool, err = dockertest.NewPool("") + if err != nil { + log.Fatalf("failed to create pool: %v", err) + } + + const p2pPort = "18333" + pwd, err = os.Getwd() + if err != nil { + log.Fatalf("failed to get working directory: %s", err) + } + + resource, err = pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "bitcoinsv/bitcoin-sv", + Tag: "1.1.0", + Env: []string{}, + ExposedPorts: []string{p2pPort}, + PortBindings: map[docker.Port][]docker.PortBinding{ + p2pPort: { + {HostIP: "0.0.0.0", HostPort: p2pPort}, + }, + }, + Cmd: []string{"/entrypoint.sh", "bitcoind"}, + Name: "node", + }, func(config *docker.HostConfig) { + // set AutoRemove to true so that stopped container goes away by itself + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } + config.Mounts = []docker.HostMount{ + { + Target: "/data/bitcoin.conf", + Source: fmt.Sprintf("%s/peer_integration_test_config/bitcoin.conf", pwd), + Type: "bind", + }, + } + }) + if err != nil { + log.Fatalf("failed to create resource: %v", err) + } + + code := m.Run() + + err = pool.Purge(resource) + if err != nil { + log.Fatalf("failed to purge pool: %v", err) + } + + os.Exit(code) +} + +// +//func TestNewPeer(t *testing.T) { +// t.Helper() +// if testing.Short() { +// t.Skip("skipping integration test") +// } +// +// t.Run("break and re-establish peer connection", func(t *testing.T) { +// logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) +// +// pm := NewPeerManager(logger, wire.TestNet) +// require.NotNil(t, pm) +// +// peerHandler := NewMockPeerHandler() +// +// time.Sleep(5 * time.Second) +// +// peer, err := NewPeer(logger, "localhost:18333", peerHandler, wire.TestNet) +// require.NoError(t, err) +// +// time.Sleep(5 * time.Second) +// +// require.True(t, peer.Connected()) +// +// dockerClient := pool.Client +// +// // restart container and break connection +// err = dockerClient.RestartContainer(resource.Container.ID, 10) +// require.NoError(t, err) +// +// time.Sleep(6 * time.Second) +// +// // expect that peer has disconnected +// require.False(t, peer.Connected()) +// +// // wait longer than the reconnect interval and expect that peer has re-established connection +// time.Sleep(reconnectInterval + 2*time.Second) +// require.True(t, peer.Connected()) +// +// //err = dockerClient.StopContainer(resource.Container.ID, 10) +// require.NoError(t, err) +// peer.Shutdown() +// }) +//} diff --git a/peer_integration_test_config/bitcoin.conf b/peer_integration_test_config/bitcoin.conf new file mode 100644 index 0000000..ad421e8 --- /dev/null +++ b/peer_integration_test_config/bitcoin.conf @@ -0,0 +1,145 @@ +server=1 +rest=1 +listen=1 +regtest=1 +printtoconsole=1 +txindex=1 +dnsseed=0 +upnp=0 +usecashaddr=0 +debug=1 + +#Bind to given address to listen for JSON-RPC connections. Use +#[host]:port notation for IPv6. This option can be specified +#multiple times (default: bind to all interfaces) +#rpcbind= + +rpcport=18332 +rpcuser=bitcoin +rpcpassword=bitcoin +rpcallowip=0.0.0.0/0 + +port=18333 + +# onlynet=ipv4 +listenonion=0 +# prune=550 + +addressindex=1 +timestampindex=1 +spentindex=1 + +#Allow JSON-RPC connections from specified source. Valid for are a +#single IP (e.g. 1.2.3.4), a network/netmask (e.g. +#1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This +#option can be specified multiple times +#rpcallowip= +#Set the number of threads to service RPC calls (default: 4) +rpcthreads=24 + +#Set the depth of the work queue to service RPC calls (default: 16) +rpcworkqueue=600 + +#Timeout during HTTP requests (default: 30) +#rpcservertimeout= +blockmaxsize=512000000 +excessiveblocksize=2000000000 +maxstackmemoryusageconsensus=200000000 + +#connect=142.93.36.189:9988 + +#node filter +#whitelist=127.0.0.1 + +#Append comment to the user agent string +#uacomment= + +#Maximum database write batch size in bytes (default: 16777216) +#dbbatchsize=16777216 + +#Set database cache size in megabytes (4 to 16384, default: 450) +dbcache=16384 + +#Limit size of signature cache to MiB (default: 32) +maxsigcachesize=260 + +#Limit size of script cache to MiB (default: 32) +maxscriptcachesize=260 + +#Keep at most unconnectable transactions in memory (default: 100) +maxorphantx=100000 + +#Keep the transaction memory pool below megabytes (default: 300) +maxmempool=2000 + +#Do not keep transactions in the mempool longer than hours (default: 336) +#mempoolexpiry=48 + +#Extra transactions to keep in memory for compact block reconstructions(default: 100) +blockreconstructionextratxn=100000 + +#Set the number of script verification threads (-4 to 16, 0 = auto, <0 = +#leave that many cores free, default: 0) +#par= + +#Threshold for disconnecting misbehaving peers (default: 100) +#banscore= + +#Number of seconds to keep misbehaving peers from reconnecting (default: 86400) +#bantime= + +#Maintain at most connections to peers (default: 125) +#maxconnections= + +#Maximum per-connection receive buffer, *1000 bytes (default: 5000) +#maxreceivebuffer= + +#Maximum per-connection send buffer, *1000 bytes (default: 1000) +#maxsendbuffer= + +#Specify connection timeout in milliseconds (minimum: 1, default: 5000) +#timeout= + +#ZeroMQ notification options: + +#Enable publish hash block in
+zmqpubhashblock=tcp://*:28332 + +#Enable publish hash transaction in
+zmqpubhashtx=tcp://*:28332 +zmqpubhashtx2=tcp://*:28332 + +#Enable publish raw block in
+zmqpubrawblock=tcp://*:28332 +zmqpubrawblock2=tcp://*:28332 + +#Enable publish raw transaction in
+zmqpubrawtx=tcp://*:28332 + +invalidtxsink=ZMQ +zmqpubinvalidtx=tcp://*:28332 +zmqpubdiscardedfrommempool=tcp://*:28332 + +#Do not accept transactions if number of in-mempool ancestors is or more (default: 1000) +#limitancestorcount= + +#Do not accept transactions whose size with all in-mempool ancestors exceeds kilobytes (default: 101) +#limitancestorsize= + +#Do not accept transactions if any ancestor would have or more in-mempool descendants (default: 25) +#limitdescendantcount= + +#Do not accept transactions if any ancestor would have more than kilobytes of in-mempool descendants (default: 101). +#limitdescendantsize= + +#Fees (in BCH/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: 0.00001) +#minrelaytxfee= + +#Relay and mine "non-standard" transactions (testnet/regtest only; default: 1) +#acceptnonstdtxn=0 + +#Use genesis rules from the 1st block instead of waiting until 10,000 (default) blocks to activate genesis rules. +genesisactivationheight=1 + +minminingtxfee=0.0000005 +whitelist=172.20.0.1/32