summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNickolai Zeldovich <nickolai@csail.mit.edu>2022-01-13 11:28:28 -0500
committerGitHub <noreply@github.com>2022-01-13 11:28:28 -0500
commita1edadc6b3890c7aa396636538a06896b1c8cb14 (patch)
treef7ad0a404309bc21c2e5ce4f4d6f4c561abac032
parent3904ed8f42bd8403931585a6cc0f5fe4f3748412 (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.go3
-rw-r--r--agreement/vote.go3
-rw-r--r--tools/debug/algodump/README.md33
-rw-r--r--tools/debug/algodump/main.go188
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)
+ }
+}