-
Notifications
You must be signed in to change notification settings - Fork 636
consensus: rename (*PeerState).ToJSON to MarshalJSON #524
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
Conversation
In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler). Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an unsynchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote. Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great find.
It appears that the renamed method is not used anywhere else, except in the RPC code addressed by this PR. So I would say that is not a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this @mark-rushakoff! I'd say that this should probably be safe to backport to v0.37 and v0.34, given the scope of the change. If folks complain about it, we could always introduce a shim ToJSON
method that just implicitly calls MarshalJSON
.
In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler). Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an unsynchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote. Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called. Example data race (which is no longer reproducible with this change): ``` ================== WARNING: DATA RACE Read at 0x00c0004a4870 by goroutine 131: reflect.Value.Bool() /opt/homebrew/Cellar/go/1.20.1/libexec/src/reflect/value.go:288 +0x7c encoding/json.boolEncoder() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:539 +0x88 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.ptrEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:943 +0x2a4 encoding/json.ptrEncoder.encode-fm() <autogenerated>:1 +0x6c encoding/json.(*encodeState).reflectValue() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:358 +0x74 encoding/json.(*encodeState).marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:330 +0x1a0 encoding/json.Marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:161 +0xa0 github.com/rs/zerolog.init.1.func1() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/encoder_json.go:21 +0x4c github.com/rs/zerolog/internal/json.Encoder.AppendInterface() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/internal/json/types.go:366 +0x5c github.com/rs/zerolog.appendFieldList() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:273 +0x2b8c github.com/rs/zerolog.appendFields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:21 +0x160 github.com/rs/zerolog.(*Event).Fields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/event.go:165 +0x90 cosmossdk.io/log.zeroLogWrapper.Debug() /Users/hh/go/pkg/mod/cosmossdk.io/log@v0.0.0-20230313123454-0fe816b71a62/logger.go:89 +0x18 github.com/cosmos/cosmos-sdk/server/log.(*CometZeroLogWrapper).Debug() <autogenerated>:1 +0x74 github.com/cometbft/cometbft/consensus.(*PeerState).PickSendVote() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1138 +0x1bc github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesForHeight() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:794 +0x260 github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:724 +0x2cc github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func2() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x58 Previous write at 0x00c0004a4870 by goroutine 130: github.com/cometbft/cometbft/consensus.(*PeerState).SetHasProposal() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1096 +0x118 github.com/cometbft/cometbft/consensus.(*Reactor).gossipDataRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:617 +0xab8 github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func1() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x58 Goroutine 131 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x240 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 Goroutine 130 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x164 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 ================== ``` --- #### PR checklist - [ ] Tests written/updated - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit 587bc0b)
In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler). Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an unsynchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote. Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called. Example data race (which is no longer reproducible with this change): ``` ================== WARNING: DATA RACE Read at 0x00c0004a4870 by goroutine 131: reflect.Value.Bool() /opt/homebrew/Cellar/go/1.20.1/libexec/src/reflect/value.go:288 +0x7c encoding/json.boolEncoder() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:539 +0x88 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.ptrEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:943 +0x2a4 encoding/json.ptrEncoder.encode-fm() <autogenerated>:1 +0x6c encoding/json.(*encodeState).reflectValue() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:358 +0x74 encoding/json.(*encodeState).marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:330 +0x1a0 encoding/json.Marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:161 +0xa0 github.com/rs/zerolog.init.1.func1() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/encoder_json.go:21 +0x4c github.com/rs/zerolog/internal/json.Encoder.AppendInterface() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/internal/json/types.go:366 +0x5c github.com/rs/zerolog.appendFieldList() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:273 +0x2b8c github.com/rs/zerolog.appendFields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:21 +0x160 github.com/rs/zerolog.(*Event).Fields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/event.go:165 +0x90 cosmossdk.io/log.zeroLogWrapper.Debug() /Users/hh/go/pkg/mod/cosmossdk.io/log@v0.0.0-20230313123454-0fe816b71a62/logger.go:89 +0x18 github.com/cosmos/cosmos-sdk/server/log.(*CometZeroLogWrapper).Debug() <autogenerated>:1 +0x74 github.com/cometbft/cometbft/consensus.(*PeerState).PickSendVote() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1138 +0x1bc github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesForHeight() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:794 +0x260 github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:724 +0x2cc github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func2() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x58 Previous write at 0x00c0004a4870 by goroutine 130: github.com/cometbft/cometbft/consensus.(*PeerState).SetHasProposal() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1096 +0x118 github.com/cometbft/cometbft/consensus.(*Reactor).gossipDataRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:617 +0xab8 github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func1() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x58 Goroutine 131 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x240 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 Goroutine 130 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x164 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 ================== ``` --- #### PR checklist - [ ] Tests written/updated - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit 587bc0b) # Conflicts: # .changelog/v0.37.0/bug-fixes/524-rename-peerstate-tojson.md
…532) * consensus: rename (*PeerState).ToJSON to MarshalJSON (#524) In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler). Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an unsynchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote. Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called. Example data race (which is no longer reproducible with this change): ``` ================== WARNING: DATA RACE Read at 0x00c0004a4870 by goroutine 131: reflect.Value.Bool() /opt/homebrew/Cellar/go/1.20.1/libexec/src/reflect/value.go:288 +0x7c encoding/json.boolEncoder() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:539 +0x88 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.ptrEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:943 +0x2a4 encoding/json.ptrEncoder.encode-fm() <autogenerated>:1 +0x6c encoding/json.(*encodeState).reflectValue() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:358 +0x74 encoding/json.(*encodeState).marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:330 +0x1a0 encoding/json.Marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:161 +0xa0 github.com/rs/zerolog.init.1.func1() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/encoder_json.go:21 +0x4c github.com/rs/zerolog/internal/json.Encoder.AppendInterface() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/internal/json/types.go:366 +0x5c github.com/rs/zerolog.appendFieldList() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:273 +0x2b8c github.com/rs/zerolog.appendFields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:21 +0x160 github.com/rs/zerolog.(*Event).Fields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/event.go:165 +0x90 cosmossdk.io/log.zeroLogWrapper.Debug() /Users/hh/go/pkg/mod/cosmossdk.io/log@v0.0.0-20230313123454-0fe816b71a62/logger.go:89 +0x18 github.com/cosmos/cosmos-sdk/server/log.(*CometZeroLogWrapper).Debug() <autogenerated>:1 +0x74 github.com/cometbft/cometbft/consensus.(*PeerState).PickSendVote() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1138 +0x1bc github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesForHeight() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:794 +0x260 github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:724 +0x2cc github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func2() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x58 Previous write at 0x00c0004a4870 by goroutine 130: github.com/cometbft/cometbft/consensus.(*PeerState).SetHasProposal() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1096 +0x118 github.com/cometbft/cometbft/consensus.(*Reactor).gossipDataRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:617 +0xab8 github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func1() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x58 Goroutine 131 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x240 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 Goroutine 130 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x164 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 ================== ``` --- #### PR checklist - [ ] Tests written/updated - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit 587bc0b) # Conflicts: # .changelog/v0.37.0/bug-fixes/524-rename-peerstate-tojson.md * Move changelog entry to correct folder Signed-off-by: Thane Thomson <connect@thanethomson.com> --------- Signed-off-by: Thane Thomson <connect@thanethomson.com> Co-authored-by: Mark Rushakoff <mark.rushakoff@gmail.com> Co-authored-by: Thane Thomson <connect@thanethomson.com>
In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler). Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an unsynchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote. Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called. Example data race (which is no longer reproducible with this change): ``` ================== WARNING: DATA RACE Read at 0x00c0004a4870 by goroutine 131: reflect.Value.Bool() /opt/homebrew/Cellar/go/1.20.1/libexec/src/reflect/value.go:288 +0x7c encoding/json.boolEncoder() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:539 +0x88 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.ptrEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:943 +0x2a4 encoding/json.ptrEncoder.encode-fm() <autogenerated>:1 +0x6c encoding/json.(*encodeState).reflectValue() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:358 +0x74 encoding/json.(*encodeState).marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:330 +0x1a0 encoding/json.Marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:161 +0xa0 github.com/rs/zerolog.init.1.func1() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/encoder_json.go:21 +0x4c github.com/rs/zerolog/internal/json.Encoder.AppendInterface() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/internal/json/types.go:366 +0x5c github.com/rs/zerolog.appendFieldList() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:273 +0x2b8c github.com/rs/zerolog.appendFields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:21 +0x160 github.com/rs/zerolog.(*Event).Fields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/event.go:165 +0x90 cosmossdk.io/log.zeroLogWrapper.Debug() /Users/hh/go/pkg/mod/cosmossdk.io/log@v0.0.0-20230313123454-0fe816b71a62/logger.go:89 +0x18 github.com/cosmos/cosmos-sdk/server/log.(*CometZeroLogWrapper).Debug() <autogenerated>:1 +0x74 github.com/cometbft/cometbft/consensus.(*PeerState).PickSendVote() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1138 +0x1bc github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesForHeight() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:794 +0x260 github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:724 +0x2cc github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func2() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x58 Previous write at 0x00c0004a4870 by goroutine 130: github.com/cometbft/cometbft/consensus.(*PeerState).SetHasProposal() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1096 +0x118 github.com/cometbft/cometbft/consensus.(*Reactor).gossipDataRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:617 +0xab8 github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func1() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x58 Goroutine 131 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x240 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 Goroutine 130 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x164 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 ================== ``` --- #### PR checklist - [ ] Tests written/updated - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit 587bc0b) Co-authored-by: Mark Rushakoff <mark.rushakoff@gmail.com> Co-authored-by: Thane Thomson <connect@thanethomson.com>
Hey, this fix potentially introduced this problem: #865 |
…ft#524) (cometbft#532) * consensus: rename (*PeerState).ToJSON to MarshalJSON (cometbft#524) In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler). Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an un 6D47 synchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote. Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called. Example data race (which is no longer reproducible with this change): ``` ================== WARNING: DATA RACE Read at 0x00c0004a4870 by goroutine 131: reflect.Value.Bool() /opt/homebrew/Cellar/go/1.20.1/libexec/src/reflect/value.go:288 +0x7c encoding/json.boolEncoder() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:539 +0x88 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.structEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:759 +0x1bc encoding/json.structEncoder.encode-fm() <autogenerated>:1 +0x94 encoding/json.ptrEncoder.encode() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:943 +0x2a4 encoding/json.ptrEncoder.encode-fm() <autogenerated>:1 +0x6c encoding/json.(*encodeState).reflectValue() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:358 +0x74 encoding/json.(*encodeState).marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:330 +0x1a0 encoding/json.Marshal() /opt/homebrew/Cellar/go/1.20.1/libexec/src/encoding/json/encode.go:161 +0xa0 github.com/rs/zerolog.init.1.func1() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/encoder_json.go:21 +0x4c github.com/rs/zerolog/internal/json.Encoder.AppendInterface() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/internal/json/types.go:366 +0x5c github.com/rs/zerolog.appendFieldList() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:273 +0x2b8c github.com/rs/zerolog.appendFields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/fields.go:21 +0x160 github.com/rs/zerolog.(*Event).Fields() /Users/hh/go/pkg/mod/github.com/rs/zerolog@v1.29.0/event.go:165 +0x90 cosmossdk.io/log.zeroLogWrapper.Debug() /Users/hh/go/pkg/mod/cosmossdk.io/log@v0.0.0-20230313123454-0fe816b71a62/logger.go:89 +0x18 github.com/cosmos/cosmos-sdk/server/log.(*CometZeroLogWrapper).Debug() <autogenerated>:1 +0x74 github.com/cometbft/cometbft/consensus.(*PeerState).PickSendVote() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1138 +0x1bc github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesForHeight() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:794 +0x260 github.com/cometbft/cometbft/consensus.(*Reactor).gossipVotesRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:724 +0x2cc github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func2() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x58 Previous write at 0x00c0004a4870 by goroutine 130: github.com/cometbft/cometbft/consensus.(*PeerState).SetHasProposal() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:1096 +0x118 github.com/cometbft/cometbft/consensus.(*Reactor).gossipDataRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:617 +0xab8 github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer.func1() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x58 Goroutine 131 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:199 +0x240 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 Goroutine 130 (running) created at: github.com/cometbft/cometbft/consensus.(*Reactor).AddPeer() /Users/hh/go/src/github.com/cometbft/cometbft/consensus/reactor.go:198 +0x164 github.com/cometbft/cometbft/p2p.(*Switch).addPeer() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:855 +0x7b4 github.com/cometbft/cometbft/p2p.(*Switch).acceptRoutine() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:707 +0x704 github.com/cometbft/cometbft/p2p.(*Switch).OnStart.func1() /Users/hh/go/src/github.com/cometbft/cometbft/p2p/switch.go:241 +0x34 ================== ``` --- #### PR checklist - [ ] Tests written/updated - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit 587bc0b) # Conflicts: # .changelog/v0.37.0/bug-fixes/524-rename-peerstate-tojson.md * Move changelog entry to correct folder Signed-off-by: Thane Thomson <connect@thanethomson.com> --------- Signed-off-by: Thane Thomson <connect@thanethomson.com> Co-authored-by: Mark Rushakoff <mark.rushakoff@gmail.com> Co-authored-by: Thane Thomson <connect@thanethomson.com>
In (*PeerState).PickSendVote, there is a Debug-level log that includes the PeerState value as a logging field. By default, zerolog json-encodes a struct passed as a log field (when the struct doesn't implement zerolog.LogObjectMarshaler).
Because PeerState didn't have a MarshalJSON method, the JSON encoder fell back to reflection to encode the PeerState value. Reflection did not acquire the lock, and there were data races resulting from an unsynchronized read while logging the PeerState, and concurrent (locked) writes at least during (*PeerState).SetHasProposal and (*PeerState).SetHasVote.
Given that there was only one call to (*PeerState).ToJSON in the cometbft repo, it seemed appropriate to just rename ToJSON to MarshalJSON, as opposed to leaving ToJSON for backwards compatibility. Any third party calls to ToJSON should be able to easily change the method being called.
Example data race (which is no longer reproducible with this change):
PR checklist
.changelog
(we use unclog to manage our changelog)docs/
orspec/
) and code comments