From 57a996b76444c76789daa9dcea3fee785217d2e6 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Fri, 29 Oct 2021 13:56:08 -0400 Subject: [PATCH 1/4] algodump is a tcpdump-like tool for algod's network protocol --- agreement/proposal.go | 3 + agreement/vote.go | 3 + debug/algodump/main.go | 190 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 debug/algodump/main.go diff --git a/agreement/proposal.go b/agreement/proposal.go index f5256decb9..c19bd34e63 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -60,6 +60,9 @@ type unauthenticatedProposal struct { OriginalProposer basics.Address `codec:"oprop"` } +// 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 c5efa40fd0..bd3b461e6d 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"` } + + // 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/debug/algodump/main.go b/debug/algodump/main.go new file mode 100644 index 0000000000..a04196862e --- /dev/null +++ b/debug/algodump/main.go @@ -0,0 +1,190 @@ +// Copyright (C) 2019-2021 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 . + +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() + } else { + return fmt.Sprintf("%s..", addr.String()[0:8]) + } +} + +func shortdigest(d crypto.Digest) string { + if *longFlag { + return d.String() + } else { + 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) + } +} From 4efb9fb42722090dea8830ab6efa48434ad5de06 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Wed, 12 Jan 2022 17:30:49 -0500 Subject: [PATCH 2/4] Fix reviewdog --- agreement/proposal.go | 2 +- agreement/vote.go | 2 +- debug/algodump/main.go | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/agreement/proposal.go b/agreement/proposal.go index c19bd34e63..506ae6cac8 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -60,7 +60,7 @@ type unauthenticatedProposal struct { OriginalProposer basics.Address `codec:"oprop"` } -// Exported for dumping textual versions of messages +// TransmittedPayload exported for dumping textual versions of messages type TransmittedPayload = transmittedPayload // ToBeHashed implements the Hashable interface. diff --git a/agreement/vote.go b/agreement/vote.go index bd3b461e6d..2decf34109 100644 --- a/agreement/vote.go +++ b/agreement/vote.go @@ -84,7 +84,7 @@ type ( Sigs [2]crypto.OneTimeSignature `codec:"sigs"` } - // Exported for dumping textual versions of messages + // UnauthenticatedVote exported for dumping textual versions of messages UnauthenticatedVote = unauthenticatedVote ) diff --git a/debug/algodump/main.go b/debug/algodump/main.go index a04196862e..39f679d2ab 100644 --- a/debug/algodump/main.go +++ b/debug/algodump/main.go @@ -49,17 +49,15 @@ type dumpHandler struct { func shortaddr(addr basics.Address) string { if *longFlag { return addr.String() - } else { - return fmt.Sprintf("%s..", addr.String()[0:8]) } + return fmt.Sprintf("%s..", addr.String()[0:8]) } func shortdigest(d crypto.Digest) string { if *longFlag { return d.String() - } else { - return fmt.Sprintf("%s..", d.String()[0:8]) } + return fmt.Sprintf("%s..", d.String()[0:8]) } func (dh *dumpHandler) Handle(msg network.IncomingMessage) network.OutgoingMessage { From 16770f56610a90ac51e75f08e624438c251773d5 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Wed, 12 Jan 2022 18:36:17 -0500 Subject: [PATCH 3/4] Fix copyright year --- tools/debug/algodump/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/debug/algodump/main.go b/tools/debug/algodump/main.go index 39f679d2ab..b5f95a1be4 100644 --- a/tools/debug/algodump/main.go +++ b/tools/debug/algodump/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2021 Algorand, Inc. +// 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 From 341efbf2b3a4c8b7d27137de9f11aa41132bb52d Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Thu, 13 Jan 2022 10:10:42 -0500 Subject: [PATCH 4/4] add a README describing algodump --- tools/debug/algodump/README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tools/debug/algodump/README.md diff --git a/tools/debug/algodump/README.md b/tools/debug/algodump/README.md new file mode 100644 index 0000000000..c3108b1268 --- /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.