diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index 950181e7b13..6b24d4db54c 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -58,6 +58,7 @@ const ( optionNameSwapFactoryAddress = "swap-factory-address" optionNameSwapInitialDeposit = "swap-initial-deposit" optionNameSwapEnable = "swap-enable" + optionNameTransactionHash = "transaction" optionNameFullNode = "full-node" optionNamePostageContractAddress = "postage-stamp-address" optionNamePriceOracleAddress = "price-oracle-address" @@ -230,6 +231,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Bool(optionNameFullNode, false, "cause the node to start in full mode") cmd.Flags().String(optionNamePostageContractAddress, "", "postage stamp contract address") cmd.Flags().String(optionNamePriceOracleAddress, "", "price oracle address") + cmd.Flags().String(optionNameTransactionHash, "", "proof-of-identity transaction hash") } func newLogger(cmd *cobra.Command, verbosity string) (logging.Logger, error) { diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 696bc46f16d..b2f5433108d 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -148,6 +148,7 @@ Welcome to the Swarm.... Bzzz Bzzzz Bzzzz SwapInitialDeposit: c.config.GetString(optionNameSwapInitialDeposit), SwapEnable: c.config.GetBool(optionNameSwapEnable), FullNodeMode: fullNode, + Transaction: c.config.GetString(optionNameTransactionHash), PostageContractAddress: c.config.GetString(optionNamePostageContractAddress), PriceOracleAddress: c.config.GetString(optionNamePriceOracleAddress), }) diff --git a/pkg/node/node.go b/pkg/node/node.go index 19becf56e74..3f2d86dcceb 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -21,6 +21,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethersphere/bee/pkg/accounting" "github.com/ethersphere/bee/pkg/addressbook" @@ -127,6 +128,7 @@ type Options struct { SwapInitialDeposit string SwapEnable bool FullNodeMode bool + Transaction string PostageContractAddress string PriceOracleAddress string } @@ -273,7 +275,14 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey, lightNodes := lightnode.NewContainer() - p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, lightNodes, logger, tracer, libp2p.Options{ + txHash, err := getTxHash(stateStore, logger, o.Transaction) + if err != nil { + return nil, errors.New("no transaction hash provided or found") + } + + senderMatcher := transaction.NewMatcher(swapBackend, types.NewEIP155Signer(big.NewInt(chainID))) + + p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, lightNodes, senderMatcher, logger, tracer, libp2p.Options{ PrivateKey: libp2pPrivateKey, NATAddr: o.NATAddr, EnableWS: o.EnableWS, @@ -281,6 +290,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey, Standalone: o.Standalone, WelcomeMessage: o.WelcomeMessage, FullNode: o.FullNodeMode, + Transaction: txHash, }) if err != nil { return nil, fmt.Errorf("p2p service: %w", err) @@ -781,3 +791,18 @@ func (e *multiError) add(err error) { func (e *multiError) hasErrors() bool { return len(e.errors) > 0 } + +func getTxHash(stateStore storage.StateStorer, logger logging.Logger, transaction string) ([]byte, error) { + if len(transaction) == 32 { + logger.Info("using the provided transaction hash") + return []byte(transaction), nil + } + + var txHash common.Hash + key := chequebook.ChequebookDeploymentKey + if err := stateStore.Get(key, &txHash); err != nil { + return nil, err + } + + return txHash.Bytes(), nil +} diff --git a/pkg/p2p/libp2p/internal/handshake/handshake.go b/pkg/p2p/libp2p/internal/handshake/handshake.go index 1ac2fc6b0b6..78735dcfa7c 100644 --- a/pkg/p2p/libp2p/internal/handshake/handshake.go +++ b/pkg/p2p/libp2p/internal/handshake/handshake.go @@ -50,6 +50,9 @@ var ( // ErrInvalidSyn is returned if observable address in ack is not a valid.. ErrInvalidSyn = errors.New("invalid syn") + // ErrAddressNotFound is returned if observable address in ack is not a valid.. + ErrAddressNotFound = errors.New("address not found") + // ErrWelcomeMessageLength is returned if the welcome message is longer than the maximum length ErrWelcomeMessageLength = fmt.Errorf("handshake welcome message longer than maximum of %d characters", MaxWelcomeMessageLength) ) @@ -59,12 +62,18 @@ type AdvertisableAddressResolver interface { Resolve(observedAdddress ma.Multiaddr) (ma.Multiaddr, error) } +type SenderMatcher interface { + Matches(ctx context.Context, tx []byte, networkID uint64, senderOverlay swarm.Address) (bool, error) +} + // Service can perform initiate or handle a handshake between peers. type Service struct { signer crypto.Signer advertisableAddresser AdvertisableAddressResolver + senderMatcher SenderMatcher overlay swarm.Address fullNode bool + transaction []byte networkID uint64 welcomeMessage atomic.Value receivedHandshakes map[libp2ppeer.ID]struct{} @@ -89,7 +98,7 @@ func (i *Info) LightString() string { } // New creates a new handshake Service. -func New(signer crypto.Signer, advertisableAddresser AdvertisableAddressResolver, overlay swarm.Address, networkID uint64, fullNode bool, welcomeMessage string, logger logging.Logger) (*Service, error) { +func New(signer crypto.Signer, advertisableAddresser AdvertisableAddressResolver, isSender SenderMatcher, overlay swarm.Address, networkID uint64, fullNode bool, transaction []byte, welcomeMessage string, logger logging.Logger) (*Service, error) { if len(welcomeMessage) > MaxWelcomeMessageLength { return nil, ErrWelcomeMessageLength } @@ -100,6 +109,8 @@ func New(signer crypto.Signer, advertisableAddresser AdvertisableAddressResolver overlay: overlay, networkID: networkID, fullNode: fullNode, + transaction: transaction, + senderMatcher: isSender, receivedHandshakes: make(map[libp2ppeer.ID]struct{}), logger: logger, Notifiee: new(network.NoopNotifiee), @@ -171,6 +182,7 @@ func (s *Service) Handshake(ctx context.Context, stream p2p.Stream, peerMultiadd }, NetworkID: s.networkID, FullNode: s.fullNode, + Transaction: s.transaction, WelcomeMessage: welcomeMessage, }); err != nil { return nil, fmt.Errorf("write ack message: %w", err) @@ -250,6 +262,7 @@ func (s *Service) Handle(ctx context.Context, stream p2p.Stream, remoteMultiaddr }, NetworkID: s.networkID, FullNode: s.fullNode, + Transaction: s.transaction, WelcomeMessage: welcomeMessage, }, }); err != nil { @@ -271,6 +284,15 @@ func (s *Service) Handle(ctx context.Context, stream p2p.Stream, remoteMultiaddr s.logger.Infof("greeting \"%s\" from peer: %s", ack.WelcomeMessage, remoteBzzAddress.Overlay.String()) } + matchesSender, err := s.senderMatcher.Matches(ctx, ack.Transaction, s.networkID, remoteBzzAddress.Overlay) + if err != nil { + return nil, err + } + + if !matchesSender { + return nil, fmt.Errorf("given address is not registered on Ethereum: %v: %w", remoteBzzAddress.Overlay, ErrAddressNotFound) + } + return &Info{ BzzAddress: remoteBzzAddress, FullNode: ack.FullNode, diff --git a/pkg/p2p/libp2p/internal/handshake/handshake_test.go b/pkg/p2p/libp2p/internal/handshake/handshake_test.go index 499a1eccf8c..e2457c4173d 100644 --- a/pkg/p2p/libp2p/internal/handshake/handshake_test.go +++ b/pkg/p2p/libp2p/internal/handshake/handshake_test.go @@ -19,6 +19,7 @@ import ( "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/handshake/mock" "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/handshake/pb" "github.com/ethersphere/bee/pkg/p2p/protobuf" + "github.com/ethersphere/bee/pkg/swarm" libp2ppeer "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" @@ -91,8 +92,9 @@ func TestHandshake(t *testing.T) { } aaddresser := &AdvertisableAddresserMock{} + senderMatcher := &MockSenderMatcher{v: true} - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, testWelcomeMessage, logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, testWelcomeMessage, logger) if err != nil { t.Fatal(err) } @@ -160,7 +162,7 @@ func TestHandshake(t *testing.T) { const LongMessage = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi consectetur urna ut lorem sollicitudin posuere. Donec sagittis laoreet sapien." expectedErr := handshake.ErrWelcomeMessageLength - _, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, LongMessage, logger) + _, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, LongMessage, logger) if err == nil || err.Error() != expectedErr.Error() { t.Fatal("expected:", expectedErr, "got:", err) } @@ -368,7 +370,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - OK", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -425,7 +427,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - read error ", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -444,7 +446,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - write error ", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -471,7 +473,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - ack read error ", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -500,7 +502,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - networkID mismatch ", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -539,7 +541,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - duplicate handshake", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -601,7 +603,7 @@ func TestHandshake(t *testing.T) { }) t.Run("Handle - invalid ack", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -635,8 +637,45 @@ func TestHandshake(t *testing.T) { } }) + t.Run("Handle - transaction is not on the blockchain", func(t *testing.T) { + sbMock := &MockSenderMatcher{v: false} + + handshakeService, err := handshake.New(signer1, aaddresser, sbMock, node1Info.BzzAddress.Overlay, networkID, true, []byte("0xff"), "", logger) + if err != nil { + t.Fatal(err) + } + var buffer1 bytes.Buffer + var buffer2 bytes.Buffer + stream1 := mock.NewStream(&buffer1, &buffer2) + stream2 := mock.NewStream(&buffer2, &buffer1) + + w := protobuf.NewWriter(stream2) + if err := w.WriteMsg(&pb.Syn{ + ObservedUnderlay: node1maBinary, + }); err != nil { + t.Fatal(err) + } + + if err := w.WriteMsg(&pb.Ack{ + Address: &pb.BzzAddress{ + Underlay: node2maBinary, + Overlay: node2BzzAddress.Overlay.Bytes(), + Signature: node2BzzAddress.Signature, + }, + NetworkID: networkID, + FullNode: true, + }); err != nil { + t.Fatal(err) + } + + _, err = handshakeService.Handle(context.Background(), stream1, node2AddrInfo.Addrs[0], node2AddrInfo.ID) + if !errors.Is(err, handshake.ErrAddressNotFound) { + t.Fatalf("expected error %v, got %v", handshake.ErrAddressNotFound, err) + } + }) + t.Run("Handle - advertisable error", func(t *testing.T) { - handshakeService, err := handshake.New(signer1, aaddresser, node1Info.BzzAddress.Overlay, networkID, true, "", logger) + handshakeService, err := handshake.New(signer1, aaddresser, senderMatcher, node1Info.BzzAddress.Overlay, networkID, true, nil, "", logger) if err != nil { t.Fatal(err) } @@ -693,3 +732,11 @@ func (a *AdvertisableAddresserMock) Resolve(observedAdddress ma.Multiaddr) (ma.M return observedAdddress, nil } + +type MockSenderMatcher struct { + v bool +} + +func (m MockSenderMatcher) Matches(context.Context, []byte, uint64, swarm.Address) (bool, error) { + return m.v, nil +} diff --git a/pkg/p2p/libp2p/internal/handshake/pb/handshake.pb.go b/pkg/p2p/libp2p/internal/handshake/pb/handshake.pb.go index c4693bea55b..80c6c588dce 100644 --- a/pkg/p2p/libp2p/internal/handshake/pb/handshake.pb.go +++ b/pkg/p2p/libp2p/internal/handshake/pb/handshake.pb.go @@ -70,6 +70,7 @@ type Ack struct { Address *BzzAddress `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"` NetworkID uint64 `protobuf:"varint,2,opt,name=NetworkID,proto3" json:"NetworkID,omitempty"` FullNode bool `protobuf:"varint,3,opt,name=FullNode,proto3" json:"FullNode,omitempty"` + Transaction []byte `protobuf:"bytes,4,opt,name=Transaction,proto3" json:"Transaction,omitempty"` WelcomeMessage string `protobuf:"bytes,99,opt,name=WelcomeMessage,proto3" json:"WelcomeMessage,omitempty"` } @@ -127,6 +128,13 @@ func (m *Ack) GetFullNode() bool { return false } +func (m *Ack) GetTransaction() []byte { + if m != nil { + return m.Transaction + } + return nil +} + func (m *Ack) GetWelcomeMessage() string { if m != nil { return m.WelcomeMessage @@ -256,26 +264,28 @@ func init() { func init() { proto.RegisterFile("handshake.proto", fileDescriptor_a77305914d5d202f) } var fileDescriptor_a77305914d5d202f = []byte{ - // 303 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcf, 0x48, 0xcc, 0x4b, - 0x29, 0xce, 0x48, 0xcc, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x28, - 0x19, 0x72, 0x31, 0x07, 0x57, 0xe6, 0x09, 0x69, 0x71, 0x09, 0xf8, 0x27, 0x15, 0xa7, 0x16, 0x95, - 0xa5, 0xa6, 0x84, 0xe6, 0xa5, 0xa4, 0x16, 0xe5, 0x24, 0x56, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, - 0x04, 0x61, 0x88, 0x2b, 0xcd, 0x60, 0xe4, 0x62, 0x76, 0x4c, 0xce, 0x16, 0xd2, 0xe7, 0x62, 0x77, - 0x4c, 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x06, 0x2b, 0xe5, 0x36, 0x12, 0xd5, 0x43, 0x58, 0xe4, 0x54, - 0x55, 0x05, 0x95, 0x0c, 0x82, 0xa9, 0x12, 0x92, 0xe1, 0xe2, 0xf4, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, - 0xca, 0xf6, 0x74, 0x91, 0x60, 0x52, 0x60, 0xd4, 0x60, 0x09, 0x42, 0x08, 0x08, 0x49, 0x71, 0x71, - 0xb8, 0x95, 0xe6, 0xe4, 0xf8, 0xe5, 0xa7, 0xa4, 0x4a, 0x30, 0x2b, 0x30, 0x6a, 0x70, 0x04, 0xc1, - 0xf9, 0x42, 0x6a, 0x5c, 0x7c, 0xe1, 0xa9, 0x39, 0xc9, 0xf9, 0xb9, 0xa9, 0xbe, 0xa9, 0xc5, 0xc5, - 0x89, 0xe9, 0xa9, 0x12, 0xc9, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x68, 0xa2, 0x4a, 0x3e, 0x5c, 0x6c, - 0xc1, 0x95, 0x79, 0x20, 0xc7, 0x29, 0x80, 0xfd, 0x05, 0x75, 0x18, 0x1f, 0x92, 0xc3, 0x82, 0x2b, - 0xf3, 0x82, 0xc0, 0x5e, 0x56, 0x00, 0xfb, 0x02, 0xec, 0x0e, 0x54, 0x15, 0x8e, 0xc9, 0xd9, 0x41, - 0x20, 0x29, 0xa5, 0x04, 0x2e, 0x2e, 0x84, 0x37, 0x40, 0xee, 0x43, 0x0b, 0x1a, 0x38, 0x1f, 0xe4, - 0xb3, 0xe0, 0xcc, 0xf4, 0xbc, 0xc4, 0x92, 0xd2, 0xa2, 0x54, 0xb0, 0x89, 0x3c, 0x41, 0x08, 0x01, - 0x21, 0x09, 0x2e, 0x76, 0xff, 0x32, 0x88, 0x46, 0x66, 0xb0, 0x1c, 0x8c, 0xeb, 0x24, 0x73, 0xe2, - 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, - 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x4c, 0x05, 0x49, 0x49, 0x6c, 0xe0, 0xd8, - 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x30, 0x28, 0x02, 0x6f, 0xc0, 0x01, 0x00, 0x00, + // 324 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0xdf, 0x4a, 0xf3, 0x30, + 0x18, 0xc6, 0x97, 0x75, 0xec, 0xcf, 0xbb, 0xb1, 0xef, 0x23, 0x20, 0x14, 0x19, 0x25, 0xf4, 0x40, + 0x8a, 0x07, 0x13, 0xf5, 0x0a, 0x36, 0x44, 0x10, 0x74, 0x83, 0x54, 0x11, 0x3c, 0x32, 0x6b, 0xc3, + 0x36, 0x5a, 0x93, 0x91, 0x74, 0x93, 0xee, 0x2a, 0xbc, 0x1e, 0xaf, 0xc0, 0xc3, 0x1d, 0x7a, 0x28, + 0xdb, 0x8d, 0x48, 0xe3, 0xd6, 0xce, 0x79, 0xf8, 0xfe, 0x9e, 0x27, 0xc9, 0xf3, 0xbc, 0x81, 0x7f, + 0x13, 0x26, 0x42, 0x3d, 0x61, 0x11, 0xef, 0xce, 0x94, 0x4c, 0x24, 0x6e, 0xe4, 0xc0, 0x3d, 0x07, + 0xcb, 0x4f, 0x05, 0x3e, 0x85, 0xff, 0xc3, 0x91, 0xe6, 0x6a, 0xc1, 0xc3, 0x07, 0x11, 0x72, 0x15, + 0xb3, 0xd4, 0x46, 0x04, 0x79, 0x2d, 0xfa, 0x87, 0xbb, 0xef, 0x08, 0xac, 0x5e, 0x10, 0xe1, 0x33, + 0xa8, 0xf5, 0xc2, 0x50, 0x71, 0xad, 0x8d, 0xb5, 0x79, 0x71, 0xd4, 0x2d, 0x1e, 0xea, 0x2f, 0x97, + 0x5b, 0x91, 0xee, 0x5c, 0xb8, 0x03, 0x8d, 0x01, 0x4f, 0x5e, 0xa5, 0x8a, 0x6e, 0xae, 0xec, 0x32, + 0x41, 0x5e, 0x85, 0x16, 0x00, 0x1f, 0x43, 0xfd, 0x7a, 0x1e, 0xc7, 0x03, 0x19, 0x72, 0xdb, 0x22, + 0xc8, 0xab, 0xd3, 0x7c, 0xc6, 0x04, 0x9a, 0xf7, 0x8a, 0x09, 0xcd, 0x82, 0x64, 0x2a, 0x85, 0x5d, + 0x31, 0xc9, 0xf6, 0x11, 0x3e, 0x81, 0xf6, 0x23, 0x8f, 0x03, 0xf9, 0xc2, 0xef, 0xb8, 0xd6, 0x6c, + 0xcc, 0xed, 0x80, 0x20, 0xaf, 0x41, 0x0f, 0xa8, 0x7b, 0x0b, 0x55, 0x3f, 0x15, 0x59, 0x7c, 0x62, + 0x9a, 0x6f, 0xa3, 0xb7, 0xf7, 0xa2, 0xfb, 0xa9, 0xa0, 0x66, 0x29, 0xc4, 0xf4, 0x34, 0x49, 0x7f, + 0x3b, 0x7a, 0x41, 0x44, 0x33, 0xc9, 0x7d, 0x06, 0x28, 0x8a, 0x66, 0x0d, 0x0e, 0x96, 0x97, 0xcf, + 0x59, 0x77, 0x7f, 0x3a, 0x16, 0x2c, 0x99, 0x2b, 0x6e, 0x6e, 0x6c, 0xd1, 0x02, 0x60, 0x1b, 0x6a, + 0xc3, 0xc5, 0xcf, 0x41, 0xcb, 0x68, 0xbb, 0xb1, 0xdf, 0xf9, 0x58, 0x3b, 0x68, 0xb5, 0x76, 0xd0, + 0xd7, 0xda, 0x41, 0x6f, 0x1b, 0xa7, 0xb4, 0xda, 0x38, 0xa5, 0xcf, 0x8d, 0x53, 0x7a, 0x2a, 0xcf, + 0x46, 0xa3, 0xaa, 0xf9, 0xcf, 0xcb, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x48, 0x2c, 0x4a, + 0xe2, 0x01, 0x00, 0x00, } func (m *Syn) Marshal() (dAtA []byte, err error) { @@ -337,6 +347,13 @@ func (m *Ack) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x9a } + if len(m.Transaction) > 0 { + i -= len(m.Transaction) + copy(dAtA[i:], m.Transaction) + i = encodeVarintHandshake(dAtA, i, uint64(len(m.Transaction))) + i-- + dAtA[i] = 0x22 + } if m.FullNode { i-- if m.FullNode { @@ -498,6 +515,10 @@ func (m *Ack) Size() (n int) { if m.FullNode { n += 2 } + l = len(m.Transaction) + if l > 0 { + n += 1 + l + sovHandshake(uint64(l)) + } l = len(m.WelcomeMessage) if l > 0 { n += 2 + l + sovHandshake(uint64(l)) @@ -740,6 +761,40 @@ func (m *Ack) Unmarshal(dAtA []byte) error { } } m.FullNode = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Transaction", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandshake + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandshake + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthHandshake + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Transaction = append(m.Transaction[:0], dAtA[iNdEx:postIndex]...) + if m.Transaction == nil { + m.Transaction = []byte{} + } + iNdEx = postIndex case 99: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field WelcomeMessage", wireType) diff --git a/pkg/p2p/libp2p/internal/handshake/pb/handshake.proto b/pkg/p2p/libp2p/internal/handshake/pb/handshake.proto index 5fd36998d47..f0c6597af3a 100644 --- a/pkg/p2p/libp2p/internal/handshake/pb/handshake.proto +++ b/pkg/p2p/libp2p/internal/handshake/pb/handshake.proto @@ -16,6 +16,7 @@ message Ack { BzzAddress Address = 1; uint64 NetworkID = 2; bool FullNode = 3; + bytes Transaction = 4; string WelcomeMessage = 99; } diff --git a/pkg/p2p/libp2p/libp2p.go b/pkg/p2p/libp2p/libp2p.go index b33a67c57c8..1fda2266378 100644 --- a/pkg/p2p/libp2p/libp2p.go +++ b/pkg/p2p/libp2p/libp2p.go @@ -84,9 +84,10 @@ type Options struct { Standalone bool FullNode bool WelcomeMessage string + Transaction []byte } -func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay swarm.Address, addr string, ab addressbook.Putter, storer storage.StateStorer, lightNodes *lightnode.Container, logger logging.Logger, tracer *tracing.Tracer, o Options) (*Service, error) { +func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay swarm.Address, addr string, ab addressbook.Putter, storer storage.StateStorer, lightNodes *lightnode.Container, swapBackend handshake.SenderMatcher, logger logging.Logger, tracer *tracing.Tracer, o Options) (*Service, error) { host, port, err := net.SplitHostPort(addr) if err != nil { return nil, fmt.Errorf("address: %w", err) @@ -209,7 +210,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay advertisableAddresser = natAddrResolver } - handshakeService, err := handshake.New(signer, advertisableAddresser, overlay, networkID, o.FullNode, o.WelcomeMessage, logger) + handshakeService, err := handshake.New(signer, advertisableAddresser, swapBackend, overlay, networkID, o.FullNode, o.Transaction, o.WelcomeMessage, logger) if err != nil { return nil, fmt.Errorf("handshake service: %w", err) } diff --git a/pkg/p2p/libp2p/libp2p_test.go b/pkg/p2p/libp2p/libp2p_test.go index defd056c8df..507b62cb169 100644 --- a/pkg/p2p/libp2p/libp2p_test.go +++ b/pkg/p2p/libp2p/libp2p_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethersphere/bee/pkg/addressbook" "github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/logging" @@ -28,6 +29,7 @@ type libp2pServiceOpts struct { Logger logging.Logger Addressbook addressbook.Interface PrivateKey *ecdsa.PrivateKey + MockPeerKey *ecdsa.PrivateKey libp2pOpts libp2p.Options } @@ -69,7 +71,12 @@ func newService(t *testing.T, networkID uint64, o libp2pServiceOpts) (s *libp2p. lightnodes := lightnode.NewContainer() - s, err = libp2p.New(ctx, crypto.NewDefaultSigner(swarmKey), networkID, overlay, addr, o.Addressbook, statestore, lightnodes, o.Logger, nil, o.libp2pOpts) + opts := o.libp2pOpts + opts.Transaction = []byte(hexutil.EncodeUint64(o.PrivateKey.Y.Uint64())) + + senderMatcher := &MockSenderMatcher{} + + s, err = libp2p.New(ctx, crypto.NewDefaultSigner(swarmKey), networkID, overlay, addr, o.Addressbook, statestore, lightnodes, senderMatcher, o.Logger, nil, opts) if err != nil { t.Fatal(err) } @@ -150,3 +157,9 @@ func serviceUnderlayAddress(t *testing.T, s *libp2p.Service) multiaddr.Multiaddr } return addrs[0] } + +type MockSenderMatcher struct{} + +func (m MockSenderMatcher) Matches(context.Context, []byte, uint64, swarm.Address) (bool, error) { + return true, nil +} diff --git a/pkg/settlement/swap/chequebook/init.go b/pkg/settlement/swap/chequebook/init.go index f79611f98df..a4057cf2c45 100644 --- a/pkg/settlement/swap/chequebook/init.go +++ b/pkg/settlement/swap/chequebook/init.go @@ -19,7 +19,7 @@ import ( const ( chequebookKey = "swap_chequebook" - chequebookDeploymentKey = "swap_chequebook_transaction_deployment" + ChequebookDeploymentKey = "swap_chequebook_transaction_deployment" balanceCheckBackoffDuration = 20 * time.Second balanceCheckMaxRetries = 10 @@ -124,7 +124,7 @@ func Init( } var txHash common.Hash - err = stateStore.Get(chequebookDeploymentKey, &txHash) + err = stateStore.Get(ChequebookDeploymentKey, &txHash) if err != nil && err != storage.ErrNotFound { return nil, err } @@ -145,7 +145,7 @@ func Init( logger.Infof("deploying new chequebook in transaction %x", txHash) - err = stateStore.Put(chequebookDeploymentKey, txHash) + err = stateStore.Put(ChequebookDeploymentKey, txHash) if err != nil { return nil, err } diff --git a/pkg/settlement/swap/transaction/sender_matcher.go b/pkg/settlement/swap/transaction/sender_matcher.go new file mode 100644 index 00000000000..a3308bd331a --- /dev/null +++ b/pkg/settlement/swap/transaction/sender_matcher.go @@ -0,0 +1,52 @@ +package transaction + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/swarm" +) + +type Matcher struct { + backend Backend + signer types.Signer +} + +var ( + ErrTransactionNotFound = errors.New("transaction not found") + ErrTransactionPending = errors.New("transaction in pending status") + ErrTransactionSenderInvalid = errors.New("invalid transaction sender") +) + +func NewMatcher(backend Backend, signer types.Signer) *Matcher { + return &Matcher{ + backend: backend, + signer: signer, + } +} + +func (m Matcher) Matches(ctx context.Context, tx []byte, networkID uint64, senderOverlay swarm.Address) (bool, error) { + incomingTx := common.BytesToHash(tx) + + nTx, isPending, err := m.backend.TransactionByHash(ctx, incomingTx) + if err != nil { + return false, fmt.Errorf("%v: %w", err, ErrTransactionNotFound) + } + + if isPending { + return false, ErrTransactionPending + } + + sender, err := types.Sender(m.signer, nTx) + if err != nil { + return false, fmt.Errorf("%v: %w", err, ErrTransactionSenderInvalid) + } + + expectedRemoteBzzAddress := crypto.NewOverlayFromEthereumAddress(sender.Bytes(), networkID) + + return expectedRemoteBzzAddress.Equal(senderOverlay), nil +} diff --git a/pkg/settlement/swap/transaction/sender_matcher_test.go b/pkg/settlement/swap/transaction/sender_matcher_test.go new file mode 100644 index 00000000000..6e0d75cfac9 --- /dev/null +++ b/pkg/settlement/swap/transaction/sender_matcher_test.go @@ -0,0 +1,135 @@ +package transaction_test + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/settlement/swap/transaction" + "github.com/ethersphere/bee/pkg/settlement/swap/transaction/backendmock" + "github.com/ethersphere/bee/pkg/swarm" +) + +func TestMatchesSender(t *testing.T) { + recipient := common.HexToAddress("0xabcd") + txData := common.Hex2Bytes("0xabcdee") + value := big.NewInt(1) + suggestedGasPrice := big.NewInt(2) + estimatedGasLimit := uint64(3) + nonce := uint64(2) + + signedTx := types.NewTransaction(nonce, recipient, value, estimatedGasLimit, suggestedGasPrice, txData) + + t.Run("fail to retrieve tx from backend", func(t *testing.T) { + txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return nil, false, errors.New("transaction not found by hash") + }) + + matcher := transaction.NewMatcher(backendmock.New(txByHash), nil) + + _, err := matcher.Matches(context.Background(), []byte("0x123"), 0, swarm.NewAddress([]byte{})) + if !errors.Is(err, transaction.ErrTransactionNotFound) { + t.Fatalf("bad error type, want %v, got %v", transaction.ErrTransactionNotFound, err) + } + }) + + t.Run("transaction in 'pending' status", func(t *testing.T) { + txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return nil, true, nil + }) + + matcher := transaction.NewMatcher(backendmock.New(txByHash), nil) + + _, err := matcher.Matches(context.Background(), []byte("0x123"), 0, swarm.NewAddress([]byte{})) + if !errors.Is(err, transaction.ErrTransactionPending) { + t.Fatalf("bad error type, want %v, got %v", transaction.ErrTransactionPending, err) + } + }) + + t.Run("signer error", func(t *testing.T) { + txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return signedTx, false, nil + }) + + signer := &mockSigner{ + err: errors.New("can not sign"), + } + + matcher := transaction.NewMatcher(backendmock.New(txByHash), signer) + + _, err := matcher.Matches(context.Background(), []byte("0x123"), 0, swarm.NewAddress([]byte{})) + if !errors.Is(err, transaction.ErrTransactionSenderInvalid) { + t.Fatalf("bad error type, want %v, got %v", transaction.ErrTransactionSenderInvalid, err) + } + }) + + t.Run("sender does not match", func(t *testing.T) { + txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return signedTx, false, nil + }) + + signer := &mockSigner{ + addr: common.HexToAddress("0xabc"), + } + + matcher := transaction.NewMatcher(backendmock.New(txByHash), signer) + + matches, err := matcher.Matches(context.Background(), []byte("0x123"), 0, swarm.NewAddress([]byte{})) + if err != nil { + t.Fatalf("expected no err, got %v", err) + } + + if matches { + t.Fatalf("expected no match, got %v", matches) + } + }) + + t.Run("sender matches", func(t *testing.T) { + txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return signedTx, false, nil + }) + + signer := &mockSigner{ + addr: common.HexToAddress("0xff"), + } + + matcher := transaction.NewMatcher(backendmock.New(txByHash), signer) + + senderOverlay := crypto.NewOverlayFromEthereumAddress(signer.addr.Bytes(), 0) + + matches, err := matcher.Matches(context.Background(), []byte("0x123"), 0, senderOverlay) + if err != nil { + t.Fatalf("expected no err, got %v", err) + } + + if !matches { + t.Fatalf("expected match, got %v", matches) + } + }) +} + +type mockSigner struct { + addr common.Address + err error +} + +func (m *mockSigner) Sender(tx *types.Transaction) (common.Address, error) { + return m.addr, m.err +} + +func (*mockSigner) SignatureValues(tx *types.Transaction, sig []byte) (r, s, v *big.Int, err error) { + zero := big.NewInt(0) + return zero, zero, zero, nil +} + +func (*mockSigner) Hash(tx *types.Transaction) common.Hash { + return common.HexToHash("0xf") +} + +func (*mockSigner) Equal(types.Signer) bool { + return false +}