diff options
author | Nickolai Zeldovich <nickolai@csail.mit.edu> | 2022-01-13 11:28:28 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-13 11:28:28 -0500 |
commit | a1edadc6b3890c7aa396636538a06896b1c8cb14 (patch) | |
tree | f7ad0a404309bc21c2e5ce4f4d6f4c561abac032 | |
parent | 3904ed8f42bd8403931585a6cc0f5fe4f3748412 (diff) |
algodump is a tcpdump-like tool for algod's network protocol (#3166)
This PR introduces algodump, a tcpdump-like tool for monitoring algod network messages.
-rw-r--r-- | agreement/proposal.go | 3 | ||||
-rw-r--r-- | agreement/vote.go | 3 | ||||
-rw-r--r-- | tools/debug/algodump/README.md | 33 | ||||
-rw-r--r-- | tools/debug/algodump/main.go | 188 |
4 files changed, 227 insertions, 0 deletions
diff --git a/agreement/proposal.go b/agreement/proposal.go index d69a2d182..30eb2ef73 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -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) diff --git a/agreement/vote.go b/agreement/vote.go index 99973d5ef..e39edd7b7 100644 --- a/agreement/vote.go +++ b/agreement/vote.go @@ -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. diff --git a/tools/debug/algodump/README.md b/tools/debug/algodump/README.md new file mode 100644 index 000000000..c3108b126 --- /dev/null +++ b/tools/debug/algodump/README.md @@ -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. diff --git a/tools/debug/algodump/main.go b/tools/debug/algodump/main.go new file mode 100644 index 000000000..b5f95a1be --- /dev/null +++ b/tools/debug/algodump/main.go @@ -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) + } +} |