Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

algodump is a tcpdump-like tool for algod's network protocol #3166

Merged
merged 5 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions agreement/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ type unauthenticatedProposal struct {
OriginalProposer basics.Address `codec:"oprop"`
}

// TransmittedPayload exported for dumping textual versions of messages
type TransmittedPayload = transmittedPayload

// ToBeHashed implements the Hashable interface.
func (p unauthenticatedProposal) ToBeHashed() (protocol.HashID, []byte) {
return protocol.Payload, protocol.Encode(&p)
Expand Down
3 changes: 3 additions & 0 deletions agreement/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type (
Proposals [2]proposalValue `codec:"props"`
Sigs [2]crypto.OneTimeSignature `codec:"sigs"`
}

// UnauthenticatedVote exported for dumping textual versions of messages
UnauthenticatedVote = unauthenticatedVote
)

// verify verifies that a vote that was received from the network is valid.
Expand Down
33 changes: 33 additions & 0 deletions tools/debug/algodump/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Algodump

This is a tool for monitoring the messages sent over Algorand's network
protocol.

By default, the tool connects to a network in the same way that `algod`
does, using SRV records and connecting to 4 relays from that list.

You can change the network by using the `-network` flag; you will likely
also need to specify the correct `-genesis` flag for that network (e.g.,
`-network testnet -genesis testnet-v1.0`).

You can also instruct `algodump` to connect to just one server. This may
be useful if you want to debug a specific server, or if you want to avoid
seeing the same message received from multiple relays. To do this, use
the `-server` flag (e.g., `-server r-po.algorand-mainnet.network:4160`).

By default, `algodump` will print all messages received. If you want to
print just some message types, use the `-tags` flag (e.g., `-tags TX`
to only see transactions, or `-tags AV` to see votes). The full list
of tag types is in `protocol/tags.go`.

Although `algodump` will print all message types, it might not know how
to meaningfully display the contents of some message types. If you
are trying to monitor messages that `algodump` doesn't know how to
pretty-print, you will see just where the message came from, the message
type, and the length of its payload. You can add more formatting code
to print the contents of other messages by adding more cases to the
`switch` statement in `dumpHandler.Handle()` in `main.go`.

Finally, `algodump` by default truncates the addresses it prints (e.g.,
the sender of a transaction or the address of a voter); you can use the
`-long` flag to print full-length addresses.
188 changes: 188 additions & 0 deletions tools/debug/algodump/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright (C) 2019-2022 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package main

import (
"flag"
"fmt"
"io"
"os"
"strings"
"time"

"github.com/algorand/go-deadlock"

"github.com/algorand/go-algorand/agreement"
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/network"
"github.com/algorand/go-algorand/protocol"
)

var serverAddress = flag.String("server", "", "Server address (host:port)")
var genesisID = flag.String("genesis", "mainnet-v1.0", "Genesis ID")
var networkID = flag.String("network", "mainnet", "Network ID")
var tags = flag.String("tags", "*", "Comma-separated list of tags to dump, or * for all")
var longFlag = flag.Bool("long", false, "Print full-length addresses and digests")

type dumpHandler struct {
tags map[protocol.Tag]bool
}

func shortaddr(addr basics.Address) string {
if *longFlag {
return addr.String()
}
return fmt.Sprintf("%s..", addr.String()[0:8])
}

func shortdigest(d crypto.Digest) string {
if *longFlag {
return d.String()
}
return fmt.Sprintf("%s..", d.String()[0:8])
}

func (dh *dumpHandler) Handle(msg network.IncomingMessage) network.OutgoingMessage {
var src string

hp, ok := msg.Sender.(network.HTTPPeer)
if ok {
a := hp.GetAddress()
if a != *serverAddress {
src = " " + hp.GetAddress()
}
}

if dh.tags != nil && !dh.tags[msg.Tag] {
return network.OutgoingMessage{Action: network.Ignore}
}

ts := time.Now().Format("15:04:05.000000")
var data string
switch msg.Tag {
case protocol.AgreementVoteTag:
var v agreement.UnauthenticatedVote
err := protocol.Decode(msg.Data, &v)
if err != nil {
data = fmt.Sprintf("[decode error: %v]", err)
goto print
}

data = fmt.Sprintf("%d/%d/%d from %s for %s", v.R.Round, v.R.Period, v.R.Step, shortaddr(v.R.Sender), shortdigest(v.R.Proposal.BlockDigest))

case protocol.ProposalPayloadTag:
var p agreement.TransmittedPayload
err := protocol.Decode(msg.Data, &p)
if err != nil {
data = fmt.Sprintf("[decode error: %v]", err)
goto print
}

data = fmt.Sprintf("proposal %s", shortdigest(crypto.Digest(p.Block.Hash())))

case protocol.TxnTag:
dec := protocol.NewDecoderBytes(msg.Data)
for {
var stx transactions.SignedTxn
err := dec.Decode(&stx)
if err == io.EOF {
break
}
if err != nil {
data = fmt.Sprintf("[decode error: %v]", err)
goto print
}
if len(data) > 0 {
data = data + ", "
}
data = data + fmt.Sprintf("%s from %s", stx.Txn.Type, shortaddr(stx.Txn.Sender))
}
}

print:
fmt.Printf("%s%s %s [%d bytes] %s\n", ts, src, msg.Tag, len(msg.Data), data)
return network.OutgoingMessage{Action: network.Ignore}
}

func setDumpHandlers(n network.GossipNode) {
var dh dumpHandler

if *tags == "*" {
// Dump all tags: nil tags
} else if *tags == "" {
// Dump nothing: empty tags
dh.tags = make(map[protocol.Tag]bool)
} else {
dh.tags = make(map[protocol.Tag]bool)
for _, t := range strings.Split(*tags, ",") {
dh.tags[protocol.Tag(t)] = true
fmt.Printf("TAG <%s>\n", t)
}
}

h := []network.TaggedMessageHandler{
{Tag: protocol.AgreementVoteTag, MessageHandler: &dh},
{Tag: protocol.CompactCertSigTag, MessageHandler: &dh},
{Tag: protocol.MsgOfInterestTag, MessageHandler: &dh},
{Tag: protocol.MsgDigestSkipTag, MessageHandler: &dh},
{Tag: protocol.NetPrioResponseTag, MessageHandler: &dh},
// {Tag: protocol.PingTag, MessageHandler: &dh},
// {Tag: protocol.PingReplyTag, MessageHandler: &dh},
{Tag: protocol.ProposalPayloadTag, MessageHandler: &dh},
{Tag: protocol.TopicMsgRespTag, MessageHandler: &dh},
{Tag: protocol.TxnTag, MessageHandler: &dh},
{Tag: protocol.UniCatchupReqTag, MessageHandler: &dh},
{Tag: protocol.UniEnsBlockReqTag, MessageHandler: &dh},
{Tag: protocol.VoteBundleTag, MessageHandler: &dh},
}
n.RegisterHandlers(h)
}

func main() {
log := logging.Base()
log.SetLevel(logging.Debug)
log.SetOutput(os.Stderr)

if *serverAddress == "" {
log.Infof("No server address specified; defaulting to DNS bootstrapping")
}

deadlock.Opts.Disable = true

flag.Parse()

conf, _ := config.LoadConfigFromDisk("/dev/null")
if *serverAddress != "" {
conf.DNSBootstrapID = ""
}

n, _ := network.NewWebsocketGossipNode(log,
conf,
[]string{*serverAddress},
*genesisID,
protocol.NetworkID(*networkID))
setDumpHandlers(n)
n.Start()

for {
time.Sleep(time.Second)
}
}