From ad43f2d453f12b8f32663c6e25c3fdb6c042aabf Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Mon, 12 Oct 2020 21:50:51 +0530 Subject: [PATCH] Resume Data Transfer (#100) * Emit events with received cids (#71) * persist received cids on channel state. * Send, Receive and Validate Restart requests (#75) * Send, Receive and Validate Requests * Initiating and Responding Tests and bug fixes (#76) * Testing for resuming data transfer work * Cleanup Push Restarts PR (#79) * cleanup of restart PR * link the peers * Tests for pull restarts (#84) * tests for pull restarts * Merge Tests cleanup work (#92) * cleanup of restart PR * cleanup timedout channels (#93) * backward compatibility of restart (#96) * backward compatibility of restart * changes and tests * more tests * better error handling for restarts * feat(message): switch to cbor map encoding (#97) switch to cbor map encoding for the 1_1 message protocol * feat(channels): setup datastore migrations (#99) setup datatransfer channels so they migrate over successfully Co-authored-by: Hannah Howard --- Makefile | 15 + channels/channel_state.go | 48 +- channels/channels.go | 87 +- channels/channels_fsm.go | 72 +- channels/channels_test.go | 281 +++- channels/{ => internal}/internalchannel.go | 40 +- channels/internal/internalchannel_cbor_gen.go | 879 ++++++++++++ channels/internal/migrations/migrations.go | 117 ++ .../migrations/migrations_cbor_gen.go} | 48 +- coverage.txt | 1250 +++++++++++++++++ events.go | 21 +- go.mod | 9 +- go.sum | 163 ++- impl/events.go | 111 +- impl/impl.go | 99 +- impl/initiating_test.go | 309 +++- impl/integration_test.go | 369 ++--- impl/receiver.go | 64 +- impl/responding_test.go | 423 +++++- impl/restart.go | 177 +++ impl/restart_integration_test.go | 484 +++++++ impl/utils.go | 8 +- manager.go | 9 + message.go | 14 + message/message.go | 170 +-- message/message1_0/message.go | 57 + message/{ => message1_0}/transfer_message.go | 2 +- .../transfer_message_cbor_gen.go | 10 +- message/{ => message1_0}/transfer_request.go | 33 +- .../transfer_request_cbor_gen.go | 2 +- message/{ => message1_0}/transfer_response.go | 28 +- .../transfer_response_cbor_gen.go | 2 +- message/message1_1/message.go | 183 +++ message/{ => message1_1}/message_test.go | 99 +- message/message1_1/transfer_message.go | 38 + .../message1_1/transfer_message_cbor_gen.go | 174 +++ message/message1_1/transfer_request.go | 170 +++ .../message1_1/transfer_request_cbor_gen.go | 392 ++++++ message/message1_1/transfer_request_test.go | 69 + message/message1_1/transfer_response.go | 126 ++ .../message1_1/transfer_response_cbor_gen.go | 260 ++++ message/message1_1/transfer_response_test.go | 49 + message/types/message_types.go | 16 + network/interface.go | 8 +- network/libp2p_impl.go | 110 +- network/libp2p_impl_test.go | 48 +- statuses.go | 3 + testutil/fakegraphsync.go | 17 +- testutil/faketransport.go | 17 +- testutil/fixtures/lorem_large.txt | 1204 ++++++++++++++++ testutil/gstestdata.go | 30 +- testutil/message.go | 2 +- testutil/testutil.go | 16 + transport.go | 6 + transport/graphsync/graphsync.go | 35 +- transport/graphsync/graphsync_test.go | 110 +- types.go | 10 +- types_cbor_gen.go | 2 +- 58 files changed, 7986 insertions(+), 609 deletions(-) rename channels/{ => internal}/internalchannel.go (58%) create mode 100644 channels/internal/internalchannel_cbor_gen.go create mode 100644 channels/internal/migrations/migrations.go rename channels/{internalchannel_cbor_gen.go => internal/migrations/migrations_cbor_gen.go} (89%) create mode 100644 coverage.txt create mode 100644 impl/restart.go create mode 100644 impl/restart_integration_test.go create mode 100644 message/message1_0/message.go rename message/{ => message1_0}/transfer_message.go (97%) rename message/{ => message1_0}/transfer_message_cbor_gen.go (90%) rename message/{ => message1_0}/transfer_request.go (76%) rename message/{ => message1_0}/transfer_request_cbor_gen.go (99%) rename message/{ => message1_0}/transfer_response.go (73%) rename message/{ => message1_0}/transfer_response_cbor_gen.go (99%) create mode 100644 message/message1_1/message.go rename message/{ => message1_1}/message_test.go (75%) create mode 100644 message/message1_1/transfer_message.go create mode 100644 message/message1_1/transfer_message_cbor_gen.go create mode 100644 message/message1_1/transfer_request.go create mode 100644 message/message1_1/transfer_request_cbor_gen.go create mode 100644 message/message1_1/transfer_request_test.go create mode 100644 message/message1_1/transfer_response.go create mode 100644 message/message1_1/transfer_response_cbor_gen.go create mode 100644 message/message1_1/transfer_response_test.go create mode 100644 message/types/message_types.go create mode 100644 testutil/fixtures/lorem_large.txt diff --git a/Makefile b/Makefile index 526630e4..6ad9e7d8 100644 --- a/Makefile +++ b/Makefile @@ -15,3 +15,18 @@ test: type-gen: build go generate ./... + +imports: + scripts/fiximports + +cbor-gen: + go generate ./... + +tidy: + go mod tidy + +lint: + git fetch + golangci-lint run -v --concurrency 2 --new-from-rev origin/master + +prepare-pr: cbor-gen tidy imports lint \ No newline at end of file diff --git a/channels/channel_state.go b/channels/channel_state.go index a1caa3bb..a9029c4f 100644 --- a/channels/channel_state.go +++ b/channels/channel_state.go @@ -11,10 +11,13 @@ import ( cbg "github.com/whyrusleeping/cbor-gen" datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels/internal" ) // channelState is immutable channel data plus mutable state type channelState struct { + // peerId of the manager peer + selfPeer peer.ID // an identifier for this channel shared by request and responder, set by requester through protocol transferID datatransfer.TransferID // base CID for the piece being transferred @@ -38,11 +41,13 @@ type channelState struct { // more informative status on a channel message string // additional vouchers - vouchers []encodedVoucher + vouchers []internal.EncodedVoucher // additional voucherResults - voucherResults []encodedVoucherResult + voucherResults []internal.EncodedVoucherResult voucherResultDecoder DecoderByTypeFunc voucherDecoder DecoderByTypeFunc + + receivedCids []cid.Cid } // EmptyChannelState is the zero value for channel state, meaning not present @@ -82,6 +87,11 @@ func (c channelState) Voucher() datatransfer.Voucher { return encodable.(datatransfer.Voucher) } +// ReceivedCids returns the cids received so far on this channel +func (c channelState) ReceivedCids() []cid.Cid { + return c.receivedCids +} + // Sender returns the peer id for the node that is sending data func (c channelState) Sender() peer.ID { return c.sender } @@ -99,9 +109,8 @@ func (c channelState) IsPull() bool { func (c channelState) ChannelID() datatransfer.ChannelID { if c.isPull { return datatransfer.ChannelID{ID: c.transferID, Initiator: c.recipient, Responder: c.sender} - } else { - return datatransfer.ChannelID{ID: c.transferID, Initiator: c.sender, Responder: c.recipient} } + return datatransfer.ChannelID{ID: c.transferID, Initiator: c.sender, Responder: c.recipient} } func (c channelState) Message() string { @@ -140,11 +149,38 @@ func (c channelState) VoucherResults() []datatransfer.VoucherResult { return voucherResults } -func (c channelState) OtherParty(thisParty peer.ID) peer.ID { - if thisParty == c.sender { +func (c channelState) SelfPeer() peer.ID { + return c.selfPeer +} + +func (c channelState) OtherPeer() peer.ID { + if c.sender == c.selfPeer { return c.recipient } return c.sender } +func fromInternalChannelState(c internal.ChannelState, voucherDecoder DecoderByTypeFunc, voucherResultDecoder DecoderByTypeFunc) datatransfer.ChannelState { + return channelState{ + selfPeer: c.SelfPeer, + isPull: c.Initiator == c.Recipient, + transferID: c.TransferID, + baseCid: c.BaseCid, + selector: c.Selector, + sender: c.Sender, + recipient: c.Recipient, + totalSize: c.TotalSize, + status: c.Status, + sent: c.Sent, + received: c.Received, + message: c.Message, + vouchers: c.Vouchers, + voucherResults: c.VoucherResults, + voucherResultDecoder: voucherResultDecoder, + voucherDecoder: voucherDecoder, + + receivedCids: c.ReceivedCids, + } +} + var _ datatransfer.ChannelState = channelState{} diff --git a/channels/channels.go b/channels/channels.go index af9947c1..41f21157 100644 --- a/channels/channels.go +++ b/channels/channels.go @@ -11,9 +11,13 @@ import ( peer "github.com/libp2p/go-libp2p-core/peer" cbg "github.com/whyrusleeping/cbor-gen" + versioning "github.com/filecoin-project/go-ds-versioning/pkg" + versionedfsm "github.com/filecoin-project/go-ds-versioning/pkg/fsm" "github.com/filecoin-project/go-statemachine/fsm" datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels/internal" + "github.com/filecoin-project/go-data-transfer/channels/internal/migrations" "github.com/filecoin-project/go-data-transfer/encoding" ) @@ -32,7 +36,8 @@ type Channels struct { notifier Notifier voucherDecoder DecoderByTypeFunc voucherResultDecoder DecoderByTypeFunc - statemachines fsm.Group + stateMachines fsm.Group + migrateStateMachines func(context.Context) error } // ChannelEnvironment -- just a proxy for DTNetwork for now @@ -44,38 +49,47 @@ type ChannelEnvironment interface { } // New returns a new thread safe list of channels -func New(ds datastore.Datastore, +func New(ds datastore.Batching, notifier Notifier, voucherDecoder DecoderByTypeFunc, voucherResultDecoder DecoderByTypeFunc, - env ChannelEnvironment) (*Channels, error) { + env ChannelEnvironment, + selfPeer peer.ID) (*Channels, error) { c := &Channels{ notifier: notifier, voucherDecoder: voucherDecoder, voucherResultDecoder: voucherResultDecoder, } - statemachines, err := fsm.New(ds, fsm.Parameters{ + channelMigrations, err := migrations.GetChannelStateMigrations(selfPeer) + if err != nil { + return nil, err + } + c.stateMachines, c.migrateStateMachines, err = versionedfsm.NewVersionedFSM(ds, fsm.Parameters{ Environment: env, - StateType: internalChannelState{}, + StateType: internal.ChannelState{}, StateKeyField: "Status", Events: ChannelEvents, StateEntryFuncs: ChannelStateEntryFuncs, Notifier: c.dispatch, FinalityStates: ChannelFinalityStates, - }) + }, channelMigrations, versioning.VersionKey("1")) if err != nil { return nil, err } - c.statemachines = statemachines return c, nil } +// Start migrates the channel data store as needed +func (c *Channels) Start(ctx context.Context) error { + return c.migrateStateMachines(ctx) +} + func (c *Channels) dispatch(eventName fsm.EventName, channel fsm.StateType) { evtCode, ok := eventName.(datatransfer.EventCode) if !ok { log.Errorf("dropped bad event %v", eventName) } - realChannel, ok := channel.(internalChannelState) + realChannel, ok := channel.(internal.ChannelState) if !ok { log.Errorf("not a ClientDeal %v", channel) } @@ -85,12 +99,12 @@ func (c *Channels) dispatch(eventName fsm.EventName, channel fsm.StateType) { Timestamp: time.Now(), } - c.notifier(evt, realChannel.ToChannelState(c.voucherDecoder, c.voucherResultDecoder)) + c.notifier(evt, fromInternalChannelState(realChannel, c.voucherDecoder, c.voucherResultDecoder)) } // CreateNew creates a new channel id and channel state and saves to channels. // returns error if the channel exists already. -func (c *Channels) CreateNew(tid datatransfer.TransferID, baseCid cid.Cid, selector ipld.Node, voucher datatransfer.Voucher, initiator, dataSender, dataReceiver peer.ID) (datatransfer.ChannelID, error) { +func (c *Channels) CreateNew(selfPeer peer.ID, tid datatransfer.TransferID, baseCid cid.Cid, selector ipld.Node, voucher datatransfer.Voucher, initiator, dataSender, dataReceiver peer.ID) (datatransfer.ChannelID, error) { var responder peer.ID if dataSender == initiator { responder = dataReceiver @@ -106,7 +120,8 @@ func (c *Channels) CreateNew(tid datatransfer.TransferID, baseCid cid.Cid, selec if err != nil { return datatransfer.ChannelID{}, err } - err = c.statemachines.Begin(chid, &internalChannelState{ + err = c.stateMachines.Begin(chid, &internal.ChannelState{ + SelfPeer: selfPeer, TransferID: tid, Initiator: initiator, Responder: responder, @@ -114,7 +129,7 @@ func (c *Channels) CreateNew(tid datatransfer.TransferID, baseCid cid.Cid, selec Selector: &cbg.Deferred{Raw: selBytes}, Sender: dataSender, Recipient: dataReceiver, - Vouchers: []encodedVoucher{ + Vouchers: []internal.EncodedVoucher{ { Type: voucher.Type(), Voucher: &cbg.Deferred{ @@ -122,25 +137,26 @@ func (c *Channels) CreateNew(tid datatransfer.TransferID, baseCid cid.Cid, selec }, }, }, - Status: datatransfer.Requested, + Status: datatransfer.Requested, + ReceivedCids: nil, }) if err != nil { return datatransfer.ChannelID{}, err } - return chid, c.statemachines.Send(chid, datatransfer.Open) + return chid, c.stateMachines.Send(chid, datatransfer.Open) } // InProgress returns a list of in progress channels func (c *Channels) InProgress() (map[datatransfer.ChannelID]datatransfer.ChannelState, error) { - var internalChannels []internalChannelState - err := c.statemachines.List(&internalChannels) + var internalChannels []internal.ChannelState + err := c.stateMachines.List(&internalChannels) if err != nil { return nil, err } channels := make(map[datatransfer.ChannelID]datatransfer.ChannelState, len(internalChannels)) for _, internalChannel := range internalChannels { channels[datatransfer.ChannelID{ID: internalChannel.TransferID, Responder: internalChannel.Responder, Initiator: internalChannel.Initiator}] = - internalChannel.ToChannelState(c.voucherDecoder, c.voucherResultDecoder) + fromInternalChannelState(internalChannel, c.voucherDecoder, c.voucherResultDecoder) } return channels, nil } @@ -148,12 +164,12 @@ func (c *Channels) InProgress() (map[datatransfer.ChannelID]datatransfer.Channel // GetByID searches for a channel in the slice of channels with id `chid`. // Returns datatransfer.EmptyChannelState if there is no channel with that id func (c *Channels) GetByID(ctx context.Context, chid datatransfer.ChannelID) (datatransfer.ChannelState, error) { - var internalChannel internalChannelState - err := c.statemachines.GetSync(ctx, chid, &internalChannel) + var internalChannel internal.ChannelState + err := c.stateMachines.GetSync(ctx, chid, &internalChannel) if err != nil { return nil, ErrNotFound } - return internalChannel.ToChannelState(c.voucherDecoder, c.voucherResultDecoder), nil + return fromInternalChannelState(internalChannel, c.voucherDecoder, c.voucherResultDecoder), nil } // Accept marks a data transfer as accepted @@ -161,16 +177,21 @@ func (c *Channels) Accept(chid datatransfer.ChannelID) error { return c.send(chid, datatransfer.Accept) } -// IncrementSent increments the total sent on the given channel by the given amount (returning -// the new total) -func (c *Channels) IncrementSent(chid datatransfer.ChannelID, delta uint64) error { - return c.send(chid, datatransfer.Progress, delta, uint64(0)) +// Restart marks a data transfer as restarted +func (c *Channels) Restart(chid datatransfer.ChannelID) error { + return c.send(chid, datatransfer.Restart) } -// IncrementReceived increments the total received on the given channel by the given amount (returning -// the new total) -func (c *Channels) IncrementReceived(chid datatransfer.ChannelID, delta uint64) error { - return c.send(chid, datatransfer.Progress, uint64(0), delta) +func (c *Channels) CompleteCleanupOnRestart(chid datatransfer.ChannelID) error { + return c.send(chid, datatransfer.CompleteCleanupOnRestart) +} + +func (c *Channels) DataSent(chid datatransfer.ChannelID, cid cid.Cid, delta uint64) error { + return c.send(chid, datatransfer.DataSent, delta, cid) +} + +func (c *Channels) DataReceived(chid datatransfer.ChannelID, cid cid.Cid, delta uint64) error { + return c.send(chid, datatransfer.DataReceived, delta, cid) } // PauseInitiator pauses the initator of this channel @@ -246,18 +267,22 @@ func (c *Channels) Error(chid datatransfer.ChannelID, err error) error { return c.send(chid, datatransfer.Error, err) } +func (c *Channels) Disconnected(chid datatransfer.ChannelID) error { + return c.send(chid, datatransfer.Disconnected) +} + // HasChannel returns true if the given channel id is being tracked func (c *Channels) HasChannel(chid datatransfer.ChannelID) (bool, error) { - return c.statemachines.Has(chid) + return c.stateMachines.Has(chid) } func (c *Channels) send(chid datatransfer.ChannelID, code datatransfer.EventCode, args ...interface{}) error { - has, err := c.statemachines.Has(chid) + has, err := c.stateMachines.Has(chid) if err != nil { return err } if !has { return ErrNotFound } - return c.statemachines.Send(chid, code, args...) + return c.stateMachines.Send(chid, code, args...) } diff --git a/channels/channels_fsm.go b/channels/channels_fsm.go index 9c344a86..f1e7ae74 100644 --- a/channels/channels_fsm.go +++ b/channels/channels_fsm.go @@ -1,12 +1,14 @@ package channels import ( + "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" cbg "github.com/whyrusleeping/cbor-gen" "github.com/filecoin-project/go-statemachine/fsm" datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels/internal" ) var log = logging.Logger("data-transfer") @@ -15,32 +17,50 @@ var log = logging.Logger("data-transfer") var ChannelEvents = fsm.Events{ fsm.Event(datatransfer.Open).FromAny().To(datatransfer.Requested), fsm.Event(datatransfer.Accept).From(datatransfer.Requested).To(datatransfer.Ongoing), + fsm.Event(datatransfer.Restart).FromAny().To(datatransfer.Ongoing), + fsm.Event(datatransfer.Cancel).FromAny().To(datatransfer.Cancelling), - fsm.Event(datatransfer.Progress).FromMany( + + fsm.Event(datatransfer.DataReceived).FromMany( + datatransfer.Requested, + datatransfer.Ongoing, + datatransfer.InitiatorPaused, + datatransfer.ResponderPaused, + datatransfer.BothPaused, + datatransfer.ResponderCompleted, + datatransfer.ResponderFinalizing).ToNoChange().Action(func(chst *internal.ChannelState, delta uint64, c cid.Cid) error { + chst.Received += delta + chst.ReceivedCids = append(chst.ReceivedCids, c) + return nil + }), + + fsm.Event(datatransfer.DataSent).FromMany( datatransfer.Requested, datatransfer.Ongoing, datatransfer.InitiatorPaused, datatransfer.ResponderPaused, datatransfer.BothPaused, datatransfer.ResponderCompleted, - datatransfer.ResponderFinalizing).ToNoChange().Action(func(chst *internalChannelState, deltaSent uint64, deltaReceived uint64) error { - chst.Received += deltaReceived - chst.Sent += deltaSent + datatransfer.ResponderFinalizing).ToNoChange().Action(func(chst *internal.ChannelState, delta uint64, c cid.Cid) error { + chst.Sent += delta return nil }), - fsm.Event(datatransfer.Error).FromAny().To(datatransfer.Failing).Action(func(chst *internalChannelState, err error) error { + + fsm.Event(datatransfer.Disconnected).FromAny().To(datatransfer.PeerDisconnected), + + fsm.Event(datatransfer.Error).FromAny().To(datatransfer.Failing).Action(func(chst *internal.ChannelState, err error) error { chst.Message = err.Error() return nil }), fsm.Event(datatransfer.NewVoucher).FromAny().ToNoChange(). - Action(func(chst *internalChannelState, vtype datatransfer.TypeIdentifier, voucherBytes []byte) error { - chst.Vouchers = append(chst.Vouchers, encodedVoucher{Type: vtype, Voucher: &cbg.Deferred{Raw: voucherBytes}}) + Action(func(chst *internal.ChannelState, vtype datatransfer.TypeIdentifier, voucherBytes []byte) error { + chst.Vouchers = append(chst.Vouchers, internal.EncodedVoucher{Type: vtype, Voucher: &cbg.Deferred{Raw: voucherBytes}}) return nil }), fsm.Event(datatransfer.NewVoucherResult).FromAny().ToNoChange(). - Action(func(chst *internalChannelState, vtype datatransfer.TypeIdentifier, voucherResultBytes []byte) error { + Action(func(chst *internal.ChannelState, vtype datatransfer.TypeIdentifier, voucherResultBytes []byte) error { chst.VoucherResults = append(chst.VoucherResults, - encodedVoucherResult{Type: vtype, VoucherResult: &cbg.Deferred{Raw: voucherResultBytes}}) + internal.EncodedVoucherResult{Type: vtype, VoucherResult: &cbg.Deferred{Raw: voucherResultBytes}}) return nil }), fsm.Event(datatransfer.PauseInitiator). @@ -82,6 +102,9 @@ var ChannelEvents = fsm.Events{ From(datatransfer.Cancelling).To(datatransfer.Cancelled). From(datatransfer.Failing).To(datatransfer.Failed). From(datatransfer.Completing).To(datatransfer.Completed), + + // will kickoff state handlers for channels that were cleaning up + fsm.Event(datatransfer.CompleteCleanupOnRestart).FromAny().ToNoChange(), } // ChannelStateEntryFuncs are handlers called as we enter different states @@ -92,7 +115,7 @@ var ChannelStateEntryFuncs = fsm.StateEntryFuncs{ datatransfer.Completing: cleanupConnection, } -func cleanupConnection(ctx fsm.Context, env ChannelEnvironment, channel internalChannelState) error { +func cleanupConnection(ctx fsm.Context, env ChannelEnvironment, channel internal.ChannelState) error { otherParty := channel.Initiator if otherParty == env.ID() { otherParty = channel.Responder @@ -102,9 +125,38 @@ func cleanupConnection(ctx fsm.Context, env ChannelEnvironment, channel internal return ctx.Trigger(datatransfer.CleanupComplete) } +// CleanupStates are the penultimate states for a channel +var CleanupStates = []fsm.StateKey{ + datatransfer.Cancelling, + datatransfer.Completing, + datatransfer.Failing, +} + // ChannelFinalityStates are the final states for a channel var ChannelFinalityStates = []fsm.StateKey{ datatransfer.Cancelled, datatransfer.Completed, datatransfer.Failed, } + +// IsChannelTerminated returns true if the channel is in a finality state +func IsChannelTerminated(st datatransfer.Status) bool { + for _, s := range ChannelFinalityStates { + if s == st { + return true + } + } + + return false +} + +// IsChannelCleaningUp returns true if channel was being cleaned up and finished +func IsChannelCleaningUp(st datatransfer.Status) bool { + for _, s := range CleanupStates { + if s == st { + return true + } + } + + return false +} diff --git a/channels/channels_test.go b/channels/channels_test.go index d0ce5e9c..123b5f06 100644 --- a/channels/channels_test.go +++ b/channels/channels_test.go @@ -1,19 +1,25 @@ package channels_test import ( + "bytes" "context" "errors" + "math/rand" "testing" "time" + "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" + "github.com/ipld/go-ipld-prime/codec/dagcbor" basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal/selector/builder" peer "github.com/libp2p/go-libp2p-core/peer" "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-data-transfer/channels" + "github.com/filecoin-project/go-data-transfer/channels/internal/migrations" "github.com/filecoin-project/go-data-transfer/encoding" "github.com/filecoin-project/go-data-transfer/testutil" ) @@ -22,25 +28,12 @@ func TestChannels(t *testing.T) { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel() - decoderByType := func(identifier datatransfer.TypeIdentifier) (encoding.Decoder, bool) { - if identifier == testutil.NewFakeDTType().Type() { - decoder, err := encoding.NewDecoder(testutil.NewFakeDTType()) - if err != nil { - return nil, false - } - return decoder, true - } - return nil, false - } ds := datastore.NewMapDatastore() received := make(chan event) notifier := func(evt datatransfer.Event, chst datatransfer.ChannelState) { received <- event{evt, chst} } - channelList, err := channels.New(ds, notifier, decoderByType, decoderByType, &fakeEnv{}) - require.NoError(t, err) - tid1 := datatransfer.TransferID(0) tid2 := datatransfer.TransferID(1) fv1 := &testutil.FakeDTType{} @@ -49,24 +42,32 @@ func TestChannels(t *testing.T) { selector := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any).Matcher().Node() peers := testutil.GeneratePeers(4) + channelList, err := channels.New(ds, notifier, decoderByType, decoderByType, &fakeEnv{}, peers[0]) + require.NoError(t, err) + + err = channelList.Start(ctx) + require.NoError(t, err) t.Run("adding channels", func(t *testing.T) { - chid, err := channelList.CreateNew(tid1, cids[0], selector, fv1, peers[0], peers[0], peers[1]) + chid, err := channelList.CreateNew(peers[0], tid1, cids[0], selector, fv1, peers[0], peers[0], peers[1]) require.NoError(t, err) require.Equal(t, peers[0], chid.Initiator) require.Equal(t, tid1, chid.ID) // cannot add twice for same channel id - _, err = channelList.CreateNew(tid1, cids[1], selector, fv2, peers[0], peers[1], peers[0]) + _, err = channelList.CreateNew(peers[0], tid1, cids[1], selector, fv2, peers[0], peers[1], peers[0]) require.Error(t, err) state := checkEvent(ctx, t, received, datatransfer.Open) require.Equal(t, datatransfer.Requested, state.Status()) + // can add for different id - chid, err = channelList.CreateNew(tid2, cids[1], selector, fv2, peers[3], peers[2], peers[3]) + chid, err = channelList.CreateNew(peers[2], tid2, cids[1], selector, fv2, peers[3], peers[2], peers[3]) require.NoError(t, err) require.Equal(t, peers[3], chid.Initiator) require.Equal(t, tid2, chid.ID) state = checkEvent(ctx, t, received, datatransfer.Open) require.Equal(t, datatransfer.Requested, state.Status()) + require.Equal(t, peers[2], state.SelfPeer()) + require.Equal(t, peers[3], state.OtherPeer()) }) t.Run("in progress channels", func(t *testing.T) { @@ -96,6 +97,7 @@ func TestChannels(t *testing.T) { state, err = channelList.GetByID(ctx, datatransfer.ChannelID{Initiator: peers[3], Responder: peers[2], ID: tid2}) require.NotEqual(t, nil, state) require.NoError(t, err) + require.Equal(t, peers[2], state.SelfPeer()) }) t.Run("accept", func(t *testing.T) { @@ -113,40 +115,61 @@ func TestChannels(t *testing.T) { }) t.Run("updating send/receive values", func(t *testing.T) { - state, err := channelList.GetByID(ctx, datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}) + ds := datastore.NewMapDatastore() + channelList, err := channels.New(ds, notifier, decoderByType, decoderByType, &fakeEnv{}, peers[0]) + require.NoError(t, err) + err = channelList.Start(ctx) require.NoError(t, err) + + _, err = channelList.CreateNew(peers[0], tid1, cids[0], selector, fv1, peers[0], peers[0], peers[1]) + require.NoError(t, err) + state := checkEvent(ctx, t, received, datatransfer.Open) + require.Equal(t, datatransfer.Requested, state.Status()) require.Equal(t, uint64(0), state.Received()) require.Equal(t, uint64(0), state.Sent()) + require.Empty(t, state.ReceivedCids()) - err = channelList.IncrementReceived(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, 50) + err = channelList.DataReceived(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, cids[0], 50) require.NoError(t, err) - state = checkEvent(ctx, t, received, datatransfer.Progress) + state = checkEvent(ctx, t, received, datatransfer.DataReceived) require.Equal(t, uint64(50), state.Received()) require.Equal(t, uint64(0), state.Sent()) + require.Equal(t, []cid.Cid{cids[0]}, state.ReceivedCids()) - err = channelList.IncrementSent(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, 100) + err = channelList.DataSent(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, cids[1], 100) require.NoError(t, err) - state = checkEvent(ctx, t, received, datatransfer.Progress) + state = checkEvent(ctx, t, received, datatransfer.DataSent) require.Equal(t, uint64(50), state.Received()) require.Equal(t, uint64(100), state.Sent()) + require.Equal(t, []cid.Cid{cids[0]}, state.ReceivedCids()) // errors if channel does not exist - err = channelList.IncrementReceived(datatransfer.ChannelID{Initiator: peers[1], Responder: peers[0], ID: tid1}, 200) + err = channelList.DataReceived(datatransfer.ChannelID{Initiator: peers[1], Responder: peers[0], ID: tid1}, cids[1], 200) require.EqualError(t, err, channels.ErrNotFound.Error()) - err = channelList.IncrementSent(datatransfer.ChannelID{Initiator: peers[1], Responder: peers[0], ID: tid1}, 200) + err = channelList.DataSent(datatransfer.ChannelID{Initiator: peers[1], Responder: peers[0], ID: tid1}, cids[1], 200) require.EqualError(t, err, channels.ErrNotFound.Error()) + require.Equal(t, []cid.Cid{cids[0]}, state.ReceivedCids()) - err = channelList.IncrementReceived(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, 50) + err = channelList.DataReceived(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, cids[1], 50) require.NoError(t, err) - state = checkEvent(ctx, t, received, datatransfer.Progress) + state = checkEvent(ctx, t, received, datatransfer.DataReceived) require.Equal(t, uint64(100), state.Received()) require.Equal(t, uint64(100), state.Sent()) + require.Equal(t, []cid.Cid{cids[0], cids[1]}, state.ReceivedCids()) - err = channelList.IncrementSent(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, 25) + err = channelList.DataSent(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, cids[1], 25) require.NoError(t, err) - state = checkEvent(ctx, t, received, datatransfer.Progress) + state = checkEvent(ctx, t, received, datatransfer.DataSent) require.Equal(t, uint64(100), state.Received()) require.Equal(t, uint64(125), state.Sent()) + require.Equal(t, []cid.Cid{cids[0], cids[1]}, state.ReceivedCids()) + + err = channelList.DataReceived(datatransfer.ChannelID{Initiator: peers[0], Responder: peers[1], ID: tid1}, cids[0], 50) + require.NoError(t, err) + state = checkEvent(ctx, t, received, datatransfer.DataReceived) + require.Equal(t, uint64(150), state.Received()) + require.Equal(t, uint64(125), state.Sent()) + require.Equal(t, []cid.Cid{cids[0], cids[1], cids[0]}, state.ReceivedCids()) }) t.Run("pause/resume", func(t *testing.T) { @@ -227,7 +250,7 @@ func TestChannels(t *testing.T) { state = checkEvent(ctx, t, received, datatransfer.CleanupComplete) require.Equal(t, datatransfer.Failed, state.Status()) - chid, err := channelList.CreateNew(tid2, cids[1], selector, fv2, peers[2], peers[1], peers[2]) + chid, err := channelList.CreateNew(peers[0], tid2, cids[1], selector, fv2, peers[2], peers[1], peers[2]) require.NoError(t, err) require.Equal(t, peers[2], chid.Initiator) require.Equal(t, tid2, chid.ID) @@ -241,6 +264,197 @@ func TestChannels(t *testing.T) { state = checkEvent(ctx, t, received, datatransfer.CleanupComplete) require.Equal(t, datatransfer.Cancelled, state.Status()) }) + + t.Run("test self peer and other peer", func(t *testing.T) { + // sender is self peer + chid, err := channelList.CreateNew(peers[1], tid1, cids[0], selector, fv1, peers[1], peers[1], peers[2]) + require.NoError(t, err) + ch, err := channelList.GetByID(context.Background(), chid) + require.NoError(t, err) + require.Equal(t, peers[1], ch.SelfPeer()) + require.Equal(t, peers[2], ch.OtherPeer()) + + // recipient is self peer + chid, err = channelList.CreateNew(peers[2], datatransfer.TransferID(1001), cids[0], selector, fv1, peers[1], peers[2], peers[1]) + require.NoError(t, err) + ch, err = channelList.GetByID(context.Background(), chid) + require.NoError(t, err) + require.Equal(t, peers[2], ch.SelfPeer()) + require.Equal(t, peers[1], ch.OtherPeer()) + }) + + t.Run("test disconnected", func(t *testing.T) { + ds := datastore.NewMapDatastore() + received := make(chan event) + notifier := func(evt datatransfer.Event, chst datatransfer.ChannelState) { + received <- event{evt, chst} + } + + channelList, err := channels.New(ds, notifier, decoderByType, decoderByType, &fakeEnv{}, peers[0]) + require.NoError(t, err) + err = channelList.Start(ctx) + require.NoError(t, err) + + chid, err := channelList.CreateNew(peers[3], tid1, cids[0], selector, fv1, peers[3], peers[0], peers[3]) + require.NoError(t, err) + state := checkEvent(ctx, t, received, datatransfer.Open) + require.Equal(t, datatransfer.Requested, state.Status()) + + err = channelList.Disconnected(chid) + require.NoError(t, err) + state = checkEvent(ctx, t, received, datatransfer.Disconnected) + require.Equal(t, datatransfer.PeerDisconnected, state.Status()) + }) + + t.Run("test self peer and other peer", func(t *testing.T) { + peers := testutil.GeneratePeers(3) + // sender is self peer + chid, err := channelList.CreateNew(peers[1], tid1, cids[0], selector, fv1, peers[1], peers[1], peers[2]) + require.NoError(t, err) + ch, err := channelList.GetByID(context.Background(), chid) + require.NoError(t, err) + require.Equal(t, peers[1], ch.SelfPeer()) + require.Equal(t, peers[2], ch.OtherPeer()) + + // recipient is self peer + chid, err = channelList.CreateNew(peers[2], datatransfer.TransferID(1001), cids[0], selector, fv1, peers[1], peers[2], peers[1]) + require.NoError(t, err) + ch, err = channelList.GetByID(context.Background(), chid) + require.NoError(t, err) + require.Equal(t, peers[2], ch.SelfPeer()) + require.Equal(t, peers[1], ch.OtherPeer()) + }) +} + +func TestIsChannelTerminated(t *testing.T) { + require.True(t, channels.IsChannelTerminated(datatransfer.Cancelled)) + require.True(t, channels.IsChannelTerminated(datatransfer.Failed)) + require.False(t, channels.IsChannelTerminated(datatransfer.Ongoing)) +} + +func TestIsChannelCleaningUp(t *testing.T) { + require.True(t, channels.IsChannelCleaningUp(datatransfer.Cancelling)) + require.True(t, channels.IsChannelCleaningUp(datatransfer.Failing)) + require.True(t, channels.IsChannelCleaningUp(datatransfer.Completing)) + require.False(t, channels.IsChannelCleaningUp(datatransfer.Cancelled)) +} + +func TestMigratins(t *testing.T) { + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 2*time.Second) + defer cancel() + + ds := datastore.NewMapDatastore() + received := make(chan event) + notifier := func(evt datatransfer.Event, chst datatransfer.ChannelState) { + received <- event{evt, chst} + } + numChannels := 5 + transferIDs := make([]datatransfer.TransferID, numChannels) + initiators := make([]peer.ID, numChannels) + responders := make([]peer.ID, numChannels) + baseCids := make([]cid.Cid, numChannels) + + totalSizes := make([]uint64, numChannels) + sents := make([]uint64, numChannels) + receiveds := make([]uint64, numChannels) + messages := make([]string, numChannels) + vouchers := make([]datatransfer.Voucher, numChannels) + voucherResults := make([]datatransfer.VoucherResult, numChannels) + + allSelector := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any).Matcher().Node() + allSelectorBuf := new(bytes.Buffer) + err := dagcbor.Encoder(allSelector, allSelectorBuf) + require.NoError(t, err) + allSelectorBytes := allSelectorBuf.Bytes() + + for i := 0; i < numChannels; i++ { + transferIDs[i] = datatransfer.TransferID(rand.Uint64()) + initiators[i] = testutil.GeneratePeers(1)[0] + responders[i] = testutil.GeneratePeers(1)[0] + baseCids[i] = testutil.GenerateCids(1)[0] + totalSizes[i] = rand.Uint64() + sents[i] = rand.Uint64() + receiveds[i] = rand.Uint64() + messages[i] = string(testutil.RandomBytes(20)) + vouchers[i] = testutil.NewFakeDTType() + vBytes, err := encoding.Encode(vouchers[i]) + require.NoError(t, err) + voucherResults[i] = testutil.NewFakeDTType() + vrBytes, err := encoding.Encode(voucherResults[i]) + require.NoError(t, err) + channel := migrations.ChannelState0{ + TransferID: transferIDs[i], + Initiator: initiators[i], + Responder: responders[i], + BaseCid: baseCids[i], + Selector: &cbg.Deferred{ + Raw: allSelectorBytes, + }, + Sender: initiators[i], + Recipient: responders[i], + TotalSize: totalSizes[i], + Status: datatransfer.Ongoing, + Sent: sents[i], + Received: receiveds[i], + Message: messages[i], + Vouchers: []migrations.EncodedVoucher0{ + { + Type: vouchers[i].Type(), + Voucher: &cbg.Deferred{ + Raw: vBytes, + }, + }, + }, + VoucherResults: []migrations.EncodedVoucherResult0{ + { + Type: voucherResults[i].Type(), + VoucherResult: &cbg.Deferred{ + Raw: vrBytes, + }, + }, + }, + } + buf := new(bytes.Buffer) + err = channel.MarshalCBOR(buf) + require.NoError(t, err) + err = ds.Put(datastore.NewKey(datatransfer.ChannelID{ + Initiator: initiators[i], + Responder: responders[i], + ID: transferIDs[i], + }.String()), buf.Bytes()) + require.NoError(t, err) + } + + selfPeer := testutil.GeneratePeers(1)[0] + channelList, err := channels.New(ds, notifier, decoderByType, decoderByType, &fakeEnv{}, selfPeer) + require.NoError(t, err) + err = channelList.Start(ctx) + require.NoError(t, err) + + for i := 0; i < numChannels; i++ { + + channel, err := channelList.GetByID(ctx, datatransfer.ChannelID{ + Initiator: initiators[i], + Responder: responders[i], + ID: transferIDs[i], + }) + require.NoError(t, err) + require.Equal(t, selfPeer, channel.SelfPeer()) + require.Equal(t, transferIDs[i], channel.TransferID()) + require.Equal(t, baseCids[i], channel.BaseCID()) + require.Equal(t, allSelector, channel.Selector()) + require.Equal(t, initiators[i], channel.Sender()) + require.Equal(t, responders[i], channel.Recipient()) + require.Equal(t, totalSizes[i], channel.TotalSize()) + require.Equal(t, datatransfer.Ongoing, channel.Status()) + require.Equal(t, sents[i], channel.Sent()) + require.Equal(t, receiveds[i], channel.Received()) + require.Equal(t, messages[i], channel.Message()) + require.Equal(t, vouchers[i], channel.LastVoucher()) + require.Equal(t, voucherResults[i], channel.LastVoucherResult()) + require.Len(t, channel.ReceivedCids(), 0) + } } type event struct { @@ -275,3 +489,14 @@ func (fe *fakeEnv) ID() peer.ID { func (fe *fakeEnv) CleanupChannel(chid datatransfer.ChannelID) { } + +func decoderByType(identifier datatransfer.TypeIdentifier) (encoding.Decoder, bool) { + if identifier == testutil.NewFakeDTType().Type() { + decoder, err := encoding.NewDecoder(testutil.NewFakeDTType()) + if err != nil { + return nil, false + } + return decoder, true + } + return nil, false +} diff --git a/channels/internalchannel.go b/channels/internal/internalchannel.go similarity index 58% rename from channels/internalchannel.go rename to channels/internal/internalchannel.go index 6979375f..bf82d3da 100644 --- a/channels/internalchannel.go +++ b/channels/internal/internalchannel.go @@ -1,4 +1,4 @@ -package channels +package internal import ( "github.com/ipfs/go-cid" @@ -8,23 +8,28 @@ import ( datatransfer "github.com/filecoin-project/go-data-transfer" ) -//go:generate cbor-gen-for internalChannelState encodedVoucher encodedVoucherResult +//go:generate cbor-gen-for --map-encoding ChannelState EncodedVoucher EncodedVoucherResult -type encodedVoucher struct { +// EncodedVoucher is how the voucher is stored on disk +type EncodedVoucher struct { // Vouchers identifier for decoding Type datatransfer.TypeIdentifier // used to verify this channel Voucher *cbg.Deferred } -type encodedVoucherResult struct { +// EncodedVoucherResult is how the voucher result is stored on disk +type EncodedVoucherResult struct { // Vouchers identifier for decoding Type datatransfer.TypeIdentifier // used to verify this channel VoucherResult *cbg.Deferred } -type internalChannelState struct { +// ChannelState is the internal representation on disk for the channel fsm +type ChannelState struct { + // PeerId of the manager peer + SelfPeer peer.ID // an identifier for this channel shared by request and responder, set by requester through protocol TransferID datatransfer.TransferID // Initiator is the person who intiated this datatransfer request @@ -49,26 +54,9 @@ type internalChannelState struct { Received uint64 // more informative status on a channel Message string - Vouchers []encodedVoucher - VoucherResults []encodedVoucherResult -} + Vouchers []EncodedVoucher + VoucherResults []EncodedVoucherResult -func (c internalChannelState) ToChannelState(voucherDecoder DecoderByTypeFunc, voucherResultDecoder DecoderByTypeFunc) datatransfer.ChannelState { - return channelState{ - isPull: c.Initiator == c.Recipient, - transferID: c.TransferID, - baseCid: c.BaseCid, - selector: c.Selector, - sender: c.Sender, - recipient: c.Recipient, - totalSize: c.TotalSize, - status: c.Status, - sent: c.Sent, - received: c.Received, - message: c.Message, - vouchers: c.Vouchers, - voucherResults: c.VoucherResults, - voucherResultDecoder: voucherResultDecoder, - voucherDecoder: voucherDecoder, - } + // ReceivedCids is all the cids the initiator has received so far + ReceivedCids []cid.Cid } diff --git a/channels/internal/internalchannel_cbor_gen.go b/channels/internal/internalchannel_cbor_gen.go new file mode 100644 index 00000000..4571f266 --- /dev/null +++ b/channels/internal/internalchannel_cbor_gen.go @@ -0,0 +1,879 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package internal + +import ( + "fmt" + "io" + + datatransfer "github.com/filecoin-project/go-data-transfer" + cid "github.com/ipfs/go-cid" + peer "github.com/libp2p/go-libp2p-core/peer" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf + +func (t *ChannelState) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{176}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.SelfPeer (peer.ID) (string) + if len("SelfPeer") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"SelfPeer\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("SelfPeer"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("SelfPeer")); err != nil { + return err + } + + if len(t.SelfPeer) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.SelfPeer was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.SelfPeer))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.SelfPeer)); err != nil { + return err + } + + // t.TransferID (datatransfer.TransferID) (uint64) + if len("TransferID") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"TransferID\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("TransferID"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("TransferID")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.TransferID)); err != nil { + return err + } + + // t.Initiator (peer.ID) (string) + if len("Initiator") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Initiator\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Initiator"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Initiator")); err != nil { + return err + } + + if len(t.Initiator) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Initiator was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Initiator))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Initiator)); err != nil { + return err + } + + // t.Responder (peer.ID) (string) + if len("Responder") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Responder\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Responder"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Responder")); err != nil { + return err + } + + if len(t.Responder) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Responder was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Responder))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Responder)); err != nil { + return err + } + + // t.BaseCid (cid.Cid) (struct) + if len("BaseCid") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"BaseCid\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("BaseCid"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("BaseCid")); err != nil { + return err + } + + if err := cbg.WriteCidBuf(scratch, w, t.BaseCid); err != nil { + return xerrors.Errorf("failed to write cid field t.BaseCid: %w", err) + } + + // t.Selector (typegen.Deferred) (struct) + if len("Selector") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Selector\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Selector"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Selector")); err != nil { + return err + } + + if err := t.Selector.MarshalCBOR(w); err != nil { + return err + } + + // t.Sender (peer.ID) (string) + if len("Sender") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Sender\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Sender"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Sender")); err != nil { + return err + } + + if len(t.Sender) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Sender was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Sender))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Sender)); err != nil { + return err + } + + // t.Recipient (peer.ID) (string) + if len("Recipient") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Recipient\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Recipient"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Recipient")); err != nil { + return err + } + + if len(t.Recipient) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Recipient was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Recipient))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Recipient)); err != nil { + return err + } + + // t.TotalSize (uint64) (uint64) + if len("TotalSize") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"TotalSize\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("TotalSize"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("TotalSize")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.TotalSize)); err != nil { + return err + } + + // t.Status (datatransfer.Status) (uint64) + if len("Status") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Status\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Status"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Status")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Status)); err != nil { + return err + } + + // t.Sent (uint64) (uint64) + if len("Sent") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Sent\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Sent"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Sent")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Sent)); err != nil { + return err + } + + // t.Received (uint64) (uint64) + if len("Received") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Received\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Received"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Received")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Received)); err != nil { + return err + } + + // t.Message (string) (string) + if len("Message") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Message\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Message"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Message")); err != nil { + return err + } + + if len(t.Message) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Message was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Message))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Message)); err != nil { + return err + } + + // t.Vouchers ([]internal.EncodedVoucher) (slice) + if len("Vouchers") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Vouchers\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Vouchers"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Vouchers")); err != nil { + return err + } + + if len(t.Vouchers) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Vouchers was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Vouchers))); err != nil { + return err + } + for _, v := range t.Vouchers { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + + // t.VoucherResults ([]internal.EncodedVoucherResult) (slice) + if len("VoucherResults") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"VoucherResults\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("VoucherResults"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("VoucherResults")); err != nil { + return err + } + + if len(t.VoucherResults) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.VoucherResults was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.VoucherResults))); err != nil { + return err + } + for _, v := range t.VoucherResults { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + + // t.ReceivedCids ([]cid.Cid) (slice) + if len("ReceivedCids") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"ReceivedCids\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("ReceivedCids"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("ReceivedCids")); err != nil { + return err + } + + if len(t.ReceivedCids) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.ReceivedCids was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.ReceivedCids))); err != nil { + return err + } + for _, v := range t.ReceivedCids { + if err := cbg.WriteCidBuf(scratch, w, v); err != nil { + return xerrors.Errorf("failed writing cid field t.ReceivedCids: %w", err) + } + } + return nil +} + +func (t *ChannelState) UnmarshalCBOR(r io.Reader) error { + *t = ChannelState{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("ChannelState: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.SelfPeer (peer.ID) (string) + case "SelfPeer": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.SelfPeer = peer.ID(sval) + } + // t.TransferID (datatransfer.TransferID) (uint64) + case "TransferID": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.TransferID = datatransfer.TransferID(extra) + + } + // t.Initiator (peer.ID) (string) + case "Initiator": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Initiator = peer.ID(sval) + } + // t.Responder (peer.ID) (string) + case "Responder": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Responder = peer.ID(sval) + } + // t.BaseCid (cid.Cid) (struct) + case "BaseCid": + + { + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.BaseCid: %w", err) + } + + t.BaseCid = c + + } + // t.Selector (typegen.Deferred) (struct) + case "Selector": + + { + + t.Selector = new(cbg.Deferred) + + if err := t.Selector.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + // t.Sender (peer.ID) (string) + case "Sender": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Sender = peer.ID(sval) + } + // t.Recipient (peer.ID) (string) + case "Recipient": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Recipient = peer.ID(sval) + } + // t.TotalSize (uint64) (uint64) + case "TotalSize": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.TotalSize = uint64(extra) + + } + // t.Status (datatransfer.Status) (uint64) + case "Status": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Status = datatransfer.Status(extra) + + } + // t.Sent (uint64) (uint64) + case "Sent": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Sent = uint64(extra) + + } + // t.Received (uint64) (uint64) + case "Received": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Received = uint64(extra) + + } + // t.Message (string) (string) + case "Message": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Message = string(sval) + } + // t.Vouchers ([]internal.EncodedVoucher) (slice) + case "Vouchers": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Vouchers: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Vouchers = make([]EncodedVoucher, extra) + } + + for i := 0; i < int(extra); i++ { + + var v EncodedVoucher + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.Vouchers[i] = v + } + + // t.VoucherResults ([]internal.EncodedVoucherResult) (slice) + case "VoucherResults": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.VoucherResults: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.VoucherResults = make([]EncodedVoucherResult, extra) + } + + for i := 0; i < int(extra); i++ { + + var v EncodedVoucherResult + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.VoucherResults[i] = v + } + + // t.ReceivedCids ([]cid.Cid) (slice) + case "ReceivedCids": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.ReceivedCids: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.ReceivedCids = make([]cid.Cid, extra) + } + + for i := 0; i < int(extra); i++ { + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("reading cid field t.ReceivedCids failed: %w", err) + } + t.ReceivedCids[i] = c + } + + default: + return fmt.Errorf("unknown struct field %d: '%s'", i, name) + } + } + + return nil +} +func (t *EncodedVoucher) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{162}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Type (datatransfer.TypeIdentifier) (string) + if len("Type") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Type\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Type"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Type")); err != nil { + return err + } + + if len(t.Type) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Type was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Type))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Type)); err != nil { + return err + } + + // t.Voucher (typegen.Deferred) (struct) + if len("Voucher") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Voucher\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Voucher"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Voucher")); err != nil { + return err + } + + if err := t.Voucher.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *EncodedVoucher) UnmarshalCBOR(r io.Reader) error { + *t = EncodedVoucher{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("EncodedVoucher: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Type (datatransfer.TypeIdentifier) (string) + case "Type": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Type = datatransfer.TypeIdentifier(sval) + } + // t.Voucher (typegen.Deferred) (struct) + case "Voucher": + + { + + t.Voucher = new(cbg.Deferred) + + if err := t.Voucher.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + + default: + return fmt.Errorf("unknown struct field %d: '%s'", i, name) + } + } + + return nil +} +func (t *EncodedVoucherResult) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{162}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Type (datatransfer.TypeIdentifier) (string) + if len("Type") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Type\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Type"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Type")); err != nil { + return err + } + + if len(t.Type) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Type was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Type))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Type)); err != nil { + return err + } + + // t.VoucherResult (typegen.Deferred) (struct) + if len("VoucherResult") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"VoucherResult\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("VoucherResult"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("VoucherResult")); err != nil { + return err + } + + if err := t.VoucherResult.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *EncodedVoucherResult) UnmarshalCBOR(r io.Reader) error { + *t = EncodedVoucherResult{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("EncodedVoucherResult: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Type (datatransfer.TypeIdentifier) (string) + case "Type": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Type = datatransfer.TypeIdentifier(sval) + } + // t.VoucherResult (typegen.Deferred) (struct) + case "VoucherResult": + + { + + t.VoucherResult = new(cbg.Deferred) + + if err := t.VoucherResult.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + + default: + return fmt.Errorf("unknown struct field %d: '%s'", i, name) + } + } + + return nil +} diff --git a/channels/internal/migrations/migrations.go b/channels/internal/migrations/migrations.go new file mode 100644 index 00000000..7c7d311b --- /dev/null +++ b/channels/internal/migrations/migrations.go @@ -0,0 +1,117 @@ +package migrations + +import ( + "github.com/ipfs/go-cid" + peer "github.com/libp2p/go-libp2p-core/peer" + cbg "github.com/whyrusleeping/cbor-gen" + + versioning "github.com/filecoin-project/go-ds-versioning/pkg" + "github.com/filecoin-project/go-ds-versioning/pkg/versioned" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels/internal" +) + +//go:generate cbor-gen-for ChannelState0 EncodedVoucher0 EncodedVoucherResult0 + +// EncodedVoucher0 is version 0 of EncodedVoucher +type EncodedVoucher0 struct { + // Vouchers identifier for decoding + Type datatransfer.TypeIdentifier + // used to verify this channel + Voucher *cbg.Deferred +} + +// EncodedVoucherResult0 is version 0 of EncodedVoucherResult +type EncodedVoucherResult0 struct { + // Vouchers identifier for decoding + Type datatransfer.TypeIdentifier + // used to verify this channel + VoucherResult *cbg.Deferred +} + +// ChannelState0 is version 0 of ChannelState +type ChannelState0 struct { + // an identifier for this channel shared by request and responder, set by requester through protocol + TransferID datatransfer.TransferID + // Initiator is the person who intiated this datatransfer request + Initiator peer.ID + // Responder is the person who is responding to this datatransfer request + Responder peer.ID + // base CID for the piece being transferred + BaseCid cid.Cid + // portion of Piece to return, specified by an IPLD selector + Selector *cbg.Deferred + // the party that is sending the data (not who initiated the request) + Sender peer.ID + // the party that is receiving the data (not who initiated the request) + Recipient peer.ID + // expected amount of data to be transferred + TotalSize uint64 + // current status of this deal + Status datatransfer.Status + // total bytes sent from this node (0 if receiver) + Sent uint64 + // total bytes received by this node (0 if sender) + Received uint64 + // more informative status on a channel + Message string + Vouchers []EncodedVoucher0 + VoucherResults []EncodedVoucherResult0 +} + +// MigrateEncodedVoucher0To1 converts a tuple encoded voucher to a map encoded voucher +func MigrateEncodedVoucher0To1(oldV EncodedVoucher0) internal.EncodedVoucher { + return internal.EncodedVoucher{ + Type: oldV.Type, + Voucher: oldV.Voucher, + } +} + +// MigrateEncodedVoucherResult0To1 converts a tuple encoded voucher to a map encoded voucher +func MigrateEncodedVoucherResult0To1(oldV EncodedVoucherResult0) internal.EncodedVoucherResult { + return internal.EncodedVoucherResult{ + Type: oldV.Type, + VoucherResult: oldV.VoucherResult, + } +} + +// GetMigrateChannelState0To1 returns a conversion function for migrating v0 channel state to v1 +func GetMigrateChannelState0To1(selfPeer peer.ID) func(*ChannelState0) (*internal.ChannelState, error) { + return func(oldCs *ChannelState0) (*internal.ChannelState, error) { + encodedVouchers := make([]internal.EncodedVoucher, 0, len(oldCs.Vouchers)) + for _, ev0 := range oldCs.Vouchers { + encodedVouchers = append(encodedVouchers, MigrateEncodedVoucher0To1(ev0)) + } + encodedVoucherResults := make([]internal.EncodedVoucherResult, 0, len(oldCs.VoucherResults)) + for _, evr0 := range oldCs.VoucherResults { + encodedVoucherResults = append(encodedVoucherResults, MigrateEncodedVoucherResult0To1(evr0)) + } + return &internal.ChannelState{ + SelfPeer: selfPeer, + TransferID: oldCs.TransferID, + Initiator: oldCs.Initiator, + Responder: oldCs.Responder, + BaseCid: oldCs.BaseCid, + Selector: oldCs.Selector, + Sender: oldCs.Sender, + Recipient: oldCs.Recipient, + TotalSize: oldCs.TotalSize, + Status: oldCs.Status, + Sent: oldCs.Sent, + Received: oldCs.Received, + Message: oldCs.Message, + Vouchers: encodedVouchers, + VoucherResults: encodedVoucherResults, + ReceivedCids: nil, + }, nil + } +} + +// GetChannelStateMigrations returns a migration list for the channel states +func GetChannelStateMigrations(selfPeer peer.ID) (versioning.VersionedMigrationList, error) { + channelStateMigration0To1 := GetMigrateChannelState0To1(selfPeer) + return versioned.BuilderList{ + versioned.NewVersionedBuilder(channelStateMigration0To1, versioning.VersionKey("1")), + }.Build() +} diff --git a/channels/internalchannel_cbor_gen.go b/channels/internal/migrations/migrations_cbor_gen.go similarity index 89% rename from channels/internalchannel_cbor_gen.go rename to channels/internal/migrations/migrations_cbor_gen.go index e481b5db..dbdfb166 100644 --- a/channels/internalchannel_cbor_gen.go +++ b/channels/internal/migrations/migrations_cbor_gen.go @@ -1,6 +1,6 @@ // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package channels +package migrations import ( "fmt" @@ -14,14 +14,14 @@ import ( var _ = xerrors.Errorf -var lengthBufinternalChannelState = []byte{142} +var lengthBufChannelState0 = []byte{142} -func (t *internalChannelState) MarshalCBOR(w io.Writer) error { +func (t *ChannelState0) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufinternalChannelState); err != nil { + if _, err := w.Write(lengthBufChannelState0); err != nil { return err } @@ -128,7 +128,7 @@ func (t *internalChannelState) MarshalCBOR(w io.Writer) error { return err } - // t.Vouchers ([]channels.encodedVoucher) (slice) + // t.Vouchers ([]migrations.EncodedVoucher0) (slice) if len(t.Vouchers) > cbg.MaxLength { return xerrors.Errorf("Slice value in field t.Vouchers was too long") } @@ -142,7 +142,7 @@ func (t *internalChannelState) MarshalCBOR(w io.Writer) error { } } - // t.VoucherResults ([]channels.encodedVoucherResult) (slice) + // t.VoucherResults ([]migrations.EncodedVoucherResult0) (slice) if len(t.VoucherResults) > cbg.MaxLength { return xerrors.Errorf("Slice value in field t.VoucherResults was too long") } @@ -158,8 +158,8 @@ func (t *internalChannelState) MarshalCBOR(w io.Writer) error { return nil } -func (t *internalChannelState) UnmarshalCBOR(r io.Reader) error { - *t = internalChannelState{} +func (t *ChannelState0) UnmarshalCBOR(r io.Reader) error { + *t = ChannelState0{} br := cbg.GetPeeker(r) scratch := make([]byte, 8) @@ -318,7 +318,7 @@ func (t *internalChannelState) UnmarshalCBOR(r io.Reader) error { t.Message = string(sval) } - // t.Vouchers ([]channels.encodedVoucher) (slice) + // t.Vouchers ([]migrations.EncodedVoucher0) (slice) maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) if err != nil { @@ -334,12 +334,12 @@ func (t *internalChannelState) UnmarshalCBOR(r io.Reader) error { } if extra > 0 { - t.Vouchers = make([]encodedVoucher, extra) + t.Vouchers = make([]EncodedVoucher0, extra) } for i := 0; i < int(extra); i++ { - var v encodedVoucher + var v EncodedVoucher0 if err := v.UnmarshalCBOR(br); err != nil { return err } @@ -347,7 +347,7 @@ func (t *internalChannelState) UnmarshalCBOR(r io.Reader) error { t.Vouchers[i] = v } - // t.VoucherResults ([]channels.encodedVoucherResult) (slice) + // t.VoucherResults ([]migrations.EncodedVoucherResult0) (slice) maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) if err != nil { @@ -363,12 +363,12 @@ func (t *internalChannelState) UnmarshalCBOR(r io.Reader) error { } if extra > 0 { - t.VoucherResults = make([]encodedVoucherResult, extra) + t.VoucherResults = make([]EncodedVoucherResult0, extra) } for i := 0; i < int(extra); i++ { - var v encodedVoucherResult + var v EncodedVoucherResult0 if err := v.UnmarshalCBOR(br); err != nil { return err } @@ -379,14 +379,14 @@ func (t *internalChannelState) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufencodedVoucher = []byte{130} +var lengthBufEncodedVoucher0 = []byte{130} -func (t *encodedVoucher) MarshalCBOR(w io.Writer) error { +func (t *EncodedVoucher0) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufencodedVoucher); err != nil { + if _, err := w.Write(lengthBufEncodedVoucher0); err != nil { return err } @@ -411,8 +411,8 @@ func (t *encodedVoucher) MarshalCBOR(w io.Writer) error { return nil } -func (t *encodedVoucher) UnmarshalCBOR(r io.Reader) error { - *t = encodedVoucher{} +func (t *EncodedVoucher0) UnmarshalCBOR(r io.Reader) error { + *t = EncodedVoucher0{} br := cbg.GetPeeker(r) scratch := make([]byte, 8) @@ -452,14 +452,14 @@ func (t *encodedVoucher) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufencodedVoucherResult = []byte{130} +var lengthBufEncodedVoucherResult0 = []byte{130} -func (t *encodedVoucherResult) MarshalCBOR(w io.Writer) error { +func (t *EncodedVoucherResult0) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufencodedVoucherResult); err != nil { + if _, err := w.Write(lengthBufEncodedVoucherResult0); err != nil { return err } @@ -484,8 +484,8 @@ func (t *encodedVoucherResult) MarshalCBOR(w io.Writer) error { return nil } -func (t *encodedVoucherResult) UnmarshalCBOR(r io.Reader) error { - *t = encodedVoucherResult{} +func (t *EncodedVoucherResult0) UnmarshalCBOR(r io.Reader) error { + *t = EncodedVoucherResult0{} br := cbg.GetPeeker(r) scratch := make([]byte, 8) diff --git a/coverage.txt b/coverage.txt new file mode 100644 index 00000000..e3bb72b7 --- /dev/null +++ b/coverage.txt @@ -0,0 +1,1250 @@ +mode: set +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:19.46,20.59 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:28.2,28.48 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:36.2,36.31 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:20.59,23.17 3 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:26.3,26.26 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:23.17,25.4 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:28.48,31.17 3 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:34.3,34.26 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:31.17,33.4 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:47.56,49.53 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:54.2,55.45 2 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:59.2,59.55 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:64.2,65.16 2 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:68.2,69.63 2 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:72.2,72.48 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:49.53,51.3 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:55.45,57.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:59.55,61.3 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:65.16,67.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:69.63,71.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:79.79,83.16 4 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:86.2,86.29 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:83.16,85.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:93.78,96.45 3 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:99.2,101.16 3 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:104.2,104.21 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:96.45,98.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:101.16,103.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:111.82,114.45 3 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:117.2,118.16 2 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:121.2,121.21 1 1 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:114.45,116.3 1 0 +github.com/filecoin-project/go-data-transfer/encoding/encoding.go:118.16,120.3 1 0 +github.com/filecoin-project/go-data-transfer/registry/registry.go:33.30,37.2 1 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:40.89,43.16 3 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:46.2,48.40 3 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:51.2,52.12 2 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:43.16,45.3 1 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:48.40,50.3 1 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:56.93,61.2 4 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:64.88,69.2 4 1 +github.com/filecoin-project/go-data-transfer/registry/registry.go:72.109,75.43 3 0 +github.com/filecoin-project/go-data-transfer/registry/registry.go:81.2,81.12 1 0 +github.com/filecoin-project/go-data-transfer/registry/registry.go:75.43,77.17 2 0 +github.com/filecoin-project/go-data-transfer/registry/registry.go:77.17,79.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:20.63,21.14 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:25.2,25.66 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:29.2,32.40 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:36.2,36.111 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:39.2,39.68 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:45.2,45.106 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:50.2,50.38 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:54.2,54.109 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:57.2,57.66 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:62.2,62.38 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:66.2,66.109 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:69.2,69.66 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:75.2,75.63 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:80.2,80.50 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:85.2,85.35 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:89.2,89.106 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:92.2,92.63 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:97.2,97.38 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:101.2,101.109 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:104.2,104.66 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:110.2,110.105 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:116.2,116.102 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:122.2,122.100 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:128.2,128.104 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:133.2,133.36 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:137.2,137.107 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:140.2,140.64 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:145.2,145.37 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:149.2,149.103 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:152.2,152.31 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:159.2,159.43 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:163.2,163.109 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:166.2,166.37 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:173.2,173.41 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:177.2,177.107 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:180.2,180.35 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:185.2,185.12 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:21.14,24.3 2 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:25.66,27.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:32.40,34.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:36.111,38.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:39.68,41.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:45.106,47.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:50.38,52.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:54.109,56.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:57.66,59.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:62.38,64.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:66.109,68.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:69.66,71.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:75.63,77.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:80.50,82.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:85.35,87.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:89.106,91.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:92.63,94.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:97.38,99.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:101.109,103.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:104.66,106.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:110.105,112.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:116.102,118.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:122.100,124.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:128.104,130.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:133.36,135.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:137.107,139.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:140.64,142.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:145.37,147.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:149.103,151.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:152.31,153.42 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:153.42,155.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:159.43,161.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:163.109,165.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:166.37,167.42 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:167.42,169.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:173.41,175.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:177.107,179.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:180.35,181.56 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:181.56,183.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:188.65,195.16 5 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:198.2,198.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:202.2,202.17 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:360.2,361.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:365.2,365.27 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:369.2,369.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:373.2,373.15 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:377.2,377.34 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:389.2,390.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:394.2,394.27 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:398.2,398.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:402.2,402.15 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:406.2,406.34 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:418.2,419.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:423.2,423.27 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:427.2,427.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:431.2,431.15 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:435.2,435.34 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:444.2,444.12 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:195.16,197.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:198.25,200.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:202.17,204.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:208.2,210.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:214.3,214.32 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:210.17,212.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:218.2,221.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:224.3,224.32 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:227.3,227.48 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:221.17,223.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:224.32,226.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:232.2,234.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:238.3,238.30 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:234.17,236.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:242.2,244.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:248.3,248.30 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:244.17,246.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:252.2,255.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:259.3,259.16 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:255.17,257.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:264.2,268.54 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:268.54,270.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:274.2,276.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:280.3,280.27 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:276.17,278.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:284.2,286.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:290.3,290.30 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:286.17,288.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:294.2,297.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:300.3,300.32 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:303.3,303.30 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:297.17,299.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:300.32,302.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:308.2,311.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:314.3,314.32 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:317.3,317.40 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:311.17,313.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:314.32,316.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:322.2,325.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:328.3,328.32 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:331.3,331.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:325.17,327.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:328.32,330.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:336.2,339.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:342.3,342.32 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:345.3,345.29 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:339.17,341.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:342.32,344.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:350.2,352.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:356.3,356.27 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:352.17,354.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:361.16,363.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:365.27,367.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:369.25,371.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:373.15,375.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:377.34,380.45 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:384.3,384.20 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:380.45,382.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:390.16,392.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:394.27,396.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:398.25,400.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:402.15,404.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:406.34,409.45 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:413.3,413.26 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:409.45,411.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:419.16,421.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:423.27,425.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:427.25,429.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:431.15,433.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:435.34,438.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:441.3,441.24 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:438.17,440.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:449.57,450.14 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:454.2,454.60 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:458.2,461.33 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:465.2,465.104 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:468.2,468.61 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:473.2,473.49 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:476.2,476.12 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:450.14,453.3 2 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:454.60,456.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:461.33,463.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:465.104,467.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:468.61,470.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:473.49,475.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:479.59,486.16 5 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:489.2,489.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:493.2,493.16 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:517.2,517.12 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:486.16,488.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:489.25,491.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:493.16,495.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:499.2,501.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:505.3,505.45 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:501.17,503.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:509.2,513.53 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:513.53,515.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:522.63,523.14 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:527.2,527.66 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:531.2,534.33 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:538.2,538.104 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:541.2,541.61 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:546.2,546.55 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:549.2,549.12 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:523.14,526.3 2 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:527.66,529.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:534.33,536.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:538.104,540.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:541.61,543.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:546.55,548.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:552.65,559.16 5 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:562.2,562.25 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:566.2,566.16 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:590.2,590.12 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:559.16,561.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:562.25,564.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:566.16,568.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:572.2,574.17 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:578.3,578.45 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:574.17,576.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:582.2,586.59 2 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel_cbor_gen.go:586.59,588.4 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:56.52,56.71 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:59.37,59.54 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:62.41,62.62 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:65.60,65.83 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:68.41,68.61 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:72.44,76.16 4 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:79.2,79.24 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:76.16,78.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:83.54,87.2 3 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:90.48,92.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:95.40,95.59 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:98.43,98.65 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:101.42,101.64 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:104.37,106.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:108.58,109.14 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:109.14,111.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:111.8,113.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:116.40,118.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:120.57,122.37 2 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:127.2,127.17 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:122.37,126.3 3 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:130.58,134.2 3 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:136.70,140.2 3 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:142.69,144.43 2 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:149.2,149.23 1 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:144.43,148.3 3 1 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:152.61,153.27 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:156.2,156.17 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:153.27,155.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channel_state.go:159.45,161.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:51.45,66.16 3 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:69.2,70.15 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:66.16,68.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:73.77,75.9 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:78.2,79.9 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:82.2,88.87 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:75.9,77.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:79.9,81.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:93.224,95.29 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:100.2,102.16 3 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:105.2,106.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:109.2,129.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:132.2,132.60 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:95.29,97.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:97.8,99.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:102.16,104.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:106.16,108.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:129.16,131.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:136.95,139.16 3 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:142.2,143.51 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:147.2,147.22 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:139.16,141.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:143.51,146.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:152.113,155.16 3 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:158.2,158.86 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:155.16,157.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:162.62,164.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:167.63,169.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:171.91,173.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:175.95,177.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:180.70,182.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:185.70,187.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:190.71,192.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:195.71,197.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:200.96,202.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:205.2,205.76 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:202.16,204.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:209.114,211.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:214.2,214.94 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:211.16,213.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:218.64,220.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:223.70,225.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:228.74,230.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:233.83,235.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:238.71,240.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:243.62,245.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:248.72,250.2 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:253.74,255.2 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:257.110,259.16 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:262.2,262.10 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:265.2,265.50 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels.go:259.16,261.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels.go:262.10,264.3 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:30.121,34.3 3 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:43.121,46.3 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:48.124,51.3 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:53.105,56.4 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:58.111,62.4 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:112.101,114.28 2 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:117.2,119.50 3 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:114.28,116.3 1 0 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:130.55,131.42 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:137.2,137.14 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:131.42,132.14 1 1 +github.com/filecoin-project/go-data-transfer/channels/channels_fsm.go:132.14,134.4 1 1 +github.com/filecoin-project/go-data-transfer/channels/internalchannel.go:61.146,82.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message.go:20.45,22.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message.go:25.65,26.20 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message.go:29.2,29.33 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message.go:26.20,28.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message.go:34.53,36.2 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:17.58,18.14 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:22.2,22.61 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:27.2,27.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:32.2,32.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:37.2,37.50 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:40.2,40.12 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:18.14,21.3 2 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:22.61,24.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:27.49,29.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:32.49,34.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:37.50,39.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:43.60,50.16 5 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:53.2,53.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:57.2,57.16 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:63.2,64.16 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:67.2,67.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:70.2,70.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:116.2,116.12 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:50.16,52.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:53.25,55.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:57.16,59.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:64.16,66.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:67.25,69.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:71.10,72.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:73.10,74.16 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:75.10,76.88 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:80.2,83.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:86.3,86.27 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:83.17,85.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:86.27,87.42 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:90.4,91.54 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:87.42,89.5 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:91.54,93.5 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:99.2,102.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:105.3,105.27 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:102.17,104.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:105.27,106.42 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:109.4,110.55 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:106.42,108.5 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_message_cbor_gen.go:110.55,112.5 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:37.46,39.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:41.46,43.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:45.68,47.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:49.80,50.44 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:53.2,53.32 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:50.44,52.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:56.42,58.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:60.45,62.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:64.46,66.2 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:68.45,70.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:72.66,74.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:78.43,80.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:83.71,85.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:88.91,89.22 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:92.2,92.46 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:89.22,91.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:95.49,97.2 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:100.47,101.21 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:104.2,104.18 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:101.21,103.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:108.59,109.21 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:112.2,115.16 4 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:118.2,118.29 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:109.21,111.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:115.16,117.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:122.45,124.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:127.46,129.2 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request.go:133.54,140.2 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:18.58,19.14 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:23.2,23.61 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:27.2,31.19 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:43.2,43.100 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:48.2,48.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:53.2,53.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:58.2,58.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:63.2,63.46 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:68.2,68.47 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:73.2,73.33 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:77.2,77.104 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:80.2,80.61 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:86.2,86.102 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:91.2,91.56 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:94.2,94.12 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:19.14,22.3 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:23.61,25.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:31.19,32.50 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:32.50,34.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:35.8,36.62 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:36.62,38.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:43.100,45.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:48.49,50.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:53.49,55.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:58.49,60.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:63.46,65.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:68.47,70.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:73.33,75.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:77.104,79.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:80.61,82.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:86.102,88.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:91.56,93.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:97.60,104.16 5 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:107.2,107.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:111.2,111.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:153.2,154.16 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:157.2,157.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:160.2,160.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:170.2,171.16 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:174.2,174.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:177.2,177.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:187.2,188.16 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:191.2,191.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:194.2,194.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:255.2,255.12 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:104.16,106.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:107.25,109.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:111.17,113.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:117.2,120.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:123.3,123.27 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:120.17,122.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:123.27,124.42 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:128.4,129.18 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:133.4,133.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:124.42,126.5 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:129.18,131.5 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:139.2,142.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:145.3,145.32 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:148.3,148.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:142.17,144.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:145.32,147.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:154.16,156.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:157.25,159.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:161.10,162.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:163.10,164.16 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:165.10,166.88 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:171.16,173.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:174.25,176.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:178.10,179.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:180.10,181.16 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:182.10,183.88 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:188.16,190.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:191.25,193.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:195.10,196.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:197.10,198.16 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:199.10,200.88 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:204.2,208.50 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:208.50,210.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:214.2,218.51 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:218.51,220.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:224.2,226.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:230.3,230.45 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:226.17,228.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:234.2,237.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:240.3,240.32 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:243.3,243.27 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:237.17,239.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:240.32,242.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:248.2,250.60 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_request_cbor_gen.go:250.60,252.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:25.68,27.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:30.48,32.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:35.44,37.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:40.47,42.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:45.47,47.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:50.47,52.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:55.49,57.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:59.54,62.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:65.47,67.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:69.79,71.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:73.99,74.22 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:77.2,77.46 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:74.22,76.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:80.47,82.2 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:84.57,86.2 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response.go:90.56,97.2 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:18.59,19.14 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:23.2,23.62 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:27.2,31.100 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:36.2,36.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:41.2,41.49 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:47.2,47.102 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:52.2,52.46 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:57.2,57.33 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:61.2,61.104 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:64.2,64.61 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:67.2,67.12 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:19.14,22.3 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:23.62,25.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:31.100,33.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:36.49,38.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:41.49,43.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:47.102,49.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:52.46,54.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:57.33,59.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:61.104,63.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:64.61,66.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:70.61,77.16 5 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:80.2,80.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:84.2,84.16 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:104.2,105.16 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:108.2,108.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:111.2,111.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:121.2,122.16 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:125.2,125.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:128.2,128.15 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:170.2,170.12 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:77.16,79.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:80.25,82.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:84.16,86.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:90.2,93.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:96.3,96.32 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:99.3,99.25 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:93.17,95.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:96.32,98.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:105.16,107.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:108.25,110.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:112.10,113.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:114.10,115.16 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:116.10,117.88 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:122.16,124.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:125.25,127.3 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:129.10,130.17 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:131.10,132.16 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:133.10,134.88 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:138.2,141.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:144.3,144.32 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:147.3,147.27 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:141.17,143.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:144.32,146.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:152.2,156.50 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:156.50,158.4 1 0 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:162.2,164.17 2 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:168.3,168.45 1 1 +github.com/filecoin-project/go-data-transfer/message/transfer_response_cbor_gen.go:164.17,166.4 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:29.204,31.16 2 1 +github.com/filecoin-project/go-data-transfer/message/message.go:34.2,34.26 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:37.2,38.16 2 1 +github.com/filecoin-project/go-data-transfer/message/message.go:42.2,43.15 2 1 +github.com/filecoin-project/go-data-transfer/message/message.go:49.2,57.8 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:31.16,33.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:34.26,36.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:38.16,40.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:43.15,45.3 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:45.8,47.3 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:61.91,65.2 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:68.69,73.2 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:76.84,82.2 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:85.142,87.16 2 0 +github.com/filecoin-project/go-data-transfer/message/message.go:90.2,95.8 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:87.16,89.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:99.192,101.16 2 0 +github.com/filecoin-project/go-data-transfer/message/message.go:104.2,111.8 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:101.16,103.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:115.188,117.16 2 1 +github.com/filecoin-project/go-data-transfer/message/message.go:120.2,127.8 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:117.16,119.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:131.198,133.16 2 0 +github.com/filecoin-project/go-data-transfer/message/message.go:136.2,143.8 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:133.16,135.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:147.86,153.2 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:156.71,161.2 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:164.195,166.16 2 1 +github.com/filecoin-project/go-data-transfer/message/message.go:169.2,176.8 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:166.16,168.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:180.57,183.16 3 1 +github.com/filecoin-project/go-data-transfer/message/message.go:186.2,186.23 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:189.2,189.28 1 1 +github.com/filecoin-project/go-data-transfer/message/message.go:183.16,185.3 1 0 +github.com/filecoin-project/go-data-transfer/message/message.go:186.23,188.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:52.74,65.2 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:78.34,79.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:82.2,83.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:86.2,90.54 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:93.2,98.29 5 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:111.2,114.12 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:79.21,81.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:83.16,85.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:90.54,92.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:98.29,100.35 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:103.3,104.17 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:107.3,109.36 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:100.35,102.4 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:104.17,106.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:117.87,119.6 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:119.6,120.10 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:121.21,122.30 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:123.29,124.11 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:127.4,127.19 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:124.11,126.5 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:132.115,135.67 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:143.2,143.61 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:135.67,137.63 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:140.3,140.9 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:137.63,139.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:143.61,145.23 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:148.3,149.17 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:145.23,147.4 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:149.17,151.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:155.112,156.6 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:156.6,159.9 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:163.3,165.18 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:168.3,168.10 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:159.9,162.4 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:165.18,167.4 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:169.21,170.58 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:171.18,171.18 0 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:179.9,180.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:183.2,184.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:187.2,187.25 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:191.2,193.48 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:196.2,196.53 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:180.21,182.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:184.16,186.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:187.25,189.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:193.48,195.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:203.9,204.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:207.2,208.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:211.2,212.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:219.2,219.25 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:222.2,225.48 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:230.2,230.70 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:204.21,206.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:208.16,210.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:212.16,214.17 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:217.3,217.42 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:214.17,216.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:219.25,221.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:225.48,229.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:234.90,235.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:238.2,239.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:242.2,242.25 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:252.2,253.48 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:256.2,257.54 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:235.21,237.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:239.16,241.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:242.25,246.10 4 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:249.3,250.13 2 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:246.10,248.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:253.48,255.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:262.65,265.8 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:268.2,268.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:265.8,267.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:272.78,273.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:276.2,285.12 10 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:273.21,275.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:289.110,291.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:294.2,297.12 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:291.16,293.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:300.135,304.20 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:308.2,310.25 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:317.2,321.16 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:325.2,326.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:330.2,331.8 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:334.2,334.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:304.20,306.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:310.25,313.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:313.8,316.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:321.16,324.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:326.16,329.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:331.8,333.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:337.160,342.9 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:346.2,347.48 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:352.2,352.34 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:342.9,344.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:347.48,350.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:352.34,354.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:357.158,360.9 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:364.2,367.38 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:370.2,373.48 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:378.2,378.34 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:382.2,382.16 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:360.9,363.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:367.38,369.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:373.48,376.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:378.34,380.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:382.16,384.17 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:388.3,388.43 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:384.17,387.4 2 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:394.127,398.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:404.2,404.16 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:408.2,410.21 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:422.2,422.28 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:431.2,431.48 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:436.2,436.34 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:440.2,442.48 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:450.2,453.21 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:458.2,459.8 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:462.2,463.31 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:398.16,401.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:404.16,406.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:410.21,415.3 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:415.8,420.3 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:422.28,424.26 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:428.3,428.43 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:424.26,427.4 2 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:431.48,434.3 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:436.34,438.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:442.48,446.34 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:446.34,448.4 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:453.21,455.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:455.8,457.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:459.8,461.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:468.128,473.9 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:477.2,477.42 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:473.9,475.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:477.42,480.17 3 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:480.17,482.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:486.85,495.8 9 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:501.2,501.24 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:495.8,497.17 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:497.17,499.4 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:504.163,509.9 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:513.2,515.28 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:524.2,524.48 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:509.9,511.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:515.28,517.26 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:521.3,521.43 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:517.26,520.4 2 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:524.48,526.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:531.139,537.9 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:541.2,543.28 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:552.2,552.16 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:537.9,539.3 1 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:543.28,545.26 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:549.3,549.53 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:545.26,548.4 2 0 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:552.16,554.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:557.136,561.16 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:566.2,566.16 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:570.2,570.21 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:581.2,581.95 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:585.2,586.59 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:561.16,563.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:566.16,568.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:570.21,573.96 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:576.3,577.53 2 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:573.96,575.4 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:581.95,583.3 1 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:589.92,594.8 4 1 +github.com/filecoin-project/go-data-transfer/transport/graphsync/graphsync.go:594.8,596.3 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:24.60,30.2 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:40.113,42.2 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:47.39,50.16 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:54.2,54.53 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:63.2,64.18 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:50.16,52.3 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:54.53,55.37 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:59.3,59.13 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:55.37,58.4 2 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:68.65,71.2 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:73.89,75.2 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:78.75,81.27 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:86.2,86.6 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:81.27,84.3 2 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:86.6,88.17 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:97.3,101.27 4 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:88.17,89.21 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:94.4,94.10 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:89.21,93.5 3 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:101.27,103.10 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:103.10,104.58 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:104.58,106.6 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:106.11,108.6 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:110.9,112.10 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:112.10,114.5 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:119.54,121.2 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:123.73,125.2 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:127.80,129.2 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:131.89,132.21 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:136.2,137.34 2 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:140.2,140.53 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:144.2,144.22 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:154.2,154.56 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:157.2,157.12 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:132.21,134.3 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:137.34,139.3 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:140.53,142.3 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:145.28,146.38 1 1 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:150.10,151.73 1 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:146.38,149.4 2 0 +github.com/filecoin-project/go-data-transfer/network/libp2p_impl.go:154.56,156.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:18.70,20.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:23.2,23.10 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:26.2,26.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:20.16,22.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:23.10,25.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:29.98,31.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:34.2,34.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:52.2,52.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:31.16,33.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:34.32,37.119 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:42.3,42.34 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:37.119,41.4 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:42.34,44.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:49.4,49.14 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:44.18,45.98 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:45.98,47.6 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:55.118,57.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:60.2,60.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:72.2,72.17 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:57.16,59.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:60.32,63.119 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:68.3,68.34 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:63.119,67.4 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:68.34,70.4 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:75.127,76.25 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:80.2,80.21 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:83.2,83.24 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:87.2,87.25 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:90.2,90.24 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:93.2,94.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:97.2,98.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:101.2,102.53 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:105.2,105.17 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:76.25,78.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:80.21,82.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:83.24,86.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:87.25,89.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:90.24,92.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:94.16,96.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:98.16,100.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:102.53,104.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:108.105,109.25 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:112.2,112.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:140.2,140.50 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:149.2,149.25 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:152.2,152.28 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:109.25,111.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:112.32,113.37 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:123.3,123.27 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:126.3,126.23 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:133.3,133.27 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:113.37,115.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:118.4,119.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:115.18,117.5 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:119.18,121.5 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:123.27,125.4 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:126.23,128.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:128.18,130.5 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:133.27,135.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:135.18,137.5 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:140.50,141.27 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:144.3,145.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:141.27,143.4 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:145.17,147.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:149.25,151.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:155.72,160.2 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:162.87,163.13 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:185.2,185.66 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:163.13,164.33 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:183.3,183.41 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:164.33,166.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:169.4,169.18 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:175.4,175.22 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:181.4,181.38 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:166.18,168.5 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:169.18,170.98 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:170.98,173.6 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:175.22,176.23 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:179.5,179.37 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:176.23,178.6 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:188.132,191.19 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:194.2,194.17 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:191.19,193.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:199.64,202.19 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:205.2,205.17 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:202.19,204.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:209.69,212.27 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:216.2,216.98 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:220.2,221.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:225.2,226.48 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:229.2,231.19 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:237.2,237.49 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:240.2,241.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:245.2,246.41 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:252.2,252.27 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:212.27,214.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:216.98,218.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:221.16,223.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:226.48,228.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:231.19,233.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:233.17,235.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:237.49,239.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:241.9,244.3 2 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:246.41,248.17 2 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:248.17,250.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:257.69,260.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:264.2,265.48 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:268.2,271.23 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:279.2,280.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:283.2,283.19 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:289.2,289.48 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:292.2,293.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:297.2,298.41 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:304.2,304.27 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:260.16,262.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:265.48,267.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:271.23,274.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:274.8,277.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:280.16,282.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:283.19,285.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:285.17,287.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:289.48,291.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:293.9,296.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:298.41,300.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:300.17,302.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:317.76,319.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:322.2,325.12 4 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:331.2,332.27 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:319.16,321.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:325.12,327.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:327.8,329.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:342.91,344.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:347.2,351.27 4 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:344.16,346.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:354.130,356.18 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:362.2,362.62 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:356.18,358.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:358.17,360.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:365.152,367.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:370.2,370.46 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:373.2,373.61 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:367.16,369.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:370.46,372.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:376.157,379.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:382.2,382.19 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:388.2,388.40 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:396.2,396.22 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:403.2,403.31 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:379.16,381.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:382.19,384.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:384.17,386.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:388.40,390.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:393.3,393.44 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:390.17,392.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:396.22,398.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:401.3,401.45 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:398.17,400.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/events.go:406.95,409.118 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:414.2,414.19 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:421.2,421.55 1 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:409.118,413.3 3 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:414.19,416.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/events.go:416.17,418.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:48.75,50.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:53.2,54.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:57.2,58.12 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:50.9,52.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:54.9,56.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:62.203,75.16 3 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:78.2,79.15 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:75.16,77.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:82.100,84.10 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:87.2,87.22 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:84.10,86.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:90.84,92.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:92.16,94.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:98.52,102.2 3 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:105.51,107.16 2 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:111.2,112.33 2 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:118.2,118.15 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:107.16,109.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:112.33,113.63 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:113.63,115.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:126.120,128.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:131.2,131.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:128.16,130.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:136.178,138.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:142.2,144.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:147.2,148.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:152.2,153.79 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:158.2,158.18 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:138.16,140.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:144.16,146.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:148.9,151.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:153.79,157.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:163.178,165.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:169.2,171.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:174.2,175.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:179.2,180.118 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:185.2,185.18 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:165.16,167.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:171.16,173.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:175.9,178.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:180.118,184.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:189.122,191.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:194.2,194.37 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:197.2,198.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:201.2,201.105 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:206.2,206.50 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:191.16,193.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:194.37,196.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:198.16,200.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:201.105,205.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:210.100,212.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:215.2,216.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:220.2,220.113 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:226.2,226.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:212.16,214.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:216.16,218.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:220.113,224.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:230.100,233.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:237.2,238.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:242.2,242.112 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:248.2,248.22 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:233.9,235.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:238.16,240.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:242.112,246.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:252.101,254.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:258.2,259.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:263.2,263.23 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:254.9,256.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:259.16,261.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:267.111,269.16 2 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:272.2,272.22 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:269.16,271.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:276.98,278.2 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:281.121,283.2 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:290.117,292.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:295.2,295.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:292.16,294.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:300.90,302.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:305.2,305.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:302.16,304.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:310.132,312.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:315.2,315.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:312.16,314.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:319.102,321.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:327.2,327.52 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:332.2,333.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:344.2,344.12 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:321.16,323.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:327.52,329.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/impl.go:334.30,335.55 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:336.30,337.55 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:338.29,339.48 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:340.29,341.48 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:347.102,349.22 2 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:360.2,360.27 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:365.2,365.31 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:349.22,351.28 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:356.3,356.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:351.28,353.4 1 1 +github.com/filecoin-project/go-data-transfer/impl/impl.go:360.27,362.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:23.33,25.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:25.16,27.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:30.112,34.42 3 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:45.2,45.21 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:67.2,67.41 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:71.2,71.23 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:76.2,76.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:34.42,36.17 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:39.3,39.61 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:42.3,42.19 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:36.17,38.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:39.61,41.4 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:45.21,46.94 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:46.94,48.28 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:56.4,57.150 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:48.28,50.19 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:53.5,53.43 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:50.19,52.6 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:57.150,59.5 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:60.9,61.94 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:61.94,63.5 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:67.41,69.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:71.23,74.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:84.34,86.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:86.16,88.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:93.40,96.34 3 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:99.2,99.16 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:103.2,103.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:96.34,98.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:99.16,102.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:106.44,108.2 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:112.33,115.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:121.2,122.34 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:128.2,128.55 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:134.2,134.52 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:140.2,140.52 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:145.2,145.52 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:115.16,118.3 2 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:122.34,125.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:128.55,131.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:134.52,137.3 2 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:140.52,143.3 2 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:146.29,147.72 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:150.29,151.72 1 1 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:154.10,155.54 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:147.72,149.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/receiver.go:151.72,153.4 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:33.111,34.65 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:39.2,41.82 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:34.65,36.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:44.111,45.64 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:49.2,52.82 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:45.64,47.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:55.96,62.16 3 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:67.2,67.130 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:71.2,71.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:62.16,64.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:67.130,69.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:74.104,82.16 7 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:86.2,87.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:91.2,92.79 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:98.2,98.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:82.16,84.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:87.9,90.3 2 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:92.79,96.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:101.104,109.16 7 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:113.2,114.9 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:118.2,119.137 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:125.2,125.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:109.16,111.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:114.9,117.3 2 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:119.137,123.3 3 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:128.143,131.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:136.2,136.52 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:141.2,141.48 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:146.2,146.40 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:151.2,152.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:155.2,155.51 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:159.2,160.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:163.2,164.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:168.2,168.36 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:172.2,172.12 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:131.16,133.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:136.52,138.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:141.48,143.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:146.40,148.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:152.16,154.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/restart.go:155.51,157.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:160.16,162.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:164.16,166.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/restart.go:168.36,170.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:18.59,19.24 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:24.2,24.14 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:19.24,20.14 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:20.14,22.4 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:34.173,36.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:39.2,40.91 2 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:36.16,38.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/utils.go:43.169,47.26 4 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:50.2,50.15 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:54.2,54.11 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:57.2,57.92 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:47.26,49.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:50.15,52.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:54.11,56.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:60.149,64.26 4 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:67.2,67.87 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:64.26,66.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:70.61,71.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:74.2,74.41 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:71.32,73.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:77.60,78.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:81.2,81.40 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:78.32,80.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:84.66,85.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:88.2,88.41 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:85.32,87.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:91.65,92.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:95.2,95.40 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:92.32,94.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:98.83,99.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:102.2,102.47 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:99.32,101.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:105.82,106.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:109.2,109.46 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:106.32,108.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:112.83,113.32 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:116.2,116.40 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:113.32,115.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:119.107,122.10 3 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:125.2,126.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:129.2,129.51 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:122.10,124.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/utils.go:126.16,128.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/utils.go:132.122,135.10 3 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:138.2,139.16 2 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:142.2,142.51 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:135.10,137.3 1 1 +github.com/filecoin-project/go-data-transfer/impl/utils.go:139.16,141.3 1 0 +github.com/filecoin-project/go-data-transfer/impl/environment.go:13.63,15.2 1 0 +github.com/filecoin-project/go-data-transfer/impl/environment.go:17.70,19.2 1 1 +github.com/filecoin-project/go-data-transfer/impl/environment.go:21.44,23.2 1 1 +github.com/filecoin-project/go-data-transfer/impl/environment.go:25.75,27.2 1 1 diff --git a/events.go b/events.go index 0d4da792..6841bcb6 100644 --- a/events.go +++ b/events.go @@ -12,8 +12,14 @@ const ( // Accept is an event that emits when the data transfer is first accepted Accept - // Progress is an event that gets emitted every time more data is transferred - Progress + // Restart is an event that emits when the data transfer is restarted + Restart + + // DataReceived is emitted when data is received on the channel from a remote peer + DataReceived + + // DataSent is emitted when data is sent on the channel to the remote peer + DataSent // Cancel indicates one side has cancelled the transfer Cancel @@ -55,15 +61,23 @@ const ( // initiator BeginFinalizing + // Disconnected emits when we are not able to connect to the other party + Disconnected + // Complete is emitted when a data transfer is complete Complete + + // CompleteCleanupOnRestart is emitted when a data transfer channel is restarted to signal + // that channels that were cleaning up should finish cleanup + CompleteCleanupOnRestart ) // Events are human readable names for data transfer events var Events = map[EventCode]string{ Open: "Open", Accept: "Accept", - Progress: "Progress", + DataSent: "DataSent", + DataReceived: "DataReceived", Cancel: "Cancel", Error: "Error", CleanupComplete: "CleanupComplete", @@ -78,6 +92,7 @@ var Events = map[EventCode]string{ ResponderCompletes: "ResponderCompletes", BeginFinalizing: "BeginFinalizing", Complete: "Complete", + CompleteCleanupOnRestart: "CompleteCleanupOnRestart", } // Event is a struct containing information about a data transfer event diff --git a/go.mod b/go.mod index 149d37e5..f7cb1319 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/filecoin-project/go-data-transfer go 1.13 require ( - github.com/filecoin-project/go-statemachine v0.0.0-20200714194326-a77c3ae20989 + github.com/filecoin-project/go-ds-versioning v0.1.0 + github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/hannahhoward/cbor-gen-for v0.0.0-20200817222906-ea96cece81f1 github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e @@ -11,7 +12,7 @@ require ( github.com/ipfs/go-block-format v0.0.2 github.com/ipfs/go-blockservice v0.1.3 github.com/ipfs/go-cid v0.0.7 - github.com/ipfs/go-datastore v0.4.4 + github.com/ipfs/go-datastore v0.4.5 github.com/ipfs/go-graphsync v0.2.1 github.com/ipfs/go-ipfs-blockstore v1.0.1 github.com/ipfs/go-ipfs-blocksutil v0.0.1 @@ -25,9 +26,11 @@ require ( github.com/ipfs/go-unixfs v0.2.4 github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c + github.com/jpillora/backoff v1.0.0 github.com/libp2p/go-libp2p v0.6.0 github.com/libp2p/go-libp2p-core v0.5.0 github.com/stretchr/testify v1.5.1 - github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c + github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163 + go.uber.org/atomic v1.6.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) diff --git a/go.sum b/go.sum index df0bad05..d8a04ff7 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,25 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= @@ -23,6 +40,10 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -49,10 +70,14 @@ github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6ps github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 h1:av5fw6wmm58FYMgJeoB/lK9XXrgdugYiTqkdxjTy9k8= github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2/go.mod h1:pqTiPHobNkOVM5thSRsHYjyQfq7O5QSCMhvuu9JoDlg= -github.com/filecoin-project/go-statemachine v0.0.0-20200714194326-a77c3ae20989 h1:1GjCS3xy/CRIw7Tq0HfzX6Al8mklrszQZ3iIFnjPzHk= -github.com/filecoin-project/go-statemachine v0.0.0-20200714194326-a77c3ae20989/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= +github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= +github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= +github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe h1:dF8u+LEWeIcTcfUcCf3WFVlc81Fr2JKg8zPzIbBDKDw= +github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statestore v0.1.0 h1:t56reH59843TwXHkMcwyuayStBIiWBRilQjQ+5IiwdQ= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= @@ -61,6 +86,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= @@ -68,20 +95,37 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f h1:KMlcu9X58lhTA/KrfX8Bi1LQSO4pzoVjTiL3h4Jk+Zk= @@ -109,6 +153,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= @@ -144,6 +189,8 @@ github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13X github.com/ipfs/go-datastore v0.4.2/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4 h1:rjvQ9+muFaJ+QZ7dN5B1MSDNQ0JVZKkkES/rMZmA8X8= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.5 h1:cwOUcGMLdLPWgu3SlrCckCMznaGADbPqE0r8h768/Dg= +github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= @@ -251,7 +298,11 @@ github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -268,6 +319,8 @@ github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jv github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -503,11 +556,13 @@ github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14/go.mod h1:uIp+gprXx github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a h1:hjZfReYVLbqFkAtr2us7vdy04YWz3LVAirzP7reh8+M= github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -555,8 +610,8 @@ github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvS github.com/whyrusleeping/cbor-gen v0.0.0-20191216205031-b047b6acb3c0/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20200710004633-5379fc63235d/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c h1:BMg3YUwLEUIYBJoYZVhA4ZDTciXRj6r7ffOCshWrsoE= -github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163 h1:TtcUeY2XZSriVWR1pXyfCBWIf/NGC2iUdNw1lofUjUU= +github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= @@ -575,6 +630,7 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -590,10 +646,14 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= +go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -604,6 +664,7 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= @@ -614,33 +675,66 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -651,22 +745,34 @@ golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190524122548-abf6ff778158/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -674,13 +780,30 @@ golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200827010519-17fd2f27a9e3 h1:r3P/5xOq/dK1991B65Oy6E1fRF/2d/fSYZJ/fXGVfJc= golang.org/x/tools v0.0.0-20200827010519-17fd2f27a9e3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -690,14 +813,40 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -717,5 +866,11 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/impl/events.go b/impl/events.go index 3642a0b4..57badec9 100644 --- a/impl/events.go +++ b/impl/events.go @@ -3,16 +3,22 @@ package impl import ( "context" "errors" + "time" "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/libp2p/go-libp2p-core/peer" + "golang.org/x/xerrors" datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels" "github.com/filecoin-project/go-data-transfer/encoding" "github.com/filecoin-project/go-data-transfer/registry" ) +var ChannelRemoveTimeout = 1 * time.Hour + func (m *manager) OnChannelOpened(chid datatransfer.ChannelID) error { has, err := m.channels.HasChannel(chid) if err != nil { @@ -25,7 +31,7 @@ func (m *manager) OnChannelOpened(chid datatransfer.ChannelID) error { } func (m *manager) OnDataReceived(chid datatransfer.ChannelID, link ipld.Link, size uint64) error { - err := m.channels.IncrementReceived(chid, size) + err := m.channels.DataReceived(chid, link.(cidlink.Link).Cid, size) if err != nil { return err } @@ -55,7 +61,7 @@ func (m *manager) OnDataReceived(chid datatransfer.ChannelID, link ipld.Link, si } func (m *manager) OnDataSent(chid datatransfer.ChannelID, link ipld.Link, size uint64) (datatransfer.Message, error) { - err := m.channels.IncrementSent(chid, size) + err := m.channels.DataSent(chid, link.(cidlink.Link).Cid, size) if err != nil { return nil, err } @@ -79,6 +85,10 @@ func (m *manager) OnDataSent(chid datatransfer.ChannelID, link ipld.Link, size u } func (m *manager) OnRequestReceived(chid datatransfer.ChannelID, request datatransfer.Request) (datatransfer.Response, error) { + if request.IsRestart() { + return m.receiveRestartRequest(chid, request) + } + if request.IsNew() { return m.receiveNewRequest(chid.Initiator, request) } @@ -131,6 +141,13 @@ func (m *manager) OnResponseReceived(chid datatransfer.ChannelID, response datat return err } } + + if response.IsRestart() { + err := m.channels.Restart(chid) + if err != nil { + return err + } + } } if response.IsComplete() && response.Accepted() { if !response.IsPaused() { @@ -147,6 +164,30 @@ func (m *manager) OnResponseReceived(chid datatransfer.ChannelID, response datat return m.resumeOther(chid) } +func (m *manager) OnRequestTimedOut(ctx context.Context, chid datatransfer.ChannelID) error { + log.Warnf("channel %+v has timed out", chid) + + go func() { + select { + case <-ctx.Done(): + case <-time.After(ChannelRemoveTimeout): + channel, err := m.channels.GetByID(ctx, chid) + if err == nil { + if !(channels.IsChannelTerminated(channel.Status()) || + channels.IsChannelCleaningUp(channel.Status())) { + if err := m.channels.Cancel(chid); err != nil { + log.Errorf("failed to cancel timed-out channel: %v", err) + return + } + log.Warnf("channel %+v has ben cancelled because of timeout", chid) + } + } + } + }() + + return nil +} + func (m *manager) OnChannelCompleted(chid datatransfer.ChannelID, success bool) error { if success { if chid.Initiator != m.peerID { @@ -156,8 +197,8 @@ func (m *manager) OnChannelCompleted(chid datatransfer.ChannelID, success bool) } if msg != nil { if err := m.dataTransferNetwork.SendMessage(context.TODO(), chid.Initiator, msg); err != nil { - _ = m.channels.Error(chid, err) - return err + log.Warnf("failed to send completion message, err : %v", err) + return m.channels.Disconnected(chid) } } if msg.Accepted() { @@ -181,17 +222,73 @@ func (m *manager) OnChannelCompleted(chid datatransfer.ChannelID, success bool) return nil } +func (m *manager) receiveRestartRequest(chid datatransfer.ChannelID, incoming datatransfer.Request) (datatransfer.Response, error) { + result, err := m.restartRequest(chid, incoming) + msg, msgErr := m.response(true, false, err, incoming.TransferID(), result) + if msgErr != nil { + return nil, msgErr + } + return msg, err +} + func (m *manager) receiveNewRequest( initiator peer.ID, incoming datatransfer.Request) (datatransfer.Response, error) { result, err := m.acceptRequest(initiator, incoming) - msg, msgErr := m.response(true, err, incoming.TransferID(), result) + msg, msgErr := m.response(false, true, err, incoming.TransferID(), result) if msgErr != nil { return nil, msgErr } return msg, err } +func (m *manager) restartRequest(chid datatransfer.ChannelID, + incoming datatransfer.Request) (datatransfer.VoucherResult, error) { + + initiator := chid.Initiator + if m.peerID == initiator { + return nil, xerrors.New("initiator cannot be manager peer for a restart request") + } + + if err := m.validateRestartRequest(context.Background(), initiator, chid, incoming); err != nil { + return nil, err + } + + stor, err := incoming.Selector() + if err != nil { + return nil, err + } + + voucher, result, err := m.validateVoucher(initiator, incoming, incoming.IsPull(), incoming.BaseCid(), stor) + if err != nil && err != datatransfer.ErrPause { + return result, xerrors.Errorf("failed to validate voucher: %w", err) + } + voucherErr := err + + if result != nil { + err := m.channels.NewVoucherResult(chid, result) + if err != nil { + return result, err + } + } + if err := m.channels.Restart(chid); err != nil { + return result, err + } + processor, has := m.transportConfigurers.Processor(voucher.Type()) + if has { + transportConfigurer := processor.(datatransfer.TransportConfigurer) + transportConfigurer(chid, voucher, m.transport) + } + m.dataTransferNetwork.Protect(initiator, chid.String()) + if voucherErr == datatransfer.ErrPause { + err := m.channels.PauseResponder(chid) + if err != nil { + return result, err + } + } + return result, voucherErr +} + func (m *manager) acceptRequest( initiator peer.ID, incoming datatransfer.Request) (datatransfer.VoucherResult, error) { @@ -216,7 +313,7 @@ func (m *manager) acceptRequest( dataReceiver = m.peerID } - chid, err := m.channels.CreateNew(incoming.TransferID(), incoming.BaseCid(), stor, voucher, initiator, dataSender, dataReceiver) + chid, err := m.channels.CreateNew(m.peerID, incoming.TransferID(), incoming.BaseCid(), stor, voucher, initiator, dataSender, dataReceiver) if err != nil { return result, err } @@ -310,7 +407,7 @@ func (m *manager) revalidationResponse(chid datatransfer.ChannelID, result datat if chst.Status() == datatransfer.Finalizing { return m.completeResponse(resultErr, chid.ID, result) } - return m.response(false, resultErr, chid.ID, result) + return m.response(false, false, resultErr, chid.ID, result) } func (m *manager) processRevalidationResult(chid datatransfer.ChannelID, result datatransfer.VoucherResult, resultErr error) (datatransfer.Response, error) { diff --git a/impl/impl.go b/impl/impl.go index a4e27abc..a2a7ef16 100644 --- a/impl/impl.go +++ b/impl/impl.go @@ -34,6 +34,7 @@ type manager struct { revalidators *registry.Registry transportConfigurers *registry.Registry pubSub *pubsub.PubSub + readySub *pubsub.PubSub channels *channels.Channels peerID peer.ID transport datatransfer.Transport @@ -58,8 +59,21 @@ func dispatcher(evt pubsub.Event, subscriberFn pubsub.SubscriberFn) error { return nil } +func readyDispatcher(evt pubsub.Event, fn pubsub.SubscriberFn) error { + migrateErr, ok := evt.(error) + if !ok && evt != nil { + return errors.New("wrong type of event") + } + cb, ok := fn.(datatransfer.ReadyFunc) + if !ok { + return errors.New("wrong type of event") + } + cb(migrateErr) + return nil +} + // NewDataTransfer initializes a new instance of a data transfer manager -func NewDataTransfer(ds datastore.Datastore, dataTransferNetwork network.DataTransferNetwork, transport datatransfer.Transport, storedCounter *storedcounter.StoredCounter) (datatransfer.Manager, error) { +func NewDataTransfer(ds datastore.Batching, dataTransferNetwork network.DataTransferNetwork, transport datatransfer.Transport, storedCounter *storedcounter.StoredCounter) (datatransfer.Manager, error) { m := &manager{ dataTransferNetwork: dataTransferNetwork, validatedTypes: registry.NewRegistry(), @@ -67,11 +81,12 @@ func NewDataTransfer(ds datastore.Datastore, dataTransferNetwork network.DataTra revalidators: registry.NewRegistry(), transportConfigurers: registry.NewRegistry(), pubSub: pubsub.New(dispatcher), + readySub: pubsub.New(readyDispatcher), peerID: dataTransferNetwork.ID(), transport: transport, storedCounter: storedCounter, } - channels, err := channels.New(ds, m.notifier, m.voucherDecoder, m.resultTypes.Decoder, &channelEnvironment{m}) + channels, err := channels.New(ds, m.notifier, m.voucherDecoder, m.resultTypes.Decoder, &channelEnvironment{m}, dataTransferNetwork.ID()) if err != nil { return nil, err } @@ -96,11 +111,26 @@ func (m *manager) notifier(evt datatransfer.Event, chst datatransfer.ChannelStat // Start initializes data transfer processing func (m *manager) Start(ctx context.Context) error { + go func() { + err := m.channels.Start(ctx) + if err != nil { + log.Errorf("Migrating data transfer state machines: %s", err.Error()) + } + err = m.readySub.Publish(err) + if err != nil { + log.Warnf("Publish data transfer ready event: %s", err.Error()) + } + }() dtReceiver := &receiver{m} m.dataTransferNetwork.SetDelegate(dtReceiver) return m.transport.SetEventHandler(m) } +// OnReady registers a listener for when the data transfer manager has finished starting up +func (m *manager) OnReady(ready datatransfer.ReadyFunc) { + m.readySub.Subscribe(ready) +} + // Stop terminates all data transfers and ends processing func (m *manager) Stop(ctx context.Context) error { openChannels, err := m.channels.InProgress() @@ -139,7 +169,7 @@ func (m *manager) OpenPushDataChannel(ctx context.Context, requestTo peer.ID, vo return datatransfer.ChannelID{}, err } - chid, err := m.channels.CreateNew(req.TransferID(), baseCid, selector, voucher, + chid, err := m.channels.CreateNew(m.peerID, req.TransferID(), baseCid, selector, voucher, m.peerID, m.peerID, requestTo) // initiator = us, sender = us, receiver = them if err != nil { return chid, err @@ -166,7 +196,7 @@ func (m *manager) OpenPullDataChannel(ctx context.Context, requestTo peer.ID, vo return datatransfer.ChannelID{}, err } // initiator = us, sender = them, receiver = us - chid, err := m.channels.CreateNew(req.TransferID(), baseCid, selector, voucher, + chid, err := m.channels.CreateNew(m.peerID, req.TransferID(), baseCid, selector, voucher, m.peerID, requestTo, m.peerID) if err != nil { return chid, err @@ -177,7 +207,7 @@ func (m *manager) OpenPullDataChannel(ctx context.Context, requestTo peer.ID, vo transportConfigurer(chid, voucher, m.transport) } m.dataTransferNetwork.Protect(requestTo, chid.String()) - if err := m.transport.OpenChannel(ctx, requestTo, chid, cidlink.Link{Cid: baseCid}, selector, req); err != nil { + if err := m.transport.OpenChannel(ctx, requestTo, chid, cidlink.Link{Cid: baseCid}, selector, nil, req); err != nil { err = fmt.Errorf("Unable to send request: %w", err) _ = m.channels.Error(chid, err) return chid, err @@ -198,7 +228,7 @@ func (m *manager) SendVoucher(ctx context.Context, channelID datatransfer.Channe if err != nil { return err } - if err := m.dataTransferNetwork.SendMessage(ctx, chst.OtherParty(m.peerID), updateRequest); err != nil { + if err := m.dataTransferNetwork.SendMessage(ctx, chst.OtherPeer(), updateRequest); err != nil { err = fmt.Errorf("Unable to send request: %w", err) _ = m.channels.Error(channelID, err) return err @@ -217,7 +247,7 @@ func (m *manager) CloseDataTransferChannel(ctx context.Context, chid datatransfe return err } - if err := m.dataTransferNetwork.SendMessage(ctx, chst.OtherParty(m.peerID), m.cancelMessage(chid)); err != nil { + if err := m.dataTransferNetwork.SendMessage(ctx, chst.OtherPeer(), m.cancelMessage(chid)); err != nil { err = fmt.Errorf("Unable to send cancel message: %w", err) _ = m.channels.Error(chid, err) return err @@ -314,3 +344,58 @@ func (m *manager) RegisterTransportConfigurer(voucherType datatransfer.Voucher, } return nil } + +// RestartDataTransferChannel restarts data transfer on the channel with the given channelId +func (m *manager) RestartDataTransferChannel(ctx context.Context, chid datatransfer.ChannelID) error { + channel, err := m.channels.GetByID(ctx, chid) + if err != nil { + return xerrors.Errorf("failed to fetch channel: %w", err) + } + + // if channel has already been completed, there is nothing to do. + // TODO We could be in a state where the channel has completed but the corresponding event hasnt fired in the client/provider. + if channels.IsChannelTerminated(channel.Status()) { + return nil + } + + // if channel is is cleanup state, finish it + if channels.IsChannelCleaningUp(channel.Status()) { + return m.channels.CompleteCleanupOnRestart(channel.ChannelID()) + } + + // initiate restart + chType := m.channelDataTransferType(channel) + switch chType { + case ManagerPeerReceivePush: + return m.restartManagerPeerReceivePush(ctx, channel) + case ManagerPeerReceivePull: + return m.restartManagerPeerReceivePull(ctx, channel) + case ManagerPeerCreatePull: + return m.openPullRestartChannel(ctx, channel) + case ManagerPeerCreatePush: + return m.openPushRestartChannel(ctx, channel) + } + + return nil +} + +func (m *manager) channelDataTransferType(channel datatransfer.ChannelState) ChannelDataTransferType { + initiator := channel.ChannelID().Initiator + if channel.IsPull() { + // we created a pull channel + if initiator == m.peerID { + return ManagerPeerCreatePull + } + + // we received a pull channel + return ManagerPeerReceivePull + } + + // we created a push channel + if initiator == m.peerID { + return ManagerPeerCreatePush + } + + // we received a push channel + return ManagerPeerReceivePush +} diff --git a/impl/initiating_test.go b/impl/initiating_test.go index 77055688..29c729c6 100644 --- a/impl/initiating_test.go +++ b/impl/initiating_test.go @@ -2,6 +2,7 @@ package impl_test import ( "context" + "math/rand" "testing" "time" @@ -81,6 +82,22 @@ func TestDataTransferInitiating(t *testing.T) { testutil.AssertFakeDTVoucher(t, receivedRequest, h.voucher) }, }, + "Remove Timed-out request": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Cancel, datatransfer.CleanupComplete}, + verify: func(t *testing.T, h *harness) { + orig := ChannelRemoveTimeout + ChannelRemoveTimeout = 10 * time.Millisecond + defer func() { + ChannelRemoveTimeout = orig + }() + + channelID, err := h.dt.OpenPullDataChannel(h.ctx, h.peers[1], h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NoError(t, h.transport.EventHandler.OnRequestTimedOut(ctx, channelID)) + // need time for the events to take place + time.Sleep(1 * time.Second) + }, + }, "SendVoucher with no channel open": { verify: func(t *testing.T, h *harness) { err := h.dt.SendVoucher(h.ctx, datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: 999999}, h.voucher) @@ -315,6 +332,8 @@ func TestDataTransferInitiating(t *testing.T) { }, } for testCase, verify := range testCases { + + // test for new protocol -> new protocol t.Run(testCase, func(t *testing.T) { h := &harness{} ctx, cancel := context.WithTimeout(ctx, 10*time.Second) @@ -327,38 +346,304 @@ func TestDataTransferInitiating(t *testing.T) { h.storedCounter = storedcounter.New(h.ds, datastore.NewKey("counter")) dt, err := NewDataTransfer(h.ds, h.network, h.transport, h.storedCounter) require.NoError(t, err) - err = dt.Start(ctx) + testutil.StartAndWaitForReady(ctx, t, dt) + h.dt = dt + ev := eventVerifier{ + expectedEvents: verify.expectedEvents, + events: make(chan datatransfer.EventCode, len(verify.expectedEvents)), + } + ev.setup(t, dt) + h.stor = testutil.AllSelector() + h.voucher = testutil.NewFakeDTType() + h.voucherResult = testutil.NewFakeDTType() + err = h.dt.RegisterVoucherResultType(h.voucherResult) + require.NoError(t, err) + h.baseCid = testutil.GenerateCids(1)[0] + verify.verify(t, h) + ev.verify(ctx, t) + }) + } +} + +func TestDataTransferRestartInitiating(t *testing.T) { + // create network + ctx := context.Background() + testCases := map[string]struct { + expectedEvents []datatransfer.EventCode + verify func(t *testing.T, h *harness) + }{ + "RestartDataTransferChannel: Manager Peer Create Pull Restart works": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.DataReceived, datatransfer.DataReceived}, + verify: func(t *testing.T, h *harness) { + // open a pull channel + channelID, err := h.dt.OpenPullDataChannel(h.ctx, h.peers[1], h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NotEmpty(t, channelID) + require.Len(t, h.transport.OpenedChannels, 1) + require.Len(t, h.network.SentMessages, 0) + + // some cids should already be received + testCids := testutil.GenerateCids(2) + ev, ok := h.dt.(datatransfer.EventsHandler) + require.True(t, ok) + require.NoError(t, ev.OnDataReceived(channelID, cidlink.Link{Cid: testCids[0]}, 12345)) + require.NoError(t, ev.OnDataReceived(channelID, cidlink.Link{Cid: testCids[1]}, 12345)) + + // restart that pull channel + err = h.dt.RestartDataTransferChannel(ctx, channelID) + require.NoError(t, err) + require.Len(t, h.transport.OpenedChannels, 2) + require.Len(t, h.network.SentMessages, 0) + + openChannel := h.transport.OpenedChannels[1] + require.Equal(t, openChannel.ChannelID, channelID) + require.Equal(t, openChannel.DataSender, h.peers[1]) + require.Equal(t, openChannel.Root, cidlink.Link{Cid: h.baseCid}) + require.Equal(t, openChannel.Selector, h.stor) + require.True(t, openChannel.Message.IsRequest()) + // received cids should be a part of the channel req + require.Equal(t, []cid.Cid{testCids[0], testCids[1]}, openChannel.DoNotSendCids) + + receivedRequest, ok := openChannel.Message.(datatransfer.Request) + require.True(t, ok) + require.Equal(t, receivedRequest.TransferID(), channelID.ID) + require.Equal(t, receivedRequest.BaseCid(), h.baseCid) + require.False(t, receivedRequest.IsCancel()) + require.True(t, receivedRequest.IsPull()) + // assert the second channel open is a restart request + require.True(t, receivedRequest.IsRestart()) + + // voucher should be sent correctly + receivedSelector, err := receivedRequest.Selector() + require.NoError(t, err) + require.Equal(t, receivedSelector, h.stor) + testutil.AssertFakeDTVoucher(t, receivedRequest, h.voucher) + }, + }, + "RestartDataTransferChannel: Manager Peer Create Push Restart works": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open}, + verify: func(t *testing.T, h *harness) { + // open a push channel + channelID, err := h.dt.OpenPushDataChannel(h.ctx, h.peers[1], h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NotEmpty(t, channelID) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 1) + + // restart that push channel + err = h.dt.RestartDataTransferChannel(ctx, channelID) + require.NoError(t, err) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 2) + + // assert restart request is well formed + messageReceived := h.network.SentMessages[1] + require.Equal(t, messageReceived.PeerID, h.peers[1]) + received := messageReceived.Message + require.True(t, received.IsRequest()) + receivedRequest, ok := received.(datatransfer.Request) + require.True(t, ok) + require.Equal(t, receivedRequest.TransferID(), channelID.ID) + require.Equal(t, receivedRequest.BaseCid(), h.baseCid) + require.False(t, receivedRequest.IsCancel()) + require.False(t, receivedRequest.IsPull()) + require.True(t, receivedRequest.IsRestart()) + + // assert voucher is sent correctly + receivedSelector, err := receivedRequest.Selector() + require.NoError(t, err) + require.Equal(t, receivedSelector, h.stor) + testutil.AssertFakeDTVoucher(t, receivedRequest, h.voucher) + }, + }, + "RestartDataTransferChannel: Manager Peer Receive Push Restart works ": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Accept}, + verify: func(t *testing.T, h *harness) { + ctx := context.Background() + // receive a push request + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], h.pushRequest) + require.Len(t, h.transport.OpenedChannels, 1) + require.Len(t, h.network.SentMessages, 0) + require.Len(t, h.voucherValidator.ValidationsReceived, 1) + + // restart the push request received above and validate it + chid := datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: h.pushRequest.TransferID()} + require.NoError(t, h.dt.RestartDataTransferChannel(ctx, chid)) + require.Len(t, h.voucherValidator.ValidationsReceived, 2) + require.Len(t, h.transport.OpenedChannels, 1) + require.Len(t, h.network.SentMessages, 1) + + // assert validation on restart + vmsg := h.voucherValidator.ValidationsReceived[1] + require.Equal(t, h.voucher, vmsg.Voucher) + require.False(t, vmsg.IsPull) + require.Equal(t, h.stor, vmsg.Selector) + require.Equal(t, h.baseCid, vmsg.BaseCid) + require.Equal(t, h.peers[1], vmsg.Other) + + // assert req was sent correctly + req := h.network.SentMessages[0] + require.Equal(t, req.PeerID, h.peers[1]) + received := req.Message + require.True(t, received.IsRequest()) + receivedRequest, ok := received.(datatransfer.Request) + require.True(t, ok) + require.True(t, receivedRequest.IsRestartExistingChannelRequest()) + achId, err := receivedRequest.RestartChannelId() + require.NoError(t, err) + require.Equal(t, chid, achId) + + h.voucherValidator.ExpectSuccessPush() + }, + }, + "RestartDataTransferChannel: Manager Peer Receive Pull Restart works ": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Accept}, + verify: func(t *testing.T, h *harness) { + ctx := context.Background() + // receive a pull request + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], h.pullRequest) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 1) + require.Len(t, h.voucherValidator.ValidationsReceived, 1) + + // restart the pull request received above + h.voucherValidator.ExpectSuccessPull() + chid := datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: h.pullRequest.TransferID()} + require.NoError(t, h.dt.RestartDataTransferChannel(ctx, chid)) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 2) + require.Len(t, h.voucherValidator.ValidationsReceived, 2) + + // assert validation on restart + vmsg := h.voucherValidator.ValidationsReceived[1] + require.Equal(t, h.voucher, vmsg.Voucher) + require.True(t, vmsg.IsPull) + require.Equal(t, h.stor, vmsg.Selector) + require.Equal(t, h.baseCid, vmsg.BaseCid) + require.Equal(t, h.peers[1], vmsg.Other) + + // assert req was sent correctly + req := h.network.SentMessages[1] + require.Equal(t, req.PeerID, h.peers[1]) + received := req.Message + require.True(t, received.IsRequest()) + receivedRequest, ok := received.(datatransfer.Request) + require.True(t, ok) + require.True(t, receivedRequest.IsRestartExistingChannelRequest()) + achId, err := receivedRequest.RestartChannelId() + require.NoError(t, err) + require.Equal(t, chid, achId) + }, + }, + "RestartDataTransferChannel: Manager Peer Receive Pull Restart fails if validation fails ": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Accept}, + verify: func(t *testing.T, h *harness) { + ctx := context.Background() + // receive a pull request + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], h.pullRequest) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 1) + require.Len(t, h.voucherValidator.ValidationsReceived, 1) + + // restart the pull request received above + h.voucherValidator.ExpectErrorPull() + chid := datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: h.pullRequest.TransferID()} + require.EqualError(t, h.dt.RestartDataTransferChannel(ctx, chid), "failed to restart channel, validation error: something went wrong") + }, + }, + "RestartDataTransferChannel: Manager Peer Receive Push Restart fails if validation fails ": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Accept}, + verify: func(t *testing.T, h *harness) { + ctx := context.Background() + // receive a push request + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], h.pushRequest) + require.Len(t, h.transport.OpenedChannels, 1) + require.Len(t, h.network.SentMessages, 0) + require.Len(t, h.voucherValidator.ValidationsReceived, 1) + + // restart the pull request received above + h.voucherValidator.ExpectErrorPush() + chid := datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: h.pushRequest.TransferID()} + require.EqualError(t, h.dt.RestartDataTransferChannel(ctx, chid), "failed to restart channel, validation error: something went wrong") + }, + }, + "Fails if channel does not exist": { + expectedEvents: nil, + verify: func(t *testing.T, h *harness) { + channelId := datatransfer.ChannelID{} + require.Error(t, h.dt.RestartDataTransferChannel(context.Background(), channelId)) + }, + }, + } + + for testCase, verify := range testCases { + t.Run(testCase, func(t *testing.T) { + h := &harness{} + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + // create the harness + h.ctx = ctx + h.peers = testutil.GeneratePeers(2) + h.network = testutil.NewFakeNetwork(h.peers[0]) + h.transport = testutil.NewFakeTransport() + h.ds = dss.MutexWrap(datastore.NewMapDatastore()) + h.storedCounter = storedcounter.New(h.ds, datastore.NewKey("counter")) + h.voucherValidator = testutil.NewStubbedValidator() + + // setup data transfer`` + dt, err := NewDataTransfer(h.ds, h.network, h.transport, h.storedCounter) require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt) h.dt = dt + + // setup eventing ev := eventVerifier{ expectedEvents: verify.expectedEvents, events: make(chan datatransfer.EventCode, len(verify.expectedEvents)), } ev.setup(t, dt) + + // setup voucher processing h.stor = testutil.AllSelector() h.voucher = testutil.NewFakeDTType() + require.NoError(t, h.dt.RegisterVoucherType(h.voucher, h.voucherValidator)) h.voucherResult = testutil.NewFakeDTType() err = h.dt.RegisterVoucherResultType(h.voucherResult) require.NoError(t, err) h.baseCid = testutil.GenerateCids(1)[0] + + h.id = datatransfer.TransferID(rand.Int31()) + h.pushRequest, err = message.NewRequest(h.id, false, false, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + h.pullRequest, err = message.NewRequest(h.id, false, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + + // run tests steps and verify verify.verify(t, h) ev.verify(ctx, t) + h.voucherValidator.VerifyExpectations(t) }) } } type harness struct { - ctx context.Context - peers []peer.ID - network *testutil.FakeNetwork - transport *testutil.FakeTransport - ds datastore.Datastore - storedCounter *storedcounter.StoredCounter - dt datatransfer.Manager - stor ipld.Node - voucher *testutil.FakeDTType - voucherResult *testutil.FakeDTType - baseCid cid.Cid + ctx context.Context + peers []peer.ID + network *testutil.FakeNetwork + transport *testutil.FakeTransport + ds datastore.Batching + storedCounter *storedcounter.StoredCounter + dt datatransfer.Manager + voucherValidator *testutil.StubbedValidator + stor ipld.Node + voucher *testutil.FakeDTType + voucherResult *testutil.FakeDTType + baseCid cid.Cid + + id datatransfer.TransferID + pushRequest datatransfer.Request + pullRequest datatransfer.Request } type eventVerifier struct { diff --git a/impl/integration_test.go b/impl/integration_test.go index fdb0e636..2924113c 100644 --- a/impl/integration_test.go +++ b/impl/integration_test.go @@ -24,6 +24,7 @@ import ( cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,6 +40,22 @@ import ( "github.com/filecoin-project/go-data-transfer/transport/graphsync/extension" ) +const loremFile = "lorem.txt" + +// nil means use the default protocols +// tests data transfer for the following protocol combinations: +// default protocol -> default protocols +// old protocol -> default protocols +// default protocols -> old protocol +var protocolsForTest = map[string]struct { + host1Protocols []protocol.ID + host2Protocols []protocol.ID +}{ + "(new -> new)": {nil, nil}, + "(old -> new, old)": {[]protocol.ID{datatransfer.ProtocolDataTransfer1_0}, nil}, + "(new, old -> old)": {nil, []protocol.ID{datatransfer.ProtocolDataTransfer1_0}}, +} + func TestRoundTrip(t *testing.T) { ctx := context.Background() testCases := map[string]struct { @@ -75,140 +92,145 @@ func TestRoundTrip(t *testing.T) { }, } for testCase, data := range testCases { - t.Run(testCase, func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() + for pname, ps := range protocolsForTest { + t.Run(testCase+pname, func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) - host1 := gsData.Host1 // initiator, data sender - host2 := gsData.Host2 // data recipient + gsData := testutil.NewGraphsyncTestingData(ctx, t, ps.host1Protocols, ps.host2Protocols) + host1 := gsData.Host1 // initiator, data sender + host2 := gsData.Host2 // data recipient - tp1 := gsData.SetupGSTransportHost1() - tp2 := gsData.SetupGSTransportHost2() + tp1 := gsData.SetupGSTransportHost1() + tp2 := gsData.SetupGSTransportHost2() - dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) - require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) - dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) - require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) + require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) + dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) + require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) + + finished := make(chan struct{}, 2) + errChan := make(chan struct{}, 2) + opened := make(chan struct{}, 2) + sent := make(chan uint64, 21) + received := make(chan uint64, 21) + var subscriber datatransfer.Subscriber = func(event datatransfer.Event, channelState datatransfer.ChannelState) { + if event.Code == datatransfer.DataSent { + if channelState.Sent() > 0 { + sent <- channelState.Sent() + } + } - finished := make(chan struct{}, 2) - errChan := make(chan struct{}, 2) - opened := make(chan struct{}, 2) - sent := make(chan uint64, 21) - received := make(chan uint64, 21) - var subscriber datatransfer.Subscriber = func(event datatransfer.Event, channelState datatransfer.ChannelState) { - if event.Code == datatransfer.Progress { - if channelState.Received() > 0 { - received <- channelState.Received() - } else if channelState.Sent() > 0 { - sent <- channelState.Sent() + if event.Code == datatransfer.DataReceived { + if channelState.Received() > 0 { + received <- channelState.Received() + } + } + + if channelState.Status() == datatransfer.Completed { + finished <- struct{}{} + } + if event.Code == datatransfer.Error { + errChan <- struct{}{} + } + if event.Code == datatransfer.Open { + opened <- struct{}{} } } - if channelState.Status() == datatransfer.Completed { - finished <- struct{}{} - } - if event.Code == datatransfer.Error { - errChan <- struct{}{} + dt1.SubscribeToEvents(subscriber) + dt2.SubscribeToEvents(subscriber) + voucher := testutil.FakeDTType{Data: "applesauce"} + sv := testutil.NewStubbedValidator() + + var sourceDagService ipldformat.DAGService + if data.customSourceStore { + ds := dss.MutexWrap(datastore.NewMapDatastore()) + bs := bstore.NewBlockstore(namespace.Wrap(ds, datastore.NewKey("blockstore"))) + loader := storeutil.LoaderForBlockstore(bs) + storer := storeutil.StorerForBlockstore(bs) + sourceDagService = merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) + err := dt1.RegisterTransportConfigurer(&testutil.FakeDTType{}, func(channelID datatransfer.ChannelID, testVoucher datatransfer.Voucher, transport datatransfer.Transport) { + fv, ok := testVoucher.(*testutil.FakeDTType) + if ok && fv.Data == voucher.Data { + gsTransport, ok := transport.(*tp.Transport) + if ok { + err := gsTransport.UseStore(channelID, loader, storer) + require.NoError(t, err) + } + } + }) + require.NoError(t, err) + } else { + sourceDagService = gsData.DagService1 } - if event.Code == datatransfer.Open { - opened <- struct{}{} + root, origBytes := testutil.LoadUnixFSFile(ctx, t, sourceDagService, loremFile) + rootCid := root.(cidlink.Link).Cid + + var destDagService ipldformat.DAGService + if data.customTargetStore { + ds := dss.MutexWrap(datastore.NewMapDatastore()) + bs := bstore.NewBlockstore(namespace.Wrap(ds, datastore.NewKey("blockstore"))) + loader := storeutil.LoaderForBlockstore(bs) + storer := storeutil.StorerForBlockstore(bs) + destDagService = merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) + err := dt2.RegisterTransportConfigurer(&testutil.FakeDTType{}, func(channelID datatransfer.ChannelID, testVoucher datatransfer.Voucher, transport datatransfer.Transport) { + fv, ok := testVoucher.(*testutil.FakeDTType) + if ok && fv.Data == voucher.Data { + gsTransport, ok := transport.(*tp.Transport) + if ok { + err := gsTransport.UseStore(channelID, loader, storer) + require.NoError(t, err) + } + } + }) + require.NoError(t, err) + } else { + destDagService = gsData.DagService2 } - } - dt1.SubscribeToEvents(subscriber) - dt2.SubscribeToEvents(subscriber) - voucher := testutil.FakeDTType{Data: "applesauce"} - sv := testutil.NewStubbedValidator() - var sourceDagService ipldformat.DAGService - if data.customSourceStore { - ds := dss.MutexWrap(datastore.NewMapDatastore()) - bs := bstore.NewBlockstore(namespace.Wrap(ds, datastore.NewKey("blockstore"))) - loader := storeutil.LoaderForBlockstore(bs) - storer := storeutil.StorerForBlockstore(bs) - sourceDagService = merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) - err := dt1.RegisterTransportConfigurer(&testutil.FakeDTType{}, func(channelID datatransfer.ChannelID, testVoucher datatransfer.Voucher, transport datatransfer.Transport) { - fv, ok := testVoucher.(*testutil.FakeDTType) - if ok && fv.Data == voucher.Data { - gsTransport, ok := transport.(*tp.Transport) - if ok { - err := gsTransport.UseStore(channelID, loader, storer) - require.NoError(t, err) - } - } - }) + var chid datatransfer.ChannelID + if data.isPull { + sv.ExpectSuccessPull() + require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv)) + chid, err = dt2.OpenPullDataChannel(ctx, host1.ID(), &voucher, rootCid, gsData.AllSelector) + } else { + sv.ExpectSuccessPush() + require.NoError(t, dt2.RegisterVoucherType(&testutil.FakeDTType{}, sv)) + chid, err = dt1.OpenPushDataChannel(ctx, host2.ID(), &voucher, rootCid, gsData.AllSelector) + } require.NoError(t, err) - } else { - sourceDagService = gsData.DagService1 - } - root, origBytes := testutil.LoadUnixFSFile(ctx, t, sourceDagService) - rootCid := root.(cidlink.Link).Cid - - var destDagService ipldformat.DAGService - if data.customTargetStore { - ds := dss.MutexWrap(datastore.NewMapDatastore()) - bs := bstore.NewBlockstore(namespace.Wrap(ds, datastore.NewKey("blockstore"))) - loader := storeutil.LoaderForBlockstore(bs) - storer := storeutil.StorerForBlockstore(bs) - destDagService = merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) - err := dt2.RegisterTransportConfigurer(&testutil.FakeDTType{}, func(channelID datatransfer.ChannelID, testVoucher datatransfer.Voucher, transport datatransfer.Transport) { - fv, ok := testVoucher.(*testutil.FakeDTType) - if ok && fv.Data == voucher.Data { - gsTransport, ok := transport.(*tp.Transport) - if ok { - err := gsTransport.UseStore(channelID, loader, storer) - require.NoError(t, err) - } + opens := 0 + completes := 0 + sentIncrements := make([]uint64, 0, 21) + receivedIncrements := make([]uint64, 0, 21) + for opens < 2 || completes < 2 || len(sentIncrements) < 21 || len(receivedIncrements) < 21 { + select { + case <-ctx.Done(): + t.Fatal("Did not complete succcessful data transfer") + case <-finished: + completes++ + case <-opened: + opens++ + case sentIncrement := <-sent: + sentIncrements = append(sentIncrements, sentIncrement) + case receivedIncrement := <-received: + receivedIncrements = append(receivedIncrements, receivedIncrement) + case <-errChan: + t.Fatal("received error on data transfer") } - }) - require.NoError(t, err) - } else { - destDagService = gsData.DagService2 - } - - var chid datatransfer.ChannelID - if data.isPull { - sv.ExpectSuccessPull() - require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv)) - chid, err = dt2.OpenPullDataChannel(ctx, host1.ID(), &voucher, rootCid, gsData.AllSelector) - } else { - sv.ExpectSuccessPush() - require.NoError(t, dt2.RegisterVoucherType(&testutil.FakeDTType{}, sv)) - chid, err = dt1.OpenPushDataChannel(ctx, host2.ID(), &voucher, rootCid, gsData.AllSelector) - } - require.NoError(t, err) - opens := 0 - completes := 0 - sentIncrements := make([]uint64, 0, 21) - receivedIncrements := make([]uint64, 0, 21) - for opens < 2 || completes < 2 || len(sentIncrements) < 21 || len(receivedIncrements) < 21 { - select { - case <-ctx.Done(): - t.Fatal("Did not complete succcessful data transfer") - case <-finished: - completes++ - case <-opened: - opens++ - case sentIncrement := <-sent: - sentIncrements = append(sentIncrements, sentIncrement) - case receivedIncrement := <-received: - receivedIncrements = append(receivedIncrements, receivedIncrement) - case <-errChan: - t.Fatal("received error on data transfer") } - } - require.Equal(t, sentIncrements, receivedIncrements) - testutil.VerifyHasFile(ctx, t, destDagService, root, origBytes) - if data.isPull { - assert.Equal(t, chid.Initiator, host2.ID()) - } else { - assert.Equal(t, chid.Initiator, host1.ID()) - } - }) - } + require.Equal(t, sentIncrements, receivedIncrements) + testutil.VerifyHasFile(ctx, t, destDagService, root, origBytes) + if data.isPull { + assert.Equal(t, chid.Initiator, host2.ID()) + } else { + assert.Equal(t, chid.Initiator, host1.ID()) + } + }) + } + } // } func TestMultipleRoundTripMultipleStores(t *testing.T) { @@ -230,7 +252,7 @@ func TestMultipleRoundTripMultipleStores(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator, data sender host2 := gsData.Host2 // data recipient @@ -239,12 +261,10 @@ func TestMultipleRoundTripMultipleStores(t *testing.T) { dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) finished := make(chan struct{}, 2*data.requestCount) errChan := make(chan string, 2*data.requestCount) @@ -268,7 +288,7 @@ func TestMultipleRoundTripMultipleStores(t *testing.T) { } sv := testutil.NewStubbedValidator() - root, origBytes := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1) + root, origBytes := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1, loremFile) rootCid := root.(cidlink.Link).Cid destDagServices := make([]ipldformat.DAGService, 0, data.requestCount) @@ -357,14 +377,13 @@ func TestManyReceiversAtOnce(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator, data sender tp1 := gsData.SetupGSTransportHost1() dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) destDagServices := make([]ipldformat.DAGService, 0, data.receiverCount) receivers := make([]datatransfer.Manager, 0, data.receiverCount) @@ -438,7 +457,7 @@ func TestManyReceiversAtOnce(t *testing.T) { } sv := testutil.NewStubbedValidator() - root, origBytes := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1) + root, origBytes := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1, loremFile) rootCid := root.(cidlink.Link).Cid if data.isPull { @@ -492,7 +511,7 @@ func TestRoundTripCancelledRequest(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator, data sender host2 := gsData.Host2 @@ -501,12 +520,10 @@ func TestRoundTripCancelledRequest(t *testing.T) { dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) finished := make(chan struct{}, 2) errChan := make(chan string, 2) @@ -534,7 +551,7 @@ func TestRoundTripCancelledRequest(t *testing.T) { dt2.SubscribeToEvents(subscriber) voucher := testutil.FakeDTType{Data: "applesauce"} sv := testutil.NewStubbedValidator() - root, _ := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1) + root, _ := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1, loremFile) rootCid := root.(cidlink.Link).Cid var chid datatransfer.ChannelID @@ -635,7 +652,7 @@ func TestSimulatedRetrievalFlow(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 4*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator, data sender root := gsData.LoadUnixFSFile(t, false) @@ -645,12 +662,10 @@ func TestSimulatedRetrievalFlow(t *testing.T) { dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) var chid datatransfer.ChannelID errChan := make(chan struct{}, 2) clientPausePoint := 0 @@ -685,7 +700,7 @@ func TestSimulatedRetrievalFlow(t *testing.T) { } } - if event.Code == datatransfer.Progress && + if event.Code == datatransfer.DataReceived && clientPausePoint < len(config.pausePoints) && channelState.Received() > config.pausePoints[clientPausePoint] { _ = dt2.SendVoucher(ctx, chid, testutil.NewFakeDTType()) @@ -764,7 +779,7 @@ func TestPauseAndResume(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator, data sender host2 := gsData.Host2 // data recipient @@ -775,12 +790,10 @@ func TestPauseAndResume(t *testing.T) { dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) finished := make(chan struct{}, 2) errChan := make(chan struct{}, 2) opened := make(chan struct{}, 2) @@ -791,13 +804,19 @@ func TestPauseAndResume(t *testing.T) { pauseResponder := make(chan struct{}, 2) resumeResponder := make(chan struct{}, 2) var subscriber datatransfer.Subscriber = func(event datatransfer.Event, channelState datatransfer.ChannelState) { - if event.Code == datatransfer.Progress { + + if event.Code == datatransfer.DataSent { + if channelState.Sent() > 0 { + sent <- channelState.Sent() + } + } + + if event.Code == datatransfer.DataReceived { if channelState.Received() > 0 { received <- channelState.Received() - } else if channelState.Sent() > 0 { - sent <- channelState.Sent() } } + if event.Code == datatransfer.PauseInitiator { pauseInitiator <- struct{}{} } @@ -901,7 +920,7 @@ func TestUnrecognizedVoucherRoundTrip(t *testing.T) { // ctx, cancel := context.WithTimeout(ctx, 5*time.Second) // defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator, data sender host2 := gsData.Host2 // data recipient @@ -910,12 +929,10 @@ func TestUnrecognizedVoucherRoundTrip(t *testing.T) { dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) finished := make(chan struct{}, 2) errChan := make(chan string, 2) @@ -935,7 +952,7 @@ func TestUnrecognizedVoucherRoundTrip(t *testing.T) { dt2.SubscribeToEvents(subscriber) voucher := testutil.FakeDTType{Data: "applesauce"} - root, _ := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1) + root, _ := testutil.LoadUnixFSFile(ctx, t, gsData.DagService1, loremFile) rootCid := root.(cidlink.Link).Cid if isPull { @@ -972,7 +989,7 @@ func TestDataTransferSubscribing(t *testing.T) { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host2 := gsData.Host2 tp1 := gsData.SetupGSTransportHost1() @@ -982,16 +999,14 @@ func TestDataTransferSubscribing(t *testing.T) { sv.StubErrorPush() dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt2.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt2) require.NoError(t, dt2.RegisterVoucherType(&testutil.FakeDTType{}, sv)) voucher := testutil.FakeDTType{Data: "applesauce"} baseCid := testutil.GenerateCids(1)[0] dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) subscribe1Calls := make(chan struct{}, 1) subscribe1 := func(event datatransfer.Event, channelState datatransfer.ChannelState) { if event.Code == datatransfer.Error { @@ -1102,7 +1117,7 @@ func TestRespondingToPushGraphsyncRequests(t *testing.T) { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator and data sender host2 := gsData.Host2 // data recipient, makes graphsync request for data voucher := testutil.NewFakeDTType() @@ -1123,8 +1138,7 @@ func TestRespondingToPushGraphsyncRequests(t *testing.T) { tp1 := gsData.SetupGSTransportHost1() dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) voucherResult := testutil.NewFakeDTType() err = dt1.RegisterVoucherResultType(voucherResult) require.NoError(t, err) @@ -1186,7 +1200,7 @@ func TestResponseHookWhenExtensionNotFound(t *testing.T) { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) host1 := gsData.Host1 // initiator and data sender host2 := gsData.Host2 // data recipient, makes graphsync request for data voucher := testutil.FakeDTType{Data: "applesauce"} @@ -1208,8 +1222,7 @@ func TestResponseHookWhenExtensionNotFound(t *testing.T) { tp1 := tp.NewTransport(host1.ID(), gs1) dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) t.Run("when it's not our extension, does not error and does not validate", func(t *testing.T) { //register a hook that validates the request so we don't fail in gs because the request //never gets processed @@ -1250,12 +1263,11 @@ func TestRespondingToPullGraphsyncRequests(t *testing.T) { dt1, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv)) voucher := testutil.NewFakeDTType() - request, err := message.NewRequest(id, true, voucher.Type(), voucher, testutil.GenerateCids(1)[0], gsData.AllSelector) + request, err := message.NewRequest(id, false, true, voucher.Type(), voucher, testutil.GenerateCids(1)[0], gsData.AllSelector) require.NoError(t, err) buf := new(bytes.Buffer) err = request.ToNet(buf) @@ -1281,11 +1293,10 @@ func TestRespondingToPullGraphsyncRequests(t *testing.T) { sv.ExpectErrorPull() dt1, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) require.NoError(t, err) - err = dt1.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt1) require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv)) voucher := testutil.NewFakeDTType() - dtRequest, err := message.NewRequest(id, true, voucher.Type(), voucher, testutil.GenerateCids(1)[0], gsData.AllSelector) + dtRequest, err := message.NewRequest(id, false, true, voucher.Type(), voucher, testutil.GenerateCids(1)[0], gsData.AllSelector) require.NoError(t, err) buf := new(bytes.Buffer) @@ -1313,7 +1324,7 @@ func TestRespondingToPullGraphsyncRequests(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - gsData := testutil.NewGraphsyncTestingData(ctx, t) + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) // setup receiving peer to just record message coming in gsr := &fakeGraphSyncReceiver{ @@ -1366,3 +1377,9 @@ func (r *receiver) ReceiveResponse( func (r *receiver) ReceiveError(err error) { } + +func (r *receiver) ReceiveRestartExistingChannelRequest(ctx context.Context, + sender peer.ID, + incoming datatransfer.Request) { + +} diff --git a/impl/receiver.go b/impl/receiver.go index fb0192f9..267f8bc8 100644 --- a/impl/receiver.go +++ b/impl/receiver.go @@ -3,10 +3,12 @@ package impl import ( "context" + "github.com/ipfs/go-cid" cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/libp2p/go-libp2p-core/peer" datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels" ) type receiver struct { @@ -41,9 +43,18 @@ func (r *receiver) receiveRequest(ctx context.Context, initiator peer.ID, incomi } if response != nil { - if response.IsNew() && response.Accepted() && !incoming.IsPull() { + if (response.IsNew() || response.IsRestart()) && response.Accepted() && !incoming.IsPull() { + var doNotSendCids []cid.Cid + if response.IsRestart() { + channel, err := r.manager.channels.GetByID(ctx, chid) + if err != nil { + return err + } + doNotSendCids = channel.ReceivedCids() + } + stor, _ := incoming.Selector() - if err := r.manager.transport.OpenChannel(ctx, initiator, chid, cidlink.Link{Cid: incoming.BaseCid()}, stor, response); err != nil { + if err := r.manager.transport.OpenChannel(ctx, initiator, chid, cidlink.Link{Cid: incoming.BaseCid()}, stor, doNotSendCids, response); err != nil { return err } } else { @@ -95,3 +106,52 @@ func (r *receiver) receiveResponse( func (r *receiver) ReceiveError(err error) { log.Errorf("received error message on data transfer: %s", err.Error()) } + +func (r *receiver) ReceiveRestartExistingChannelRequest(ctx context.Context, + sender peer.ID, + incoming datatransfer.Request) { + + ch, err := incoming.RestartChannelId() + if err != nil { + log.Errorf("failed to fetch restart channel Id: %w", err) + return + } + + // validate channel exists -> in non-terminal state and that the sender matches + channel, err := r.manager.channels.GetByID(ctx, ch) + if err != nil || channel == nil { + // nothing to do here, we wont handle the request + return + } + + // initiator should be me + if channel.ChannelID().Initiator != r.manager.peerID { + log.Error("channel initiator is not the manager peer") + return + } + + // other peer should be the counter party on the channel + if channel.OtherPeer() != sender { + log.Error("channel counterparty is not the sender peer") + return + } + + // channel should NOT be terminated + if channels.IsChannelTerminated(channel.Status()) { + log.Error("channel is already terminated") + return + } + + switch r.manager.channelDataTransferType(channel) { + case ManagerPeerCreatePush: + if err := r.manager.openPushRestartChannel(ctx, channel); err != nil { + log.Errorf("failed to open push restart channel: %w", err) + } + case ManagerPeerCreatePull: + if err := r.manager.openPullRestartChannel(ctx, channel); err != nil { + log.Errorf("failed to open pull restart channel: %w", err) + } + default: + log.Error("peer is not the creator of the channel") + } +} diff --git a/impl/responding_test.go b/impl/responding_test.go index 546ed103..72264230 100644 --- a/impl/responding_test.go +++ b/impl/responding_test.go @@ -261,7 +261,7 @@ func TestDataTransferResponding(t *testing.T) { datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept, - datatransfer.Progress, + datatransfer.DataReceived, datatransfer.NewVoucherResult, datatransfer.PauseResponder, datatransfer.NewVoucher, @@ -311,7 +311,7 @@ func TestDataTransferResponding(t *testing.T) { datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept, - datatransfer.Progress, + datatransfer.DataReceived, datatransfer.NewVoucherResult, }, configureValidator: func(sv *testutil.StubbedValidator) { @@ -346,7 +346,7 @@ func TestDataTransferResponding(t *testing.T) { datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept, - datatransfer.Progress, + datatransfer.DataReceived, datatransfer.NewVoucherResult, datatransfer.PauseResponder, datatransfer.NewVoucher, @@ -395,7 +395,7 @@ func TestDataTransferResponding(t *testing.T) { datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept, - datatransfer.Progress, + datatransfer.DataSent, datatransfer.NewVoucherResult, datatransfer.PauseResponder, datatransfer.NewVoucher, @@ -561,8 +561,7 @@ func TestDataTransferResponding(t *testing.T) { h.storedCounter = storedcounter.New(h.ds, datastore.NewKey("counter")) dt, err := NewDataTransfer(h.ds, h.network, h.transport, h.storedCounter) require.NoError(t, err) - err = dt.Start(ctx) - require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt) h.dt = dt ev := eventVerifier{ expectedEvents: verify.expectedEvents, @@ -573,9 +572,9 @@ func TestDataTransferResponding(t *testing.T) { h.voucher = testutil.NewFakeDTType() h.baseCid = testutil.GenerateCids(1)[0] h.id = datatransfer.TransferID(rand.Int31()) - h.pullRequest, err = message.NewRequest(h.id, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + h.pullRequest, err = message.NewRequest(h.id, false, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) require.NoError(t, err) - h.pushRequest, err = message.NewRequest(h.id, false, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + h.pushRequest, err = message.NewRequest(h.id, false, false, h.voucher.Type(), h.voucher, h.baseCid, h.stor) require.NoError(t, err) h.pauseUpdate = message.UpdateRequest(h.id, true) require.NoError(t, err) @@ -604,6 +603,412 @@ func TestDataTransferResponding(t *testing.T) { } } +func TestDataTransferRestartResponding(t *testing.T) { + // create network + ctx := context.Background() + testCases := map[string]struct { + expectedEvents []datatransfer.EventCode + configureValidator func(sv *testutil.StubbedValidator) + configureRevalidator func(sv *testutil.StubbedRevalidator) + verify func(t *testing.T, h *receiverHarness) + }{ + "receiving a pull restart response": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Restart, datatransfer.ResumeResponder}, + verify: func(t *testing.T, h *receiverHarness) { + channelID, err := h.dt.OpenPushDataChannel(h.ctx, h.peers[1], h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NotEmpty(t, channelID) + + response, err := message.RestartResponse(channelID.ID, true, false, datatransfer.EmptyTypeIdentifier, nil) + require.NoError(t, err) + err = h.transport.EventHandler.OnResponseReceived(channelID, response) + require.NoError(t, err) + }, + }, + "receiving a push restart request validates and opens a channel for pull": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept, + datatransfer.DataReceived, datatransfer.DataReceived, datatransfer.NewVoucherResult, datatransfer.Restart}, + configureValidator: func(sv *testutil.StubbedValidator) { + sv.ExpectSuccessPush() + sv.StubResult(testutil.NewFakeDTType()) + }, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming push + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], h.pushRequest) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 1) + require.Len(t, h.network.SentMessages, 0) + + // some cids are received + chid := datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: h.pushRequest.TransferID()} + testCids := testutil.GenerateCids(2) + ev, ok := h.dt.(datatransfer.EventsHandler) + require.True(t, ok) + require.NoError(t, ev.OnDataReceived(chid, cidlink.Link{Cid: testCids[0]}, 12345)) + require.NoError(t, ev.OnDataReceived(chid, cidlink.Link{Cid: testCids[1]}, 12345)) + + // receive restart push request + req, err := message.NewRequest(h.pushRequest.TransferID(), true, false, h.voucher.Type(), h.voucher, + h.baseCid, h.stor) + require.NoError(t, err) + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], req) + require.Len(t, h.sv.ValidationsReceived, 2) + require.Len(t, h.transport.OpenedChannels, 2) + require.Len(t, h.network.SentMessages, 0) + + // validate channel that is opened second time + openChannel := h.transport.OpenedChannels[1] + require.Equal(t, openChannel.ChannelID, channelID(h.id, h.peers)) + require.Equal(t, openChannel.DataSender, h.peers[1]) + require.Equal(t, openChannel.Root, cidlink.Link{Cid: h.baseCid}) + require.Equal(t, openChannel.Selector, h.stor) + // assert do not send cids are sent + require.Equal(t, []cid.Cid{testCids[0], testCids[1]}, openChannel.DoNotSendCids) + require.False(t, openChannel.Message.IsRequest()) + response, ok := openChannel.Message.(datatransfer.Response) + require.True(t, ok) + require.True(t, response.IsRestart()) + require.True(t, response.Accepted()) + require.Equal(t, response.TransferID(), h.id) + require.False(t, response.IsUpdate()) + require.False(t, response.IsCancel()) + require.False(t, response.IsPaused()) + require.False(t, response.IsNew()) + require.True(t, response.IsVoucherResult()) + + // validate the voucher that is validated the second time + vmsg := h.sv.ValidationsReceived[1] + require.Equal(t, h.voucher, vmsg.Voucher) + require.False(t, vmsg.IsPull) + require.Equal(t, h.stor, vmsg.Selector) + require.Equal(t, h.baseCid, vmsg.BaseCid) + require.Equal(t, h.peers[1], vmsg.Other) + }, + }, + "receiving a pull restart request validates and sends a success response": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept, + datatransfer.NewVoucherResult, datatransfer.Restart}, + configureValidator: func(sv *testutil.StubbedValidator) { + sv.ExpectSuccessPull() + sv.StubResult(testutil.NewFakeDTType()) + }, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming pull + _, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), h.pullRequest) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // receive restart pull request + restartReq, err := message.NewRequest(h.id, true, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + response, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), restartReq) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 2) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // validate response + require.True(t, response.IsRestart()) + require.True(t, response.Accepted()) + require.Equal(t, response.TransferID(), h.id) + require.False(t, response.IsUpdate()) + require.False(t, response.IsCancel()) + require.False(t, response.IsPaused()) + require.False(t, response.IsNew()) + require.True(t, response.IsVoucherResult()) + + // validate the voucher that is validated the second time + vmsg := h.sv.ValidationsReceived[1] + require.Equal(t, h.voucher, vmsg.Voucher) + require.True(t, vmsg.IsPull) + require.Equal(t, h.stor, vmsg.Selector) + require.Equal(t, h.baseCid, vmsg.BaseCid) + require.Equal(t, h.peers[1], vmsg.Other) + }, + }, + "restart request fails if channel does not exist": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept}, + configureValidator: func(sv *testutil.StubbedValidator) { + sv.ExpectSuccessPull() + sv.StubResult(testutil.NewFakeDTType()) + }, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming pull + _, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), h.pullRequest) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // receive restart pull request + restartReq, err := message.NewRequest(h.id, true, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + p := testutil.GeneratePeers(1)[0] + chid := datatransfer.ChannelID{ID: h.pullRequest.TransferID(), Initiator: p, Responder: h.peers[0]} + _, err = h.transport.EventHandler.OnRequestReceived(chid, restartReq) + require.EqualError(t, err, "No channel for this channel ID") + }, + }, + "restart request fails if voucher validation fails": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept}, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming pull + h.sv.ExpectSuccessPull() + h.sv.StubResult(testutil.NewFakeDTType()) + _, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), h.pullRequest) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // receive restart pull request + h.sv.ExpectErrorPull() + restartReq, err := message.NewRequest(h.id, true, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + _, err = h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), restartReq) + require.EqualError(t, err, "failed to validate voucher: something went wrong") + }, + }, + "restart request fails if base cid does not match": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept}, + configureValidator: func(sv *testutil.StubbedValidator) { + sv.ExpectSuccessPull() + sv.StubResult(testutil.NewFakeDTType()) + }, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming pull + _, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), h.pullRequest) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // receive restart pull request + randCid := testutil.GenerateCids(1)[0] + restartReq, err := message.NewRequest(h.id, true, true, h.voucher.Type(), h.voucher, randCid, h.stor) + require.NoError(t, err) + _, err = h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), restartReq) + require.EqualError(t, err, "base cid does not match") + }, + }, + "restart request fails if voucher type is not decodable": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept}, + configureValidator: func(sv *testutil.StubbedValidator) { + sv.ExpectSuccessPull() + sv.StubResult(testutil.NewFakeDTType()) + }, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming pull + _, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), h.pullRequest) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // receive restart pull request + + restartReq, err := message.NewRequest(h.id, true, true, "rand", h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + _, err = h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), restartReq) + require.EqualError(t, err, "failed to decode request voucher: unknown voucher type: rand") + }, + }, + "restart request fails if voucher does not match": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.NewVoucherResult, datatransfer.Accept}, + configureValidator: func(sv *testutil.StubbedValidator) { + sv.ExpectSuccessPull() + sv.StubResult(testutil.NewFakeDTType()) + }, + verify: func(t *testing.T, h *receiverHarness) { + // receive an incoming pull + _, err := h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), h.pullRequest) + require.NoError(t, err) + require.Len(t, h.sv.ValidationsReceived, 1) + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 0) + + // receive restart pull request + v := testutil.NewFakeDTType() + v.Data = "rand" + restartReq, err := message.NewRequest(h.id, true, true, h.voucher.Type(), v, h.baseCid, h.stor) + require.NoError(t, err) + _, err = h.transport.EventHandler.OnRequestReceived(channelID(h.id, h.peers), restartReq) + require.EqualError(t, err, "channel and request vouchers do not match") + }, + }, + "ReceiveRestartExistingChannelRequest: Reopen Pull Channel": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.DataReceived, datatransfer.DataReceived}, + configureValidator: func(sv *testutil.StubbedValidator) { + }, + verify: func(t *testing.T, h *receiverHarness) { + // create an outgoing pull channel first + channelID, err := h.dt.OpenPullDataChannel(h.ctx, h.peers[1], h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NotEmpty(t, channelID) + + // some cids should already be received + testCids := testutil.GenerateCids(2) + ev, ok := h.dt.(datatransfer.EventsHandler) + require.True(t, ok) + require.NoError(t, ev.OnDataReceived(channelID, cidlink.Link{Cid: testCids[0]}, 12345)) + require.NoError(t, ev.OnDataReceived(channelID, cidlink.Link{Cid: testCids[1]}, 12345)) + + // send a request to restart the same pull channel + restartReq := message.RestartExistingChannelRequest(channelID) + h.network.Delegate.ReceiveRestartExistingChannelRequest(ctx, h.peers[1], restartReq) + + require.Len(t, h.transport.OpenedChannels, 2) + require.Len(t, h.network.SentMessages, 0) + + // assert correct channel was created in response to this + require.Len(t, h.transport.OpenedChannels, 2) + openChannel := h.transport.OpenedChannels[1] + require.Equal(t, openChannel.ChannelID, channelID) + require.Equal(t, openChannel.DataSender, h.peers[1]) + require.Equal(t, openChannel.Root, cidlink.Link{Cid: h.baseCid}) + require.Equal(t, openChannel.Selector, h.stor) + require.True(t, openChannel.Message.IsRequest()) + // received cids should be a part of the channel req + require.Equal(t, []cid.Cid{testCids[0], testCids[1]}, openChannel.DoNotSendCids) + + // assert a restart request is in the channel + request, ok := openChannel.Message.(datatransfer.Request) + require.True(t, ok) + require.True(t, request.IsRestart()) + require.Equal(t, request.TransferID(), channelID.ID) + require.Equal(t, request.BaseCid(), h.baseCid) + require.False(t, request.IsCancel()) + require.True(t, request.IsPull()) + require.False(t, request.IsNew()) + + // voucher should be sent correctly + receivedSelector, err := request.Selector() + require.NoError(t, err) + require.Equal(t, receivedSelector, h.stor) + testutil.AssertFakeDTVoucher(t, request, h.voucher) + }, + }, + "ReceiveRestartExistingChannelRequest: Resend Push Request": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open}, + configureValidator: func(sv *testutil.StubbedValidator) { + }, + verify: func(t *testing.T, h *receiverHarness) { + // create an outgoing push request first + channelID, err := h.dt.OpenPushDataChannel(h.ctx, h.peers[1], h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NotEmpty(t, channelID) + + // send a request to restart the same push request + restartReq := message.RestartExistingChannelRequest(channelID) + h.network.Delegate.ReceiveRestartExistingChannelRequest(ctx, h.peers[1], restartReq) + + // assert correct message was sent in response to this + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 2) + + // assert restart request is well formed + messageReceived := h.network.SentMessages[1] + require.Equal(t, messageReceived.PeerID, h.peers[1]) + received := messageReceived.Message + require.True(t, received.IsRequest()) + receivedRequest, ok := received.(datatransfer.Request) + require.True(t, ok) + require.Equal(t, receivedRequest.TransferID(), channelID.ID) + require.Equal(t, receivedRequest.BaseCid(), h.baseCid) + require.False(t, receivedRequest.IsCancel()) + require.False(t, receivedRequest.IsPull()) + require.True(t, receivedRequest.IsRestart()) + require.False(t, receivedRequest.IsNew()) + + // assert voucher is sent correctly + receivedSelector, err := receivedRequest.Selector() + require.NoError(t, err) + require.Equal(t, receivedSelector, h.stor) + testutil.AssertFakeDTVoucher(t, receivedRequest, h.voucher) + }, + }, + "ReceiveRestartExistingChannelRequest: errors if peer is not the initiator": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open, datatransfer.Accept}, + configureValidator: func(sv *testutil.StubbedValidator) { + }, + verify: func(t *testing.T, h *receiverHarness) { + // create an incoming push first + h.network.Delegate.ReceiveRequest(h.ctx, h.peers[1], h.pushRequest) + require.Len(t, h.sv.ValidationsReceived, 1) + + // restart req does not anything as we are not the initiator + chid := datatransfer.ChannelID{Initiator: h.peers[1], Responder: h.peers[0], ID: h.pushRequest.TransferID()} + restartReq := message.RestartExistingChannelRequest(chid) + h.network.Delegate.ReceiveRestartExistingChannelRequest(ctx, h.peers[1], restartReq) + + require.Len(t, h.transport.OpenedChannels, 1) + require.Len(t, h.network.SentMessages, 0) + }, + }, + "ReceiveRestartExistingChannelRequest: errors if sending peer is not the counter-party on the channel": { + expectedEvents: []datatransfer.EventCode{datatransfer.Open}, + configureValidator: func(sv *testutil.StubbedValidator) { + }, + verify: func(t *testing.T, h *receiverHarness) { + // create an outgoing push request first + p := testutil.GeneratePeers(1)[0] + channelID, err := h.dt.OpenPushDataChannel(h.ctx, p, h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + require.NotEmpty(t, channelID) + + // sending peer is not the counter-party on the channel + restartReq := message.RestartExistingChannelRequest(channelID) + h.network.Delegate.ReceiveRestartExistingChannelRequest(ctx, h.peers[1], restartReq) + + require.Len(t, h.transport.OpenedChannels, 0) + require.Len(t, h.network.SentMessages, 1) + }, + }, + } + for testCase, verify := range testCases { + t.Run(testCase, func(t *testing.T) { + h := &receiverHarness{} + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + h.ctx = ctx + h.peers = testutil.GeneratePeers(2) + h.network = testutil.NewFakeNetwork(h.peers[0]) + h.transport = testutil.NewFakeTransport() + h.ds = dss.MutexWrap(datastore.NewMapDatastore()) + h.storedCounter = storedcounter.New(h.ds, datastore.NewKey("counter")) + dt, err := NewDataTransfer(h.ds, h.network, h.transport, h.storedCounter) + require.NoError(t, err) + testutil.StartAndWaitForReady(ctx, t, dt) + h.dt = dt + ev := eventVerifier{ + expectedEvents: verify.expectedEvents, + events: make(chan datatransfer.EventCode, len(verify.expectedEvents)), + } + ev.setup(t, dt) + h.stor = testutil.AllSelector() + h.voucher = testutil.NewFakeDTType() + h.baseCid = testutil.GenerateCids(1)[0] + h.id = datatransfer.TransferID(rand.Int31()) + h.pullRequest, err = message.NewRequest(h.id, false, true, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + h.pushRequest, err = message.NewRequest(h.id, false, false, h.voucher.Type(), h.voucher, h.baseCid, h.stor) + require.NoError(t, err) + + h.sv = testutil.NewStubbedValidator() + if verify.configureValidator != nil { + verify.configureValidator(h.sv) + } + require.NoError(t, h.dt.RegisterVoucherType(h.voucher, h.sv)) + + verify.verify(t, h) + h.sv.VerifyExpectations(t) + ev.verify(ctx, t) + }) + } +} + type receiverHarness struct { id datatransfer.TransferID pushRequest datatransfer.Request @@ -618,7 +1023,7 @@ type receiverHarness struct { transport *testutil.FakeTransport sv *testutil.StubbedValidator srv *testutil.StubbedRevalidator - ds datastore.Datastore + ds datastore.Batching storedCounter *storedcounter.StoredCounter dt datatransfer.Manager stor ipld.Node diff --git a/impl/restart.go b/impl/restart.go new file mode 100644 index 00000000..21b1fa85 --- /dev/null +++ b/impl/restart.go @@ -0,0 +1,177 @@ +package impl + +import ( + "bytes" + "context" + + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/libp2p/go-libp2p-core/peer" + "golang.org/x/xerrors" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/channels" + "github.com/filecoin-project/go-data-transfer/encoding" + "github.com/filecoin-project/go-data-transfer/message" +) + +type ChannelDataTransferType int + +const ( + // ManagerPeerCreatePull is the type of a channel wherein the manager peer created a Pull Data Transfer + ManagerPeerCreatePull ChannelDataTransferType = iota + + // ManagerPeerCreatePush is the type of a channel wherein the manager peer created a Push Data Transfer + ManagerPeerCreatePush + + // ManagerPeerCreatePush is the type of a channel wherein the manager peer received a Pull Data Transfer Request + ManagerPeerReceivePull + + // ManagerPeerCreatePush is the type of a channel wherein the manager peer received a Push Data Transfer Request + ManagerPeerReceivePush +) + +func (m *manager) restartManagerPeerReceivePush(ctx context.Context, channel datatransfer.ChannelState) error { + if err := m.validateRestartVoucher(channel, false); err != nil { + return xerrors.Errorf("failed to restart channel, validation error: %w", err) + } + + // send a libp2p message to the other peer asking to send a "restart push request" + req := message.RestartExistingChannelRequest(channel.ChannelID()) + + if err := m.dataTransferNetwork.SendMessage(ctx, channel.OtherPeer(), req); err != nil { + return xerrors.Errorf("unable to send restart request: %w", err) + } + + return nil +} + +func (m *manager) restartManagerPeerReceivePull(ctx context.Context, channel datatransfer.ChannelState) error { + if err := m.validateRestartVoucher(channel, true); err != nil { + return xerrors.Errorf("failed to restart channel, validation error: %w", err) + } + + req := message.RestartExistingChannelRequest(channel.ChannelID()) + + // send a libp2p message to the other peer asking to send a "restart pull request" + if err := m.dataTransferNetwork.SendMessage(ctx, channel.OtherPeer(), req); err != nil { + return xerrors.Errorf("unable to send restart request: %w", err) + } + + return nil +} + +func (m *manager) validateRestartVoucher(channel datatransfer.ChannelState, isPull bool) error { + // re-validate the original voucher received for safety + chid := channel.ChannelID() + + // recreate the request that would have led to this pull channel being created for validation + req, err := message.NewRequest(chid.ID, false, isPull, channel.Voucher().Type(), channel.Voucher(), + channel.BaseCID(), channel.Selector()) + if err != nil { + return err + } + + // revalidate the voucher by reconstructing the request that would have led to the creation of this channel + if _, _, err := m.validateVoucher(channel.OtherPeer(), req, isPull, channel.BaseCID(), channel.Selector()); err != nil { + return err + } + + return nil +} + +func (m *manager) openPushRestartChannel(ctx context.Context, channel datatransfer.ChannelState) error { + selector := channel.Selector() + voucher := channel.Voucher() + baseCid := channel.BaseCID() + requestTo := channel.OtherPeer() + chid := channel.ChannelID() + + req, err := message.NewRequest(chid.ID, true, false, voucher.Type(), voucher, baseCid, selector) + if err != nil { + return err + } + + processor, has := m.transportConfigurers.Processor(voucher.Type()) + if has { + transportConfigurer := processor.(datatransfer.TransportConfigurer) + transportConfigurer(chid, voucher, m.transport) + } + m.dataTransferNetwork.Protect(requestTo, chid.String()) + if err := m.dataTransferNetwork.SendMessage(ctx, requestTo, req); err != nil { + return xerrors.Errorf("Unable to send restart request: %w", err) + } + + return nil +} + +func (m *manager) openPullRestartChannel(ctx context.Context, channel datatransfer.ChannelState) error { + selector := channel.Selector() + voucher := channel.Voucher() + baseCid := channel.BaseCID() + requestTo := channel.OtherPeer() + chid := channel.ChannelID() + + req, err := message.NewRequest(chid.ID, true, true, voucher.Type(), voucher, baseCid, selector) + if err != nil { + return err + } + + processor, has := m.transportConfigurers.Processor(voucher.Type()) + if has { + transportConfigurer := processor.(datatransfer.TransportConfigurer) + transportConfigurer(chid, voucher, m.transport) + } + m.dataTransferNetwork.Protect(requestTo, chid.String()) + if err := m.transport.OpenChannel(ctx, requestTo, chid, cidlink.Link{Cid: baseCid}, selector, channel.ReceivedCids(), req); err != nil { + return xerrors.Errorf("Unable to send open channel restart request: %w", err) + } + + return nil +} + +func (m *manager) validateRestartRequest(ctx context.Context, otherPeer peer.ID, chid datatransfer.ChannelID, req datatransfer.Request) error { + // channel should exist + channel, err := m.channels.GetByID(ctx, chid) + if err != nil { + return err + } + + // channel is not terminated + if channels.IsChannelTerminated(channel.Status()) { + return xerrors.New("channel is already terminated") + } + + // channel initator should be the sender peer + if channel.ChannelID().Initiator != otherPeer { + return xerrors.New("other peer is not the initiator of the channel") + } + + // channel and request baseCid should match + if req.BaseCid() != channel.BaseCID() { + return xerrors.New("base cid does not match") + } + + // vouchers should match + reqVoucher, err := m.decodeVoucher(req, m.validatedTypes) + if err != nil { + return xerrors.Errorf("failed to decode request voucher: %w", err) + } + if reqVoucher.Type() != channel.Voucher().Type() { + return xerrors.New("channel and request voucher types do not match") + } + + reqBz, err := encoding.Encode(reqVoucher) + if err != nil { + return xerrors.New("failed to encode request voucher") + } + channelBz, err := encoding.Encode(channel.Voucher()) + if err != nil { + return xerrors.New("failed to encode channel voucher") + } + + if !bytes.Equal(reqBz, channelBz) { + return xerrors.New("channel and request vouchers do not match") + } + + return nil +} diff --git a/impl/restart_integration_test.go b/impl/restart_integration_test.go new file mode 100644 index 00000000..f7ac9bdc --- /dev/null +++ b/impl/restart_integration_test.go @@ -0,0 +1,484 @@ +package impl_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/ipfs/go-cid" + ipldformat "github.com/ipfs/go-ipld-format" + "github.com/ipld/go-ipld-prime" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + "golang.org/x/xerrors" + + datatransfer "github.com/filecoin-project/go-data-transfer" + . "github.com/filecoin-project/go-data-transfer/impl" + "github.com/filecoin-project/go-data-transfer/testutil" +) + +const totalIncrements = 204 + +// has 204 chunks/blocks +const largeFile = "lorem_large.txt" + +type peerError struct { + p peer.ID + err error +} + +func TestRestartPush(t *testing.T) { + tcs := map[string]struct { + stopAt int + openPushF func(rh *restartHarness) datatransfer.ChannelID + restartF func(rh *restartHarness, chId datatransfer.ChannelID, subFnc datatransfer.Subscriber) + }{ + "Restart peer create push": { + stopAt: 40, + openPushF: func(rh *restartHarness) datatransfer.ChannelID { + voucher := testutil.FakeDTType{Data: "applesauce"} + chid, err := rh.dt1.OpenPushDataChannel(rh.testCtx, rh.peer2, &voucher, rh.rootCid, rh.gsData.AllSelector) + require.NoError(rh.t, err) + return chid + }, + restartF: func(rh *restartHarness, chId datatransfer.ChannelID, subscriber datatransfer.Subscriber) { + var err error + tp1 := rh.gsData.SetupGSTransportHost1() + rh.dt1, err = NewDataTransfer(rh.gsData.DtDs1, rh.gsData.DtNet1, tp1, rh.gsData.StoredCounter1) + require.NoError(rh.t, err) + require.NoError(rh.t, rh.dt1.RegisterVoucherType(&testutil.FakeDTType{}, rh.sv)) + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt1) + rh.dt1.SubscribeToEvents(subscriber) + require.NoError(rh.t, rh.dt1.RestartDataTransferChannel(rh.testCtx, chId)) + }, + }, + "Restart peer receive push": { + stopAt: 50, + openPushF: func(rh *restartHarness) datatransfer.ChannelID { + voucher := testutil.FakeDTType{Data: "applesauce"} + chid, err := rh.dt1.OpenPushDataChannel(rh.testCtx, rh.peer2, &voucher, rh.rootCid, rh.gsData.AllSelector) + require.NoError(rh.t, err) + return chid + }, + restartF: func(rh *restartHarness, chId datatransfer.ChannelID, subscriber datatransfer.Subscriber) { + var err error + tp2 := rh.gsData.SetupGSTransportHost2() + rh.dt2, err = NewDataTransfer(rh.gsData.DtDs2, rh.gsData.DtNet2, tp2, rh.gsData.StoredCounter2) + require.NoError(rh.t, err) + require.NoError(rh.t, rh.dt2.RegisterVoucherType(&testutil.FakeDTType{}, rh.sv)) + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt2) + rh.dt2.SubscribeToEvents(subscriber) + require.NoError(rh.t, rh.dt2.RestartDataTransferChannel(rh.testCtx, chId)) + }, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + // CREATE HARNESS + rh := newRestartHarness(t) + defer rh.cancel() + + // START DATA TRANSFER INSTANCES + rh.sv.ExpectSuccessPush() + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt1) + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt2) + + // SETUP DATA TRANSFER SUBSCRIBERS AND SUBSCRIBE + finished := make(chan peer.ID, 2) + errChan := make(chan *peerError, 2) + sent := make(chan uint64, totalIncrements) + received := make(chan uint64, totalIncrements) + receivedTillNow := atomic.NewInt32(0) + + // counters we will check at the end for correctness + opens := atomic.NewInt32(0) + var finishedPeersLk sync.Mutex + var finishedPeers []peer.ID + disConnChan := make(chan struct{}) + + var subscriber datatransfer.Subscriber = func(event datatransfer.Event, channelState datatransfer.ChannelState) { + if event.Code == datatransfer.DataSent { + if channelState.Sent() > 0 { + sent <- channelState.Sent() + } + } + + // disconnect and unlink the peers after we've received the required number of increments + if event.Code == datatransfer.DataReceived { + if channelState.Received() > 0 { + receivedTillNow.Inc() + received <- channelState.Received() + if receivedTillNow.Load() == int32(tc.stopAt) { + require.NoError(t, rh.gsData.Mn.UnlinkPeers(rh.peer1, rh.peer2)) + require.NoError(t, rh.gsData.Mn.DisconnectPeers(rh.peer1, rh.peer2)) + disConnChan <- struct{}{} + } + } + } + + if channelState.Status() == datatransfer.Completed { + finishedPeersLk.Lock() + finishedPeers = append(finishedPeers, channelState.SelfPeer()) + finishedPeersLk.Unlock() + finished <- channelState.SelfPeer() + } + if event.Code == datatransfer.Error { + err := xerrors.New(channelState.Message()) + errChan <- &peerError{channelState.SelfPeer(), err} + } + if event.Code == datatransfer.Open { + opens.Inc() + } + + if event.Code == datatransfer.Restart { + t.Logf("got restart event for peer %s", channelState.SelfPeer().Pretty()) + } + } + rh.dt1.SubscribeToEvents(subscriber) + rh.dt2.SubscribeToEvents(subscriber) + + // OPEN PUSH + chid := tc.openPushF(rh) + // wait for disconnection to happen + <-disConnChan + t.Logf("peers unlinked and disconnected, total increments received till now: %d", receivedTillNow.Load()) + + // Define function to wait for data transfer to complete + waitF := func(wait time.Duration, nCompletes int) (sentI, receivedI []uint64, err error) { + completes := 0 + + waitCtx, cancel := context.WithTimeout(rh.testCtx, wait) + defer cancel() + for completes < nCompletes { + select { + case <-waitCtx.Done(): + return sentI, receivedI, xerrors.New("context timed-out without completing data transfer") + case p := <-finished: + t.Logf("peer %s completed", p.Pretty()) + completes++ + case perr := <-errChan: + t.Fatalf("\n received error on peer %s, err: %v", perr.p.Pretty(), perr.err) + case s := <-sent: + sentI = append(sentI, s) + case r := <-received: + receivedI = append(receivedI, r) + } + } + + return sentI, receivedI, nil + } + + // WAIT FOR TRANSFER TO COMPLETE -> THIS SHOULD NOT HAPPEN + sentI, receivedI, err := waitF(10*time.Second, 1) + require.EqualError(t, err, "context timed-out without completing data transfer") + require.True(t, len(receivedI) < totalIncrements) + require.NotEmpty(t, sentI) + + // Connect the peers and restart + require.NoError(t, rh.gsData.Mn.LinkAll()) + // let linking take effect + time.Sleep(1 * time.Second) + conn, err := rh.gsData.Mn.ConnectPeers(rh.peer1, rh.peer2) + require.NoError(t, err) + require.NotNil(t, conn) + tc.restartF(rh, chid, subscriber) + t.Logf("peers have been connected and datatransfer has restarted") + + // WAIT FOR DATA TRANSFER TO FINISH -> SHOULD WORK NOW + // we should get 2 completes + _, _, err = waitF(10*time.Second, 2) + require.NoError(t, err) + + // verify all cids are present on the receiver + chs, err := rh.dt2.InProgressChannels(rh.testCtx) + require.NoError(t, err) + require.Len(t, chs, 1) + cids := chs[chid].ReceivedCids() + set := cid.NewSet() + for _, c := range cids { + set.Add(c) + } + require.Equal(t, totalIncrements, set.Len()) + + testutil.VerifyHasFile(rh.testCtx, t, rh.destDagService, rh.root, rh.origBytes) + rh.sv.VerifyExpectations(t) + + // we should ONLY see two opens and two completes + require.EqualValues(t, 2, opens.Load()) + finishedPeersLk.Lock() + require.Len(t, finishedPeers, 2) + require.Contains(t, finishedPeers, rh.peer1) + require.Contains(t, finishedPeers, rh.peer2) + finishedPeersLk.Unlock() + }) + } +} + +func TestRestartPull(t *testing.T) { + tcs := map[string]struct { + stopAt int + openPullF func(rh *restartHarness) datatransfer.ChannelID + restartF func(rh *restartHarness, chId datatransfer.ChannelID, subFnc datatransfer.Subscriber) + }{ + "Restart peer create pull": { + stopAt: 40, + openPullF: func(rh *restartHarness) datatransfer.ChannelID { + voucher := testutil.FakeDTType{Data: "applesauce"} + chid, err := rh.dt2.OpenPullDataChannel(rh.testCtx, rh.peer1, &voucher, rh.rootCid, rh.gsData.AllSelector) + require.NoError(rh.t, err) + return chid + }, + restartF: func(rh *restartHarness, chId datatransfer.ChannelID, subscriber datatransfer.Subscriber) { + var err error + tp2 := rh.gsData.SetupGSTransportHost2() + rh.dt2, err = NewDataTransfer(rh.gsData.DtDs2, rh.gsData.DtNet2, tp2, rh.gsData.StoredCounter2) + require.NoError(rh.t, err) + require.NoError(rh.t, rh.dt2.RegisterVoucherType(&testutil.FakeDTType{}, rh.sv)) + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt2) + rh.dt2.SubscribeToEvents(subscriber) + require.NoError(rh.t, rh.dt2.RestartDataTransferChannel(rh.testCtx, chId)) + }, + }, + "Restart peer receive pull": { + stopAt: 40, + openPullF: func(rh *restartHarness) datatransfer.ChannelID { + voucher := testutil.FakeDTType{Data: "applesauce"} + chid, err := rh.dt2.OpenPullDataChannel(rh.testCtx, rh.peer1, &voucher, rh.rootCid, rh.gsData.AllSelector) + require.NoError(rh.t, err) + return chid + }, + restartF: func(rh *restartHarness, chId datatransfer.ChannelID, subscriber datatransfer.Subscriber) { + var err error + tp1 := rh.gsData.SetupGSTransportHost1() + rh.dt1, err = NewDataTransfer(rh.gsData.DtDs1, rh.gsData.DtNet1, tp1, rh.gsData.StoredCounter1) + require.NoError(rh.t, err) + require.NoError(rh.t, rh.dt1.RegisterVoucherType(&testutil.FakeDTType{}, rh.sv)) + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt1) + rh.dt1.SubscribeToEvents(subscriber) + require.NoError(rh.t, rh.dt1.RestartDataTransferChannel(rh.testCtx, chId)) + }, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + // CREATE HARNESS + rh := newRestartHarness(t) + defer rh.cancel() + + // START DATA TRANSFER INSTANCES + rh.sv.ExpectSuccessPull() + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt1) + testutil.StartAndWaitForReady(rh.testCtx, t, rh.dt2) + + // SETUP DATA TRANSFER SUBSCRIBERS AND SUBSCRIBE + finished := make(chan peer.ID, 2) + errChan := make(chan *peerError, 2) + sent := make(chan uint64, totalIncrements) + received := make(chan uint64, totalIncrements) + receivedTillNow := atomic.NewInt32(0) + + // counters we will check at the end for correctness + opens := atomic.NewInt32(0) + var finishedPeersLk sync.Mutex + var finishedPeers []peer.ID + disConnChan := make(chan struct{}) + + var subscriber datatransfer.Subscriber = func(event datatransfer.Event, channelState datatransfer.ChannelState) { + if event.Code == datatransfer.DataSent { + if channelState.Sent() > 0 { + sent <- channelState.Sent() + } + } + + // disconnect and unlink the peers after we've received the required number of increments + if event.Code == datatransfer.DataReceived { + if channelState.Received() > 0 { + receivedTillNow.Inc() + received <- channelState.Received() + if receivedTillNow.Load() == int32(tc.stopAt) { + require.NoError(t, rh.gsData.Mn.UnlinkPeers(rh.peer1, rh.peer2)) + require.NoError(t, rh.gsData.Mn.DisconnectPeers(rh.peer1, rh.peer2)) + disConnChan <- struct{}{} + } + } + } + + if channelState.Status() == datatransfer.Completed { + finishedPeersLk.Lock() + finishedPeers = append(finishedPeers, channelState.SelfPeer()) + finishedPeersLk.Unlock() + finished <- channelState.SelfPeer() + } + if event.Code == datatransfer.Error { + err := xerrors.New(channelState.Message()) + errChan <- &peerError{channelState.SelfPeer(), err} + } + if event.Code == datatransfer.Open { + opens.Inc() + } + + if event.Code == datatransfer.Restart { + t.Logf("got restart event for peer %s", channelState.SelfPeer().Pretty()) + } + } + rh.dt1.SubscribeToEvents(subscriber) + rh.dt2.SubscribeToEvents(subscriber) + + // OPEN pull + chid := tc.openPullF(rh) + + // wait for disconnection to happen + select { + case <-time.After(10 * time.Second): + t.Fatal("did not hear a diconnection: test timed out") + case <-disConnChan: + t.Logf("peers unlinked and disconnected, total increments received till now: %d", receivedTillNow.Load()) + } + // Define function to wait for data transfer to complete + waitF := func(wait time.Duration, nCompletes int) (sentI, receivedI []uint64, err error) { + completes := 0 + + waitCtx, cancel := context.WithTimeout(rh.testCtx, wait) + defer cancel() + for completes < nCompletes { + select { + case <-waitCtx.Done(): + return sentI, receivedI, xerrors.New("context timed-out without completing data transfer") + case p := <-finished: + t.Logf("peer %s completed", p.Pretty()) + completes++ + case perr := <-errChan: + t.Fatalf("\n received error on peer %s, err: %v", perr.p.Pretty(), perr.err) + case s := <-sent: + sentI = append(sentI, s) + case r := <-received: + receivedI = append(receivedI, r) + } + } + + return sentI, receivedI, nil + } + + // WAIT FOR TRANSFER TO COMPLETE -> THIS SHOULD NOT HAPPEN + sentI, receivedI, err := waitF(10*time.Second, 1) + require.EqualError(t, err, "context timed-out without completing data transfer") + require.True(t, len(receivedI) < totalIncrements) + require.NotEmpty(t, sentI) + + // Connect the peers and restart + require.NoError(t, rh.gsData.Mn.LinkAll()) + // let linking take effect + time.Sleep(500 * time.Millisecond) + // let linking take effect + time.Sleep(500 * time.Millisecond) + conn, err := rh.gsData.Mn.ConnectPeers(rh.peer1, rh.peer2) + require.NoError(t, err) + require.NotNil(t, conn) + tc.restartF(rh, chid, subscriber) + t.Logf("peers have been connected and datatransfer has restarted") + + // WAIT FOR DATA TRANSFER TO FINISH -> SHOULD WORK NOW + // we should get 2 completes + _, _, err = waitF(10*time.Second, 2) + require.NoError(t, err) + + // verify all cids are present on the receiver + chs, err := rh.dt2.InProgressChannels(rh.testCtx) + require.NoError(t, err) + require.Len(t, chs, 1) + cids := chs[chid].ReceivedCids() + set := cid.NewSet() + for _, c := range cids { + set.Add(c) + } + require.Equal(t, totalIncrements, set.Len()) + + testutil.VerifyHasFile(rh.testCtx, t, rh.destDagService, rh.root, rh.origBytes) + rh.sv.VerifyExpectations(t) + + // we should ONLY see two opens and two completes + require.EqualValues(t, 2, opens.Load()) + finishedPeersLk.Lock() + require.Len(t, finishedPeers, 2) + require.Contains(t, finishedPeers, rh.peer1) + require.Contains(t, finishedPeers, rh.peer2) + finishedPeersLk.Unlock() + }) + } +} + +type restartHarness struct { + t *testing.T + testCtx context.Context + cancel context.CancelFunc + + peer1 peer.ID + peer2 peer.ID + + gsData *testutil.GraphsyncTestingData + dt1 datatransfer.Manager + dt2 datatransfer.Manager + sv *testutil.StubbedValidator + + origBytes []byte + root ipld.Link + rootCid cid.Cid + destDagService ipldformat.DAGService +} + +func newRestartHarness(t *testing.T) *restartHarness { + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 60*time.Second) + + // Setup host + gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil) + host1 := gsData.Host1 // initiator, data sender + host2 := gsData.Host2 // data recipient + peer1 := host1.ID() + peer2 := host2.ID() + t.Logf("peer1 is %s", peer1.Pretty()) + t.Logf("peer2 is %s", peer2.Pretty()) + + // Setup data transfer + tp1 := gsData.SetupGSTransportHost1() + tp2 := gsData.SetupGSTransportHost2() + + dt1, err := NewDataTransfer(gsData.DtDs1, gsData.DtNet1, tp1, gsData.StoredCounter1) + require.NoError(t, err) + + dt2, err := NewDataTransfer(gsData.DtDs2, gsData.DtNet2, tp2, gsData.StoredCounter2) + require.NoError(t, err) + + sv := testutil.NewStubbedValidator() + require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv)) + require.NoError(t, dt2.RegisterVoucherType(&testutil.FakeDTType{}, sv)) + + sourceDagService := gsData.DagService1 + root, origBytes := testutil.LoadUnixFSFile(ctx, t, sourceDagService, largeFile) + rootCid := root.(cidlink.Link).Cid + destDagService := gsData.DagService2 + + return &restartHarness{ + t: t, + testCtx: ctx, + cancel: cancel, + + peer1: peer1, + peer2: peer2, + + gsData: gsData, + dt1: dt1, + dt2: dt2, + sv: sv, + + origBytes: origBytes, + root: root, + rootCid: rootCid, + destDagService: destDagService, + } +} diff --git a/impl/utils.go b/impl/utils.go index f4744e1e..bf082f9f 100644 --- a/impl/utils.go +++ b/impl/utils.go @@ -37,16 +37,20 @@ func (m *manager) newRequest(ctx context.Context, selector ipld.Node, isPull boo return nil, err } tid := datatransfer.TransferID(next) - return message.NewRequest(tid, isPull, voucher.Type(), voucher, baseCid, selector) + return message.NewRequest(tid, false, isPull, voucher.Type(), voucher, baseCid, selector) } -func (m *manager) response(isNew bool, err error, tid datatransfer.TransferID, voucherResult datatransfer.VoucherResult) (datatransfer.Response, error) { +func (m *manager) response(isRestart bool, isNew bool, err error, tid datatransfer.TransferID, voucherResult datatransfer.VoucherResult) (datatransfer.Response, error) { isAccepted := err == nil || err == datatransfer.ErrPause isPaused := err == datatransfer.ErrPause resultType := datatransfer.EmptyTypeIdentifier if voucherResult != nil { resultType = voucherResult.Type() } + if isRestart { + return message.RestartResponse(tid, isAccepted, isPaused, resultType, voucherResult) + } + if isNew { return message.NewResponse(tid, isAccepted, isPaused, resultType, voucherResult) } diff --git a/manager.go b/manager.go index 2f00bae7..820542f3 100644 --- a/manager.go +++ b/manager.go @@ -60,6 +60,9 @@ type Revalidator interface { // TransportConfigurer provides a mechanism to provide transport specific configuration for a given voucher type type TransportConfigurer func(chid ChannelID, voucher Voucher, transport Transport) +// ReadyFunc is function that gets called once when the data transfer module is ready +type ReadyFunc func(error) + // Manager is the core interface presented by all implementations of // of the data transfer sub system type Manager interface { @@ -67,6 +70,9 @@ type Manager interface { // Start initializes data transfer processing Start(ctx context.Context) error + // OnReady registers a listener for when the data transfer comes on line + OnReady(ReadyFunc) + // Stop terminates all data transfers and ends processing Stop(ctx context.Context) error @@ -118,4 +124,7 @@ type Manager interface { // get all in progress transfers InProgressChannels(ctx context.Context) (map[ChannelID]ChannelState, error) + + // RestartDataTransferChannel restarts an existing data transfer channel + RestartDataTransferChannel(ctx context.Context, chid ChannelID) error } diff --git a/message.go b/message.go index d4401f65..f0339683 100644 --- a/message.go +++ b/message.go @@ -5,15 +5,26 @@ import ( "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" + "github.com/libp2p/go-libp2p-core/protocol" cborgen "github.com/whyrusleeping/cbor-gen" "github.com/filecoin-project/go-data-transfer/encoding" ) +var ( + // ProtocolDataTransfer1_1 is the protocol identifier for graphsync messages + ProtocolDataTransfer1_1 protocol.ID = "/fil/datatransfer/1.1.0" + + // ProtocolDataTransfer1_0 is the protocol identifier for legacy graphsync messages + // This protocol does NOT support the `Restart` functionality for data transfer channels. + ProtocolDataTransfer1_0 protocol.ID = "/fil/datatransfer/1.0.0" +) + // Message is a message for the data transfer protocol // (either request or response) that can serialize to a protobuf type Message interface { IsRequest() bool + IsRestart() bool IsNew() bool IsUpdate() bool IsPaused() bool @@ -22,6 +33,7 @@ type Message interface { cborgen.CBORMarshaler cborgen.CBORUnmarshaler ToNet(w io.Writer) error + MessageForProtocol(targetProtocol protocol.ID) (newMsg Message, err error) } // Request is a response message for the data transfer protocol @@ -33,6 +45,8 @@ type Request interface { Voucher(decoder encoding.Decoder) (encoding.Encodable, error) BaseCid() cid.Cid Selector() (ipld.Node, error) + IsRestartExistingChannelRequest() bool + RestartChannelId() (ChannelID, error) } // Response is a response message for the data transfer protocol diff --git a/message/message.go b/message/message.go index ee514f29..4fa34793 100644 --- a/message/message.go +++ b/message/message.go @@ -1,162 +1,18 @@ package message import ( - "io" - - "github.com/ipfs/go-cid" - "github.com/ipld/go-ipld-prime" - cborgen "github.com/whyrusleeping/cbor-gen" - xerrors "golang.org/x/xerrors" - - datatransfer "github.com/filecoin-project/go-data-transfer" - "github.com/filecoin-project/go-data-transfer/encoding" -) - -type messageType uint64 - -const ( - newMessage messageType = iota - updateMessage - cancelMessage - completeMessage - voucherMessage - voucherResultMessage + "github.com/filecoin-project/go-data-transfer/message/message1_1" ) -// NewRequest generates a new request for the data transfer protocol -func NewRequest(id datatransfer.TransferID, isPull bool, vtype datatransfer.TypeIdentifier, voucher encoding.Encodable, baseCid cid.Cid, selector ipld.Node) (datatransfer.Request, error) { - vbytes, err := encoding.Encode(voucher) - if err != nil { - return nil, xerrors.Errorf("Creating request: %w", err) - } - if baseCid == cid.Undef { - return nil, xerrors.Errorf("base CID must be defined") - } - selBytes, err := encoding.Encode(selector) - if err != nil { - return nil, xerrors.Errorf("Error encoding selector") - } - return &transferRequest{ - Type: uint64(newMessage), - Pull: isPull, - Vouch: &cborgen.Deferred{Raw: vbytes}, - Stor: &cborgen.Deferred{Raw: selBytes}, - BCid: &baseCid, - VTyp: vtype, - XferID: uint64(id), - }, nil -} - -// CancelRequest request generates a request to cancel an in progress request -func CancelRequest(id datatransfer.TransferID) datatransfer.Request { - return &transferRequest{ - Type: uint64(cancelMessage), - XferID: uint64(id), - } -} - -// UpdateRequest generates a new request update -func UpdateRequest(id datatransfer.TransferID, isPaused bool) datatransfer.Request { - return &transferRequest{ - Type: uint64(updateMessage), - Paus: isPaused, - XferID: uint64(id), - } -} - -// VoucherRequest generates a new request for the data transfer protocol -func VoucherRequest(id datatransfer.TransferID, vtype datatransfer.TypeIdentifier, voucher encoding.Encodable) (datatransfer.Request, error) { - vbytes, err := encoding.Encode(voucher) - if err != nil { - return nil, xerrors.Errorf("Creating request: %w", err) - } - return &transferRequest{ - Type: uint64(voucherMessage), - Vouch: &cborgen.Deferred{Raw: vbytes}, - VTyp: vtype, - XferID: uint64(id), - }, nil -} - -// NewResponse builds a new Data Transfer response -func NewResponse(id datatransfer.TransferID, accepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { - vbytes, err := encoding.Encode(voucherResult) - if err != nil { - return nil, xerrors.Errorf("Creating request: %w", err) - } - return &transferResponse{ - Acpt: accepted, - Type: uint64(newMessage), - Paus: isPaused, - XferID: uint64(id), - VTyp: voucherResultType, - VRes: &cborgen.Deferred{Raw: vbytes}, - }, nil -} - -// VoucherResultResponse builds a new response for a voucher result -func VoucherResultResponse(id datatransfer.TransferID, accepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { - vbytes, err := encoding.Encode(voucherResult) - if err != nil { - return nil, xerrors.Errorf("Creating request: %w", err) - } - return &transferResponse{ - Acpt: accepted, - Type: uint64(voucherResultMessage), - Paus: isPaused, - XferID: uint64(id), - VTyp: voucherResultType, - VRes: &cborgen.Deferred{Raw: vbytes}, - }, nil -} - -// UpdateResponse returns a new update response -func UpdateResponse(id datatransfer.TransferID, isPaused bool) datatransfer.Response { - return &transferResponse{ - Type: uint64(updateMessage), - Paus: isPaused, - XferID: uint64(id), - } -} - -// CancelResponse makes a new cancel response message -func CancelResponse(id datatransfer.TransferID) datatransfer.Response { - return &transferResponse{ - Type: uint64(cancelMessage), - XferID: uint64(id), - } -} - -// CompleteResponse returns a new complete response message -func CompleteResponse(id datatransfer.TransferID, isAccepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { - vbytes, err := encoding.Encode(voucherResult) - if err != nil { - return nil, xerrors.Errorf("Creating request: %w", err) - } - return &transferResponse{ - Type: uint64(completeMessage), - Acpt: isAccepted, - Paus: isPaused, - VTyp: voucherResultType, - VRes: &cborgen.Deferred{Raw: vbytes}, - XferID: uint64(id), - }, nil -} - -// FromNet can read a network stream to deserialize a GraphSyncMessage -func FromNet(r io.Reader) (datatransfer.Message, error) { - tresp := transferMessage{} - err := tresp.UnmarshalCBOR(r) - if err != nil { - return nil, err - } - - if (tresp.IsRequest() && tresp.Request == nil) || (!tresp.IsRequest() && tresp.Response == nil) { - return nil, xerrors.Errorf("invalid/malformed message") - } - - if tresp.IsRequest() { - return tresp.Request, nil - } - return tresp.Response, nil -} +var NewRequest = message1_1.NewRequest +var RestartExistingChannelRequest = message1_1.RestartExistingChannelRequest +var UpdateRequest = message1_1.UpdateRequest +var VoucherRequest = message1_1.VoucherRequest +var RestartResponse = message1_1.RestartResponse +var NewResponse = message1_1.NewResponse +var VoucherResultResponse = message1_1.VoucherResultResponse +var CancelResponse = message1_1.CancelResponse +var UpdateResponse = message1_1.UpdateResponse +var FromNet = message1_1.FromNet +var CompleteResponse = message1_1.CompleteResponse +var CancelRequest = message1_1.CancelRequest diff --git a/message/message1_0/message.go b/message/message1_0/message.go new file mode 100644 index 00000000..23856886 --- /dev/null +++ b/message/message1_0/message.go @@ -0,0 +1,57 @@ +package message1_0 + +import ( + "io" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" + + datatransfer "github.com/filecoin-project/go-data-transfer" +) + +// NewTransferRequest creates a transfer request for the 1_0 Data Transfer Protocol. +func NewTransferRequest(bcid *cid.Cid, typ uint64, paus, part, pull bool, stor, vouch *cbg.Deferred, + vtyp datatransfer.TypeIdentifier, xferId uint64) datatransfer.Request { + return &transferRequest{ + BCid: bcid, + Type: typ, + Paus: paus, + Part: part, + Pull: pull, + Stor: stor, + Vouch: vouch, + VTyp: vtyp, + XferID: xferId, + } +} + +// NewTransferRequest creates a transfer response for the 1_0 Data Transfer Protocol. +func NewTransferResponse(typ uint64, acpt bool, paus bool, xferId uint64, vRes *cbg.Deferred, vtyp datatransfer.TypeIdentifier) datatransfer.Response { + return &transferResponse{ + Type: typ, + Acpt: acpt, + Paus: paus, + XferID: xferId, + VRes: vRes, + VTyp: vtyp, + } +} + +// FromNet can read a network stream to deserialize a GraphSyncMessage +func FromNet(r io.Reader) (datatransfer.Message, error) { + tresp := transferMessage{} + err := tresp.UnmarshalCBOR(r) + if err != nil { + return nil, err + } + + if (tresp.IsRequest() && tresp.Request == nil) || (!tresp.IsRequest() && tresp.Response == nil) { + return nil, xerrors.Errorf("invalid/malformed message") + } + + if tresp.IsRequest() { + return tresp.Request, nil + } + return tresp.Response, nil +} diff --git a/message/transfer_message.go b/message/message1_0/transfer_message.go similarity index 97% rename from message/transfer_message.go rename to message/message1_0/transfer_message.go index 303b29a3..4da04b1d 100644 --- a/message/transfer_message.go +++ b/message/message1_0/transfer_message.go @@ -1,4 +1,4 @@ -package message +package message1_0 import ( "io" diff --git a/message/transfer_message_cbor_gen.go b/message/message1_0/transfer_message_cbor_gen.go similarity index 90% rename from message/transfer_message_cbor_gen.go rename to message/message1_0/transfer_message_cbor_gen.go index f6211da0..ab1f2695 100644 --- a/message/transfer_message_cbor_gen.go +++ b/message/message1_0/transfer_message_cbor_gen.go @@ -1,6 +1,6 @@ // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package message +package message1_0 import ( "fmt" @@ -28,12 +28,12 @@ func (t *transferMessage) MarshalCBOR(w io.Writer) error { return err } - // t.Request (message.transferRequest) (struct) + // t.Request (message1_0.transferRequest) (struct) if err := t.Request.MarshalCBOR(w); err != nil { return err } - // t.Response (message.transferResponse) (struct) + // t.Response (message1_0.transferResponse) (struct) if err := t.Response.MarshalCBOR(w); err != nil { return err } @@ -75,7 +75,7 @@ func (t *transferMessage) UnmarshalCBOR(r io.Reader) error { default: return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) } - // t.Request (message.transferRequest) (struct) + // t.Request (message1_0.transferRequest) (struct) { @@ -94,7 +94,7 @@ func (t *transferMessage) UnmarshalCBOR(r io.Reader) error { } } - // t.Response (message.transferResponse) (struct) + // t.Response (message1_0.transferResponse) (struct) { diff --git a/message/transfer_request.go b/message/message1_0/transfer_request.go similarity index 76% rename from message/transfer_request.go rename to message/message1_0/transfer_request.go index 2185a218..9c426861 100644 --- a/message/transfer_request.go +++ b/message/message1_0/transfer_request.go @@ -1,4 +1,4 @@ -package message +package message1_0 import ( "bytes" @@ -8,11 +8,13 @@ import ( "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/codec/dagcbor" basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/libp2p/go-libp2p-core/protocol" cbg "github.com/whyrusleeping/cbor-gen" xerrors "golang.org/x/xerrors" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-data-transfer/encoding" + "github.com/filecoin-project/go-data-transfer/message/types" ) //go:generate cbor-gen-for transferRequest @@ -36,16 +38,25 @@ func (trq *transferRequest) IsRequest() bool { return true } +func (trq *transferRequest) MessageForProtocol(targetProtocol protocol.ID) (datatransfer.Message, error) { + switch targetProtocol { + case datatransfer.ProtocolDataTransfer1_0: + return trq, nil + default: + return nil, xerrors.Errorf("protocol not supported") + } +} + func (trq *transferRequest) IsNew() bool { - return trq.Type == uint64(newMessage) + return trq.Type == uint64(types.NewMessage) } func (trq *transferRequest) IsUpdate() bool { - return trq.Type == uint64(updateMessage) + return trq.Type == uint64(types.UpdateMessage) } func (trq *transferRequest) IsVoucher() bool { - return trq.Type == uint64(voucherMessage) || trq.Type == uint64(newMessage) + return trq.Type == uint64(types.VoucherMessage) || trq.Type == uint64(types.NewMessage) } func (trq *transferRequest) IsPaused() bool { @@ -103,7 +114,7 @@ func (trq *transferRequest) Selector() (ipld.Node, error) { // IsCancel returns true if this is a cancel request func (trq *transferRequest) IsCancel() bool { - return trq.Type == uint64(cancelMessage) + return trq.Type == uint64(types.CancelMessage) } // IsPartial returns true if this is a partial request @@ -121,3 +132,15 @@ func (trq *transferRequest) ToNet(w io.Writer) error { } return msg.MarshalCBOR(w) } + +func (trq *transferRequest) IsRestart() bool { + return false +} + +func (trq *transferRequest) IsRestartExistingChannelRequest() bool { + return false +} + +func (trq *transferRequest) RestartChannelId() (datatransfer.ChannelID, error) { + return datatransfer.ChannelID{}, xerrors.New("not supported") +} diff --git a/message/transfer_request_cbor_gen.go b/message/message1_0/transfer_request_cbor_gen.go similarity index 99% rename from message/transfer_request_cbor_gen.go rename to message/message1_0/transfer_request_cbor_gen.go index de606c55..61c15dee 100644 --- a/message/transfer_request_cbor_gen.go +++ b/message/message1_0/transfer_request_cbor_gen.go @@ -1,6 +1,6 @@ // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package message +package message1_0 import ( "fmt" diff --git a/message/transfer_response.go b/message/message1_0/transfer_response.go similarity index 73% rename from message/transfer_response.go rename to message/message1_0/transfer_response.go index 3ff0f6b8..7de7a381 100644 --- a/message/transfer_response.go +++ b/message/message1_0/transfer_response.go @@ -1,13 +1,15 @@ -package message +package message1_0 import ( "io" + "github.com/libp2p/go-libp2p-core/protocol" cbg "github.com/whyrusleeping/cbor-gen" xerrors "golang.org/x/xerrors" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-data-transfer/encoding" + "github.com/filecoin-project/go-data-transfer/message/types" ) //go:generate cbor-gen-for transferResponse @@ -26,6 +28,19 @@ func (trsp *transferResponse) TransferID() datatransfer.TransferID { return datatransfer.TransferID(trsp.XferID) } +func (trsp *transferResponse) IsRestart() bool { + return false +} + +func (trsp *transferResponse) MessageForProtocol(targetProtocol protocol.ID) (datatransfer.Message, error) { + switch targetProtocol { + case datatransfer.ProtocolDataTransfer1_0: + return trsp, nil + default: + return nil, xerrors.Errorf("protocol not supported") + } +} + // IsRequest always returns false in this case because this is a transfer response func (trsp *transferResponse) IsRequest() bool { return false @@ -33,12 +48,12 @@ func (trsp *transferResponse) IsRequest() bool { // IsNew returns true if this is the first response sent func (trsp *transferResponse) IsNew() bool { - return trsp.Type == uint64(newMessage) + return trsp.Type == uint64(types.NewMessage) } // IsUpdate returns true if this response is an update func (trsp *transferResponse) IsUpdate() bool { - return trsp.Type == uint64(updateMessage) + return trsp.Type == uint64(types.UpdateMessage) } // IsPaused returns true if the responder is paused @@ -48,16 +63,17 @@ func (trsp *transferResponse) IsPaused() bool { // IsCancel returns true if the responder has cancelled this response func (trsp *transferResponse) IsCancel() bool { - return trsp.Type == uint64(cancelMessage) + return trsp.Type == uint64(types.CancelMessage) } // IsComplete returns true if the responder has completed this response func (trsp *transferResponse) IsComplete() bool { - return trsp.Type == uint64(completeMessage) + return trsp.Type == uint64(types.CompleteMessage) } func (trsp *transferResponse) IsVoucherResult() bool { - return trsp.Type == uint64(voucherResultMessage) || trsp.Type == uint64(newMessage) || trsp.Type == uint64(completeMessage) + return trsp.Type == uint64(types.VoucherResultMessage) || trsp.Type == uint64(types.NewMessage) || + trsp.Type == uint64(types.CompleteMessage) } // Accepted returns true if the request is accepted in the response diff --git a/message/transfer_response_cbor_gen.go b/message/message1_0/transfer_response_cbor_gen.go similarity index 99% rename from message/transfer_response_cbor_gen.go rename to message/message1_0/transfer_response_cbor_gen.go index ab86a0c9..f07de23c 100644 --- a/message/transfer_response_cbor_gen.go +++ b/message/message1_0/transfer_response_cbor_gen.go @@ -1,6 +1,6 @@ // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package message +package message1_0 import ( "fmt" diff --git a/message/message1_1/message.go b/message/message1_1/message.go new file mode 100644 index 00000000..cf60dc4a --- /dev/null +++ b/message/message1_1/message.go @@ -0,0 +1,183 @@ +package message1_1 + +import ( + "io" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-ipld-prime" + cborgen "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/encoding" + "github.com/filecoin-project/go-data-transfer/message/types" +) + +// NewRequest generates a new request for the data transfer protocol +func NewRequest(id datatransfer.TransferID, isRestart bool, isPull bool, vtype datatransfer.TypeIdentifier, voucher encoding.Encodable, baseCid cid.Cid, selector ipld.Node) (datatransfer.Request, error) { + vbytes, err := encoding.Encode(voucher) + if err != nil { + return nil, xerrors.Errorf("Creating request: %w", err) + } + if baseCid == cid.Undef { + return nil, xerrors.Errorf("base CID must be defined") + } + selBytes, err := encoding.Encode(selector) + if err != nil { + return nil, xerrors.Errorf("Error encoding selector") + } + + var typ uint64 + if isRestart { + typ = uint64(types.RestartMessage) + } else { + typ = uint64(types.NewMessage) + } + + return &transferRequest1_1{ + Type: typ, + Pull: isPull, + Vouch: &cborgen.Deferred{Raw: vbytes}, + Stor: &cborgen.Deferred{Raw: selBytes}, + BCid: &baseCid, + VTyp: vtype, + XferID: uint64(id), + }, nil +} + +// RestartExistingChannelRequest creates a request to ask the other side to restart an existing channel +func RestartExistingChannelRequest(channelId datatransfer.ChannelID) datatransfer.Request { + + return &transferRequest1_1{Type: uint64(types.RestartExistingChannelRequestMessage), + RestartChannel: channelId} +} + +// CancelRequest request generates a request to cancel an in progress request +func CancelRequest(id datatransfer.TransferID) datatransfer.Request { + return &transferRequest1_1{ + Type: uint64(types.CancelMessage), + XferID: uint64(id), + } +} + +// UpdateRequest generates a new request update +func UpdateRequest(id datatransfer.TransferID, isPaused bool) datatransfer.Request { + return &transferRequest1_1{ + Type: uint64(types.UpdateMessage), + Paus: isPaused, + XferID: uint64(id), + } +} + +// VoucherRequest generates a new request for the data transfer protocol +func VoucherRequest(id datatransfer.TransferID, vtype datatransfer.TypeIdentifier, voucher encoding.Encodable) (datatransfer.Request, error) { + vbytes, err := encoding.Encode(voucher) + if err != nil { + return nil, xerrors.Errorf("Creating request: %w", err) + } + return &transferRequest1_1{ + Type: uint64(types.VoucherMessage), + Vouch: &cborgen.Deferred{Raw: vbytes}, + VTyp: vtype, + XferID: uint64(id), + }, nil +} + +// RestartResponse builds a new Data Transfer response +func RestartResponse(id datatransfer.TransferID, accepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { + vbytes, err := encoding.Encode(voucherResult) + if err != nil { + return nil, xerrors.Errorf("Creating request: %w", err) + } + return &transferResponse1_1{ + Acpt: accepted, + Type: uint64(types.RestartMessage), + Paus: isPaused, + XferID: uint64(id), + VTyp: voucherResultType, + VRes: &cborgen.Deferred{Raw: vbytes}, + }, nil +} + +// NewResponse builds a new Data Transfer response +func NewResponse(id datatransfer.TransferID, accepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { + vbytes, err := encoding.Encode(voucherResult) + if err != nil { + return nil, xerrors.Errorf("Creating request: %w", err) + } + return &transferResponse1_1{ + Acpt: accepted, + Type: uint64(types.NewMessage), + Paus: isPaused, + XferID: uint64(id), + VTyp: voucherResultType, + VRes: &cborgen.Deferred{Raw: vbytes}, + }, nil +} + +// VoucherResultResponse builds a new response for a voucher result +func VoucherResultResponse(id datatransfer.TransferID, accepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { + vbytes, err := encoding.Encode(voucherResult) + if err != nil { + return nil, xerrors.Errorf("Creating request: %w", err) + } + return &transferResponse1_1{ + Acpt: accepted, + Type: uint64(types.VoucherResultMessage), + Paus: isPaused, + XferID: uint64(id), + VTyp: voucherResultType, + VRes: &cborgen.Deferred{Raw: vbytes}, + }, nil +} + +// UpdateResponse returns a new update response +func UpdateResponse(id datatransfer.TransferID, isPaused bool) datatransfer.Response { + return &transferResponse1_1{ + Type: uint64(types.UpdateMessage), + Paus: isPaused, + XferID: uint64(id), + } +} + +// CancelResponse makes a new cancel response message +func CancelResponse(id datatransfer.TransferID) datatransfer.Response { + return &transferResponse1_1{ + Type: uint64(types.CancelMessage), + XferID: uint64(id), + } +} + +// CompleteResponse returns a new complete response message +func CompleteResponse(id datatransfer.TransferID, isAccepted bool, isPaused bool, voucherResultType datatransfer.TypeIdentifier, voucherResult encoding.Encodable) (datatransfer.Response, error) { + vbytes, err := encoding.Encode(voucherResult) + if err != nil { + return nil, xerrors.Errorf("Creating request: %w", err) + } + return &transferResponse1_1{ + Type: uint64(types.CompleteMessage), + Acpt: isAccepted, + Paus: isPaused, + VTyp: voucherResultType, + VRes: &cborgen.Deferred{Raw: vbytes}, + XferID: uint64(id), + }, nil +} + +// FromNet can read a network stream to deserialize a GraphSyncMessage +func FromNet(r io.Reader) (datatransfer.Message, error) { + tresp := transferMessage1_1{} + err := tresp.UnmarshalCBOR(r) + if err != nil { + return nil, err + } + + if (tresp.IsRequest() && tresp.Request == nil) || (!tresp.IsRequest() && tresp.Response == nil) { + return nil, xerrors.Errorf("invalid/malformed message") + } + + if tresp.IsRequest() { + return tresp.Request, nil + } + return tresp.Response, nil +} diff --git a/message/message_test.go b/message/message1_1/message_test.go similarity index 75% rename from message/message_test.go rename to message/message1_1/message_test.go index b134cd5e..378542bf 100644 --- a/message/message_test.go +++ b/message/message1_1/message_test.go @@ -1,4 +1,4 @@ -package message_test +package message1_1_test import ( "bytes" @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" datatransfer "github.com/filecoin-project/go-data-transfer" - . "github.com/filecoin-project/go-data-transfer/message" + "github.com/filecoin-project/go-data-transfer/message/message1_1" "github.com/filecoin-project/go-data-transfer/testutil" ) @@ -21,7 +21,7 @@ func TestNewRequest(t *testing.T) { isPull := true id := datatransfer.TransferID(rand.Int31()) voucher := testutil.NewFakeDTType() - request, err := NewRequest(id, isPull, voucher.Type(), voucher, baseCid, selector) + request, err := message1_1.NewRequest(id, false, isPull, voucher.Type(), voucher, baseCid, selector) require.NoError(t, err) assert.Equal(t, id, request.TransferID()) assert.False(t, request.IsCancel()) @@ -39,7 +39,58 @@ func TestNewRequest(t *testing.T) { assert.True(t, msg.IsRequest()) assert.Equal(t, request.TransferID(), msg.TransferID()) + assert.False(t, msg.IsRestart()) + assert.True(t, msg.IsNew()) +} + +func TestRestartRequest(t *testing.T) { + baseCid := testutil.GenerateCids(1)[0] + selector := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any).Matcher().Node() + isPull := true + id := datatransfer.TransferID(rand.Int31()) + voucher := testutil.NewFakeDTType() + request, err := message1_1.NewRequest(id, true, isPull, voucher.Type(), voucher, baseCid, selector) + require.NoError(t, err) + assert.Equal(t, id, request.TransferID()) + assert.False(t, request.IsCancel()) + assert.False(t, request.IsUpdate()) + assert.True(t, request.IsPull()) + assert.True(t, request.IsRequest()) + assert.Equal(t, baseCid.String(), request.BaseCid().String()) + testutil.AssertFakeDTVoucher(t, request, voucher) + receivedSelector, err := request.Selector() + require.NoError(t, err) + require.Equal(t, selector, receivedSelector) + // Sanity check to make sure we can cast to datatransfer.Message + msg, ok := request.(datatransfer.Message) + require.True(t, ok) + + assert.True(t, msg.IsRequest()) + assert.Equal(t, request.TransferID(), msg.TransferID()) + assert.True(t, msg.IsRestart()) + assert.False(t, msg.IsNew()) +} + +func TestRestartExistingChannelRequest(t *testing.T) { + peers := testutil.GeneratePeers(2) + tid := uint64(1) + chid := datatransfer.ChannelID{Initiator: peers[0], + Responder: peers[1], ID: datatransfer.TransferID(tid)} + req := message1_1.RestartExistingChannelRequest(chid) + + wbuf := new(bytes.Buffer) + require.NoError(t, req.ToNet(wbuf)) + + desMsg, err := message1_1.FromNet(wbuf) + require.NoError(t, err) + req, ok := (desMsg).(datatransfer.Request) + require.True(t, ok) + require.True(t, req.IsRestartExistingChannelRequest()) + achid, err := req.RestartChannelId() + require.NoError(t, err) + require.Equal(t, chid, achid) } + func TestTransferRequest_MarshalCBOR(t *testing.T) { // sanity check MarshalCBOR does its thing w/o error req, err := NewTestTransferRequest() @@ -55,7 +106,7 @@ func TestTransferRequest_UnmarshalCBOR(t *testing.T) { // use ToNet / FromNet require.NoError(t, req.ToNet(wbuf)) - desMsg, err := FromNet(wbuf) + desMsg, err := message1_1.FromNet(wbuf) require.NoError(t, err) // Verify round-trip @@ -73,7 +124,7 @@ func TestTransferRequest_UnmarshalCBOR(t *testing.T) { func TestResponses(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) voucherResult := testutil.NewFakeDTType() - response, err := NewResponse(id, false, true, voucherResult.Type(), voucherResult) // not accepted + response, err := message1_1.NewResponse(id, false, true, voucherResult.Type(), voucherResult) // not accepted require.NoError(t, err) assert.Equal(t, response.TransferID(), id) assert.False(t, response.Accepted()) @@ -96,7 +147,7 @@ func TestResponses(t *testing.T) { func TestTransferResponse_MarshalCBOR(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) voucherResult := testutil.NewFakeDTType() - response, err := NewResponse(id, true, false, voucherResult.Type(), voucherResult) // accepted + response, err := message1_1.NewResponse(id, true, false, voucherResult.Type(), voucherResult) // accepted require.NoError(t, err) // sanity check that we can marshal data @@ -108,14 +159,14 @@ func TestTransferResponse_MarshalCBOR(t *testing.T) { func TestTransferResponse_UnmarshalCBOR(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) voucherResult := testutil.NewFakeDTType() - response, err := NewResponse(id, true, false, voucherResult.Type(), voucherResult) // accepted + response, err := message1_1.NewResponse(id, true, false, voucherResult.Type(), voucherResult) // accepted require.NoError(t, err) wbuf := new(bytes.Buffer) require.NoError(t, response.ToNet(wbuf)) // verify round trip - desMsg, err := FromNet(wbuf) + desMsg, err := message1_1.FromNet(wbuf) require.NoError(t, err) assert.False(t, desMsg.IsRequest()) assert.True(t, desMsg.IsNew()) @@ -134,7 +185,7 @@ func TestTransferResponse_UnmarshalCBOR(t *testing.T) { func TestRequestCancel(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) - req := CancelRequest(id) + req := message1_1.CancelRequest(id) require.Equal(t, req.TransferID(), id) require.True(t, req.IsRequest()) require.True(t, req.IsCancel()) @@ -143,7 +194,7 @@ func TestRequestCancel(t *testing.T) { wbuf := new(bytes.Buffer) require.NoError(t, req.ToNet(wbuf)) - deserialized, err := FromNet(wbuf) + deserialized, err := message1_1.FromNet(wbuf) require.NoError(t, err) deserializedRequest, ok := deserialized.(datatransfer.Request) @@ -156,7 +207,7 @@ func TestRequestCancel(t *testing.T) { func TestRequestUpdate(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) - req := UpdateRequest(id, true) + req := message1_1.UpdateRequest(id, true) require.Equal(t, req.TransferID(), id) require.True(t, req.IsRequest()) require.False(t, req.IsCancel()) @@ -166,7 +217,7 @@ func TestRequestUpdate(t *testing.T) { wbuf := new(bytes.Buffer) require.NoError(t, req.ToNet(wbuf)) - deserialized, err := FromNet(wbuf) + deserialized, err := message1_1.FromNet(wbuf) require.NoError(t, err) deserializedRequest, ok := deserialized.(datatransfer.Request) @@ -180,7 +231,7 @@ func TestRequestUpdate(t *testing.T) { func TestUpdateResponse(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) - response := UpdateResponse(id, true) // not accepted + response := message1_1.UpdateResponse(id, true) // not accepted assert.Equal(t, response.TransferID(), id) assert.False(t, response.Accepted()) assert.False(t, response.IsNew()) @@ -201,7 +252,7 @@ func TestUpdateResponse(t *testing.T) { func TestCancelResponse(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) - response := CancelResponse(id) + response := message1_1.CancelResponse(id) assert.Equal(t, response.TransferID(), id) assert.False(t, response.IsNew()) assert.False(t, response.IsUpdate()) @@ -220,7 +271,7 @@ func TestCancelResponse(t *testing.T) { func TestCompleteResponse(t *testing.T) { id := datatransfer.TransferID(rand.Int31()) - response, err := CompleteResponse(id, true, true, datatransfer.EmptyTypeIdentifier, nil) + response, err := message1_1.CompleteResponse(id, true, true, datatransfer.EmptyTypeIdentifier, nil) require.NoError(t, err) assert.Equal(t, response.TransferID(), id) assert.False(t, response.IsNew()) @@ -247,13 +298,13 @@ func TestToNetFromNetEquivalency(t *testing.T) { accepted := false voucher := testutil.NewFakeDTType() voucherResult := testutil.NewFakeDTType() - request, err := NewRequest(id, isPull, voucher.Type(), voucher, baseCid, selector) + request, err := message1_1.NewRequest(id, false, isPull, voucher.Type(), voucher, baseCid, selector) require.NoError(t, err) buf := new(bytes.Buffer) err = request.ToNet(buf) require.NoError(t, err) require.Greater(t, buf.Len(), 0) - deserialized, err := FromNet(buf) + deserialized, err := message1_1.FromNet(buf) require.NoError(t, err) deserializedRequest, ok := deserialized.(datatransfer.Request) @@ -267,11 +318,11 @@ func TestToNetFromNetEquivalency(t *testing.T) { testutil.AssertEqualFakeDTVoucher(t, request, deserializedRequest) testutil.AssertEqualSelector(t, request, deserializedRequest) - response, err := NewResponse(id, accepted, false, voucherResult.Type(), voucherResult) + response, err := message1_1.NewResponse(id, accepted, false, voucherResult.Type(), voucherResult) require.NoError(t, err) err = response.ToNet(buf) require.NoError(t, err) - deserialized, err = FromNet(buf) + deserialized, err = message1_1.FromNet(buf) require.NoError(t, err) deserializedResponse, ok := deserialized.(datatransfer.Response) @@ -284,10 +335,10 @@ func TestToNetFromNetEquivalency(t *testing.T) { require.Equal(t, deserializedResponse.IsPaused(), response.IsPaused()) testutil.AssertEqualFakeDTVoucherResult(t, response, deserializedResponse) - request = CancelRequest(id) + request = message1_1.CancelRequest(id) err = request.ToNet(buf) require.NoError(t, err) - deserialized, err = FromNet(buf) + deserialized, err = message1_1.FromNet(buf) require.NoError(t, err) deserializedRequest, ok = deserialized.(datatransfer.Request) @@ -301,13 +352,13 @@ func TestToNetFromNetEquivalency(t *testing.T) { func TestFromNetMessageValidation(t *testing.T) { // craft request message with nil request struct buf := []byte{0x83, 0xf5, 0xf6, 0xf6} - msg, err := FromNet(bytes.NewBuffer(buf)) + msg, err := message1_1.FromNet(bytes.NewBuffer(buf)) assert.Error(t, err) assert.Nil(t, msg) // craft response message with nil response struct buf = []byte{0x83, 0xf4, 0xf6, 0xf6} - msg, err = FromNet(bytes.NewBuffer(buf)) + msg, err = message1_1.FromNet(bytes.NewBuffer(buf)) assert.Error(t, err) assert.Nil(t, msg) } @@ -318,5 +369,5 @@ func NewTestTransferRequest() (datatransfer.Request, error) { isPull := false id := datatransfer.TransferID(rand.Int31()) voucher := testutil.NewFakeDTType() - return NewRequest(id, isPull, voucher.Type(), voucher, bcid, selector) + return message1_1.NewRequest(id, false, isPull, voucher.Type(), voucher, bcid, selector) } diff --git a/message/message1_1/transfer_message.go b/message/message1_1/transfer_message.go new file mode 100644 index 00000000..ac55e1ae --- /dev/null +++ b/message/message1_1/transfer_message.go @@ -0,0 +1,38 @@ +package message1_1 + +import ( + "io" + + datatransfer "github.com/filecoin-project/go-data-transfer" +) + +//go:generate cbor-gen-for --map-encoding transferMessage1_1 + +// transferMessage1_1 is the transfer message for the 1.1 Data Transfer Protocol. +type transferMessage1_1 struct { + IsRq bool + + Request *transferRequest1_1 + Response *transferResponse1_1 +} + +// ========= datatransfer.Message interface + +// IsRequest returns true if this message is a data request +func (tm *transferMessage1_1) IsRequest() bool { + return tm.IsRq +} + +// TransferID returns the TransferID of this message +func (tm *transferMessage1_1) TransferID() datatransfer.TransferID { + if tm.IsRequest() { + return tm.Request.TransferID() + } + return tm.Response.TransferID() +} + +// ToNet serializes a transfer message type. It is simply a wrapper for MarshalCBOR, to provide +// symmetry with FromNet +func (tm *transferMessage1_1) ToNet(w io.Writer) error { + return tm.MarshalCBOR(w) +} diff --git a/message/message1_1/transfer_message_cbor_gen.go b/message/message1_1/transfer_message_cbor_gen.go new file mode 100644 index 00000000..a9b826d7 --- /dev/null +++ b/message/message1_1/transfer_message_cbor_gen.go @@ -0,0 +1,174 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package message1_1 + +import ( + "fmt" + "io" + + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf + +func (t *transferMessage1_1) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{163}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.IsRq (bool) (bool) + if len("IsRq") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"IsRq\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("IsRq"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("IsRq")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.IsRq); err != nil { + return err + } + + // t.Request (message1_1.transferRequest1_1) (struct) + if len("Request") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Request\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Request"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Request")); err != nil { + return err + } + + if err := t.Request.MarshalCBOR(w); err != nil { + return err + } + + // t.Response (message1_1.transferResponse1_1) (struct) + if len("Response") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Response\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Response"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Response")); err != nil { + return err + } + + if err := t.Response.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *transferMessage1_1) UnmarshalCBOR(r io.Reader) error { + *t = transferMessage1_1{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("transferMessage1_1: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.IsRq (bool) (bool) + case "IsRq": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.IsRq = false + case 21: + t.IsRq = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.Request (message1_1.transferRequest1_1) (struct) + case "Request": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + t.Request = new(transferRequest1_1) + if err := t.Request.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Request pointer: %w", err) + } + } + + } + // t.Response (message1_1.transferResponse1_1) (struct) + case "Response": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + t.Response = new(transferResponse1_1) + if err := t.Response.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Response pointer: %w", err) + } + } + + } + + default: + return fmt.Errorf("unknown struct field %d: '%s'", i, name) + } + } + + return nil +} diff --git a/message/message1_1/transfer_request.go b/message/message1_1/transfer_request.go new file mode 100644 index 00000000..e0536261 --- /dev/null +++ b/message/message1_1/transfer_request.go @@ -0,0 +1,170 @@ +package message1_1 + +import ( + "bytes" + "io" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/codec/dagcbor" + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/libp2p/go-libp2p-core/protocol" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/encoding" + "github.com/filecoin-project/go-data-transfer/message/message1_0" + "github.com/filecoin-project/go-data-transfer/message/types" +) + +//go:generate cbor-gen-for --map-encoding transferRequest1_1 + +// transferRequest1_1 is a struct for the 1.1 Data Transfer Protocol that fulfills the datatransfer.Request interface. +// its members are exported to be used by cbor-gen +type transferRequest1_1 struct { + BCid *cid.Cid + Type uint64 + Paus bool + Part bool + Pull bool + Stor *cbg.Deferred + Vouch *cbg.Deferred + VTyp datatransfer.TypeIdentifier + XferID uint64 + + RestartChannel datatransfer.ChannelID +} + +func (trq *transferRequest1_1) MessageForProtocol(targetProtocol protocol.ID) (datatransfer.Message, error) { + switch targetProtocol { + case datatransfer.ProtocolDataTransfer1_1: + return trq, nil + case datatransfer.ProtocolDataTransfer1_0: + if trq.IsRestart() || trq.IsRestartExistingChannelRequest() { + return nil, xerrors.New("restart not supported on 1.0") + } + + lreq := message1_0.NewTransferRequest( + trq.BCid, + trq.Type, + trq.Paus, + trq.Part, + trq.Pull, + trq.Stor, + trq.Vouch, + trq.VTyp, + trq.XferID, + ) + return lreq, nil + + default: + return nil, xerrors.Errorf("protocol not supported") + } +} + +// IsRequest always returns true in this case because this is a transfer request +func (trq *transferRequest1_1) IsRequest() bool { + return true +} + +func (trq *transferRequest1_1) IsRestart() bool { + return trq.Type == uint64(types.RestartMessage) +} + +func (trq *transferRequest1_1) IsRestartExistingChannelRequest() bool { + return trq.Type == uint64(types.RestartExistingChannelRequestMessage) +} + +func (trq *transferRequest1_1) RestartChannelId() (datatransfer.ChannelID, error) { + if !trq.IsRestartExistingChannelRequest() { + return datatransfer.ChannelID{}, xerrors.New("not a restart request") + } + return trq.RestartChannel, nil +} + +func (trq *transferRequest1_1) IsNew() bool { + return trq.Type == uint64(types.NewMessage) +} + +func (trq *transferRequest1_1) IsUpdate() bool { + return trq.Type == uint64(types.UpdateMessage) +} + +func (trq *transferRequest1_1) IsVoucher() bool { + return trq.Type == uint64(types.VoucherMessage) || trq.Type == uint64(types.NewMessage) +} + +func (trq *transferRequest1_1) IsPaused() bool { + return trq.Paus +} + +func (trq *transferRequest1_1) TransferID() datatransfer.TransferID { + return datatransfer.TransferID(trq.XferID) +} + +// ========= datatransfer.Request interface +// IsPull returns true if this is a data pull request +func (trq *transferRequest1_1) IsPull() bool { + return trq.Pull +} + +// VoucherType returns the Voucher ID +func (trq *transferRequest1_1) VoucherType() datatransfer.TypeIdentifier { + return trq.VTyp +} + +// Voucher returns the Voucher bytes +func (trq *transferRequest1_1) Voucher(decoder encoding.Decoder) (encoding.Encodable, error) { + if trq.Vouch == nil { + return nil, xerrors.New("No voucher present to read") + } + return decoder.DecodeFromCbor(trq.Vouch.Raw) +} + +func (trq *transferRequest1_1) EmptyVoucher() bool { + return trq.VTyp == datatransfer.EmptyTypeIdentifier +} + +// BaseCid returns the Base CID +func (trq *transferRequest1_1) BaseCid() cid.Cid { + if trq.BCid == nil { + return cid.Undef + } + return *trq.BCid +} + +// Selector returns the message Selector bytes +func (trq *transferRequest1_1) Selector() (ipld.Node, error) { + if trq.Stor == nil { + return nil, xerrors.New("No selector present to read") + } + builder := basicnode.Prototype.Any.NewBuilder() + reader := bytes.NewReader(trq.Stor.Raw) + err := dagcbor.Decoder(builder, reader) + if err != nil { + return nil, xerrors.Errorf("Error decoding selector: %w", err) + } + return builder.Build(), nil +} + +// IsCancel returns true if this is a cancel request +func (trq *transferRequest1_1) IsCancel() bool { + return trq.Type == uint64(types.CancelMessage) +} + +// IsPartial returns true if this is a partial request +func (trq *transferRequest1_1) IsPartial() bool { + return trq.Part +} + +// ToNet serializes a transfer request. It's a wrapper for MarshalCBOR to provide +// symmetry with FromNet +func (trq *transferRequest1_1) ToNet(w io.Writer) error { + msg := transferMessage1_1{ + IsRq: true, + Request: trq, + Response: nil, + } + return msg.MarshalCBOR(w) +} diff --git a/message/message1_1/transfer_request_cbor_gen.go b/message/message1_1/transfer_request_cbor_gen.go new file mode 100644 index 00000000..1e683e49 --- /dev/null +++ b/message/message1_1/transfer_request_cbor_gen.go @@ -0,0 +1,392 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package message1_1 + +import ( + "fmt" + "io" + + datatransfer "github.com/filecoin-project/go-data-transfer" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf + +func (t *transferRequest1_1) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{170}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.BCid (cid.Cid) (struct) + if len("BCid") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"BCid\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("BCid"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("BCid")); err != nil { + return err + } + + if t.BCid == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCidBuf(scratch, w, *t.BCid); err != nil { + return xerrors.Errorf("failed to write cid field t.BCid: %w", err) + } + } + + // t.Type (uint64) (uint64) + if len("Type") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Type\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Type"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Type")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Type)); err != nil { + return err + } + + // t.Paus (bool) (bool) + if len("Paus") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Paus\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Paus"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Paus")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Paus); err != nil { + return err + } + + // t.Part (bool) (bool) + if len("Part") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Part\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Part"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Part")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Part); err != nil { + return err + } + + // t.Pull (bool) (bool) + if len("Pull") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Pull\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Pull"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Pull")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Pull); err != nil { + return err + } + + // t.Stor (typegen.Deferred) (struct) + if len("Stor") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Stor\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Stor"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Stor")); err != nil { + return err + } + + if err := t.Stor.MarshalCBOR(w); err != nil { + return err + } + + // t.Vouch (typegen.Deferred) (struct) + if len("Vouch") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Vouch\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Vouch"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Vouch")); err != nil { + return err + } + + if err := t.Vouch.MarshalCBOR(w); err != nil { + return err + } + + // t.VTyp (datatransfer.TypeIdentifier) (string) + if len("VTyp") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"VTyp\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("VTyp"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("VTyp")); err != nil { + return err + } + + if len(t.VTyp) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.VTyp was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.VTyp))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.VTyp)); err != nil { + return err + } + + // t.XferID (uint64) (uint64) + if len("XferID") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"XferID\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("XferID"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("XferID")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.XferID)); err != nil { + return err + } + + // t.RestartChannel (datatransfer.ChannelID) (struct) + if len("RestartChannel") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"RestartChannel\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("RestartChannel"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("RestartChannel")); err != nil { + return err + } + + if err := t.RestartChannel.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *transferRequest1_1) UnmarshalCBOR(r io.Reader) error { + *t = transferRequest1_1{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("transferRequest1_1: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.BCid (cid.Cid) (struct) + case "BCid": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.BCid: %w", err) + } + + t.BCid = &c + } + + } + // t.Type (uint64) (uint64) + case "Type": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Type = uint64(extra) + + } + // t.Paus (bool) (bool) + case "Paus": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Paus = false + case 21: + t.Paus = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.Part (bool) (bool) + case "Part": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Part = false + case 21: + t.Part = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.Pull (bool) (bool) + case "Pull": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Pull = false + case 21: + t.Pull = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.Stor (typegen.Deferred) (struct) + case "Stor": + + { + + t.Stor = new(cbg.Deferred) + + if err := t.Stor.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + // t.Vouch (typegen.Deferred) (struct) + case "Vouch": + + { + + t.Vouch = new(cbg.Deferred) + + if err := t.Vouch.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + // t.VTyp (datatransfer.TypeIdentifier) (string) + case "VTyp": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.VTyp = datatransfer.TypeIdentifier(sval) + } + // t.XferID (uint64) (uint64) + case "XferID": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.XferID = uint64(extra) + + } + // t.RestartChannel (datatransfer.ChannelID) (struct) + case "RestartChannel": + + { + + if err := t.RestartChannel.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.RestartChannel: %w", err) + } + + } + + default: + return fmt.Errorf("unknown struct field %d: '%s'", i, name) + } + } + + return nil +} diff --git a/message/message1_1/transfer_request_test.go b/message/message1_1/transfer_request_test.go new file mode 100644 index 00000000..ee1e0940 --- /dev/null +++ b/message/message1_1/transfer_request_test.go @@ -0,0 +1,69 @@ +package message1_1_test + +import ( + "math/rand" + "testing" + + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/ipld/go-ipld-prime/traversal/selector/builder" + "github.com/stretchr/testify/require" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/message/message1_1" + "github.com/filecoin-project/go-data-transfer/testutil" +) + +func TestRequestMessageForProtocol(t *testing.T) { + baseCid := testutil.GenerateCids(1)[0] + selector := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any).Matcher().Node() + isPull := true + id := datatransfer.TransferID(rand.Int31()) + voucher := testutil.NewFakeDTType() + + // for the new protocol + request, err := message1_1.NewRequest(id, false, isPull, voucher.Type(), voucher, baseCid, selector) + require.NoError(t, err) + + out, err := request.MessageForProtocol(datatransfer.ProtocolDataTransfer1_1) + require.NoError(t, err) + require.Equal(t, request, out) + + // for the old protocol + out, err = request.MessageForProtocol(datatransfer.ProtocolDataTransfer1_0) + require.NoError(t, err) + req, ok := out.(datatransfer.Request) + require.True(t, ok) + require.False(t, req.IsRestart()) + require.False(t, req.IsRestartExistingChannelRequest()) + require.Equal(t, baseCid, req.BaseCid()) + require.True(t, req.IsPull()) + n, err := req.Selector() + require.NoError(t, err) + require.Equal(t, selector, n) + require.Equal(t, voucher.Type(), req.VoucherType()) + + // random protocol + out, err = request.MessageForProtocol("RAND") + require.Error(t, err) + require.Nil(t, out) +} + +func TestRequestMessageForProtocolRestartDowngradeFails(t *testing.T) { + baseCid := testutil.GenerateCids(1)[0] + selector := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any).Matcher().Node() + isPull := true + id := datatransfer.TransferID(rand.Int31()) + voucher := testutil.NewFakeDTType() + + request, err := message1_1.NewRequest(id, true, isPull, voucher.Type(), voucher, baseCid, selector) + require.NoError(t, err) + + out, err := request.MessageForProtocol(datatransfer.ProtocolDataTransfer1_0) + require.Nil(t, out) + require.EqualError(t, err, "restart not supported on 1.0") + + req2 := message1_1.RestartExistingChannelRequest(datatransfer.ChannelID{}) + out, err = req2.MessageForProtocol(datatransfer.ProtocolDataTransfer1_0) + require.Nil(t, out) + require.EqualError(t, err, "restart not supported on 1.0") +} diff --git a/message/message1_1/transfer_response.go b/message/message1_1/transfer_response.go new file mode 100644 index 00000000..2453fe41 --- /dev/null +++ b/message/message1_1/transfer_response.go @@ -0,0 +1,126 @@ +package message1_1 + +import ( + "io" + + "github.com/libp2p/go-libp2p-core/protocol" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/encoding" + "github.com/filecoin-project/go-data-transfer/message/message1_0" + "github.com/filecoin-project/go-data-transfer/message/types" +) + +//go:generate cbor-gen-for --map-encoding transferResponse1_1 + +// transferResponse1_1 is a private struct that satisfies the datatransfer.Response interface +// It is the response message for the Data Transfer 1.1 Protocol. +type transferResponse1_1 struct { + Type uint64 + Acpt bool + Paus bool + XferID uint64 + VRes *cbg.Deferred + VTyp datatransfer.TypeIdentifier +} + +func (trsp *transferResponse1_1) TransferID() datatransfer.TransferID { + return datatransfer.TransferID(trsp.XferID) +} + +// IsRequest always returns false in this case because this is a transfer response +func (trsp *transferResponse1_1) IsRequest() bool { + return false +} + +// IsNew returns true if this is the first response sent +func (trsp *transferResponse1_1) IsNew() bool { + return trsp.Type == uint64(types.NewMessage) +} + +// IsUpdate returns true if this response is an update +func (trsp *transferResponse1_1) IsUpdate() bool { + return trsp.Type == uint64(types.UpdateMessage) +} + +// IsPaused returns true if the responder is paused +func (trsp *transferResponse1_1) IsPaused() bool { + return trsp.Paus +} + +// IsCancel returns true if the responder has cancelled this response +func (trsp *transferResponse1_1) IsCancel() bool { + return trsp.Type == uint64(types.CancelMessage) +} + +// IsComplete returns true if the responder has completed this response +func (trsp *transferResponse1_1) IsComplete() bool { + return trsp.Type == uint64(types.CompleteMessage) +} + +func (trsp *transferResponse1_1) IsVoucherResult() bool { + return trsp.Type == uint64(types.VoucherResultMessage) || trsp.Type == uint64(types.NewMessage) || trsp.Type == uint64(types.CompleteMessage) || + trsp.Type == uint64(types.RestartMessage) +} + +// Accepted returns true if the request is accepted in the response +func (trsp *transferResponse1_1) Accepted() bool { + return trsp.Acpt +} + +func (trsp *transferResponse1_1) VoucherResultType() datatransfer.TypeIdentifier { + return trsp.VTyp +} + +func (trsp *transferResponse1_1) VoucherResult(decoder encoding.Decoder) (encoding.Encodable, error) { + if trsp.VRes == nil { + return nil, xerrors.New("No voucher present to read") + } + return decoder.DecodeFromCbor(trsp.VRes.Raw) +} + +func (trq *transferResponse1_1) IsRestart() bool { + return trq.Type == uint64(types.RestartMessage) +} + +func (trsp *transferResponse1_1) EmptyVoucherResult() bool { + return trsp.VTyp == datatransfer.EmptyTypeIdentifier +} + +func (trsp *transferResponse1_1) MessageForProtocol(targetProtocol protocol.ID) (datatransfer.Message, error) { + switch targetProtocol { + case datatransfer.ProtocolDataTransfer1_1: + return trsp, nil + case datatransfer.ProtocolDataTransfer1_0: + // this should never happen but dosen't hurt to have this here for sanity + if trsp.IsRestart() { + return nil, xerrors.New("restart not supported for 1.0 protocol") + } + + lresp := message1_0.NewTransferResponse( + trsp.Type, + trsp.Acpt, + trsp.Paus, + trsp.XferID, + trsp.VRes, + trsp.VTyp, + ) + + return lresp, nil + default: + return nil, xerrors.Errorf("protocol %s not supported", targetProtocol) + } +} + +// ToNet serializes a transfer response. It's a wrapper for MarshalCBOR to provide +// symmetry with FromNet +func (trsp *transferResponse1_1) ToNet(w io.Writer) error { + msg := transferMessage1_1{ + IsRq: false, + Request: nil, + Response: trsp, + } + return msg.MarshalCBOR(w) +} diff --git a/message/message1_1/transfer_response_cbor_gen.go b/message/message1_1/transfer_response_cbor_gen.go new file mode 100644 index 00000000..214483aa --- /dev/null +++ b/message/message1_1/transfer_response_cbor_gen.go @@ -0,0 +1,260 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package message1_1 + +import ( + "fmt" + "io" + + datatransfer "github.com/filecoin-project/go-data-transfer" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf + +func (t *transferResponse1_1) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{166}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Type (uint64) (uint64) + if len("Type") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Type\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Type"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Type")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Type)); err != nil { + return err + } + + // t.Acpt (bool) (bool) + if len("Acpt") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Acpt\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Acpt"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Acpt")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Acpt); err != nil { + return err + } + + // t.Paus (bool) (bool) + if len("Paus") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Paus\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Paus"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Paus")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Paus); err != nil { + return err + } + + // t.XferID (uint64) (uint64) + if len("XferID") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"XferID\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("XferID"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("XferID")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.XferID)); err != nil { + return err + } + + // t.VRes (typegen.Deferred) (struct) + if len("VRes") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"VRes\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("VRes"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("VRes")); err != nil { + return err + } + + if err := t.VRes.MarshalCBOR(w); err != nil { + return err + } + + // t.VTyp (datatransfer.TypeIdentifier) (string) + if len("VTyp") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"VTyp\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("VTyp"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("VTyp")); err != nil { + return err + } + + if len(t.VTyp) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.VTyp was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.VTyp))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.VTyp)); err != nil { + return err + } + return nil +} + +func (t *transferResponse1_1) UnmarshalCBOR(r io.Reader) error { + *t = transferResponse1_1{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("transferResponse1_1: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Type (uint64) (uint64) + case "Type": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Type = uint64(extra) + + } + // t.Acpt (bool) (bool) + case "Acpt": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Acpt = false + case 21: + t.Acpt = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.Paus (bool) (bool) + case "Paus": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Paus = false + case 21: + t.Paus = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.XferID (uint64) (uint64) + case "XferID": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.XferID = uint64(extra) + + } + // t.VRes (typegen.Deferred) (struct) + case "VRes": + + { + + t.VRes = new(cbg.Deferred) + + if err := t.VRes.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + // t.VTyp (datatransfer.TypeIdentifier) (string) + case "VTyp": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.VTyp = datatransfer.TypeIdentifier(sval) + } + + default: + return fmt.Errorf("unknown struct field %d: '%s'", i, name) + } + } + + return nil +} diff --git a/message/message1_1/transfer_response_test.go b/message/message1_1/transfer_response_test.go new file mode 100644 index 00000000..9ea922d9 --- /dev/null +++ b/message/message1_1/transfer_response_test.go @@ -0,0 +1,49 @@ +package message1_1_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-data-transfer/message/message1_1" + "github.com/filecoin-project/go-data-transfer/testutil" +) + +func TestResponseMessageForProtocol(t *testing.T) { + id := datatransfer.TransferID(rand.Int31()) + voucherResult := testutil.NewFakeDTType() + response, err := message1_1.NewResponse(id, false, true, voucherResult.Type(), voucherResult) // not accepted + require.NoError(t, err) + + // new protocol + out, err := response.MessageForProtocol(datatransfer.ProtocolDataTransfer1_1) + require.NoError(t, err) + require.Equal(t, response, out) + + // old protocol + out, err = response.MessageForProtocol(datatransfer.ProtocolDataTransfer1_0) + require.NoError(t, err) + resp, ok := (out).(datatransfer.Response) + require.True(t, ok) + require.True(t, resp.IsPaused()) + require.Equal(t, voucherResult.Type(), resp.VoucherResultType()) + require.True(t, resp.IsVoucherResult()) + + // random protocol + out, err = response.MessageForProtocol("RAND") + require.Error(t, err) + require.Nil(t, out) +} + +func TestResponseMessageForProtocolFail(t *testing.T) { + id := datatransfer.TransferID(rand.Int31()) + voucherResult := testutil.NewFakeDTType() + response, err := message1_1.RestartResponse(id, false, true, voucherResult.Type(), voucherResult) // not accepted + require.NoError(t, err) + + out, err := response.MessageForProtocol(datatransfer.ProtocolDataTransfer1_0) + require.Nil(t, out) + require.EqualError(t, err, "restart not supported for 1.0 protocol") +} diff --git a/message/types/message_types.go b/message/types/message_types.go new file mode 100644 index 00000000..3144df0a --- /dev/null +++ b/message/types/message_types.go @@ -0,0 +1,16 @@ +package types + +type MessageType uint64 + +// Always append at the end to avoid breaking backward compatibility for cbor messages +const ( + NewMessage MessageType = iota + UpdateMessage + CancelMessage + CompleteMessage + VoucherMessage + VoucherResultMessage + + RestartMessage + RestartExistingChannelRequestMessage +) diff --git a/network/interface.go b/network/interface.go index 833ac134..d6e2ffc9 100644 --- a/network/interface.go +++ b/network/interface.go @@ -4,16 +4,10 @@ import ( "context" "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" datatransfer "github.com/filecoin-project/go-data-transfer" ) -var ( - // ProtocolDataTransfer is the protocol identifier for graphsync messages - ProtocolDataTransfer protocol.ID = "/fil/datatransfer/1.0.0" -) - // DataTransferNetwork provides network connectivity for GraphSync. type DataTransferNetwork interface { Protect(id peer.ID, tag string) @@ -48,5 +42,7 @@ type Receiver interface { sender peer.ID, incoming datatransfer.Response) + ReceiveRestartExistingChannelRequest(ctx context.Context, sender peer.ID, incoming datatransfer.Request) + ReceiveError(error) } diff --git a/network/libp2p_impl.go b/network/libp2p_impl.go index 88b70a8a..ebb82b98 100644 --- a/network/libp2p_impl.go +++ b/network/libp2p_impl.go @@ -7,23 +7,62 @@ import ( "time" logging "github.com/ipfs/go-log/v2" + "github.com/jpillora/backoff" "github.com/libp2p/go-libp2p-core/helpers" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + "golang.org/x/xerrors" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-data-transfer/message" + "github.com/filecoin-project/go-data-transfer/message/message1_0" ) var log = logging.Logger("data_transfer_network") var sendMessageTimeout = time.Minute * 10 +const defaultMaxStreamOpenAttempts = 5 +const defaultMinAttemptDuration = 1 * time.Second +const defaultMaxAttemptDuration = 5 * time.Minute + +var defaultDataTransferProtocols = []protocol.ID{datatransfer.ProtocolDataTransfer1_1, datatransfer.ProtocolDataTransfer1_0} + +// Option is an option for configuring the libp2p storage market network +type Option func(*libp2pDataTransferNetwork) + +// DataTransferProtocols OVERWRITES the default libp2p protocols we use for data transfer with the given protocols. +func DataTransferProtocols(protocols []protocol.ID) Option { + return func(impl *libp2pDataTransferNetwork) { + impl.dtProtocols = nil + impl.dtProtocols = append(impl.dtProtocols, protocols...) + } +} + +// RetryParameters changes the default parameters around connection reopening +func RetryParameters(minDuration time.Duration, maxDuration time.Duration, attempts float64) Option { + return func(impl *libp2pDataTransferNetwork) { + impl.maxStreamOpenAttempts = attempts + impl.minAttemptDuration = minDuration + impl.maxAttemptDuration = maxDuration + } +} + // NewFromLibp2pHost returns a GraphSyncNetwork supported by underlying Libp2p host. -func NewFromLibp2pHost(host host.Host) DataTransferNetwork { +func NewFromLibp2pHost(host host.Host, options ...Option) DataTransferNetwork { dataTransferNetwork := libp2pDataTransferNetwork{ host: host, + + maxStreamOpenAttempts: defaultMaxStreamOpenAttempts, + minAttemptDuration: defaultMinAttemptDuration, + maxAttemptDuration: defaultMaxAttemptDuration, + dtProtocols: defaultDataTransferProtocols, + } + + for _, option := range options { + option(&dataTransferNetwork) } return &dataTransferNetwork @@ -35,10 +74,35 @@ type libp2pDataTransferNetwork struct { host host.Host // inbound messages from the network are forwarded to the receiver receiver Receiver + + maxStreamOpenAttempts float64 + minAttemptDuration time.Duration + maxAttemptDuration time.Duration + dtProtocols []protocol.ID } -func (dtnet *libp2pDataTransferNetwork) newStreamToPeer(ctx context.Context, p peer.ID) (network.Stream, error) { - return dtnet.host.NewStream(ctx, p, ProtocolDataTransfer) +func (impl *libp2pDataTransferNetwork) openStream(ctx context.Context, id peer.ID, protocols ...protocol.ID) (network.Stream, error) { + b := &backoff.Backoff{ + Min: impl.minAttemptDuration, + Max: impl.maxAttemptDuration, + Factor: impl.maxStreamOpenAttempts, + Jitter: true, + } + + for { + // will use the first among the given protocols that the remote peer supports + s, err := impl.host.NewStream(ctx, id, protocols...) + if err == nil { + return s, err + } + + nAttempts := b.Attempt() + if nAttempts == impl.maxStreamOpenAttempts { + return nil, xerrors.Errorf("exhausted %d attempts but failed to open stream, err: %w", impl.maxStreamOpenAttempts, err) + } + d := b.Duration() + time.Sleep(d) + } } func (dtnet *libp2pDataTransferNetwork) SendMessage( @@ -46,11 +110,16 @@ func (dtnet *libp2pDataTransferNetwork) SendMessage( p peer.ID, outgoing datatransfer.Message) error { - s, err := dtnet.newStreamToPeer(ctx, p) + s, err := dtnet.openStream(ctx, p, dtnet.dtProtocols...) if err != nil { return err } + outgoing, err = outgoing.MessageForProtocol(s.Protocol()) + if err != nil { + return xerrors.Errorf("failed to convert message for protocol: %w", err) + } + if err = msgToStream(ctx, s, outgoing); err != nil { if err2 := s.Reset(); err2 != nil { log.Error(err) @@ -62,12 +131,13 @@ func (dtnet *libp2pDataTransferNetwork) SendMessage( // TODO(https://github.com/libp2p/go-libp2p-net/issues/28): Avoid this goroutine. go helpers.AwaitEOF(s) // nolint: errcheck,gosec return s.Close() - } func (dtnet *libp2pDataTransferNetwork) SetDelegate(r Receiver) { dtnet.receiver = r - dtnet.host.SetStreamHandler(ProtocolDataTransfer, dtnet.handleNewStream) + for _, p := range dtnet.dtProtocols { + dtnet.host.SetStreamHandler(p, dtnet.handleNewStream) + } } func (dtnet *libp2pDataTransferNetwork) ConnectTo(ctx context.Context, p peer.ID) error { @@ -84,7 +154,14 @@ func (dtnet *libp2pDataTransferNetwork) handleNewStream(s network.Stream) { } for { - received, err := message.FromNet(s) + var received datatransfer.Message + var err error + if s.Protocol() == datatransfer.ProtocolDataTransfer1_1 { + received, err = message.FromNet(s) + } else { + received, err = message1_0.FromNet(s) + } + if err != nil { if err != io.EOF { s.Reset() // nolint: errcheck,gosec @@ -97,10 +174,15 @@ func (dtnet *libp2pDataTransferNetwork) handleNewStream(s network.Stream) { p := s.Conn().RemotePeer() ctx := context.Background() log.Debugf("graphsync net handleNewStream from %s", s.Conn().RemotePeer()) + if received.IsRequest() { receivedRequest, ok := received.(datatransfer.Request) if ok { - dtnet.receiver.ReceiveRequest(ctx, p, receivedRequest) + if receivedRequest.IsRestartExistingChannelRequest() { + dtnet.receiver.ReceiveRestartExistingChannelRequest(ctx, p, receivedRequest) + } else { + dtnet.receiver.ReceiveRequest(ctx, p, receivedRequest) + } } } else { receivedResponse, ok := received.(datatransfer.Response) @@ -137,15 +219,17 @@ func msgToStream(ctx context.Context, s network.Stream, msg datatransfer.Message } switch s.Protocol() { - case ProtocolDataTransfer: - if err := msg.ToNet(s); err != nil { - log.Debugf("error: %s", err) - return err - } + case datatransfer.ProtocolDataTransfer1_1: + case datatransfer.ProtocolDataTransfer1_0: default: return fmt.Errorf("unrecognized protocol on remote: %s", s.Protocol()) } + if err := msg.ToNet(s); err != nil { + log.Debugf("error: %s", err) + return err + } + if err := s.SetWriteDeadline(time.Time{}); err != nil { log.Warnf("error resetting deadline: %s", err) } diff --git a/network/libp2p_impl_test.go b/network/libp2p_impl_test.go index 7dac87a7..a5f8346d 100644 --- a/network/libp2p_impl_test.go +++ b/network/libp2p_impl_test.go @@ -21,11 +21,12 @@ import ( // Receiver is an interface for receiving messages from the DataTransferNetwork. type receiver struct { - messageReceived chan struct{} - lastRequest datatransfer.Request - lastResponse datatransfer.Response - lastSender peer.ID - connectedPeers chan peer.ID + messageReceived chan struct{} + lastRequest datatransfer.Request + lastRestartRequest datatransfer.Request + lastResponse datatransfer.Response + lastSender peer.ID + connectedPeers chan peer.ID } func (r *receiver) ReceiveRequest( @@ -55,6 +56,15 @@ func (r *receiver) ReceiveResponse( func (r *receiver) ReceiveError(err error) { } +func (r *receiver) ReceiveRestartExistingChannelRequest(ctx context.Context, sender peer.ID, incoming datatransfer.Request) { + r.lastSender = sender + r.lastRestartRequest = incoming + select { + case <-ctx.Done(): + case r.messageReceived <- struct{}{}: + } +} + func TestMessageSendAndReceive(t *testing.T) { // create network ctx := context.Background() @@ -87,7 +97,7 @@ func TestMessageSendAndReceive(t *testing.T) { isPull := false id := datatransfer.TransferID(rand.Int31()) voucher := testutil.NewFakeDTType() - request, err := message.NewRequest(id, isPull, voucher.Type(), voucher, baseCid, selector) + request, err := message.NewRequest(id, false, isPull, voucher.Type(), voucher, baseCid, selector) require.NoError(t, err) require.NoError(t, dtnet1.SendMessage(ctx, host2.ID(), request)) @@ -137,4 +147,30 @@ func TestMessageSendAndReceive(t *testing.T) { assert.Equal(t, response.IsRequest(), receivedResponse.IsRequest()) testutil.AssertEqualFakeDTVoucherResult(t, response, receivedResponse) }) + + t.Run("Send Restart Request", func(t *testing.T) { + peers := testutil.GeneratePeers(2) + id := datatransfer.TransferID(rand.Int31()) + chId := datatransfer.ChannelID{Initiator: peers[0], + Responder: peers[1], ID: id} + + request := message.RestartExistingChannelRequest(chId) + require.NoError(t, dtnet1.SendMessage(ctx, host2.ID(), request)) + + select { + case <-ctx.Done(): + t.Fatal("did not receive message sent") + case <-r.messageReceived: + } + + sender := r.lastSender + require.Equal(t, sender, host1.ID()) + + receivedRequest := r.lastRestartRequest + require.NotNil(t, receivedRequest) + achid, err := receivedRequest.RestartChannelId() + require.NoError(t, err) + require.Equal(t, chId, achid) + }) + } diff --git a/statuses.go b/statuses.go index 6a4c89be..d3173979 100644 --- a/statuses.go +++ b/statuses.go @@ -58,6 +58,9 @@ const ( // ChannelNotFoundError means the searched for data transfer does not exist ChannelNotFoundError + + // PeerDisconnected means that we do NOT have a connection to the other peer + PeerDisconnected ) // Statuses are human readable names for data transfer states diff --git a/testutil/fakegraphsync.go b/testutil/fakegraphsync.go index 852c9743..e825e813 100644 --- a/testutil/fakegraphsync.go +++ b/testutil/fakegraphsync.go @@ -38,10 +38,12 @@ func matchDtMessage(t *testing.T, extensions []graphsync.ExtensionData) datatran // ReceivedGraphSyncRequest contains data about a received graphsync request type ReceivedGraphSyncRequest struct { - P peer.ID - Root ipld.Link - Selector ipld.Node - Extensions []graphsync.ExtensionData + Ctx context.Context + P peer.ID + Root ipld.Link + Selector ipld.Node + Extensions []graphsync.ExtensionData + ResponseErrChan chan error } // DTMessage returns the data transfer message among the graphsync extensions sent with this request @@ -113,7 +115,7 @@ type FakeGraphSync struct { // NewFakeGraphSync returns a new fake graphsync implementation func NewFakeGraphSync() *FakeGraphSync { return &FakeGraphSync{ - requests: make(chan ReceivedGraphSyncRequest, 1), + requests: make(chan ReceivedGraphSyncRequest, 2), pauseRequests: make(chan PauseRequest, 1), resumeRequests: make(chan ResumeRequest, 1), pauseResponses: make(chan PauseResponse, 1), @@ -242,10 +244,9 @@ func (fgs *FakeGraphSync) AssertDoesNotHavePersistenceOption(t *testing.T, name // Request initiates a new GraphSync request to the given peer using the given selector spec. func (fgs *FakeGraphSync) Request(ctx context.Context, p peer.ID, root ipld.Link, selector ipld.Node, extensions ...graphsync.ExtensionData) (<-chan graphsync.ResponseProgress, <-chan error) { - - fgs.requests <- ReceivedGraphSyncRequest{p, root, selector, extensions} - responses := make(chan graphsync.ResponseProgress) errors := make(chan error) + fgs.requests <- ReceivedGraphSyncRequest{ctx, p, root, selector, extensions, errors} + responses := make(chan graphsync.ResponseProgress) if !fgs.leaveRequestsOpen { close(responses) close(errors) diff --git a/testutil/faketransport.go b/testutil/faketransport.go index 2412641c..21cdacfe 100644 --- a/testutil/faketransport.go +++ b/testutil/faketransport.go @@ -3,6 +3,7 @@ package testutil import ( "context" + "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" "github.com/libp2p/go-libp2p-core/peer" @@ -11,11 +12,12 @@ import ( // OpenedChannel records a call to open a channel type OpenedChannel struct { - DataSender peer.ID - ChannelID datatransfer.ChannelID - Root ipld.Link - Selector ipld.Node - Message datatransfer.Message + DataSender peer.ID + ChannelID datatransfer.ChannelID + Root ipld.Link + Selector ipld.Node + DoNotSendCids []cid.Cid + Message datatransfer.Message } // ResumedChannel records a call to resume a channel @@ -56,8 +58,9 @@ func NewFakeTransport() *FakeTransport { // Note: from a data transfer symantic standpoint, it doesn't matter if the // request is push or pull -- OpenChannel is called by the party that is // intending to receive data -func (ft *FakeTransport) OpenChannel(ctx context.Context, dataSender peer.ID, channelID datatransfer.ChannelID, root ipld.Link, stor ipld.Node, msg datatransfer.Message) error { - ft.OpenedChannels = append(ft.OpenedChannels, OpenedChannel{dataSender, channelID, root, stor, msg}) +func (ft *FakeTransport) OpenChannel(ctx context.Context, dataSender peer.ID, channelID datatransfer.ChannelID, root ipld.Link, stor ipld.Node, doNotSend []cid.Cid, + msg datatransfer.Message) error { + ft.OpenedChannels = append(ft.OpenedChannels, OpenedChannel{dataSender, channelID, root, stor, doNotSend, msg}) return ft.OpenChannelErr } diff --git a/testutil/fixtures/lorem_large.txt b/testutil/fixtures/lorem_large.txt new file mode 100644 index 00000000..92c8059c --- /dev/null +++ b/testutil/fixtures/lorem_large.txt @@ -0,0 +1,1204 @@ + + +Wiki Loves Monuments: Photograph a monument, help Wikipedia and win! +Learn more + +This is a good article. Click here for more information. Page semi-protected +Mahatma Gandhi +From Wikipedia, the free encyclopedia +Jump to navigationJump to search +"Gandhi" redirects here. For other uses, see Gandhi (disambiguation). +Mahātmā +Gandhi +Mahatma-Gandhi, studio, 1931.jpg +Studio photograph of Mohandas K. Gandhi, London, 1931. +Born Mohandas Karamchand Gandhi +2 October 1869 +Porbandar, Porbandar State, Kathiawar Agency, Bombay Presidency, British India +Died 30 January 1948 (aged 78) +New Delhi, India +Cause of death Assassination (gunshot) +Monuments Raj Ghat, +Gandhi Smriti +Other names Mahatma Gandhi, Bapu ji, Gandhi ji, M. K. Gandhi +Citizenship Indian +Alma mater University College London[1] +Inner Temple +Occupation +LawyerPoliticianActivistWriter +Years active 1893–1948 +Era British Raj +Known for Indian Independence Movement, +Nonviolent resistance +Notable work +The Story of My Experiments with Truth +Office President of the Indian National Congress +Term 1924–1925 +Political party Indian National Congress +Movement Indian independence movement +Spouse(s) Kasturba Gandhi +​ +​(m. 1883; died 1944) +Children +HarilalManilalRamdasDevdas +Parents +Karamchand Gandhi (father) +Putlibai Gandhi (mother) +Signature +Signature of Gandhi +Mohandas Karamchand Gandhi (/ˈɡɑːndi, ˈɡændi/;[2] 2 October 1869 – 30 January 1948) was an Indian lawyer,[3] anti-colonial nationalist,[4] and political ethicist,[5] who employed nonviolent resistance to lead the successful campaign for India's independence from British rule,[6] and in turn inspired movements for civil rights and freedom across the world. The honorific Mahātmā (Sanskrit: "great-souled", "venerable"), first applied to him in 1914 in South Africa, is now used throughout the world.[7][8] + +Born and raised in a Hindu family in coastal Gujarat, western India, Gandhi trained in law at the Inner Temple, London, and was called to the bar at age 22 in June 1891. After two uncertain years in India, where he was unable to start a successful law practice, he moved to South Africa in 1893 to represent an Indian merchant in a lawsuit. He went on to stay for 21 years. It was in South Africa that Gandhi raised a family, and first employed nonviolent resistance in a campaign for civil rights. In 1915, aged 45, he returned to India. He set about organising peasants, farmers, and urban labourers to protest against excessive land-tax and discrimination. Assuming leadership of the Indian National Congress in 1921, Gandhi led nationwide campaigns for easing poverty, expanding women's rights, building religious and ethnic amity, ending untouchability, and above all for achieving Swaraj or self-rule.[9] + +The same year Gandhi adopted the Indian loincloth, or short dhoti and, in the winter, a shawl, both woven with yarn hand-spun on a traditional Indian spinning wheel, or charkha, as a mark of identification with India's rural poor. Thereafter, he lived modestly in a self-sufficient residential community, ate simple vegetarian food, and undertook long fasts as a means of self-purification and political protest. Bringing anti-colonial nationalism to the common Indians, Gandhi led them in challenging the British-imposed salt tax with the 400 km (250 mi) Dandi Salt March in 1930, and later in calling for the British to Quit India in 1942. He was imprisoned for many years, upon many occasions, in both South Africa and India. + +Gandhi's vision of an independent India based on religious pluralism was challenged in the early 1940s by a new Muslim nationalism which was demanding a separate Muslim homeland carved out of India.[10] In August 1947, Britain granted independence, but the British Indian Empire[10] was partitioned into two dominions, a Hindu-majority India and Muslim-majority Pakistan.[11] As many displaced Hindus, Muslims, and Sikhs made their way to their new lands, religious violence broke out, especially in the Punjab and Bengal. Eschewing the official celebration of independence in Delhi, Gandhi visited the affected areas, attempting to provide solace. In the months following, he undertook several fasts unto death to stop religious violence. The last of these, undertaken on 12 January 1948 when he was 78,[12] also had the indirect goal of pressuring India to pay out some cash assets owed to Pakistan.[12] Some Indians thought Gandhi was too accommodating.[12][13] Among them was Nathuram Godse, a Hindu nationalist, who assassinated Gandhi on 30 January 1948 by firing three bullets into his chest.[13] + +Gandhi's birthday, 2 October, is commemorated in India as Gandhi Jayanti, a national holiday, and worldwide as the International Day of Nonviolence. Gandhi is commonly, though not formally, considered the Father of the Nation in India,[14][15] and was commonly called Bapu[16] (Gujarati: endearment for father,[17] papa[17][18]). + + +Contents +1 Biography +1.1 Early life and background +1.2 Three years in London +1.3 Civil rights activist in South Africa (1893–1914) +1.4 Struggle for Indian independence (1915–1947) +1.5 Death +2 Principles, practices, and beliefs +2.1 Influences +2.2 On wars and nonviolence +2.3 On inter-religious relations +2.4 On life, society and other application of his ideas +3 Literary works +4 Legacy and depictions in popular culture +4.1 Followers and international influence +4.2 Global days that celebrate Gandhi +4.3 Awards +4.4 Film, theatre and literature +4.5 Current impact within India +4.6 Descendants +5 See also +6 References +7 Bibliography +7.1 Books +7.2 Primary sources +8 External links +Biography +Early life and background +Mohandas Karamchand Gandhi[19] was born on 2 October 1869[20] into an Gujarati Modh Bania family of the Vaishya varna[21] in Porbandar (also known as Sudamapuri), a coastal town on the Kathiawar Peninsula and then part of the small princely state of Porbandar in the Kathiawar Agency of the Indian Empire. His father, Karamchand Uttamchand Gandhi (1822–1885), served as the diwan (chief minister) of Porbandar state.[22] + +Although he only had an elementary education and had previously been a clerk in the state administration, Karamchand proved a capable chief minister.[23] During his tenure, Karamchand married four times. His first two wives died young, after each had given birth to a daughter, and his third marriage was childless. In 1857, Karamchand sought his third wife's permission to remarry; that year, he married Putlibai (1844–1891), who also came from Junagadh,[23] and was from a Pranami Vaishnava family.[24][25][26][27] Karamchand and Putlibai had three children over the ensuing decade: a son, Laxmidas (c. 1860–1914); a daughter, Raliatbehn (1862–1960); and another son, Karsandas (c. 1866–1913).[28][29] + +On 2 October 1869, Putlibai gave birth to her last child, Mohandas, in a dark, windowless ground-floor room of the Gandhi family residence in Porbandar city. As a child, Gandhi was described by his sister Raliat as "restless as mercury, either playing or roaming about. One of his favourite pastimes was twisting dogs' ears."[30] The Indian classics, especially the stories of Shravana and king Harishchandra, had a great impact on Gandhi in his childhood. In his autobiography, he admits that they left an indelible impression on his mind. He writes: "It haunted me and I must have acted Harishchandra to myself times without number." Gandhi's early self-identification with truth and love as supreme values is traceable to these epic characters.[31][32] + +The family's religious background was eclectic. Gandhi's father Karamchand was Hindu and his mother Putlibai was from a Pranami Vaishnava Hindu family.[33][34] Gandhi's father was of Modh Baniya caste in the varna of Vaishya.[35] His mother came from the medieval Krishna bhakti-based Pranami tradition, whose religious texts include the Bhagavad Gita, the Bhagavata Purana, and a collection of 14 texts with teachings that the tradition believes to include the essence of the Vedas, the Quran and the Bible.[34][36] Gandhi was deeply influenced by his mother, an extremely pious lady who "would not think of taking her meals without her daily prayers... she would take the hardest vows and keep them without flinching. To keep two or three consecutive fasts was nothing to her."[37] + +In 1874, Gandhi's father Karamchand left Porbandar for the smaller state of Rajkot, where he became a counsellor to its ruler, the Thakur Sahib; though Rajkot was a less prestigious state than Porbandar, the British regional political agency was located there, which gave the state's diwan a measure of security.[38] In 1876, Karamchand became diwan of Rajkot and was succeeded as diwan of Porbandar by his brother Tulsidas. His family then rejoined him in Rajkot.[39] + + +Gandhi (right) with his eldest brother Laxmidas in 1886.[40] +At age 9, Gandhi entered the local school in Rajkot, near his home. There he studied the rudiments of arithmetic, history, the Gujarati language and geography.[39] At age 11, he joined the High School in Rajkot.[41] He was an average student, won some prizes, but was a shy and tongue tied student, with no interest in games; his only companions were books and school lessons.[42] + +In May 1883, the 13-year-old Mohandas was married to 14-year-old Kasturbai Makhanji Kapadia (her first name was usually shortened to "Kasturba", and affectionately to "Ba") in an arranged marriage, according to the custom of the region at that time.[43] In the process, he lost a year at school but was later allowed to make up by accelerating his studies.[44] His wedding was a joint event, where his brother and cousin were also married. Recalling the day of their marriage, he once said, "As we didn't know much about marriage, for us it meant only wearing new clothes, eating sweets and playing with relatives." As was prevailing tradition, the adolescent bride was to spend much time at her parents' house, and away from her husband.[45] + +Writing many years later, Mohandas described with regret the lustful feelings he felt for his young bride, "even at school I used to think of her, and the thought of nightfall and our subsequent meeting was ever haunting me." He later recalled feeling jealous and possessive of her, such as when she would visit a temple with her girlfriends, and being sexually lustful in his feelings for her.[46] + +In late 1885, Gandhi's father Karamchand died.[47] Gandhi, then 16 years old, and his wife of age 17 had their first baby, who survived only a few days. The two deaths anguished Gandhi.[47] The Gandhi couple had four more children, all sons: Harilal, born in 1888; Manilal, born in 1892; Ramdas, born in 1897; and Devdas, born in 1900.[43] + +In November 1887, the 18-year-old Gandhi graduated from high school in Ahmedabad.[48] In January 1888, he enrolled at Samaldas College in Bhavnagar State, then the sole degree-granting institution of higher education in the region. But he dropped out and returned to his family in Porbandar.[49] + +Three years in London +Student of law +Gandhi came from a poor family, and he had dropped out of the cheapest college he could afford.[50] Mavji Dave Joshiji, a Brahmin priest and family friend, advised Gandhi and his family that he should consider law studies in London.[51] In July 1888, his wife Kasturba gave birth to their first surviving son, Harilal.[52] His mother was not comfortable about Gandhi leaving his wife and family, and going so far from home. Gandhi's uncle Tulsidas also tried to dissuade his nephew. Gandhi wanted to go. To persuade his wife and mother, Gandhi made a vow in front of his mother that he would abstain from meat, alcohol and women. Gandhi's brother Laxmidas, who was already a lawyer, cheered Gandhi's London studies plan and offered to support him. Putlibai gave Gandhi her permission and blessing.[49][53] + +On 10 August 1888, Gandhi aged 18, left Porbandar for Mumbai, then known as Bombay. Upon arrival, he stayed with the local Modh Bania community whose elders warned him that England would tempt him to compromise his religion, and eat and drink in Western ways. Despite Gandhi informing them of his promise to his mother and her blessings, he was excommunicated from his caste. Gandhi ignored this, and on 4 September, he sailed from Bombay to London, with his brother seeing him off.[52][54] Gandhi attended University College, London which is a constituent college of University of London. + + +Gandhi in London as a law student +At UCL, he studied law and jurisprudence and was invited to enroll at Inner Temple with the intention of becoming a barrister. His childhood shyness and self withdrawal had continued through his teens, and he remained so when he arrived in London, but he joined a public speaking practice group and overcame this handicap to practise law.[55] + +Vegetarianism and committee work +Gandhi's time in London was influenced by the vow he had made to his mother. He tried to adopt "English" customs, including taking dancing lessons. However, he could not appreciate the bland vegetarian food offered by his landlady and was frequently hungry until he found one of London's few vegetarian restaurants. Influenced by Henry Salt's writing, he joined the London Vegetarian Society and was elected to its executive committee[56] under the aegis of its president and benefactor Arnold Hills. An achievement while on the committee was the establishment of a Bayswater chapter.[26] Some of the vegetarians he met were members of the Theosophical Society, which had been founded in 1875 to further universal brotherhood, and which was devoted to the study of Buddhist and Hindu literature. They encouraged Gandhi to join them in reading the Bhagavad Gita both in translation as well as in the original.[56] + +Gandhi had a friendly and productive relationship with Hills, but the two men took a different view on the continued LVS membership of fellow committee member Dr Thomas Allinson. Their disagreement is the first known example of Gandhi challenging authority, despite his shyness and temperamental disinclination towards confrontation. + +Allinson had been promoting newly available birth control methods, but Hills disapproved of these, believing they undermined public morality. He believed vegetarianism to be a moral movement and that Allinson should therefore no longer remain a member of the LVS. Gandhi shared Hills views on the dangers of birth control, but defended Allinson's right to differ.[57] It would have been hard for Gandhi to challenge Hills; Hills was 12 years his senior and unlike Gandhi, highly eloquent. He bankrolled the LVS and was a captain of industry with his Thames Ironworks company employing more than 6,000 people in the East End of London. He was also a highly accomplished sportsman who would go on to found the football club West Ham United. + +The question deeply interested me...I had a high regard for Mr. Hills and his generosity. But I thought it was quite improper to exclude a man from a vegetarian society simply because he refused to regard puritan morals as one of the objects of the society[57] + +A motion to remove Allinson was raised, and was debated and voted on by the committee. Gandhi's shyness was an obstacle to his defence of Allinson at the committee meeting. He wrote his views down on paper but shyness prevented him from reading out his arguments, so Hills, the President, asked another committee member to read them out for him. Although some other members of the committee agreed with Gandhi, the vote was lost and Allinson excluded. There were no hard feelings, with Hills proposing the toast at the LVS farewell dinner in honour of Gandhi's return to India.[58] + +Called to the bar +Gandhi, at age 22, was called to the bar in June 1891 and then left London for India, where he learned that his mother had died while he was in London and that his family had kept the news from him.[56] His attempts at establishing a law practice in Bombay failed because he was psychologically unable to cross-examine witnesses. He returned to Rajkot to make a modest living drafting petitions for litigants, but he was forced to stop when he ran afoul of a British officer Sam Sunny.[26][56] + +In 1893, a Muslim merchant in Kathiawar named Dada Abdullah contacted Gandhi. Abdullah owned a large successful shipping business in South Africa. His distant cousin in Johannesburg needed a lawyer, and they preferred someone with Kathiawari heritage. Gandhi inquired about his pay for the work. They offered a total salary of £105 (~$17,200 in 2019 money) plus travel expenses. He accepted it, knowing that it would be at least a one-year commitment in the Colony of Natal, South Africa, also a part of the British Empire.[26][59] + +Civil rights activist in South Africa (1893–1914) + +This bronze statue of Gandhi commemorating the centenary of the incident at the Pietermaritzburg Railway Station was unveiled by Archbishop Desmond Tutu on Church Street, Pietermaritzburg, in June 1993 +In April 1893, Gandhi aged 23, set sail for South Africa to be the lawyer for Abdullah's cousin.[59][60] He spent 21 years in South Africa, where he developed his political views, ethics and politics.[61][62] + +Immediately upon arriving in South Africa, Gandhi faced discrimination because of his skin colour and heritage, like all people of colour.[63] He was not allowed to sit with European passengers in the stagecoach and told to sit on the floor near the driver, then beaten when he refused; elsewhere he was kicked into a gutter for daring to walk near a house, in another instance thrown off a train at Pietermaritzburg after refusing to leave the first-class.[64][65] He sat in the train station, shivering all night and pondering if he should return to India or protest for his rights.[65] He chose to protest and was allowed to board the train the next day.[66] In another incident, the magistrate of a Durban court ordered Gandhi to remove his turban, which he refused to do.[67] Indians were not allowed to walk on public footpaths in South Africa. Gandhi was kicked by a police officer out of the footpath onto the street without warning.[68] + +When Gandhi arrived in South Africa, according to Herman, he thought of himself as "a Briton first, and an Indian second".[69] However, the prejudice against him and his fellow Indians from British people that Gandhi experienced and observed deeply bothered him. He found it humiliating, struggling to understand how some people can feel honour or superiority or pleasure in such inhumane practices.[65] Gandhi began to question his people's standing in the British Empire.[70] + +The Abdullah case that had brought him to South Africa concluded in May 1894, and the Indian community organised a farewell party for Gandhi as he prepared to return to India.[71] However, a new Natal government discriminatory proposal led to Gandhi extending his original period of stay in South Africa. He planned to assist Indians in opposing a bill to deny them the right to vote, a right then proposed to be an exclusive European right. He asked Joseph Chamberlain, the British Colonial Secretary, to reconsider his position on this bill.[61] Though unable to halt the bill's passage, his campaign was successful in drawing attention to the grievances of Indians in South Africa. He helped found the Natal Indian Congress in 1894,[26][66] and through this organisation, he moulded the Indian community of South Africa into a unified political force. In January 1897, when Gandhi landed in Durban, a mob of white settlers attacked him[72] and he escaped only through the efforts of the wife of the police superintendent. However, he refused to press charges against any member of the mob.[26] + + +Gandhi with the stretcher-bearers of the Indian Ambulance Corps during the Boer War. +During the Boer War, Gandhi volunteered in 1900 to form a group of stretcher-bearers as the Natal Indian Ambulance Corps. According to Arthur Herman, Gandhi wanted to disprove the imperial British stereotype that Hindus were not fit for "manly" activities involving danger and exertion, unlike the Muslim "martial races".[73] Gandhi raised eleven hundred Indian volunteers, to support British combat troops against the Boers. They were trained and medically certified to serve on the front lines. They were auxiliaries at the Battle of Colenso to a White volunteer ambulance corps. At the battle of Spion Kop Gandhi and his bearers moved to the front line and had to carry wounded soldiers for miles to a field hospital because the terrain was too rough for the ambulances. Gandhi and thirty-seven other Indians received the Queen's South Africa Medal.[74][75] + + +Gandhi (left) and his wife Kasturba (right) (1902) +In 1906, the Transvaal government promulgated a new Act compelling registration of the colony's Indian and Chinese populations. At a mass protest meeting held in Johannesburg on 11 September that year, Gandhi adopted his still evolving methodology of Satyagraha (devotion to the truth), or nonviolent protest, for the first time.[76] According to Anthony Parel, Gandhi was also influenced by the Tamil text Tirukkuṛaḷ because Leo Tolstoy mentioned it in their correspondence that began with "A Letter to a Hindu".[77][78] Gandhi urged Indians to defy the new law and to suffer the punishments for doing so. Gandhi's ideas of protests, persuasion skills and public relations had emerged. He took these back to India in 1915.[79][80] + +Europeans, Indians and Africans +Gandhi focused his attention on Indians while in South Africa. He initially was not interested in politics. This changed, however, after he was discriminated against and bullied, such as by being thrown out of a train coach because of his skin colour by a white train official. After several such incidents with Whites in South Africa, Gandhi's thinking and focus changed, and he felt he must resist this and fight for rights. He entered politics by forming the Natal Indian Congress.[81] According to Ashwin Desai and Goolam Vahed, Gandhi's views on racism are contentious, and in some cases, distressing to those who admire him. Gandhi suffered persecution from the beginning in South Africa. Like with other coloured people, white officials denied him his rights, and the press and those in the streets bullied and called him a "parasite", "semi-barbarous", "canker", "squalid coolie", "yellow man", and other epithets. People would spit on him as an expression of racial hate.[82] + +While in South Africa, Gandhi focused on racial persecution of Indians but ignored those of Africans. In some cases, state Desai and Vahed, his behaviour was one of being a willing part of racial stereotyping and African exploitation.[82] During a speech in September 1896, Gandhi complained that the whites in the British colony of South Africa were degrading Indian Hindus and Muslims to "a level of Kaffir".[83] Scholars cite it as an example of evidence that Gandhi at that time thought of Indians and black South Africans differently.[82] As another example given by Herman, Gandhi, at age 24, prepared a legal brief for the Natal Assembly in 1895, seeking voting rights for Indians. Gandhi cited race history and European Orientalists' opinions that "Anglo-Saxons and Indians are sprung from the same Aryan stock or rather the Indo-European peoples", and argued that Indians should not be grouped with the Africans.[71] + +Years later, Gandhi and his colleagues served and helped Africans as nurses and by opposing racism, according to the Nobel Peace Prize winner Nelson Mandela. The general image of Gandhi, state Desai and Vahed, has been reinvented since his assassination as if he was always a saint when in reality his life was more complex, contained inconvenient truths and was one that evolved over time.[82] In contrast, other Africa scholars state the evidence points to a rich history of co-operation and efforts by Gandhi and Indian people with nonwhite South Africans against persecution of Africans and the Apartheid.[84] + +In 1906, when the British declared war against the Zulu Kingdom in Natal, Gandhi at age 36, sympathised with the Zulus and encouraged the Indian volunteers to help as an ambulance unit.[85] He argued that Indians should participate in the war efforts to change attitudes and perceptions of the British people against the coloured people.[86] Gandhi, a group of 20 Indians and black people of South Africa volunteered as a stretcher-bearer corps to treat wounded British soldiers and the opposite side of the war: Zulu victims.[85] + + +Gandhi photographed in South Africa (1909) +White soldiers stopped Gandhi and team from treating the injured Zulu, and some African stretcher-bearers with Gandhi were shot dead by the British. The medical team commanded by Gandhi operated for less than two months.[85] Gandhi volunteering to help as a "staunch loyalist" during the Zulu and other wars made no difference in the British attitude, states Herman, and the African experience was a part of his great disillusionment with the West, transforming him into an "uncompromising non-cooperator".[86] + +In 1910, Gandhi established, with the help of his friend Hermann Kallenbach, an idealistic community they named Tolstoy Farm near Johannesburg.[87] There he nurtured his policy of peaceful resistance.[88] + +In the years after black South Africans gained the right to vote in South Africa (1994), Gandhi was proclaimed a national hero with numerous monuments.[89] + +Struggle for Indian independence (1915–1947) +See also: Indian independence movement +At the request of Gopal Krishna Gokhale, conveyed to him by C. F. Andrews, Gandhi returned to India in 1915. He brought an international reputation as a leading Indian nationalist, theorist and community organiser. + +Gandhi joined the Indian National Congress and was introduced to Indian issues, politics and the Indian people primarily by Gokhale. Gokhale was a key leader of the Congress Party best known for his restraint and moderation, and his insistence on working inside the system. Gandhi took Gokhale's liberal approach based on British Whiggish traditions and transformed it to make it look Indian.[90] + +Gandhi took leadership of the Congress in 1920 and began escalating demands until on 26 January 1930 the Indian National Congress declared the independence of India. The British did not recognise the declaration but negotiations ensued, with the Congress taking a role in provincial government in the late 1930s. Gandhi and the Congress withdrew their support of the Raj when the Viceroy declared war on Germany in September 1939 without consultation. Tensions escalated until Gandhi demanded immediate independence in 1942 and the British responded by imprisoning him and tens of thousands of Congress leaders. Meanwhile, the Muslim League did co-operate with Britain and moved, against Gandhi's strong opposition, to demands for a totally separate Muslim state of Pakistan. In August 1947 the British partitioned the land with India and Pakistan each achieving independence on terms that Gandhi disapproved.[91] + +Role in World War I +See also: The role of India in World War I +In April 1918, during the latter part of World War I, the Viceroy invited Gandhi to a War Conference in Delhi.[92] Gandhi agreed to actively recruit Indians for the war effort.[93][94] In contrast to the Zulu War of 1906 and the outbreak of World War I in 1914, when he recruited volunteers for the Ambulance Corps, this time Gandhi attempted to recruit combatants. In a June 1918 leaflet entitled "Appeal for Enlistment", Gandhi wrote "To bring about such a state of things we should have the ability to defend ourselves, that is, the ability to bear arms and to use them... If we want to learn the use of arms with the greatest possible despatch, it is our duty to enlist ourselves in the army."[95] He did, however, stipulate in a letter to the Viceroy's private secretary that he "personally will not kill or injure anybody, friend or foe."[96] + +Gandhi's war recruitment campaign brought into question his consistency on nonviolence. Gandhi's private secretary noted that "The question of the consistency between his creed of 'Ahimsa' (nonviolence) and his recruiting campaign was raised not only then but has been discussed ever since."[93] + +Champaran agitations +Main article: Champaran Satyagraha + +Gandhi in 1918, at the time of the Kheda and Champaran Satyagrahas +Gandhi's first major achievement came in 1917 with the Champaran agitation in Bihar. The Champaran agitation pitted the local peasantry against their largely British landlords who were backed by the local administration. The peasantry was forced to grow Indigofera, a cash crop for Indigo dye whose demand had been declining over two decades, and were forced to sell their crops to the planters at a fixed price. Unhappy with this, the peasantry appealed to Gandhi at his ashram in Ahmedabad. Pursuing a strategy of nonviolent protest, Gandhi took the administration by surprise and won concessions from the authorities.[97] + +Kheda agitations +Main article: Kheda Satyagraha +In 1918, Kheda was hit by floods and famine and the peasantry was demanding relief from taxes. Gandhi moved his headquarters to Nadiad,[98] organising scores of supporters and fresh volunteers from the region, the most notable being Vallabhbhai Patel.[99] Using non-co-operation as a technique, Gandhi initiated a signature campaign where peasants pledged non-payment of revenue even under the threat of confiscation of land. A social boycott of mamlatdars and talatdars (revenue officials within the district) accompanied the agitation. Gandhi worked hard to win public support for the agitation across the country. For five months, the administration refused but finally in end-May 1918, the Government gave way on important provisions and relaxed the conditions of payment of revenue tax until the famine ended. In Kheda, Vallabhbhai Patel represented the farmers in negotiations with the British, who suspended revenue collection and released all the prisoners.[100] + +Khilafat movement +Every revolution begins with a single act of defiance. + +In 1919, following World War I, Gandhi (aged 49) sought political co-operation from Muslims in his fight against British imperialism by supporting the Ottoman Empire that had been defeated in the World War. Before this initiative of Gandhi, communal disputes and religious riots between Hindus and Muslims were common in British India, such as the riots of 1917–18. Gandhi had already supported the British crown with resources and by recruiting Indian soldiers to fight the war in Europe on the British side. This effort of Gandhi was in part motivated by the British promise to reciprocate the help with swaraj (self-government) to Indians after the end of World War I.[101] The British government, instead of self government, had offered minor reforms instead, disappointing Gandhi.[102] Gandhi announced his satyagraha (civil disobedience) intentions. The British colonial officials made their counter move by passing the Rowlatt Act, to block Gandhi's movement. The Act allowed the British government to treat civil disobedience participants as criminals and gave it the legal basis to arrest anyone for "preventive indefinite detention, incarceration without judicial review or any need for a trial".[103] + +Gandhi felt that Hindu-Muslim co-operation was necessary for political progress against the British. He leveraged the Khilafat movement, wherein Sunni Muslims in India, their leaders such as the sultans of princely states in India and Ali brothers championed the Turkish Caliph as a solidarity symbol of Sunni Islamic community (ummah). They saw the Caliph as their means to support Islam and the Islamic law after the defeat of Ottoman Empire in World War I.[104][105][106] Gandhi's support to the Khilafat movement led to mixed results. It initially led to a strong Muslim support for Gandhi. However, the Hindu leaders including Rabindranath Tagore questioned Gandhi's leadership because they were largely against recognising or supporting the Sunni Islamic Caliph in Turkey.[103][107][108][109] + +The increasing Muslim support for Gandhi, after he championed the Caliph's cause, temporarily stopped the Hindu-Muslim communal violence. It offered evidence of inter-communal harmony in joint Rowlatt satyagraha demonstration rallies, raising Gandhi's stature as the political leader to the British.[110][111] His support for the Khilafat movement also helped him sideline Muhammad Ali Jinnah, who had announced his opposition to the satyagraha non-co-operation movement approach of Gandhi. Jinnah began creating his independent support, and later went on to lead the demand for West and East Pakistan. Though they agreed in general terms on Indian independence, they disagreed on the means of achieving this. Jinnah was mainly interested in dealing with the British via constitutional negotiation, rather than attempting to agitate the masses.[112][113][114] + +By the end of 1922 the Khilafat movement had collapsed.[115] Turkey's Atatürk had ended the Caliphate, Khilafat movement ended, and Muslim support for Gandhi largely evaporated.[105][106] Muslim leaders and delegates abandoned Gandhi and his Congress.[116] Hindu-Muslim communal conflicts reignited. Deadly religious riots re-appeared in numerous cities, with 91 in United Provinces of Agra and Oudh alone.[117][118] + +Non-co-operation +Main article: Non-co-operation movement +With his book Hind Swaraj (1909) Gandhi, aged 40, declared that British rule was established in India with the co-operation of Indians and had survived only because of this co-operation. If Indians refused to co-operate, British rule would collapse and swaraj would come.[119] + + +Gandhi with Dr. Annie Besant en route to a meeting in Madras in September 1921. Earlier, in Madurai, on 21 September 1921, Gandhi had adopted the loin-cloth for the first time as a symbol of his identification with India's poor. +In February 1919, Gandhi cautioned the Viceroy of India with a cable communication that if the British were to pass the Rowlatt Act, he would appeal to Indians to start civil disobedience.[120] The British government ignored him and passed the law, stating it would not yield to threats. The satyagraha civil disobedience followed, with people assembling to protest the Rowlatt Act. On 30 March 1919, British law officers opened fire on an assembly of unarmed people, peacefully gathered, participating in satyagraha in Delhi.[120] + +People rioted in retaliation. On 6 April 1919, a Hindu festival day, he asked a crowd to remember not to injure or kill British people, but to express their frustration with peace, to boycott British goods and burn any British clothing they owned. He emphasised the use of non-violence to the British and towards each other, even if the other side uses violence. Communities across India announced plans to gather in greater numbers to protest. Government warned him to not enter Delhi. Gandhi defied the order. On 9 April, Gandhi was arrested.[120] + +People rioted. On 13 April 1919, people including women with children gathered in an Amritsar park, and a British officer named Reginald Dyer surrounded them and ordered his troops to fire on them. The resulting Jallianwala Bagh massacre (or Amritsar massacre) of hundreds of Sikh and Hindu civilians enraged the subcontinent, but was cheered by some Britons and parts of the British media as an appropriate response. Gandhi in Ahmedabad, on the day after the massacre in Amritsar, did not criticise the British and instead criticised his fellow countrymen for not exclusively using love to deal with the hate of the British government.[120] Gandhi demanded that people stop all violence, stop all property destruction, and went on fast-to-death to pressure Indians to stop their rioting.[121] + +The massacre and Gandhi's non-violent response to it moved many, but also made some Sikhs and Hindus upset that Dyer was getting away with murder. Investigation committees were formed by the British, which Gandhi asked Indians to boycott.[120] The unfolding events, the massacre and the British response, led Gandhi to the belief that Indians will never get a fair equal treatment under British rulers, and he shifted his attention to Swaraj or self rule and political independence for India.[122] In 1921, Gandhi was the leader of the Indian National Congress.[106] He reorganised the Congress. With Congress now behind him, and Muslim support triggered by his backing the Khilafat movement to restore the Caliph in Turkey,[106] Gandhi had the political support and the attention of the British Raj.[109][103][105] + + +Gandhi spinning yarn, in the late 1920s +Gandhi expanded his nonviolent non-co-operation platform to include the swadeshi policy – the boycott of foreign-made goods, especially British goods. Linked to this was his advocacy that khadi (homespun cloth) be worn by all Indians instead of British-made textiles. Gandhi exhorted Indian men and women, rich or poor, to spend time each day spinning khadi in support of the independence movement.[123] In addition to boycotting British products, Gandhi urged the people to boycott British institutions and law courts, to resign from government employment, and to forsake British titles and honours. Gandhi thus began his journey aimed at crippling the British India government economically, politically and administratively.[124] + +The appeal of "Non-cooperation" grew, its social popularity drew participation from all strata of Indian society. Gandhi was arrested on 10 March 1922, tried for sedition, and sentenced to six years' imprisonment. He began his sentence on 18 March 1922. With Gandhi isolated in prison, the Indian National Congress split into two factions, one led by Chitta Ranjan Das and Motilal Nehru favouring party participation in the legislatures, and the other led by Chakravarti Rajagopalachari and Sardar Vallabhbhai Patel, opposing this move.[125] Furthermore, co-operation among Hindus and Muslims ended as Khilafat movement collapsed with the rise of Atatürk in Turkey. Muslim leaders left the Congress and began forming Muslim organisations. The political base behind Gandhi had broken into factions. Gandhi was released in February 1924 for an appendicitis operation, having served only two years.[126] + +Salt Satyagraha (Salt March) +Main article: Salt Satyagraha +File:Salt March.ogv +Original footage of Gandhi and his followers marching to Dandi in the Salt Satyagraha +After his early release from prison for political crimes in 1924, over the second half of the 1920s, Gandhi continued to pursue swaraj. He pushed through a resolution at the Calcutta Congress in December 1928 calling on the British government to grant India dominion status or face a new campaign of non-co-operation with complete independence for the country as its goal.[127] After his support for the World War I with Indian combat troops, and the failure of Khilafat movement in preserving the rule of Caliph in Turkey, followed by a collapse in Muslim support for his leadership, some such as Subhas Chandra Bose and Bhagat Singh questioned his values and non-violent approach.[105][128] While many Hindu leaders championed a demand for immediate independence, Gandhi revised his own call to a one-year wait, instead of two.[127] + +The British did not respond favourably to Gandhi's proposal. British political leaders such as Lord Birkenhead and Winston Churchill announced opposition to "the appeasers of Gandhi", in their discussions with European diplomats who sympathised with Indian demands.[129] On 31 December 1929, the flag of India was unfurled in Lahore. Gandhi led Congress celebrated 26 January 1930 as India's Independence Day in Lahore. This day was commemorated by almost every other Indian organisation. Gandhi then launched a new Satyagraha against the tax on salt in March 1930. Gandhi sent an ultimatum in the form of a polite letter to the viceroy of India, Lord Irwin, on 2 March. Gandhi condemned British rule in the letter, describing it as "a curse" that "has impoverished the dumb millions by a system of progressive exploitation and by a ruinously expensive military and civil administration... It has reduced us politically to serfdom." Gandhi also mentioned in the letter that the viceroy received a salary "over five thousand times India's average income."[130] British violence, Gandhi promised, was going to be defeated by Indian non-violence. + +This was highlighted by the Salt March to Dandi from 12 March to 6 April, where, together with 78 volunteers, he marched 388 kilometres (241 mi) from Ahmedabad to Dandi, Gujarat to make salt himself, with the declared intention of breaking the salt laws. The march took 25 days to cover 240 miles with Gandhi speaking to often huge crowds along the way. Thousands of Indians joined him in Dandi. On 5 May he was interned under a regulation dating from 1827 in anticipation of a protest that he had planned. The protest at Dharasana salt works on 21 May went ahead without its leader, Gandhi. A horrified American journalist, Webb Miller, described the British response thus: + +In complete silence the Gandhi men drew up and halted a hundred yards from the stockade. A picked column advanced from the crowd, waded the ditches and approached the barbed wire stockade... at a word of command, scores of native policemen rushed upon the advancing marchers and rained blows on their heads with their steel-shot lathis [long bamboo sticks]. Not one of the marchers even raised an arm to fend off blows. They went down like ninepins. From where I stood I heard the sickening whack of the clubs on unprotected skulls... Those struck down fell sprawling, unconscious or writhing with fractured skulls or broken shoulders.[131] + +This went on for hours until some 300 or more protesters had been beaten, many seriously injured and two killed. At no time did they offer any resistance. + +This campaign was one of his most successful at upsetting British hold on India; Britain responded by imprisoning over 60,000 people.[132] Congress estimates, however, put the figure at 90,000. Among them was one of Gandhi's lieutenants, Jawaharlal Nehru. + +According to Sarma, Gandhi recruited women to participate in the salt tax campaigns and the boycott of foreign products, which gave many women a new self-confidence and dignity in the mainstream of Indian public life.[133] However, other scholars such as Marilyn French state that Gandhi barred women from joining his civil disobedience movement because he feared he would be accused of using women as political shield.[134] When women insisted that they join the movement and public demonstrations, according to Thapar-Bjorkert, Gandhi asked the volunteers to get permissions of their guardians and only those women who can arrange child-care should join him.[135] Regardless of Gandhi's apprehensions and views, Indian women joined the Salt March by the thousands to defy the British salt taxes and monopoly on salt mining. After Gandhi's arrest, the women marched and picketed shops on their own, accepting violence and verbal abuse from British authorities for the cause in a manner Gandhi inspired.[134] + +Gandhi as folk hero + +Indian workers on strike in support of Gandhi in 1930. +According to Atlury Murali, Indian Congress in the 1920s appealed to Andhra Pradesh peasants by creating Telugu language plays that combined Indian mythology and legends, linked them to Gandhi's ideas, and portrayed Gandhi as a messiah, a reincarnation of ancient and medieval Indian nationalist leaders and saints. The plays built support among peasants steeped in traditional Hindu culture, according to Murali, and this effort made Gandhi a folk hero in Telugu speaking villages, a sacred messiah-like figure.[136] + +According to Dennis Dalton, it was the ideas that were responsible for his wide following. Gandhi criticised Western civilisation as one driven by "brute force and immorality", contrasting it with his categorisation of Indian civilisation as one driven by "soul force and morality".[137] Gandhi captured the imagination of the people of his heritage with his ideas about winning "hate with love". These ideas are evidenced in his pamphlets from the 1890s, in South Africa, where too he was popular among the Indian indentured workers. After he returned to India, people flocked to him because he reflected their values.[137] + +Gandhi also campaigned hard going from one rural corner of the Indian subcontinent to another. He used terminology and phrases such as Rama-rajya from Ramayana, Prahlada as a paradigmatic icon, and such cultural symbols as another facet of swaraj and satyagraha.[138] These ideas sounded strange outside India, during his lifetime, but they readily and deeply resonated with the culture and historic values of his people.[137][139] + +Negotiations +The government, represented by Lord Irwin, decided to negotiate with Gandhi. The Gandhi–Irwin Pact was signed in March 1931. The British Government agreed to free all political prisoners, in return for the suspension of the civil disobedience movement. According to the pact, Gandhi was invited to attend the Round Table Conference in London for discussions and as the sole representative of the Indian National Congress. The conference was a disappointment to Gandhi and the nationalists. Gandhi expected to discuss India's independence, while the British side focused on the Indian princes and Indian minorities rather than on a transfer of power. Lord Irwin's successor, Lord Willingdon, took a hard line against India as an independent nation, began a new campaign of controlling and subduing the nationalist movement. Gandhi was again arrested, and the government tried and failed to negate his influence by completely isolating him from his followers.[140] + +In Britain, Winston Churchill, a prominent Conservative politician who was then out of office but later became its prime minister, became a vigorous and articulate critic of Gandhi and opponent of his long-term plans. Churchill often ridiculed Gandhi, saying in a widely reported 1931 speech: + +It is alarming and also nauseating to see Mr Gandhi, a seditious Middle Temple lawyer, now posing as a fakir of a type well known in the East, striding half-naked up the steps of the Vice-regal palace....to parley on equal terms with the representative of the King-Emperor.[141] + +Churchill's bitterness against Gandhi grew in the 1930s. He called Gandhi as the one who was "seditious in aim" whose evil genius and multiform menace was attacking the British empire. Churchill called him a dictator, a "Hindu Mussolini", fomenting a race war, trying to replace the Raj with Brahmin cronies, playing on the ignorance of Indian masses, all for selfish gain.[142] Churchill attempted to isolate Gandhi, and his criticism of Gandhi was widely covered by European and American press. It gained Churchill sympathetic support, but it also increased support for Gandhi among Europeans. The developments heightened Churchill's anxiety that the "British themselves would give up out of pacifism and misplaced conscience".[142] + +Round Table Conferences + +Gandhi and his personal assistant Mahadev Desai at Birla House, 1939 +During the discussions between Gandhi and the British government over 1931–32 at the Round Table Conferences, Gandhi, now aged about 62, sought constitutional reforms as a preparation to the end of colonial British rule, and begin the self-rule by Indians.[143] The British side sought reforms that would keep Indian subcontinent as a colony. The British negotiators proposed constitutional reforms on a British Dominion model that established separate electorates based on religious and social divisions. The British questioned the Congress party and Gandhi's authority to speak for all of India.[144] They invited Indian religious leaders, such as Muslims and Sikhs, to press their demands along religious lines, as well as B. R. Ambedkar as the representative leader of the untouchables.[143] Gandhi vehemently opposed a constitution that enshrined rights or representations based on communal divisions, because he feared that it would not bring people together but divide them, perpetuate their status and divert the attention from India's struggle to end the colonial rule.[145][146] + +The Second Round Table conference was the only time he left India between 1914 and his death in 1948. He declined the government's offer of accommodation in an expensive West End hotel, preferring to stay in the East End, to live among working-class people, as he did in India.[147] He based himself in a small cell-bedroom at Kingsley Hall for the three-month duration of his stay and was enthusiastically received by East Enders.[148] During this time he renewed his links with the British vegetarian movement. + +After Gandhi returned from the Second Round Table conference, he started a new satyagraha. He was arrested and imprisoned at the Yerwada Jail, Pune. While he was in prison, the British government enacted a new law that granted untouchables a separate electorate. It came to be known as the Communal Award.[149] In protest, Gandhi started a fast-unto-death, while he was held in prison.[150] The resulting public outcry forced the government, in consultations with Ambedkar, to replace the Communal Award with a compromise Poona Pact.[151][152] + +Congress politics +In 1934 Gandhi resigned from Congress party membership. He did not disagree with the party's position but felt that if he resigned, his popularity with Indians would cease to stifle the party's membership, which actually varied, including communists, socialists, trade unionists, students, religious conservatives, and those with pro-business convictions, and that these various voices would get a chance to make themselves heard. Gandhi also wanted to avoid being a target for Raj propaganda by leading a party that had temporarily accepted political accommodation with the Raj.[153] + +Gandhi returned to active politics again in 1936, with the Nehru presidency and the Lucknow session of the Congress. Although Gandhi wanted a total focus on the task of winning independence and not speculation about India's future, he did not restrain the Congress from adopting socialism as its goal. Gandhi had a clash with Subhas Chandra Bose, who had been elected president in 1938, and who had previously expressed a lack of faith in nonviolence as a means of protest.[154] Despite Gandhi's opposition, Bose won a second term as Congress President, against Gandhi's nominee, Dr. Pattabhi Sitaramayya; but left the Congress when the All-India leaders resigned en masse in protest of his abandonment of the principles introduced by Gandhi.[155][156] Gandhi declared that Sitaramayya's defeat was his defeat.[157] + +World War II and Quit India movement +Main article: Quit India Movement + +Jawaharlal Nehru and Gandhi in 1946 +Gandhi opposed providing any help to the British war effort and he campaigned against any Indian participation in the World War II.[158] Gandhi's campaign did not enjoy the support of Indian masses and many Indian leaders such as Sardar Patel and Rajendra Prasad. His campaign was a failure.[158] Over 2.5 million Indians ignored Gandhi, volunteered and joined the British military to fight on various fronts of the allied forces.[158] + +Gandhi opposition to the Indian participation in the World War II was motivated by his belief that India could not be party to a war ostensibly being fought for democratic freedom while that freedom was denied to India itself.[159] He also condemned Nazism and Fascism, a view which won endorsement of other Indian leaders. As the war progressed, Gandhi intensified his demand for independence, calling for the British to Quit India in a 1942 speech in Mumbai.[160] This was Gandhi's and the Congress Party's most definitive revolt aimed at securing the British exit from India.[161] The British government responded quickly to the Quit India speech, and within hours after Gandhi's speech arrested Gandhi and all the members of the Congress Working Committee.[162] His countrymen retaliated the arrests by damaging or burning down hundreds of government owned railway stations, police stations, and cutting down telegraph wires.[163] + +In 1942, Gandhi now nearing age 73, urged his people to completely stop co-operating with the imperial government. In this effort, he urged that they neither kill nor injure British people, but be willing to suffer and die if violence is initiated by the British officials.[160] He clarified that the movement would not be stopped because of any individual acts of violence, saying that the "ordered anarchy" of "the present system of administration" was "worse than real anarchy."[164][165] He urged Indians to Karo ya maro ("Do or die") in the cause of their rights and freedoms.[160][166] + + +Gandhi in 1942, the year he launched the Quit India Movement +Gandhi's arrest lasted two years, as he was held in the Aga Khan Palace in Pune. During this period, his long time secretary Mahadev Desai died of a heart attack, his wife Kasturba died after 18 months' imprisonment on 22 February 1944; and Gandhi suffered a severe malaria attack.[163] While in jail, he agreed to an interview with Stuart Gelder, a British journalist. Gelder then composed and released an interview summary, cabled it to the mainstream press, that announced sudden concessions Gandhi was willing to make, comments that shocked his countrymen, the Congress workers and even Gandhi. The latter two claimed that it distorted what Gandhi actually said on a range of topics and falsely repudiated the Quit India movement.[163] + +Gandhi was released before the end of the war on 6 May 1944 because of his failing health and necessary surgery; the Raj did not want him to die in prison and enrage the nation. He came out of detention to an altered political scene – the Muslim League for example, which a few years earlier had appeared marginal, "now occupied the centre of the political stage"[167] and the topic of Muhammad Ali Jinnah's campaign for Pakistan was a major talking point. Gandhi and Jinnah had extensive correspondence and the two men met several times over a period of two weeks in September 1944, where Gandhi insisted on a united religiously plural and independent India which included Muslims and non-Muslims of the Indian subcontinent coexisting. Jinnah rejected this proposal and insisted instead for partitioning the subcontinent on religious lines to create a separate Muslim India (later Pakistan).[10][168] These discussions continued through 1947.[169] + +While the leaders of Congress languished in jail, the other parties supported the war and gained organizational strength. Underground publications flailed at the ruthless suppression of Congress, but it had little control over events.[170] At the end of the war, the British gave clear indications that power would be transferred to Indian hands. At this point Gandhi called off the struggle, and around 100,000 political prisoners were released, including the Congress's leadership.[171] + +Partition and independence +See also: Indian independence movement and Partition of India + +Gandhi with Muhammad Ali Jinnah in 1944 +Gandhi opposed the partition of the Indian subcontinent along religious lines.[172] The Indian National Congress and Gandhi called for the British to Quit India. However, the Muslim League demanded "Divide and Quit India".[173][174] Gandhi suggested an agreement which required the Congress and the Muslim League to co-operate and attain independence under a provisional government, thereafter, the question of partition could be resolved by a plebiscite in the districts with a Muslim majority.[175] + +Jinnah rejected Gandhi's proposal and called for Direct Action Day, on 16 August 1946, to press Muslims to publicly gather in cities and support his proposal for the partition of the Indian subcontinent into a Muslim state and non-Muslim state. Huseyn Shaheed Suhrawardy, the Muslim League Chief Minister of Bengal – now Bangladesh and West Bengal, gave Calcutta's police special holiday to celebrate the Direct Action Day.[176] The Direct Action Day triggered a mass murder of Calcutta Hindus and the torching of their property, and holidaying police were missing to contain or stop the conflict.[177] The British government did not order its army to move in to contain the violence.[176] The violence on Direct Action Day led to retaliatory violence against Muslims across India. Thousands of Hindus and Muslims were murdered, and tens of thousands were injured in the cycle of violence in the days that followed.[178] Gandhi visited the most riot-prone areas to appeal a stop to the massacres.[177] + + +Gandhi in 1947, with Lord Louis Mountbatten, Britain's last Viceroy of India, and his wife Edwina Mountbatten +Archibald Wavell, the Viceroy and Governor-General of British India for three years through February 1947, had worked with Gandhi and Jinnah to find a common ground, before and after accepting Indian independence in principle. Wavell condemned Gandhi's character and motives as well as his ideas. Wavell accused Gandhi of harbouring the single minded idea to "overthrow British rule and influence and to establish a Hindu raj", and called Gandhi a "malignant, malevolent, exceedingly shrewd" politician.[179] Wavell feared a civil war on the Indian subcontinent, and doubted Gandhi would be able to stop it.[179] + +The British reluctantly agreed to grant independence to the people of the Indian subcontinent, but accepted Jinnah's proposal of partitioning the land into Pakistan and India. Gandhi was involved in the final negotiations, but Stanley Wolpert states the "plan to carve up British India was never approved of or accepted by Gandhi".[180] + +The partition was controversial and violently disputed. More than half a million were killed in religious riots as 10 million to 12 million non-Muslims (Hindus and Sikhs mostly) migrated from Pakistan into India, and Muslims migrated from India into Pakistan, across the newly created borders of India, West Pakistan and East Pakistan.[181] + +Gandhi spent the day of independence not celebrating the end of the British rule but appealing for peace among his countrymen by fasting and spinning in Calcutta on 15 August 1947. The partition had gripped the Indian subcontinent with religious violence and the streets were filled with corpses.[182] Some writers credit Gandhi's fasting and protests for stopping the religious riots and communal violence.[179] + +Death +Main article: Assassination of Mahatma Gandhi +At 5:17 pm on 30 January 1948, Gandhi was with his grandnieces in the garden of Birla House (now Gandhi Smriti), on his way to address a prayer meeting, when Nathuram Godse, a Hindu nationalist, fired three bullets into his chest from a pistol at close range. According to some accounts, Gandhi died instantly.[183][184] In other accounts, such as one prepared by an eyewitness journalist, Gandhi was carried into the Birla House, into a bedroom. There he died about 30 minutes later as one of Gandhi's family members read verses from Hindu scriptures.[185] + +Prime Minister Jawaharlal Nehru addressed his countrymen over the All-India Radio saying:[186] + +Friends and comrades, the light has gone out of our lives, and there is darkness everywhere, and I do not quite know what to tell you or how to say it. Our beloved leader, Bapu as we called him, the father of the nation, is no more. Perhaps I am wrong to say that; nevertheless, we will not see him again, as we have seen him for these many years, we will not run to him for advice or seek solace from him, and that is a terrible blow, not only for me, but for millions and millions in this country.[187] + + +Memorial where Gandhi was assassinated in 1948. His stylised footsteps lead to the memorial. +Godse, a Hindu nationalist with links to the extremist Hindu Mahasabha,[188] made no attempt to escape; several other conspirators were soon arrested as well.[189][190] They were tried in court at Delhi's Red Fort. At his trial, Godse did not deny the charges nor express any remorse. According to Claude Markovits, a French historian noted for his studies of colonial India, Godse stated that he killed Gandhi because of his complacence towards Muslims, holding Gandhi responsible for the frenzy of violence and sufferings during the subcontinent's partition into Pakistan and India. Godse accused Gandhi of subjectivism and of acting as if only he had a monopoly of the truth. Godse was found guilty and executed in 1949.[191][192] + + +Gandhi's funeral was marked by millions of Indians.[193] +Gandhi's death was mourned nationwide. Over a million people joined the five-mile-long funeral procession that took over five hours to reach Raj Ghat from Birla house, where he was assassinated, and another million watched the procession pass by.[193] Gandhi's body was transported on a weapons carrier, whose chassis was dismantled overnight to allow a high-floor to be installed so that people could catch a glimpse of his body. The engine of the vehicle was not used; instead four drag-ropes manned by 50 people each pulled the vehicle.[194] All Indian-owned establishments in London remained closed in mourning as thousands of people from all faiths and denominations and Indians from all over Britain converged at India House in London.[195] + +Gandhi's assassination dramatically changed the political landscape. Nehru became his political heir. According to Markovits, while Gandhi was alive, Pakistan's declaration that it was a "Muslim state" had led Indian groups to demand that it be declared a "Hindu state".[191] Nehru used Gandhi's martyrdom as a political weapon to silence all advocates of Hindu nationalism as well as his political challengers. He linked Gandhi's assassination to politics of hatred and ill-will.[191] + +According to Guha, Nehru and his Congress colleagues called on Indians to honour Gandhi's memory and even more his ideals.[196][197] Nehru used the assassination to consolidate the authority of the new Indian state. Gandhi's death helped marshal support for the new government and legitimise the Congress Party's control, leveraged by the massive outpouring of Hindu expressions of grief for a man who had inspired them for decades. The government suppressed the RSS, the Muslim National Guards, and the Khaksars, with some 200,000 arrests.[198] + +For years after the assassination, states Markovits, "Gandhi's shadow loomed large over the political life of the new Indian Republic". The government quelled any opposition to its economic and social policies, despite these being contrary to Gandhi's ideas, by reconstructing Gandhi's image and ideals.[199] + +Funeral and memorials +Gandhi was cremated in accordance with Hindu tradition. Gandhi's ashes were poured into urns which were sent across India for memorial services.[200] Most of the ashes were immersed at the Sangam at Allahabad on 12 February 1948, but some were secretly taken away. In 1997, Tushar Gandhi immersed the contents of one urn, found in a bank vault and reclaimed through the courts, at the Sangam at Allahabad.[201][202] Some of Gandhi's ashes were scattered at the source of the Nile River near Jinja, Uganda, and a memorial plaque marks the event. On 30 January 2008, the contents of another urn were immersed at Girgaum Chowpatty. Another urn is at the palace of the Aga Khan in Pune (where Gandhi was held as a political prisoner from 1942 to 1944) and another in the Self-Realization Fellowship Lake Shrine in Los Angeles.[201][203] + +The Birla House site where Gandhi was assassinated is now a memorial called Gandhi Smriti. The place near Yamuna river where he was cremated is the Rāj Ghāt memorial in New Delhi.[204] A black marble platform, it bears the epigraph "Hē Rāma" (Devanagari: हे ! राम or, Hey Raam). These are widely believed to be Gandhi's last words after he was shot, though the veracity of this statement has been disputed.[205] + +Principles, practices, and beliefs +Gandhi's statements, letters and life have attracted much political and scholarly analysis of his principles, practices and beliefs, including what influenced him. Some writers present him as a paragon of ethical living and pacifism, while others present him as a more complex, contradictory and evolving character influenced by his culture and circumstances.[206][207] + +Influences + +Gandhi with poet Rabindranath Tagore, 1940 +Gandhi grew up in a Hindu and Jain religious atmosphere in his native Gujarat, which were his primary influences, but he was also influenced by his personal reflections and literature of Hindu Bhakti saints, Advaita Vedanta, Islam, Buddhism, Christianity, and thinkers such as Tolstoy, Ruskin and Thoreau.[208][209] At age 57 he declared himself to be Advaitist Hindu in his religious persuasion, but added that he supported Dvaitist viewpoints and religious pluralism.[210][211][212] + +Gandhi was influenced by his devout Vaishnava Hindu mother, the regional Hindu temples and saint tradition which co-existed with Jain tradition in Gujarat.[208][213] Historian R.B. Cribb states that Gandhi's thought evolved over time, with his early ideas becoming the core or scaffolding for his mature philosophy. He committed himself early to truthfulness, temperance, chastity, and vegetarianism.[214] + +Gandhi's London lifestyle incorporated the values he had grown up with. When he returned to India in 1891, his outlook was parochial and he could not make a living as a lawyer. This challenged his belief that practicality and morality necessarily coincided. By moving in 1893 to South Africa he found a solution to this problem and developed the central concepts of his mature philosophy.[215] + +According to Bhikhu Parekh, three books that influenced Gandhi most in South Africa were William Salter's Ethical Religion (1889); Henry David Thoreau's On the Duty of Civil Disobedience (1849); and Leo Tolstoy's The Kingdom of God Is Within You (1894). Ruskin inspired his decision to live an austere life on a commune, at first on the Phoenix Farm in Natal and then on the Tolstoy Farm just outside Johannesburg, South Africa.[63] The most profound influence on Gandhi were those from Hinduism, Christianity and Jainism, states Parekh, with his thoughts "in harmony with the classical Indian traditions, specially the Advaita or monistic tradition".[216] + +According to Indira Carr and others, Gandhi was influenced by Vaishnavism, Jainism and Advaita Vedanta.[217][218] Balkrishna Gokhale states that Gandhi was influenced by Hinduism and Jainism, and his studies of Sermon on the Mount of Christianity, Ruskin and Tolstoy.[219] + +Additional theories of possible influences on Gandhi have been proposed. For example, in 1935, N. A. Toothi stated that Gandhi was influenced by the reforms and teachings of the Swaminarayan tradition of Hinduism. According to Raymond Williams, Toothi may have overlooked the influence of the Jain community, and adds close parallels do exist in programs of social reform in the Swaminarayan tradition and those of Gandhi, based on "nonviolence, truth-telling, cleanliness, temperance and upliftment of the masses."[220][221] Historian Howard states the culture of Gujarat influenced Gandhi and his methods.[222] + +Leo Tolstoy + +Mohandas K. Gandhi and other residents of Tolstoy Farm, South Africa, 1910 +Along with the book mentioned above, in 1908 Leo Tolstoy wrote A Letter to a Hindu, which said that only by using love as a weapon through passive resistance could the Indian people overthrow colonial rule. In 1909, Gandhi wrote to Tolstoy seeking advice and permission to republish A Letter to a Hindu in Gujarati. Tolstoy responded and the two continued a correspondence until Tolstoy's death in 1910 (Tolstoy's last letter was to Gandhi).[223] The letters concern practical and theological applications of nonviolence.[224] Gandhi saw himself a disciple of Tolstoy, for they agreed regarding opposition to state authority and colonialism; both hated violence and preached non-resistance. However, they differed sharply on political strategy. Gandhi called for political involvement; he was a nationalist and was prepared to use nonviolent force. He was also willing to compromise.[225] It was at Tolstoy Farm where Gandhi and Hermann Kallenbach systematically trained their disciples in the philosophy of nonviolence.[226] + +Shrimad Rajchandra +Gandhi credited Shrimad Rajchandra, a poet and Jain philosopher, as his influential counsellor. In Modern Review, June 1930, Gandhi wrote about their first encounter in 1891 at Dr. P.J. Mehta's residence in Bombay. He was introduced to Shrimad by Dr. Pranjivan Mehta.[227] Gandhi exchanged letters with Rajchandra when he was in South Africa, referring to him as Kavi (literally, "poet"). In 1930, Gandhi wrote, "Such was the man who captivated my heart in religious matters as no other man ever has till now."[228] 'I have said elsewhere that in moulding my inner life Tolstoy and Ruskin vied with Kavi. But Kavi's influence was undoubtedly deeper if only because I had come in closest personal touch with him.'[229] + +Gandhi, in his autobiography, called Rajchandra his "guide and helper" and his "refuge [...] in moments of spiritual crisis". He had advised Gandhi to be patient and to study Hinduism deeply.[230][231][232] + +Religious texts +During his stay in South Africa, along with scriptures and philosophical texts of Hinduism and other Indian religions, Gandhi read translated texts of Christianity such as the Bible, and Islam such as the Quran.[233] A Quaker mission in South Africa attempted to convert him to Christianity. Gandhi joined them in their prayers and debated Christian theology with them, but refused conversion stating he did not accept the theology therein or that Christ was the only son of God.[233][234][235] + +His comparative studies of religions and interaction with scholars, led him to respect all religions as well as become concerned about imperfections in all of them and frequent misinterpretations.[233] Gandhi grew fond of Hinduism, and referred to the Bhagavad Gita as his spiritual dictionary and greatest single influence on his life.[233][236][237] Later, Gandhi translated the Gita into Gujarati in 1930.[238] + +Sufism +Gandhi was acquainted with Sufi Islam's Chishti Order during his stay in South Africa. He attended Khanqah gatherings there at Riverside. According to Margaret Chatterjee, Gandhi as a Vaishnava Hindu shared values such as humility, devotion and brotherhood for the poor that is also found in Sufism.[239][240] Winston Churchill also compared Gandhi to a Sufi fakir.[141] + +On wars and nonviolence +Support for wars +Gandhi participated in the South African war against the Boers, on the British side in 1899.[241] Both the Dutch settlers called Boers and the imperial British at that time discriminated against the coloured races they considered as inferior, and Gandhi later wrote about his conflicted beliefs during the Boer war. He stated that "when the war was declared, my personal sympathies were all with the Boers, but my loyalty to the British rule drove me to participation with the British in that war". According to Gandhi, he felt that since he was demanding his rights as a British citizen, it was also his duty to serve the British forces in the defence of the British Empire.[242][243] + +During World War I (1914–1918), nearing the age of 50, Gandhi supported the British and its allied forces by recruiting Indians to join the British army, expanding the Indian contingent from about 100,000 to over 1.1 million.[102][241] He encouraged Indian people to fight on one side of the war in Europe and Africa at the cost of their lives.[241] Pacifists criticised and questioned Gandhi, who defended these practices by stating, according to Sankar Ghose, "it would be madness for me to sever my connection with the society to which I belong".[241] According to Keith Robbins, the recruitment effort was in part motivated by the British promise to reciprocate the help with swaraj (self-government) to Indians after the end of World War I.[101] After the war, the British government offered minor reforms instead, which disappointed Gandhi.[102] He launched his satyagraha movement in 1919. In parallel, Gandhi's fellowmen became sceptical of his pacifist ideas and were inspired by the ideas of nationalism and anti-imperialism.[244] + +In a 1920 essay, after the World War I, Gandhi wrote, "where there is only a choice between cowardice and violence, I would advise violence." Rahul Sagar interprets Gandhi's efforts to recruit for the British military during the War, as Gandhi's belief that, at that time, it would demonstrate that Indians were willing to fight. Further, it would also show the British that his fellow Indians were "their subjects by choice rather than out of cowardice." In 1922, Gandhi wrote that abstinence from violence is effective and true forgiveness only when one has the power to punish, not when one decides not to do anything because one is helpless.[245] + +After World War II engulfed Britain, Gandhi actively campaigned to oppose any help to the British war effort and any Indian participation in the war. According to Arthur Herman, Gandhi believed that his campaign would strike a blow to imperialism.[158] Gandhi's position was not supported by many Indian leaders, and his campaign against the British war effort was a failure. The Hindu leader, Tej Bahadur Sapru, declared in 1941, states Herman, "A good many Congress leaders are fed up with the barren program of the Mahatma".[158] Over 2.5 million Indians ignored Gandhi, volunteered and joined on the British side. They fought and died as a part of the Allied forces in Europe, North Africa and various fronts of the World War II.[158] + +Truth and Satyagraha + +Plaque displaying one of Gandhi's quotes on rumour +Gandhi dedicated his life to discovering and pursuing truth, or Satya, and called his movement satyagraha, which means "appeal to, insistence on, or reliance on the Truth".[246] The first formulation of the satyagraha as a political movement and principle occurred in 1920, which he tabled as "Resolution on Non-cooperation" in September that year before a session of the Indian Congress. It was the satyagraha formulation and step, states Dennis Dalton, that deeply resonated with beliefs and culture of his people, embedded him into the popular consciousness, transforming him quickly into Mahatma.[247] + + +"God is truth. The way to truth lies through ahimsa (nonviolence)" – Sabarmati, 13 March 1927 +Gandhi based Satyagraha on the Vedantic ideal of self-realization, ahimsa (nonviolence), vegetarianism, and universal love. William Borman states that the key to his satyagraha is rooted in the Hindu Upanishadic texts.[248] According to Indira Carr, Gandhi's ideas on ahimsa and satyagraha were founded on the philosophical foundations of Advaita Vedanta.[249] I. Bruce Watson states that some of these ideas are found not only in traditions within Hinduism, but also in Jainism or Buddhism, particularly those about non-violence, vegetarianism and universal love, but Gandhi's synthesis was to politicise these ideas.[250] Gandhi's concept of satya as a civil movement, states Glyn Richards, are best understood in the context of the Hindu terminology of Dharma and Ṛta.[251] + +Gandhi stated that the most important battle to fight was overcoming his own demons, fears, and insecurities. Gandhi summarised his beliefs first when he said "God is Truth". He would later change this statement to "Truth is God". Thus, satya (truth) in Gandhi's philosophy is "God".[252] Gandhi, states Richards, described the term "God" not as a separate power, but as the Being (Brahman, Atman) of the Advaita Vedanta tradition, a nondual universal that pervades in all things, in each person and all life.[251] According to Nicholas Gier, this to Gandhi meant the unity of God and humans, that all beings have the same one soul and therefore equality, that atman exists and is same as everything in the universe, ahimsa (non-violence) is the very nature of this atman.[253] + + +Gandhi picking salt during Salt Satyagraha to defy colonial law giving salt collection monopoly to the British.[254] His satyagraha attracted vast numbers of Indian men and women.[255] +The essence of Satyagraha is "soul force" as a political means, refusing to use brute force against the oppressor, seeking to eliminate antagonisms between the oppressor and the oppressed, aiming to transform or "purify" the oppressor. It is not inaction but determined passive resistance and non-co-operation where, states Arthur Herman, "love conquers hate".[256] A euphemism sometimes used for Satyagraha is that it is a "silent force" or a "soul force" (a term also used by Martin Luther King Jr. during his "I Have a Dream" speech). It arms the individual with moral power rather than physical power. Satyagraha is also termed a "universal force", as it essentially "makes no distinction between kinsmen and strangers, young and old, man and woman, friend and foe."[257] + +Gandhi wrote: "There must be no impatience, no barbarity, no insolence, no undue pressure. If we want to cultivate a true spirit of democracy, we cannot afford to be intolerant. Intolerance betrays want of faith in one's cause."[258] Civil disobedience and non-co-operation as practised under Satyagraha are based on the "law of suffering",[259] a doctrine that the endurance of suffering is a means to an end. This end usually implies a moral upliftment or progress of an individual or society. Therefore, non-co-operation in Satyagraha is in fact a means to secure the co-operation of the opponent consistently with truth and justice.[260] + +While Gandhi's idea of satyagraha as a political means attracted a widespread following among Indians, the support was not universal. For example, Muslim leaders such as Jinnah opposed the satyagraha idea, accused Gandhi to be reviving Hinduism through political activism, and began effort to counter Gandhi with Muslim nationalism and a demand for Muslim homeland.[261][262][263] The untouchability leader Ambedkar, in June 1945, after his decision to convert to Buddhism and a key architect of the Constitution of modern India, dismissed Gandhi's ideas as loved by "blind Hindu devotees", primitive, influenced by spurious brew of Tolstoy and Ruskin, and "there is always some simpleton to preach them".[264][265] Winston Churchill caricatured Gandhi as a "cunning huckster" seeking selfish gain, an "aspiring dictator", and an "atavistic spokesman of a pagan Hinduism". Churchill stated that the civil disobedience movement spectacle of Gandhi only increased "the danger to which white people there [British India] are exposed".[266] + +Nonviolence + +Gandhi with textile workers at Darwen, Lancashire, 26 September 1931 +Although Gandhi was not the originator of the principle of nonviolence, he was the first to apply it in the political field on a large scale.[267] The concept of nonviolence (ahimsa) has a long history in Indian religious thought, with it being considered the highest dharma (ethical value virtue), a precept to be observed towards all living beings (sarvbhuta), at all times (sarvada), in all respects (sarvatha), in action, words and thought.[268] Gandhi explains his philosophy and ideas about ahimsa as a political means in his autobiography The Story of My Experiments with Truth.[269][270][271] + +Gandhi was criticised for refusing to protest the hanging of Bhagat Singh, Sukhdev, Udham Singh and Rajguru.[272][273] He was accused of accepting a deal with the King's representative Irwin that released civil disobedience leaders from prison and accepted the death sentence against the highly popular revolutionary Bhagat Singh, who at his trial had replied, "Revolution is the inalienable right of mankind".[128] + +Gandhi's views came under heavy criticism in Britain when it was under attack from Nazi Germany, and later when the Holocaust was revealed. He told the British people in 1940, "I would like you to lay down the arms you have as being useless for saving you or humanity. You will invite Herr Hitler and Signor Mussolini to take what they want of the countries you call your possessions... If these gentlemen choose to occupy your homes, you will vacate them. If they do not give you free passage out, you will allow yourselves, man, woman, and child, to be slaughtered, but you will refuse to owe allegiance to them."[274] George Orwell remarked that Gandhi's methods confronted "an old-fashioned and rather shaky despotism which treated him in a fairly chivalrous way", not a totalitarian power, "where political opponents simply disappear."[275] + +In a post-war interview in 1946, he said, "Hitler killed five million Jews. It is the greatest crime of our time. But the Jews should have offered themselves to the butcher's knife. They should have thrown themselves into the sea from cliffs... It would have aroused the world and the people of Germany... As it is they succumbed anyway in their millions."[276] Gandhi believed this act of "collective suicide", in response to the Holocaust, "would have been heroism".[277] + +On inter-religious relations +Buddhists, Jains and Sikhs +Gandhi believed that Buddhism, Jainism and Sikhism were traditions of Hinduism, with a shared history, rites and ideas. At other times, he acknowledged that he knew little about Buddhism other than his reading of Edwin Arnold's book on it. Based on that book, he considered Buddhism to be a reform movement and the Buddha to be a Hindu.[278] He stated he knew Jainism much more, and he credited Jains to have profoundly influenced him. Sikhism, to Gandhi, was an integral part of Hinduism, in the form of another reform movement. Sikh and Buddhist leaders disagreed with Gandhi, a disagreement Gandhi respected as a difference of opinion.[278][279] + +Muslims +Gandhi had generally positive and empathetic views of Islam, and he extensively studied the Quran. He viewed Islam as a faith that proactively promoted peace, and felt that non-violence had a predominant place in the Quran.[280] He also read the Islamic prophet Muhammad's biography, and argued that it was "not the sword that won a place for Islam in those days in the scheme of life. It was the rigid simplicity, the utter self-effacement of the Prophet, the scrupulous regard for pledges, his intense devotion to his friends and followers, his intrepidity, his fearlessness, his absolute trust in God and in his own mission."[281] Gandhi had a large Indian Muslim following, who he encouraged to join him in a mutual nonviolent jihad against the social oppression of their time. Prominent Muslim allies in his nonviolent resistance movement included Maulana Abul Kalam Azad and Abdul Ghaffar Khan. However, Gandhi's empathy towards Islam, and his eager willingness to valorise peaceful Muslim social activists, was viewed by many Hindus as an appeasement of Muslims and later became a leading cause for his assassination at the hands of intolerant Hindu extremists.[282] + +While Gandhi expressed mostly positive views of Islam, he did occasionally criticise Muslims.[280] He stated in 1925 that he did not criticise the teachings of the Quran, but he did criticise the interpreters of the Quran. Gandhi believed that numerous interpreters have interpreted it to fit their preconceived notions.[283] He believed Muslims should welcome criticism of the Quran, because "every true scripture only gains from criticism". Gandhi criticised Muslims who "betray intolerance of criticism by a non-Muslim of anything related to Islam", such as the penalty of stoning to death under Islamic law. To Gandhi, Islam has "nothing to fear from criticism even if it be unreasonable".[284][285] He also believed there were material contradictions between Hinduism and Islam,[285] and he criticised Muslims along with communists that were quick to resort to violence.[286] + +One of the strategies Gandhi adopted was to work with Muslim leaders of pre-partition India, to oppose the British imperialism in and outside the Indian subcontinent.[105][106] After the World War I, in 1919–22, he won Muslim leadership support of Ali Brothers by backing the Khilafat Movement in favour the Islamic Caliph and his historic Ottoman Caliphate, and opposing the secular Islam supporting Mustafa Kemal Atatürk. By 1924, Atatürk had ended the Caliphate, the Khilafat Movement was over, and Muslim support for Gandhi had largely evaporated.[105][287][106] + +In 1925, Gandhi gave another reason to why he got involved in the Khilafat movement and the Middle East affairs between Britain and the Ottoman Empire. Gandhi explained to his co-religionists (Hindu) that he sympathised and campaigned for the Islamic cause, not because he cared for the Sultan, but because "I wanted to enlist the Mussalman's sympathy in the matter of cow protection".[288] According to the historian M. Naeem Qureshi, like the then Indian Muslim leaders who had combined religion and politics, Gandhi too imported his religion into his political strategy during the Khilafat movement.[289] + +In the 1940s, Gandhi pooled ideas with some Muslim leaders who sought religious harmony like him, and opposed the proposed partition of British India into India and Pakistan. For example, his close friend Badshah Khan suggested that they should work towards opening Hindu temples for Muslim prayers, and Islamic mosques for Hindu prayers, to bring the two religious groups closer.[290] Gandhi accepted this and began having Muslim prayers read in Hindu temples to play his part, but was unable to get Hindu prayers read in mosques. The Hindu nationalist groups objected and began confronting Gandhi for this one-sided practice, by shouting and demonstrating inside the Hindu temples, in the last years of his life.[291][192][292] + +Christians +Gandhi criticised as well as praised Christianity. He was critical of Christian missionary efforts in British India, because they mixed medical or education assistance with demands that the beneficiary convert to Christianity.[293] According to Gandhi, this was not true "service" but one driven by an ulterior motive of luring people into religious conversion and exploiting the economically or medically desperate. It did not lead to inner transformation or moral advance or to the Christian teaching of "love", but was based on false one-sided criticisms of other religions, when Christian societies faced similar problems in South Africa and Europe. It led to the converted person hating his neighbours and other religions, and divided people rather than bringing them closer in compassion. According to Gandhi, "no religious tradition could claim a monopoly over truth or salvation".[293][294] Gandhi did not support laws to prohibit missionary activity, but demanded that Christians should first understand the message of Jesus, and then strive to live without stereotyping and misrepresenting other religions. According to Gandhi, the message of Jesus was not to humiliate and imperialistically rule over other people considering them inferior or second class or slaves, but that "when the hungry are fed and peace comes to our individual and collective life, then Christ is born".[295] + +Gandhi believed that his long acquaintance with Christianity had made him like it as well as find it imperfect. He asked Christians to stop humiliating his country and his people as heathens, idolators and other abusive language, and to change their negative views of India. He believed that Christians should introspect on the "true meaning of religion" and get a desire to study and learn from Indian religions in the spirit of universal brotherhood.[295] According to Eric Sharpe – a professor of Religious Studies, though Gandhi was born in a Hindu family and later became Hindu by conviction, many Christians in time thought of him as an "exemplary Christian and even as a saint".[296] + +Some colonial era Christian preachers and faithfuls considered Gandhi as a saint.[297][298][299] Biographers from France and Britain have drawn parallels between Gandhi and Christian saints. Recent scholars question these romantic biographies and state that Gandhi was neither a Christian figure nor mirrored a Christian saint.[300] Gandhi's life is better viewed as exemplifying his belief in the "convergence of various spiritualities" of a Christian and a Hindu, states Michael de Saint-Cheron.[300] + +Jews +According to Kumaraswamy, Gandhi initially supported Arab demands with respect to Palestine. He justified this support by invoking Islam, stating that "non-Muslims cannot acquire sovereign jurisdiction" in Jazirat al-Arab (the Arabian Peninsula).[301] These arguments, states Kumaraswamy, were a part of his political strategy to win Muslim support during the Khilafat movement. In the post-Khilafat period, Gandhi neither negated Jewish demands nor did he use Islamic texts or history to support Muslim claims against Israel. Gandhi's silence after the Khilafat period may represent an evolution in his understanding of the conflicting religious claims over Palestine, according to Kumaraswamy.[301] In 1938, Gandhi spoke in favour of Jewish claims, and in March 1946, he said to the Member of British Parliament Sidney Silverman, "if the Arabs have a claim to Palestine, the Jews have a prior claim", a position very different from his earlier stance.[301][302] + +Gandhi discussed the persecution of the Jews in Germany and the emigration of Jews from Europe to Palestine through his lens of Satyagraha.[182][303] In 1937, Gandhi discussed Zionism with his close Jewish friend Hermann Kallenbach.[304] He said that Zionism was not the right answer to the problems faced by Jews[305] and instead recommended Satyagraha. Gandhi thought the Zionists in Palestine represented European imperialism and used violence to achieve their goals; he argued that "the Jews should disclaim any intention of realizing their aspiration under the protection of arms and should rely wholly on the goodwill of Arabs. No exception can possibly be taken to the natural desire of the Jews to find a home in Palestine. But they must wait for its fulfillment till Arab opinion is ripe for it."[182] + +In 1938, Gandhi stated that his "sympathies are all with the Jews. I have known them intimately in South Africa. Some of them became life-long companions." Philosopher Martin Buber was highly critical of Gandhi's approach and in 1939 wrote an open letter to him on the subject. Gandhi reiterated his stance that "the Jews seek to convert the Arab heart", and use "satyagraha in confronting the Arabs" in 1947.[306] According to Simone Panter-Brick, Gandhi's political position on Jewish-Arab conflict evolved over the 1917–1947 period, shifting from a support for the Arab position first, and for the Jewish position in the 1940s.[307] + +On life, society and other application of his ideas +Vegetarianism, food, and animals +Gandhi was brought up as a vegetarian by his devout Hindu mother.[308][309] The idea of vegetarianism is deeply ingrained in Hindu Vaishnavism and Jain traditions in India, such as in his native Gujarat, where meat is considered as a form of food obtained by violence to animals.[310][311] Gandhi's rationale for vegetarianism was largely along those found in Hindu and Jain texts. Gandhi believed that any form of food inescapably harms some form of living organism, but one should seek to understand and reduce the violence in what one consumes because "there is essential unity of all life".[309][312] + +Gandhi believed that some life forms are more capable of suffering, and non-violence to him meant not having the intent as well as active efforts to minimise hurt, injury or suffering to all life forms.[312] Gandhi explored food sources that reduced violence to various life forms in the food chain. He believed that slaughtering animals is unnecessary, as other sources of foods are available.[310] He also consulted with vegetarianism campaigners during his lifetime, such as with Henry Stephens Salt. Food to Gandhi was not only a source of sustaining one's body, but a source of his impact on other living beings, and one that affected his mind, character and spiritual well being.[313][314][315] He avoided not only meat, but also eggs and milk. Gandhi wrote the book The Moral Basis of Vegetarianism and wrote for the London Vegetarian Society's publication.[316] + +Beyond his religious beliefs, Gandhi stated another motivation for his experiments with diet. He attempted to find the most non-violent vegetarian meal that the poorest human could afford, taking meticulous notes on vegetables and fruits, and his observations with his own body and his ashram in Gujarat.[317][318] He tried fresh and dry fruits (Fruitarianism), then just sun dried fruits, before resuming his prior vegetarian diet on advice of his doctor and concerns of his friends. His experiments with food began in the 1890s and continued for several decades.[317][318] For some of these experiments, Gandhi combined his own ideas with those found on diet in Indian yoga texts. He believed that each vegetarian should experiment with their diet because, in his studies at his ashram he saw "one man's food may be poison for another".[319][320] + +Gandhi championed animal rights in general. Other than making vegetarian choices, he actively campaigned against dissection studies and experimentation on live animals (vivisection) in the name of science and medical studies.[310] He considered it a violence against animals, something that inflicted pain and suffering. He wrote, "Vivisection in my opinion is the blackest of all the blackest crimes that man is at present committing against God and His fair creation."[321] + +Fasting +See also: List of fasts undertaken by Mahatma Gandhi + +Gandhi's last political protest using fasting, in January 1948 +Gandhi used fasting as a political device, often threatening suicide unless demands were met. Congress publicised the fasts as a political action that generated widespread sympathy. In response the government tried to manipulate news coverage to minimise his challenge to the Raj. He fasted in 1932 to protest the voting scheme for separate political representation for Dalits; Gandhi did not want them segregated. The British government stopped the London press from showing photographs of his emaciated body, because it would elicit sympathy. Gandhi's 1943 hunger strike took place during a two-year prison term for the anticolonial Quit India movement. The government called on nutritional experts to demystify his action, and again no photos were allowed. However, his final fast in 1948, after the end of British rule in India, his hunger strike was lauded by the British press and this time did include full-length photos.[322] + +Alter states that Gandhi's fasting, vegetarianism and diet was more than a political leverage, it was a part of his experiments with self restraint and healthy living. He was "profoundly skeptical of traditional Ayurveda", encouraging it to study the scientific method and adopt its progressive learning approach. Gandhi believed yoga offered health benefits. He believed that a healthy nutritional diet based on regional foods and hygiene were essential to good health.[323] Recently ICMR made Gandhi's health records public in a book 'Gandhi and Health@150'. These records indicate that despite being underweight at 46.7 kg Gandhi was generally healthy. He avoided modern medication and experimented extensively with water and earth healing. While his cardio records show his heart was normal, there were several instances he suffered from ailments like Malaria and was also operated on twice for piles and appendicitis. Despite health challenges Gandhi was able to walk about 79000 kms in his lifetime which comes to an average of 18 kms per day and is equivalent to walking around the earth twice.[324] + +Women +Gandhi strongly favoured the emancipation of women, and urged "the women to fight for their own self-development." He opposed purdah, child marriage, dowry and sati.[325] A wife is not a slave of the husband, stated Gandhi, but his comrade, better half, colleague and friend, according to Lyn Norvell.[325] In his own life however, according to Suruchi Thapar-Bjorkert, Gandhi's relationship with his wife were at odds with some of these values.[135] + +At various occasions, Gandhi credited his orthodox Hindu mother, and his wife, for first lessons in satyagraha.[326] He used the legends of Hindu goddess Sita to expound women's innate strength, autonomy and "lioness in spirit" whose moral compass can make any demon "as helpless as a goat".[326] To Gandhi, the women of India were an important part of the "swadeshi movement" (Buy Indian), and his goal of decolonising the Indian economy.[326] + +Some historians such as Angela Woollacott and Kumari Jayawardena state that even though Gandhi often and publicly expressed his belief in the equality of sexes, yet his vision was one of gender difference and complementarity between them. Women, to Gandhi, should be educated to be better in the domestic realm and educate the next generation. His views on women's rights were less liberal and more similar to puritan-Victorian expectations of women, states Jayawardena, than other Hindu leaders with him who supported economic independence and equal gender rights in all aspects.[327][328] + +Brahmacharya: abstinence from sex and food +Along with many other texts, Gandhi studied Bhagavad Gita while in South Africa.[329] This Hindu scripture discusses jnana yoga, bhakti yoga and karma yoga along with virtues such as non-violence, patience, integrity, lack of hypocrisy, self restraint and abstinence.[330] Gandhi began experiments with these, and in 1906 at age 37, although married and a father, he vowed to abstain from sexual relations.[329] + +Gandhi's experiment with abstinence went beyond sex, and extended to food. He consulted the Jain scholar Rajchandra, whom he fondly called Raychandbhai.[331] Rajchandra advised him that milk stimulated sexual passion. Gandhi began abstaining from cow's milk in 1912, and did so even when doctors advised him to consume milk.[231][332] According to Sankar Ghose, Tagore described Gandhi as someone who did not abhor sex or women, but considered sexual life as inconsistent with his moral goals.[333] + +Gandhi tried to test and prove to himself his brahmacharya. The experiments began some time after the death of his wife in February 1944. At the start of his experiment he had women sleep in the same room but in different beds. He later slept with women in the same bed but clothed, and finally he slept naked with women. In April 1945, Gandhi referenced being naked with several "women or girls" in a letter to Birla as part of the experiments.[334] According to the 1960s memoir of his grandniece Manu, Gandhi feared in early 1947 that he and she may be killed by Muslims in the run up to India's independence in August 1947, and asked her when she was 18 years old if she wanted to help him with his experiments to test their "purity", for which she readily accepted.[335] Gandhi slept naked in the same bed with Manu with the bedroom doors open all night. Manu stated that the experiment had no "ill effect" on her. Gandhi also shared his bed with 18-year-old Abha, wife of his grandnephew Kanu. Gandhi would sleep with both Manu and Abha at the same time.[335][336] None of the women who participated in the brahmachari experiments of Gandhi indicated that they had sex or that Gandhi behaved in any sexual way. Those who went public said they felt as though they were sleeping with their aging mother.[333][334][337] + +According to Sean Scalmer, Gandhi in his final year of life was an ascetic, and his sickly skeletal figure was caricatured in Western media.[338] In February 1947, he asked his confidants such as Birla and Ramakrishna if it was wrong for him to experiment his brahmacharya oath.[333] Gandhi's public experiments, as they progressed, were widely discussed and criticised by his family members and leading politicians. However, Gandhi said that if he would not let Manu sleep with him, it would be a sign of weakness. Some of his staff resigned, including two of his newspaper's editors who had refused to print some of Gandhi's sermons dealing with his experiments.[335] Nirmalkumar Bose, Gandhi's Bengali interpreter, for example criticised Gandhi, not because Gandhi did anything wrong, but because Bose was concerned about the psychological effect on the women who participated in his experiments.[336] Veena Howard states Gandhi's views on brahmacharya and religious renunciation experiments were a method to confront women issues in his times.[339] + +Untouchability and castes +Gandhi spoke out against untouchability early in his life.[340] Before 1932, he and his associates used the word antyaja for untouchables. In a major speech on untouchability at Nagpur in 1920, Gandhi called it a great evil in Hindu society but observed that it was not unique to Hinduism, having deeper roots, and stated that Europeans in South Africa treated "all of us, Hindus and Muslims, as untouchables; we may not reside in their midst, nor enjoy the rights which they do".[341] Calling the doctrine of untouchability intolerable, he asserted that the practice could be eradicated, that Hinduism was flexible enough to allow eradication, and that a concerted effort was needed to persuade people of the wrong and to urge them to eradicate it.[341] + +According to Christophe Jaffrelot, while Gandhi considered untouchability to be wrong and evil, he believed that caste or class is based on neither inequality nor inferiority.[340] Gandhi believed that individuals should freely intermarry whomever they wish, but that no one should expect everyone to be his friend: every individual, regardless of background, has a right to choose whom he will welcome into his home, whom he will befriend, and whom he will spend time with.[340][341] + +In 1932, Gandhi began a new campaign to improve the lives of the untouchables, whom he began to call harijans, "the children of god".[342] On 8 May 1933, Gandhi began a 21-day fast of self-purification and launched a year-long campaign to help the harijan movement.[343] This campaign was not universally embraced by the Dalit community: Ambedkar and his allies felt Gandhi was being paternalistic and was undermining Dalit political rights. Ambedkar described him as "devious and untrustworthy".[344] He accused Gandhi as someone who wished to retain the caste system.[150] Ambedkar and Gandhi debated their ideas and concerns, each trying to persuade the other.[345][346] + +In 1935, Ambedkar announced his intentions to leave Hinduism and join Buddhism.[150] According to Sankar Ghose, the announcement shook Gandhi, who reappraised his views and wrote many essays with his views on castes, intermarriage, and what Hinduism says on the subject. These views contrasted with those of Ambedkar.[347] Yet in the elections of 1937, excepting some seats in Mumbai which Ambedkar's party won, India's untouchables voted heavily in favour of Gandhi's campaign and his party, the Congress.[348] + +Gandhi and his associates continued to consult Ambedkar, keeping him influential. Ambedkar worked with other Congress leaders through the 1940s and wrote large parts of India's constitution in the late 1940s, but did indeed convert to Buddhism in 1956.[150] According to Jaffrelot, Gandhi's views evolved between the 1920s and 1940s; by 1946, he actively encouraged intermarriage between castes. His approach, too, to untouchability differed from Ambedkar's, championing fusion, choice, and free intermixing, while Ambedkar envisioned each segment of society maintaining its group identity, and each group then separately advancing the "politics of equality".[340] + +Ambedkar's criticism of Gandhi continued to influence the Dalit movement past Gandhi's death. According to Arthur Herman, Ambedkar's hatred for Gandhi and Gandhi's ideas was so strong that, when he heard of Gandhi's assassination, he remarked after a momentary silence a sense of regret and then added, "My real enemy is gone; thank goodness the eclipse is over now".[264][349] According to Ramachandra Guha, "ideologues have carried these old rivalries into the present, with the demonization of Gandhi now common among politicians who presume to speak in Ambedkar's name."[350] + +Nai Talim, basic education +Main article: Nai Talim +Gandhi rejected the colonial Western format of education system. He stated that it led to disdain for manual work, generally created an elite administrative bureaucracy. Gandhi favoured an education system with far greater emphasis on learning skills in practical and useful work, one that included physical, mental and spiritual studies. His methodology sought to treat all professions equal and pay everyone the same.[351][352] + +Gandhi called his ideas Nai Talim (literally, 'new education'). He believed that the Western style education violated and destroyed the indigenous cultures. A different basic education model, he believed, would lead to better self awareness, prepare people to treat all work equally respectable and valued, and lead to a society with less social diseases.[353][354] + +Nai Talim evolved out of his experiences at the Tolstoy Farm in South Africa, and Gandhi attempted to formulate the new system at the Sevagram ashram after 1937.[352] Nehru government's vision of an industrialised, centrally planned economy after 1947 had scant place for Gandhi's village-oriented approach.[355] + +In his autobiography, Gandhi wrote that he believed every Hindu child must learn Sanskrit because its historic and spiritual texts are in that language.[44] + +Swaraj, self-rule +Main article: Swaraj +Gandhi believed that swaraj not only can be attained with non-violence, it can be run with non-violence. A military is unnecessary, because any aggressor can be thrown out using the method of non-violent non-co-operation. While military is unnecessary in a nation organised under swaraj principle, Gandhi added that a police force is necessary given human nature. However, the state would limit the use of weapons by the police to the minimum, aiming for their use as a restraining force.[356] + +According to Gandhi, a non-violent state is like an "ordered anarchy".[356] In a society of mostly non-violent individuals, those who are violent will sooner or later accept discipline or leave the community, stated Gandhi.[356] He emphasised a society where individuals believed more in learning about their duties and responsibilities, not demanded rights and privileges. On returning from South Africa, when Gandhi received a letter asking for his participation in writing a world charter for human rights, he responded saying, "in my experience, it is far more important to have a charter for human duties."[357] + +Swaraj to Gandhi did not mean transferring colonial era British power brokering system, favours-driven, bureaucratic, class exploitative structure and mindset into Indian hands. He warned such a transfer would still be English rule, just without the Englishman. "This is not the Swaraj I want", said Gandhi.[358][359] Tewari states that Gandhi saw democracy as more than a system of government; it meant promoting both individuality and the self-discipline of the community. Democracy meant settling disputes in a nonviolent manner; it required freedom of thought and expression. For Gandhi, democracy was a way of life.[360] + +Hindu nationalism and revivalism +Some scholars state Gandhi supported a religiously diverse India,[361] while others state that the Muslim leaders who championed the partition and creation of a separate Muslim Pakistan considered Gandhi to be Hindu nationalist or revivalist.[362][363] For example, in his letters to Mohammad Iqbal, Jinnah accused Gandhi to be favouring a Hindu rule and revivalism, that Gandhi led Indian National Congress was a fascist party.[364] + +In an interview with C.F. Andrews, Gandhi stated that if we believe all religions teach the same message of love and peace between all human beings, then there is neither any rationale nor need for proselytisation or attempts to convert people from one religion to another.[365] Gandhi opposed missionary organisations who criticised Indian religions then attempted to convert followers of Indian religions to Islam or Christianity. In Gandhi's view, those who attempt to convert a Hindu, "they must harbour in their breasts the belief that Hinduism is an error" and that their own religion is "the only true religion".[365][366] Gandhi believed that people who demand religious respect and rights must also show the same respect and grant the same rights to followers of other religions. He stated that spiritual studies must encourage "a Hindu to become a better Hindu, a Mussalman to become a better Mussalman, and a Christian a better Christian."[365] + +According to Gandhi, religion is not about what a man believes, it is about how a man lives, how he relates to other people, his conduct towards others, and one's relationship to one's conception of god.[367] It is not important to convert or to join any religion, but it is important to improve one's way of life and conduct by absorbing ideas from any source and any religion, believed Gandhi.[367] + +Gandhian economics +Main article: Gandhian economics +Gandhi believed in the sarvodaya economic model, which literally means "welfare, upliftment of all".[368] This, states Bhatt, was a very different economic model than the socialism model championed and followed by free India by Nehru – India's first prime minister. To both, according to Bhatt, removing poverty and unemployment were the objective, but the Gandhian economic and development approach preferred adapting technology and infrastructure to suit the local situation, in contrast to Nehru's large scale, socialised state owned enterprises.[369] + +To Gandhi, the economic philosophy that aims at "greatest good for the greatest number" was fundamentally flawed, and his alternative proposal sarvodaya set its aim at the "greatest good for all". He believed that the best economic system not only cared to lift the "poor, less skilled, of impoverished background" but also empowered to lift the "rich, highly skilled, of capital means and landlords". Violence against any human being, born poor or rich, is wrong, believed Gandhi.[368][370] He stated that the mandate theory of majoritarian democracy should not be pushed to absurd extremes, individual freedoms should never be denied, and no person should ever be made a social or economic slave to the "resolutions of majorities".[371] + +Gandhi challenged Nehru and the modernisers in the late 1930s who called for rapid industrialisation on the Soviet model; Gandhi denounced that as dehumanising and contrary to the needs of the villages where the great majority of the people lived.[372] After Gandhi's assassination, Nehru led India in accordance with his personal socialist convictions.[373][374] Historian Kuruvilla Pandikattu says "it was Nehru's vision, not Gandhi's, that was eventually preferred by the Indian State."[375] + +Gandhi called for ending poverty through improved agriculture and small-scale cottage rural industries.[376] Gandhi's economic thinking disagreed with Marx, according to the political theory scholar and economist Bhikhu Parekh. Gandhi refused to endorse the view that economic forces are best understood as "antagonistic class interests".[377] He argued that no man can degrade or brutalise the other without degrading and brutalising himself and that sustainable economic growth comes from service, not from exploitation. Further, believed Gandhi, in a free nation, victims exist only when they co-operate with their oppressor, and an economic and political system that offered increasing alternatives gave power of choice to the poorest man.[377] + +While disagreeing with Nehru about the socialist economic model, Gandhi also critiqued capitalism that was driven by endless wants and a materialistic view of man. This, he believed, created a vicious vested system of materialism at the cost of other human needs, such as spirituality and social relationships.[377] To Gandhi, states Parekh, both communism and capitalism were wrong, in part because both focused exclusively on a materialistic view of man, and because the former deified the state with unlimited power of violence, while the latter deified capital. He believed that a better economic system is one which does not impoverish one's culture and spiritual pursuits.[378] + +Gandhism +Main article: Gandhism +Gandhism designates the ideas and principles Gandhi promoted; of central importance is nonviolent resistance. A Gandhian can mean either an individual who follows, or a specific philosophy which is attributed to, Gandhism.[97] M. M. Sankhdher argues that Gandhism is not a systematic position in metaphysics or in political philosophy. Rather, it is a political creed, an economic doctrine, a religious outlook, a moral precept, and especially, a humanitarian world view. It is an effort not to systematise wisdom but to transform society and is based on an undying faith in the goodness of human nature.[379] However Gandhi himself did not approve of the notion of "Gandhism", as he explained in 1936: + +There is no such thing as "Gandhism", and I do not want to leave any sect after me. I do not claim to have originated any new principle or doctrine. I have simply tried in my own way to apply the eternal truths to our daily life and problems...The opinions I have formed and the conclusions I have arrived at are not final. I may change them tomorrow. I have nothing new to teach the world. Truth and nonviolence are as old as the hills.[380] + +Literary works + +Young India, a weekly journal published by Gandhi from 1919 to 1932 +Gandhi was a prolific writer. One of Gandhi's earliest publications, Hind Swaraj, published in Gujarati in 1909, became "the intellectual blueprint" for India's independence movement. The book was translated into English the next year, with a copyright legend that read "No Rights Reserved".[381] For decades he edited several newspapers including Harijan in Gujarati, in Hindi and in the English language; Indian Opinion while in South Africa and, Young India, in English, and Navajivan, a Gujarati monthly, on his return to India. Later, Navajivan was also published in Hindi. In addition, he wrote letters almost every day to individuals and newspapers.[382] + +Gandhi also wrote several books including his autobiography, The Story of My Experiments with Truth (Gujarātī "સત્યના પ્રયોગો અથવા આત્મકથા"), of which he bought the entire first edition to make sure it was reprinted.[344] His other autobiographies included: Satyagraha in South Africa about his struggle there, Hind Swaraj or Indian Home Rule, a political pamphlet, and a paraphrase in Gujarati of John Ruskin's Unto This Last.[383] This last essay can be considered his programme on economics. He also wrote extensively on vegetarianism, diet and health, religion, social reforms, etc. Gandhi usually wrote in Gujarati, though he also revised the Hindi and English translations of his books.[384] + +Gandhi's complete works were published by the Indian government under the name The Collected Works of Mahatma Gandhi in the 1960s. The writings comprise about 50,000 pages published in about a hundred volumes. In 2000, a revised edition of the complete works sparked a controversy, as it contained a large number of errors and omissions.[385] The Indian government later withdrew the revised edition.[386] + +Legacy and depictions in popular culture +See also: List of artistic depictions of Mahatma Gandhi and List of roads named after Mahatma Gandhi +The word Mahatma, while often mistaken for Gandhi's given name in the West, is taken from the Sanskrit words maha (meaning Great) and atma (meaning Soul). Rabindranath Tagore is said to have accorded the title to Gandhi.[387] In his autobiography, Gandhi nevertheless explains that he never valued the title, and was often pained by it.[388][389][390] +Innumerable streets, roads and localities in India are named after M.K.Gandhi. These include M.G.Road (the main street of a number of Indian cities including Mumbai and Bangalore), Gandhi Market (near Sion, Mumbai) and Gandhinagar (the capital of the state of Gujarat, Gandhi's birthplace).[391] +Followers and international influence + +Statue of Mahatma Gandhi at York University + +Mahatma Gandhi on a 1969 postage stamp of the Soviet Union + +Mahatma Gandhi at Praça Túlio Fontoura, São Paulo, Brazil. Statue by Gautam Pal + +Largest Gandhi statue located between Vidhana Soudha and Vikasa Soudha, Bengaluru +Gandhi influenced important leaders and political movements. Leaders of the civil rights movement in the United States, including Martin Luther King Jr., James Lawson, and James Bevel, drew from the writings of Gandhi in the development of their own theories about nonviolence.[392][393][394] King said "Christ gave us the goals and Mahatma Gandhi the tactics."[395] King sometimes referred to Gandhi as "the little brown saint."[396] Anti-apartheid activist and former President of South Africa, Nelson Mandela, was inspired by Gandhi.[397] Others include Khan Abdul Ghaffar Khan,[398] Steve Biko, and Aung San Suu Kyi.[399] + +In his early years, the former President of South Africa Nelson Mandela was a follower of the nonviolent resistance philosophy of Gandhi.[397] Bhana and Vahed commented on these events as "Gandhi inspired succeeding generations of South African activists seeking to end White rule. This legacy connects him to Nelson Mandela...in a sense Mandela completed what Gandhi started."[400] + +Gandhi's life and teachings inspired many who specifically referred to Gandhi as their mentor or who dedicated their lives to spreading Gandhi's ideas. In Europe, Romain Rolland was the first to discuss Gandhi in his 1924 book Mahatma Gandhi, and Brazilian anarchist and feminist Maria Lacerda de Moura wrote about Gandhi in her work on pacifism. In 1931, notable European physicist Albert Einstein exchanged written letters with Gandhi, and called him "a role model for the generations to come" in a letter writing about him.[401] Einstein said of Gandhi: + +Mahatma Gandhi's life achievement stands unique in political history. He has invented a completely new and humane means for the liberation war of an oppressed country, and practised it with greatest energy and devotion. The moral influence he had on the consciously thinking human being of the entire civilised world will probably be much more lasting than it seems in our time with its overestimation of brutal violent forces. Because lasting will only be the work of such statesmen who wake up and strengthen the moral power of their people through their example and educational works. We may all be happy and grateful that destiny gifted us with such an enlightened contemporary, a role model for the generations to come. Generations to come will scarce believe that such a one as this walked the earth in flesh and blood. + +Lanza del Vasto went to India in 1936 intending to live with Gandhi; he later returned to Europe to spread Gandhi's philosophy and founded the Community of the Ark in 1948 (modelled after Gandhi's ashrams). Madeleine Slade (known as "Mirabehn") was the daughter of a British admiral who spent much of her adult life in India as a devotee of Gandhi.[402][403] + +In addition, the British musician John Lennon referred to Gandhi when discussing his views on nonviolence.[404] At the Cannes Lions International Advertising Festival in 2007, former US Vice-President and environmentalist Al Gore spoke of Gandhi's influence on him.[405] + +US President Barack Obama in a 2010 address to the Parliament of India said that: + +I am mindful that I might not be standing before you today, as President of the United States, had it not been for Gandhi and the message he shared with America and the world.[406] + +Obama in September 2009 said that his biggest inspiration came from Gandhi. His reply was in response to the question 'Who was the one person, dead or live, that you would choose to dine with?'. He continued that "He's somebody I find a lot of inspiration in. He inspired Dr. King with his message of nonviolence. He ended up doing so much and changed the world just by the power of his ethics."[407] + +Time Magazine named The 14th Dalai Lama, Lech Wałęsa, Martin Luther King Jr., Cesar Chavez, Aung San Suu Kyi, Benigno Aquino, Jr., Desmond Tutu, and Nelson Mandela as Children of Gandhi and his spiritual heirs to nonviolence.[408] The Mahatma Gandhi District in Houston, Texas, United States, an ethnic Indian enclave, is officially named after Gandhi.[409] + +Gandhi's ideas had a significant influence on 20th-century philosophy. It began with his engagement with Romain Rolland and Martin Buber. Jean-Luc Nancy said that the French philosopher Maurice Blanchot engaged critically with Gandhi from the point of view of "European spirituality".[410] Since then philosophers including Hannah Arendt, Etienne Balibar and Slavoj Žižek found that Gandhi was a necessary reference to discuss morality in politics. Recently in the light of climate change Gandhi's views on technology are gaining importance in the fields of environmental philosophy and philosophy of technology.[410] + +Global days that celebrate Gandhi +In 2007, the United Nations General Assembly declared Gandhi's birthday 2 October as "the International Day of Nonviolence."[411] First proposed by UNESCO in 1948, as the School Day of Nonviolence and Peace (DENIP in Spanish),[412] 30 January is observed as the School Day of Nonviolence and Peace in schools of many countries[413] In countries with a Southern Hemisphere school calendar, it is observed on 30 March.[413] + +Awards + +Monument to M. K. Gandhi in Madrid, Spain +Time magazine named Gandhi the Man of the Year in 1930. The University of Nagpur awarded him an LL.D. in 1937.[414] Gandhi was also the runner-up to Albert Einstein as "Person of the Century"[415] at the end of 1999. The Government of India awarded the annual Gandhi Peace Prize to distinguished social workers, world leaders and citizens. Nelson Mandela, the leader of South Africa's struggle to eradicate racial discrimination and segregation, was a prominent non-Indian recipient. In 2011, Time magazine named Gandhi as one of the top 25 political icons of all time.[416] + +Gandhi did not receive the Nobel Peace Prize, although he was nominated five times between 1937 and 1948, including the first-ever nomination by the American Friends Service Committee,[417] though he made the short list only twice, in 1937 and 1947.[418] Decades later, the Nobel Committee publicly declared its regret for the omission, and admitted to deeply divided nationalistic opinion denying the award.[418] Gandhi was nominated in 1948 but was assassinated before nominations closed. That year, the committee chose not to award the peace prize stating that "there was no suitable living candidate" and later research shows that the possibility of awarding the prize posthumously to Gandhi was discussed and that the reference to no suitable living candidate was to Gandhi.[418] Geir Lundestad, Secretary of Norwegian Nobel Committee in 2006 said, "The greatest omission in our 106-year history is undoubtedly that Mahatma Gandhi never received the Nobel Peace prize. Gandhi could do without the Nobel Peace prize, whether Nobel committee can do without Gandhi is the question".[419] When the 14th Dalai Lama was awarded the Prize in 1989, the chairman of the committee said that this was "in part a tribute to the memory of Mahatma Gandhi".[418] In the summer of 1995, the North American Vegetarian Society inducted him posthumously into the Vegetarian Hall of Fame.[420] + +Father of the Nation +Indians widely describe Gandhi as the father of the nation.[14][15] Origin of this title is traced back to a radio address (on Singapore radio) on 6 July 1944 by Subhash Chandra Bose where Bose addressed Gandhi as "The Father of the Nation".[421] On 28 April 1947, Sarojini Naidu during a conference also referred Gandhi as "Father of the Nation".[422][423] However, in response to an RTI application in 2012, the Government of India stated that the Constitution of India did not permit any titles except ones acquired through education or military service.[424] + +Film, theatre and literature +A five-hour nine-minute long biographical documentary film,[425] Mahatma: Life of Gandhi, 1869–1948, made by Vithalbhai Jhaveri[426] in 1968, quoting Gandhi's words and using black and white archival footage and photographs, captures the history of those times. Ben Kingsley portrayed him in Richard Attenborough's 1982 film Gandhi,[427] which won the Academy Award for Best Picture. It was based on the biography by Louis Fischer.[428] The 1996 film The Making of the Mahatma documented Gandhi's time in South Africa and his transformation from an inexperienced barrister to recognised political leader.[429] Gandhi was a central figure in the 2006 Bollywood comedy film Lage Raho Munna Bhai. Jahnu Barua's Maine Gandhi Ko Nahin Mara (I did not kill Gandhi), places contemporary society as a backdrop with its vanishing memory of Gandhi's values as a metaphor for the senile forgetfulness of the protagonist of his 2005 film,[430] writes Vinay Lal.[431] + +The 1979 opera Satyagraha by American composer Philip Glass is loosely based on Gandhi's life.[432][433] The opera's libretto, taken from the Bhagavad Gita, is sung in the original Sanskrit.[434] + +Anti-Gandhi themes have also been showcased through films and plays. The 1995 Marathi play Gandhi Virudh Gandhi explored the relationship between Gandhi and his son Harilal. The 2007 film, Gandhi, My Father was inspired on the same theme. The 1989 Marathi play Me Nathuram Godse Boltoy and the 1997 Hindi play Gandhi Ambedkar criticised Gandhi and his principles.[435][436] + +Several biographers have undertaken the task of describing Gandhi's life. Among them are D. G. Tendulkar with his Mahatma. Life of Mohandas Karamchand Gandhi in eight volumes, Chaman Nahal's Gandhi Quartet, and Pyarelal and Sushila Nayyar with their Mahatma Gandhi in 10 volumes. The 2010 biography, Great Soul: Mahatma Gandhi and His Struggle With India by Joseph Lelyveld contained controversial material speculating about Gandhi's sexual life.[437] Lelyveld, however, stated that the press coverage "grossly distort[s]" the overall message of the book.[438] The 2014 film Welcome Back Gandhi takes a fictionalised look at how Gandhi might react to modern day India.[439] The 2019 play Bharat Bhagya Vidhata, inspired by Pujya Gurudevshri Rakeshbhai and produced by Sangeet Natak Akademi and Shrimad Rajchandra Mission Dharampur takes a look at how Gandhi cultivated the values of truth and non-violence.[440] + +"Mahatma Gandhi" is used by Cole Porter in his lyrics for the song You're the Top which is included in the 1934 musical Anything Goes. In the song Porter rhymes "Mahatma Gandhi' with "Napoleon Brandy." + +Current impact within India + +The Gandhi Mandapam, a temple in Kanyakumari, Tamil Nadu in India, was erected to honour M.K. Gandhi. +India, with its rapid economic modernisation and urbanisation, has rejected Gandhi's economics[441] but accepted much of his politics and continues to revere his memory. Reporter Jim Yardley notes that, "modern India is hardly a Gandhian nation, if it ever was one. His vision of a village-dominated economy was shunted aside during his lifetime as rural romanticism, and his call for a national ethos of personal austerity and nonviolence has proved antithetical to the goals of an aspiring economic and military power." By contrast Gandhi is "given full credit for India's political identity as a tolerant, secular democracy."[442] + +Gandhi's birthday, 2 October, is a national holiday in India, Gandhi Jayanti. Gandhi's image also appears on paper currency of all denominations issued by Reserve Bank of India, except for the one rupee note.[443] Gandhi's date of death, 30 January, is commemorated as a Martyrs' Day in India.[444] + +There are three temples in India dedicated to Gandhi.[445] One is located at Sambalpur in Orissa and the second at Nidaghatta village near Kadur in Chikmagalur district of Karnataka and the third one at Chityal in the district of Nalgonda, Telangana.[445][446] The Gandhi Memorial in Kanyakumari resembles central Indian Hindu temples and the Tamukkam or Summer Palace in Madurai now houses the Mahatma Gandhi Museum.[447] + +Descendants + +Family tree of Mohandas Karamchand Gandhi and Kasturba Gandhi. Source: Gandhi Ashram Sabarmati +Gandhi's children and grandchildren live in India and other countries. Grandson Rajmohan Gandhi is a professor in Illinois and an author of Gandhi's biography titled Mohandas,[448] while another, Tarun Gandhi, has authored several authoritative books on his grandfather. Another grandson, Kanu Ramdas Gandhi (the son of Gandhi's third son Ramdas), was found living in an old age home in Delhi despite having taught earlier in the United States.[449][450] + +See also +icon Religion portal + Hinduism portal +flag India portal + Philosophy portal +Gandhi cap +Gandhi Teerth – Gandhi International Research Institute and Museum for Gandhian study, research on Mahatma Gandhi and dialogue +List of civil rights leaders +List of peace activists +Seven Social Sins (AKA Seven Blunders of the World) +Trikaranasuddhi +References + Jeffrey M. Shaw; Timothy J. Demy (2017). War and Religion: An Encyclopedia of Faith and Conflict. ABC-CLIO. p. 309. ISBN 978-1-61069-517-6. + "Gandhi". Archived 14 January 2015 at the Wayback Machine Random House Webster's Unabridged Dictionary. + B. R. Nanda (2019), "Mahatma Gandhi", Encyclopædia Britannica Quote: "Mahatma Gandhi, byname of Mohandas Karamchand Gandhi, (born October 2, 1869, Porbandar, India – died January 30, 1948, Delhi), Indian lawyer, politician, ..." + Ganguly, Debjani; Docker, John (25 March 2008), Rethinking Gandhi and Nonviolent Relationality: Global Perspectives, Routledge, pp. 4–, ISBN 978-1-134-07431-0 Quote: "... marks Gandhi as a hybrid cosmopolitan figure who transformed ... anti-colonial nationalist politics in the twentieth-century in ways that neither indigenous nor westernized Indian nationalists could." + Parel, Anthony J (2016), Pax Gandhiana: The Political Philosophy of Mahatma Gandhi, Oxford University Press, pp. 202–, ISBN 978-0-19-049146-8 Quote: "Gandhi staked his reputation as an original political thinker on this specific issue. Hitherto, violence had been used in the name of political rights, such as in street riots, regicide, or armed revolutions. Gandhi believes there is a better way of securing political rights, that of nonviolence, and that this new way marks an advance in political ethics." + Stein, Burton (2010), A History of India, John Wiley & Sons, pp. 289–, ISBN 978-1-4443-2351-1, Gandhi was the leading genius of the later, and ultimately successful, campaign for India's independence. + McGregor, Ronald Stuart (1993). The Oxford Hindi-English Dictionary. Oxford University Press. p. 799. ISBN 978-0-19-864339-5. Retrieved 31 August 2013. Quote: (mahā- (S. "great, mighty, large, ..., eminent") + ātmā (S. "1. soul, spirit; the self, the individual; the mind, the heart; 2. the ultimate being."): "high-souled, of noble nature; a noble or venerable man." + Gandhi, Rajmohan (2006). Gandhi: The Man, His People, and the Empire. p. 172. ISBN 9780520255708. ...Kasturba would accompany Gandhi on his departure from Cape Town for England in July 1914 en route to India. ... In different South African towns (Pretoria, Cape Town, Bloemfontein, Johannesburg, and the Natal cities of Durban and Verulam), the struggle's martyrs were honoured and the Gandhi's bade farewell. Addresses in Durban and Verulam referred to Gandhi as a 'Mahatma', 'great soul'. He was seen as a great soul because he had taken up the poor's cause. The whites too said good things about Gandhi, who predicted a future for the Empire if it respected justice. + Maeleine Slade, Mirabehn. Gleanings Gathered at Bapu's Feet. Ahmedabad: Navjivan publications. Retrieved 4 March 2019. + Khan, Yasmin (2007). The Great Partition: The Making of India and Pakistan. Yale University Press. p. 18. ISBN 978-0-300-12078-3. Retrieved 1 September 2013. Quote: "the Muslim League had only caught on among South Asian Muslims during the Second World War. ... By the late 1940s, the League and the Congress had impressed in the British their own visions of a free future for Indian people. ... one, articulated by the Congress, rested on the idea of a united, plural India as a home for all Indians and the other, spelt out by the League, rested on the foundation of Muslim nationalism and the carving out of a separate Muslim homeland." (p. 18) + Khan, Yasmin (2007). The Great Partition: The Making of India and Pakistan. Yale University Press. p. 1. ISBN 978-0-300-12078-3. Retrieved 1 September 2013. Quote: "South Asians learned that the British Indian Empire would be partitioned on 3 June 1947. They heard about it on the radio, from relations and friends, by reading newspapers and, later, through government pamphlets. Among a population of almost four hundred million, where the vast majority lived in the countryside, ..., it is hardly surprising that many ... did not hear the news for many weeks afterwards. For some, the butchery and forced relocation of the summer months of 1947 may have been the first they know about the creation of the two new states rising from the fragmentary and terminally weakened British empire in India." (p. 1) + Brown (1991), p. 380: "Despite and indeed because of his sense of helplessness Delhi was to be the scene of what he called his greatest fast. ... His decision was made suddenly, though after considerable thought – he gave no hint of it even to Nehru and Patel who were with him shortly before he announced his intention at a prayer-meeting on 12 January 1948. He said he would fast until communal peace was restored, real peace rather than the calm of a dead city imposed by police and troops. Patel and the government took the fast partly as condemnation of their decision to withhold a considerable cash sum still outstanding to Pakistan as a result of the allocation of undivided India's assets because the hostilities that had broken out in Kashmir; ... But even when the government agreed to pay out the cash, Gandhi would not break his fast: that he would only do after a large number of important politicians and leaders of communal bodies agreed to a joint plan for restoration of normal life in the city." + Cush, Denise; Robinson, Catherine; York, Michael (2008). Encyclopedia of Hinduism. Taylor & Francis. p. 544. ISBN 978-0-7007-1267-0. Archived from the original on 12 October 2013. Retrieved 31 August 2013. Quote: "The apotheosis of this contrast is the assassination of Gandhi in 1948 by a militant Nathuram Godse, on the basis of his 'weak' accommodationist approach towards the new state of Pakistan." (p. 544) + "Gandhi not formally conferred 'Father of the Nation' title: Govt". The Indian Express. 11 July 2012. Archived from the original on 6 September 2014. + "Constitution doesn't permit 'Father of the Nation' title: Government". The Times of India. 26 October 2012. Archived from the original on 7 January 2017. + Nehru, Jawaharlal. An Autobiography. Bodley Head. + McAllister, Pam (1982). Reweaving the Web of Life: Feminism and Nonviolence. New Society Publishers. p. 194. ISBN 978-0-86571-017-7. Retrieved 31 August 2013. Quote: "With love, Yours, Bapu (You closed with the term of endearment used by your close friends, the term you used with all the movement leaders, roughly meaning 'Papa'." Another letter written in 1940 shows similar tenderness and caring. + Eck, Diana L. (2003). Encountering God: A Spiritual Journey from Bozeman to Banaras. Beacon Press. p. 210. ISBN 978-0-8070-7301-8. Archived from the original on 12 October 2013. Retrieved 31 August 2013. Quote: "... his niece Manu, who, like others called this immortal Gandhi 'Bapu,' meaning not 'father,' but the familiar, 'daddy'." (p. 210) + Todd, Anne M. (2012). Mohandas Gandhi. Infobase Publishing. p. 8. ISBN 978-1-4381-0662-5. The name Gandhi means "grocer", although Mohandas's father and grandfather were politicians not grocers. + Gandhi, Rajmohan (2006) pp. 1–3. + Renard, John (1999). Responses to One Hundred and One Questions on Hinduism By John Renard. p. 139. ISBN 978-0-8091-3845-6. Retrieved 16 August 2020. + Gandhi, Mohandas K. (January 2009). An Autobiography: The Story of My Experiments With Truth. p. 21. ISBN 9781775414056. + Guha 2015 pp. 19–21 + Misra, Amalendu (2004). Identity and Religion: Foundations of anti-Islamism in India. p. 67. ISBN 978-0-7619-3227-7. + Gandhi, Rajmohan (2006). Mohandas: A True Story of a Man, His People, and an Empire By Gandhi. p. 5. ISBN 978-0-14-310411-7. + Tendulkar, D. G. (1951). Mahatma; life of Mohandas Karamchand Gandhi. Delhi: Ministry of Information and Broadcasting, Government of India. + Malhotra, S.L (2001). Lawyer to Mahatma: Life, Work and Transformation of M. K. Gandhi. p. 5. ISBN 978-81-7629-293-1. + Guha 2015, p. 21 + Guha 2015, p. 512 + Guha 2015, p. 22 + Sorokin, Pitirim Aleksandrovich (2002). The Ways and Power of Love: types, factors, and techniques of moral transformation. Templeton Foundation Press. p. 169. ISBN 978-1-890151-86-7. + Rudolph, Susanne Hoeber & Rudolph, Lloyd I. (1983). Gandhi: The Traditional Roots of Charisma. University of Chicago Press. p. 48. ISBN 978-0-226-73136-0. + Gandhi, Rajmohan (2006) pp. 2, 8, 269 + Arvind Sharma (2013). Gandhi: A Spiritual Biography. Yale University Press. pp. 11–14. ISBN 978-0-300-18738-0. + Rudolph, Susanne Hoeber & Rudolph, Lloyd I. (1983). Gandhi: The Traditional Roots of Charisma. University of Chicago Press. p. 17. ISBN 978-0-226-73136-0. + Gerard Toffin (2012). John Zavos; et al. (eds.). Public Hinduisms. SAGE Publications. pp. 249–257. ISBN 978-81-321-1696-7. + Guha 2015, p. 23 + Guha 2015, pp. 24–25 + Rajmohan Gandhi (2015). Gandhi before India. Vintage Books. pp. 24–25. ISBN 978-0-385-53230-3. + Louis Fischer (1982). Gandhi, his life and message for the world. Penguin. p. 96. ISBN 978-0-451-62142-9. + Rajmohan Gandhi (2015). Gandhi before India. Vintage Books. pp. 25–26. ISBN 978-0-385-53230-3. + Sankar Ghose (1991). Mahatma Gandhi. Allied Publishers. p. 4. ISBN 978-81-7023-205-6. + Mohanty, Rekha (2011). "From Satya to Sadbhavna" (PDF). Orissa Review (January 2011): 45–49. Archived from the original (PDF) on 1 January 2016. Retrieved 23 February 2012. + Gandhi (1940). The_Story_of_My_Experiments_with_Truth "At the High School"]; Archived 30 June 2012 at the Wayback Machine. + Gandhi (1940). The_Story_of_My_Experiments_with_Truth: "Playing the Husband"]; Archived 1 July 2012 at the Wayback Machine. + Ramachandra Guha (2015). Gandhi before India. Vintage Books. pp. 28–29. ISBN 978-0-385-53230-3. + Guha 2015, p. 29 + Guha 2015, p. 30 + Guha 2015, p. 32 + Gandhi (1940). Chapter "Preparation for England". Archived 2 July 2012 at the Wayback Machine + Rajmohan Gandhi (2015). Gandhi before India. Vintage Books. p. 32. ISBN 978-0-385-53230-3. + Guha 2015, pp. 33-34 + Rajmohan, Gandhi (2006). Gandhi: The Man, His People, and the Empire. p. 20–21. ISBN 9780520255708. + M K Gandhi (1940), The Story of My Experiments with Truth Archived 17 April 2016 at the Wayback Machine, Autobiography, Wikisource + Thomas Weber (2004). Gandhi as Disciple and Mentor. Cambridge University Press. pp. 19–25. ISBN 978-1-139-45657-9. + Brown (1991). + "Shyness my shield". Autobiography. 1927. + From International Vegetarian Union website. The article describes the farewell dinner https://ivu.org/history/gandhi/1891-11.html + Herman (2008), pp. 82–83 + Giliomee, Hermann & Mbenga, Bernard (2007). "3". In Roxanne Reid (ed.). New History of South Africa (1st ed.). Tafelberg. p. 193. ISBN 978-0-624-04359-1. + Power, Paul F. (1969). "Gandhi in South Africa". The Journal of Modern African Studies. 7 (3): 441–55. doi:10.1017/S0022278X00018590. JSTOR 159062. + "Into that Heaven of Freedom: The impact of apartheid on an Indian family's diasporic history", Mohamed M Keshavjee, 2015, by Mawenzi House Publishers, Ltd., Toronto, ON, Canada, ISBN 978-1-927494-27-1 + Parekh, Bhikhu C. (2001). Gandhi: a very short introduction. Oxford University Press. p. 7. ISBN 978-0-19-285457-5. + Gandhi (1940). Chapter "More Hardships". Archived 2 July 2012 at the Wayback Machine + S. Dhiman (2016). Gandhi and Leadership: New Horizons in Exemplary Leadership. Springer. pp. 25–27. ISBN 978-1-137-49235-7. + Fischer (2002) + Gandhi (1940). Chapter "Some Experiences". Archived 2 July 2012 at the Wayback Machine + Gandhi (1940). Chapter "What it is to be a coolie". Archived 11 April 2016 at the Wayback Machine + Herman (2008), pp. 87–88 + Allen, Jeremiah (2011). Sleeping with Strangers: A Vagabond's Journey Tramping the Globe. Other Places Publishing. p. 273. ISBN 978-1-935850-01-4. + Herman (2008), pp. 88–89 + "March 1897 Memorial" . The Collected Works of Mahatma Gandhi – via Wikisource.: correspondence and newspaper accounts of the incident. + Herman (2008), page 125 + Herman (2008) chapter 6. + "South African Medals that Mahatma Returned Put on View at Gandhi Mandap Exhibition" (PDF). Press Information Bureau of India - Archive. 5 March 1949. Retrieved 18 July 2020. + Rai, Ajay Shanker (2000). Gandhian Satyagraha: An Analytical And Critical Approach. Concept Publishing Company. p. 35. ISBN 978-81-7022-799-1. + Tolstoy, Leo (14 December 1908). "A Letter to A Hindu: The Subjection of India-Its Cause and Cure". The Literature Network. The Literature Network. Retrieved 12 February 2012. The Hindu Kural + Parel, Anthony J. (2002), "Gandhi and Tolstoy", in M. P. Mathai; M. S. John; Siby K. Joseph (eds.), Meditations on Gandhi : a Ravindra Varma festschrift, New Delhi: Concept, pp. 96–112, ISBN 9788170229612, retrieved 8 September 2012 + Guha, Ramachandra (2013), Gandhi Before India, Vol. 1, Ch. 22, Allen Lane, ISBN 0-670-08387-9. + Charles R. DiSalvo (2013). M.K. Gandhi, Attorney at Law: The Man before the Mahatma. pp. 14–15. ISBN 9780520956629. + Jones, Constance; Ryan, James (2009). Encyclopedia of Hinduism. Infobase Publishing. pp. 158–59. ISBN 978-1-4381-0873-5. Archived from the original on 21 October 2015. Retrieved 5 October 2012. + Ashwin Desai; Goolem Vahed (2015). The South African Gandhi: Stretcher-Bearer of Empire. Stanford University Press. pp. 22–26, 33–38. ISBN 978-0-8047-9717-7. + "Ram Guha is wrong. Gandhi went from a racist young man to a racist middle-aged man". 24 December 2018. + Edward Ramsamy; Michael Mbanaso; Chima Korieh. Minorities and the State in Africa. Cambria Press. pp. 71–73. ISBN 978-1-62196-874-0. + Herman (2008), pp. 136–137. + Herman (2008), pp. 280–281, 154–157 + For Kallenbach and the naming of Tolstoy Farm, see Vashi, Ashish (31 March 2011) "For Gandhi, Kallenbach was a Friend and Guide", The Times of India. Retrieved 1 January 2019. +For Johannesburg, see "Gandhi – A Medium for Truth" (link to article in Philosophy Now magazine) Archived 24 March 2014 at the Wayback Machine. Retrieved March 2014. + Corder, Catherine; Plaut, Martin (2014). "Gandhi's Decisive South African 1913 Campaign: A Personal Perspective from the Letters of Betty Molteno". South African Historical Journal. 66 (1): 22–54. doi:10.1080/02582473.2013.862565. S2CID 162635102. + Smith, Colleen (1 October 2006). "Mbeki: Mahatma Gandhi Satyagraha 100th Anniversary (01/10/2006)". Speeches. Polityorg.za. Archived from the original on 2 May 2013. Retrieved 20 January 2012. + Prashad, Ganesh (September 1966). "Whiggism in India". Political Science Quarterly. 81 (3): 412–31. doi:10.2307/2147642. JSTOR 2147642. + Markovits, Claude (2004). A History of Modern India, 1480–1950. Anthem Press. pp. 367–86. ISBN 978-1-84331-004-4. + Chronology of Mahatma Gandhi's Life:India 1918 in WikiSource based on the Collected Works of Mahatma Gandhi. Based on public domain volumes. + Desai, Mahadev Haribhai (1930). "Preface". Day-to-day with Gandhi: secretary's diary. Hemantkumar Nilkanth (translation). Sarva Seva Sangh Prakashan. Archived from the original on 3 June 2007. + Gandhi (1940). Chapter "Recruiting Campaign" Archived 2 July 2012 at the Wayback Machine. + Gandhi (1965), Collected Works, Vol 17. Archived 15 October 2009 at the Wayback Machine Chapter "67. Appeal for enlistment", Nadiad, 22 June 1918. + Gandhi (1965), Collected Works, Vol 17. Archived 15 October 2009 at the Wayback Machine "Chapter 8. Letter to J. L. Maffey", Nadiad, 30 April 1918. + Hardiman, David (April 2001). "Champaran and Gandhi: Planters, Peasants and Gandhian Politics by Jacques Pouchepadass (Review)". Journal of the Royal Asiatic Society. 11 (1): 99–101. doi:10.1017/S1356186301450152. JSTOR 25188108. + "Satyagraha Laboratories of Mahatma Gandhi". Indian National Congress website. All India Congress Committee. 2004. Archived from the original on 6 December 2006. Retrieved 25 February 2012. + Gandhi, Rajmohan (2006) pp. 196–97. + Brown, Judith M. (1974). Gandhi's Rise to Power: Indian Politics 1915–1922. Cambridge University Press. pp. 94–102. ISBN 978-0-521-09873-1. + Keith Robbins (2002). The First World War. Oxford University Press. pp. 133–137. ISBN 978-0-19-280318-4. + Michael J. Green; Nicholas Szechenyi (2017). A Global History of the Twentieth Century: Legacies and Lessons from Six National Perspectives. Rowman & Littlefield. pp. 89–90. ISBN 978-1-4422-7972-8. + Minault, Gail (1982) The Khilafat Movement Religious Symbolism and Political Mobilization in India, Columbia University Press, ISBN 0-231-05072-0, pp. 68–72, 78–82, 96–102, 108–109 + Minault, Gail (1982) The Khilafat Movement Religious Symbolism and Political Mobilization in India, Columbia University Press, ISBN 0-231-05072-0, pp. 4–8 + Sarah C.M. Paine (2015). Nation Building, State Building, and Economic Development: Case Studies and Comparisons. Routledge. pp. 20–21. ISBN 978-1-317-46409-9. + Ghose, Sankar (1991). Mahatma Gandhi. Allied Publishers. pp. 161–164. ISBN 978-81-7023-205-6. + Roderick Matthews (2012). Jinnah vs. Gandhi. Hachette. p. 31. ISBN 978-93-5009-078-7. Rabindranath Tagore heavily criticized Gandhi at the time in private letters (...). They reveal Tagore's belief that Gandhi had committed the Indian political nation to a cause that was mistakenly anti-Western and fundamentally negative. + Kham, Aqeeluzzafar (1990). "The All-India Muslim Conference and the Origin of the Khilafat Movement in India". Journal of the Pakistan Historical Society. 38 (2): 155–62. + Roberts, W. H. (1923). "A Review of the Gandhi Movement in India". Political Science Quarterly. 38 (2): 227–48. doi:10.2307/2142634. JSTOR 2142634. + Bose, Sugata & Jalal, Ayesha (2004). Modern South History, Culture, Political Economy. Psychology Press. pp. 112–14. ISBN 978-0-203-71253-5. + Brown (1991) pp. 140–47. + Minault, Gail (1982) The Khilafat Movement Religious Symbolism and Political Mobilization in India, Columbia University Press, ISBN 0-231-05072-0, pp. 113–116 + Akbar S. Ahmed (1997). Jinnah, Pakistan and Islamic Identity: The Search for Saladin. Routledge. pp. 57–71. ISBN 978-0-415-14966-2. + "Gandhi and Islam - IslamiCity". www.islamicity.org. Retrieved 18 April 2020. + von Pochhammer, Wilhelm (2005). India's Road to Nationhood: A Political History of the Subcontinent. Allied Publishers. p. 440. ISBN 978-81-7764-715-0. + Brown, Judith Margaret (1994). Modern India: the origins of an Asian democracy. Oxford U. Press. p. 228. ISBN 978-0-19-873112-2. + Sarkar, Sumit (1983). Modern India: 1885–1947. Macmillan. p. 233. ISBN 978-0-333-90425-1. + Markovits, Claude, ed. (2004). A History of Modern India, 1480–1950. Anthem Press. p. 372. ISBN 978-1-84331-004-4. + Mary Elizabeth King, "Mohandas K, Gandhi and Martin Luther King, Jr.'s Bequest: Nonviolent Civil Resistance in a Globalized World" in Lewis V. Baldwin & Paul R. Dekar (2013). "In an Inescapable Network of Mutuality": Martin Luther King, Jr. and the Globalization of an Ethical Ideal. Wipf and Stock. pp. 168–69. ISBN 9781610974349. Archived from the original on 1 January 2016. + Stanley Wolpert (2002). Gandhi's Passion: The Life and Legacy of Mahatma Gandhi. Oxford University Press. pp. 99–103. ISBN 978-0-19-515634-8. Archived from the original on 19 February 2017. + Gandhi, Mohandas Karamchand (1940). An Autobiography or The Story of My Experiments With Truth (2 ed.). Ahmedabad: Navajivan Publishing House. p. 82. ISBN 0-8070-5909-9. Also available at Wikisource. + Chakrabarty, Bidyut (2008). Indian Politics and Society since Independence: events, processes and ideology. Routledge. p. 154. ISBN 978-0-415-40868-4. Retrieved 4 April 2012. + Desai, p. 89. + Shashi, p. 9. + Desai, p. 131. + Datta, Amaresh (1 January 2006). The Encyclopaedia of Indian Literature (Volume Two) (Devraj To Jyoti). Sahitya Akademi. p. 1345. ISBN 978-81-260-1194-0. Retrieved 4 April 2012. + Gandhi 1990, p. 172. + Sankar Ghose (1991). Mahatma Gandhi. Allied Publishers. pp. 199–204. ISBN 978-81-7023-205-6. + Herman (2008) pp. 419–420 + S R Bakshi (1988). Gandhi and Gandhi and the Mass Movement. New Delhi. pp. 133–34. + L. Fischer (1950). Gandhi and the Mass Movement. pp. 298–99. + Hatt (2002), p. 33. + Sarma, Bina Kumari (January 1994). "Gandhian Movement and Women's Awakening in Orissa". Indian Historical Review. 21 (1/2): 78–79. ISSN 0376-9836. + Marilyn French (2008). From Eve to Dawn, A History of Women in the World, Volume IV: Revolutions and Struggles for Justice in the 20th Century. City University of New York Press. pp. 219–220. ISBN 978-1-55861-628-8. + Suruchi Thapar-Bjorkert (2006). Women in the Indian National Movement: Unseen Faces and Unheard Voices, 1930–42. SAGE Publications. pp. 77–79. ISBN 978-0-7619-3407-3. + Murali, Atlury (January 1985). "Non-Cooperation in Andhra in 1920–22: Nationalist Intelligentsia and the Mobilization of Peasantry". Indian Historical Review. 12 (1/2): 188–217. ISSN 0376-9836. + Dennis Dalton (2012). Mahatma Gandhi: Nonviolent Power in Action. Columbia University Press. pp. 8–14, 20–23, 30–35. ISBN 978-0-231-15959-3. + S. Dhiman (2016). Gandhi and Leadership: New Horizons in Exemplary Leadership. Springer. pp. 46–49. ISBN 978-1-137-49235-7. + John M Levine; Michael A. Hogg (2010). Encyclopedia of Group Processes and Intergroup Relations. SAGE Publications. p. 73. ISBN 978-1-4129-4208-9. + Herman (2008) pp. 375–77. + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. p. 359. ISBN 9780553905045. Archived from the original on 1 January 2016. + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. pp. 378–381. ISBN 978-0-553-90504-5. Archived from the original on 13 September 2014. + Andrew Muldoon (2016). Empire, Politics and the Creation of the 1935 India Act: Last Act of the Raj. Routledge. pp. 92–99. ISBN 978-1-317-14431-1. + Gandhi, Rajmohan (2006). Gandhi: The Man, His People, and the Empire. University of California Press. pp. 332–333. ISBN 978-0-520-25570-8. Archived from the original on 22 February 2017. + Andrew Muldoon (2016). Empire, Politics and the Creation of the 1935 India Act: Last Act of the Raj. Routledge. p. 97. ISBN 978-1-317-14431-1. + Judith Margaret Brown (1991). Gandhi: Prisoner of Hope. Yale University Press. pp. 252–257. ISBN 978-0-300-05125-4. + English Heritage website with information on his stay at Kingsley Hall https://www.english-heritage.org.uk/visit/blue-plaques/mahatma-gandhi-kingsley-hall/ + the video shows MKGs populariy in the poorer districts https://www.youtube.com/watch?v=sLYIEajnqnI + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. pp. 382–390. ISBN 978-0-553-90504-5. Archived from the original on 13 September 2014. + Nicholas B. Dirks (2011). Castes of Mind: Colonialism and the Making of Modern India. Princeton University Press. pp. 267–274. ISBN 978-1-4008-4094-6. + Kamath, M. V. (1995). Gandhi's Coolie: Life & Times of Ramkrishna Bajaj. Allied Publishers. p. 24. ISBN 81-7023-487-5. + Rachel Fell McDermott; et al. (2014). Sources of Indian Traditions: Modern India, Pakistan, and Bangladesh. Columbia University Press. pp. 369–370. ISBN 978-0-231-51092-9. + Gandhi 1990, p. 246. + Ghose, Sankar (1992). Jawaharlal Nehru, A Biography, p. 137. Allied Publishers Limited. + Gandhi 1990, pp. 277–281. + Sarkar, Jayabrata (18 April 2006). "Power, Hegemony and Politics: Leadership Struggle in Congress in the 1930s". Modern Asian Studies. 40 (2): 333–70. doi:10.1017/S0026749X0600179X. + Dash, Siddhartha (January 2005). "Gandhi and Subhas Chandra Bose" (PDF). Orissa Review. Archived from the original (PDF) on 24 December 2012. Retrieved 12 April 2012. + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. pp. 467–470. ISBN 978-0-553-90504-5. Archived from the original on 13 September 2014. + Bipan Chandra (2000). India's Struggle for Independence. Penguin Books. p. 543. ISBN 978-81-8475-183-3. + Stanley Wolpert (2002). Gandhi's Passion: The Life and Legacy of Mahatma Gandhi. Oxford University Press. pp. 74–75. ISBN 978-0-19-515634-8. Archived from the original on 19 February 2017. + Gandhi 1990, p. 309. + Gurcharan Das (1990). A Fine Family. Penguin Books. pp. 49–50. ISBN 978-0-14-012258-9. + Stanley Wolpert (2002). Gandhi's Passion: The Life and Legacy of Mahatma Gandhi. Oxford University Press. pp. 205–211. ISBN 978-0-19-515634-8. Archived from the original on 19 February 2017. + Brock, Peter (1983). The Mahatma and mother India: essays on Gandhiʼs nonviolence and nationalism. Navajivan Publishing House. p. 34. + Limaye, Madhu (1990). Mahatma Gandhi and Jawaharlal Nehru: a historic partnership. B. R. Publishing Corporation. p. 11. ISBN 81-7018-547-5. + von Pochhammer, Wilhelm (2005). India's Road to Nationhood: A Political History of the Subcontinent. Allied Publishers. p. 469. ISBN 81-7764-715-6. + Lapping, Brian (1989). End of empire. Paladin. ISBN 978-0-586-08870-8. + Mahatma Gandhi (2000). The Collected Works of Mahatma Gandhi. pp. 456–462. ISBN 978-81-230-0169-2., Archive of Gandhi-Jinnah communications (pp. 11–34) + "Gandhi, Jinnah Meet First Time Since '44; Disagree on Pakistan, but Will Push Peace". The New York Times. 7 May 1947. Archived from the original on 30 April 2013. Retrieved 25 March 2012. (subscription required) + Bhattacharya, Sanjoy (2001). Propaganda and information in Eastern India, 1939–45: a necessary weapon of war. Psychology Press. p. 33. ISBN 978-0-7007-1406-3. + Shashi, p. 13. + Reprinted in Fischer (2002), pp. 106–08. + Hermann Kulke; Dietmar Rothermund (2004). A History of India. Routledge. pp. 311–312, context: 308–316. ISBN 978-0-415-32920-0. + Penderel Moon (1962). Divide and Quit. University of California Press. pp. 11–28. + Jack, p. 418. + Stanley Wolpert (2009). Shameful Flight: The Last Years of the British Empire in India. Oxford University Press. pp. 118–121. ISBN 978-0-19-539394-1. Archived from the original on 1 October 2013. + Wolpert, Chapter 1. Archived 21 March 2016 at the Wayback Machine, Oxford University Press + Stanley Wolpert (2009). Shameful Flight: The Last Years of the British Empire in India. Oxford University Press. pp. 118–127. ISBN 978-0-19-539394-1. Archived from the original on 1 October 2013. + Dennis Dalton (2012). Mahatma Gandhi: Nonviolent Power in Action. Columbia University Press. pp. 64–66. ISBN 978-0-231-53039-2. + Wolpert, Oxford University Press, p. 7. + Metcalf, Barbara Daly; Metcalf, Thomas R. (2006). A concise history of modern India. Cambridge University Press. pp. 221–22. ISBN 978-0-521-86362-9. + Lelyveld, Joseph (2011). Great Soul: Mahatma Gandhi and His Struggle with India. Random House Digital, Inc. pp. 278–81. ISBN 978-0-307-26958-4. + Mahatma Gandhi (2000). The Collected Works of Mahatma Gandhi. Publications Division, Ministry of Information and Broadcasting, Government of India. p. 130. ISBN 978-81-230-0154-8. + Gandhi, Tushar A. (2007). "Let's Kill Gandhi !": A Chronicle of His Last Days, the Conspiracy, Murder, Investigation, and Trial. Rupa & Company. p. 12. ISBN 978-81-291-1094-7. Archived from the original on 1 January 2016. + Nicholas Henry Pronko (2013). Empirical Foundations of Psychology. Routledge. pp. 342–343. ISBN 978-1-136-32701-8. + Sankar Ghose (1991). Mahatma Gandhi. Allied Publishers. p. 386. ISBN 978-81-7023-205-6. + Jain, 1996, pp. 45–47. + Hardiman, David (2003). Gandhi in His Time and Ours: The Global Legacy of His Ideas. Columbia University Press. pp. 174–76. ISBN 978-0-231-13114-8. + Jay Robert Nash (1981). Almanac of World Crime. New York: Rowman & Littlefield. p. 69. ISBN 978-1-4617-4768-0. + G.D. Khosla (1965), The Murder of the Mahatma Archived 21 September 2015 at the Wayback Machine, Chief Justice of Punjab, Jaico Publishers, pages 38 + Claude Markovits (2004). The UnGandhian Gandhi: The Life and Afterlife of the Mahatma. Anthem Press. pp. 57–59. ISBN 978-1-84331-127-0. + N V Godse (1948). Why I assassinated Mahatma Gandhi?. Surya Bharti Parkashan (Reprint: 1993). OCLC 33991989. + Mahatma Gandhi (1994). The Gandhi Reader: A Sourcebook of His Life and Writings. Grove Press. pp. 483–489. ISBN 978-0-8021-3161-4. + "Over a million get last darshan". The Indian Express. 1 February 1948. p. 1 (bottom left). Retrieved 19 January 2012. + "Of all faiths and races, together they shed their silent tears". The Indian Express. 31 January 1948. p. 5 (top centre). Retrieved 19 January 2012. + Guha, Ramachandra (2007), India after Gandhi, Harper Collins, ISBN 978-0-330-50554-3, pp. 37–40. + Gopal, Sarvepalli (1979), Jawaharlal Nehru, Jonathan Cape, London, ISBN 0-224-01621-0, pp. 16–17. + Khan, Yasmin (2011). "Performing Peace: Gandhi's assassination as a critical moment in the consolidation of the Nehruvian state". Modern Asian Studies. 45 (1): 57–80. doi:10.1017/S0026749X10000223. S2CID 144894540. (subscription required) + Claude Markovits (2004). The UnGandhian Gandhi: The Life and Afterlife of the Mahatma. Anthem Press. pp. 58–62. ISBN 978-1-84331-127-0. + LIFE. Time Inc. 15 March 1948. p. 76. ISSN 0024-3019. + Ramesh, Randeep (16 January 2008). "Gandhi's ashes to rest at sea, not in a museum". The Guardian. London. Archived from the original on 1 September 2013. Retrieved 14 January 2012. + Kumar, Shanti (2006). Gandhi meets primetime: globalization and nationalism in Indian television. University of Illinois Press. p. 170. ISBN 978-0-252-07244-4. + Ferrell, David (27 September 2001). "A Little Serenity in a City of Madness" (Abstract). Los Angeles Times. p. B 2. Archived from the original on 5 October 2013. Retrieved 14 January 2012. + Margot Bigg (2012). Delhi. Avalon. p. 14. ISBN 978-1-61238-490-0. + Lal, Vinay (January 2001). "'Hey Ram': The Politics of Gandhi's Last Words". Humanscape. 8 (1): 34–38. Archived from the original on 4 June 2004. + William Borman (1986). Gandhi and Non-Violence. State University of New York Press. pp. 192–195, 208–209. ISBN 978-0-88706-331-2. + Dennis Dalton (2012). Mahatma Gandhi: Nonviolent Power in Action. Columbia University Press. pp. 30–35. ISBN 978-0-231-15959-3., Quote: "Yet he [Gandhi] must bear some of the responsibility for losing his followers along the way. The sheer vagueness and contradictions recurrent throughout his writing made it easier to accept him as a saint than to fathom the challenge posed by his demanding beliefs. Gandhi saw no harm in self-contradictions: life was a series of experiments, and any principle might change if Truth so dictated". + Brown, Judith M. & Parel, Anthony (2011). The Cambridge Companion to Gandhi. Cambridge University Press. p. 93. ISBN 978-0-521-13345-6. + Indira Carr (2012). Stuart Brown; et al. (eds.). Biographical Dictionary of Twentieth-Century Philosophers. Routledge. pp. 263–264. ISBN 978-1-134-92796-8., Quote: "Gandhi, Mohandas Karamchand. Indian. born: 2 October 1869, Gujarat; (...) Influences: Vaishnavism, Jainism and Advaita Vedanta." + J. Jordens (1998). Gandhi's Religion: A Homespun Shawl. Palgrave Macmillan. p. 116. ISBN 978-0-230-37389-1., Quote: "I am an advaitist, and yet I can support Dvaitism". + Jeffrey D. Long (2008). Rita Sherma and Arvind Sharma (ed.). Hermeneutics and Hindu Thought: Toward a Fusion of Horizons. Springer. p. 194. ISBN 978-1-4020-8192-7. + Gandhi, Mahatma (2013). Hinduism According to Gandhi: Thoughts, Writings and Critical Interpretation. Orient Paperbacks. p. 85. ISBN 978-81-222-0558-9. + Anil Mishra (2012). Reading Gandhi. Pearson. p. 2. ISBN 978-81-317-9964-2. + Cribb, R. B. (1985). "The Early Political Philosophy of M. K. Gandhi, 1869–1893". Asian Profile. 13 (4): 353–60. + Crib (1985). + Bhikhu C. Parekh (2001). Gandhi. Sterling Publishing. pp. 43, 71. ISBN 978-1-4027-6887-3. + Indira Carr (2012). Stuart Brown; et al. (eds.). Biographical Dictionary of Twentieth-Century Philosophers. Routledge. p. 263. ISBN 978-1-134-92796-8. + Glyn Richards (2016). Studies in Religion: A Comparative Approach to Theological and Philosophical Themes. Springer. pp. 64–78. ISBN 978-1-349-24147-7. + Gokhale, Balkrishna Govind (1972). "Gandhi and History". History and Theory. 11 (2): 214–25. doi:10.2307/2504587. JSTOR 2504587. + Williams, Raymond Brady (2001). An introduction to Swaminarayan Hinduism. Cambridge University Press. p. 173. ISBN 0-521-65422-X. + Meller, Helen Elizabeth (1994). Patrick Geddes: social evolutionist and city planner. Routledge. p. 159. ISBN 0-415-10393-2. + Spodek, Howard (1971). "On the Origins of Gandhi's Political Methodology: The Heritage of Kathiawad and Gujarat". Journal of Asian Studies. 30 (2): 361–72. doi:10.2307/2942919. JSTOR 2942919. + B. Srinivasa Murthy, ed. (1987). Mahatma Gandhi and Leo Tolstoy: Letters. ISBN 0-941910-03-2. + Murthy, B. Srinivasa, ed. (1987). Mahatma Gandhi and Leo Tolstoy: Letters (PDF). Long Beach, California: Long Beach Publications. ISBN 0-941910-03-2. Archived (PDF) from the original on 17 September 2012. Retrieved 14 January 2012. + Green, Martin Burgess (1986). The origins of nonviolence: Tolstoy and Gandhi in their historical settings. Pennsylvania State University Press. ISBN 978-0-271-00414-3. Retrieved 17 January 2012. + Bhana, Surendra (1979). "Tolstoy Farm, A Satyagrahi's Battle Ground". Journal of Indian History. 57 (2/3): 431–40. + "Raychandbhai". MK Gandhi. Bombay Sarvodaya Mandal & Gandhi Research Foundation. Retrieved 2 July 2020. + Gandhi, Mahatma (1993). Gandhi: An Autobiography (Beacon Press ed.). pp. 63–65. ISBN 0-8070-5909-9. + Webber, Thomas (3 March 2011). Gandhi as Disciple and Mentor (3 ed.). Cambridge University Press. pp. 33–36. ISBN 978-0-521-17448-0. + Gandhi, Mahatma (June 1930). "Modern Review". + Mahatma Gandhi (1957). An Autobiography: The Story of My Experiments with Truth. 39. Beacon Press. p. 262. ISBN 978-0-8070-5909-8. Retrieved 23 November 2016. + Thomas Weber (2 December 2004). Gandhi as Disciple and Mentor. Cambridge University Press. pp. 34–36. ISBN 978-1-139-45657-9. + "Mahatma Gandhi – The religious quest | Biography, Accomplishments, & Facts". Encyclopædia Britannica. 2015. Archived from the original on 13 May 2017. Retrieved 3 June 2017. + Martin Burgess Green (1993). Gandhi: voice of a new age revolution. Continuum. pp. 123–125. ISBN 978-0-8264-0620-0. + Fischer Louis (1950). The life of Mahatma Gandhi. HarperCollins. pp. 43–44. ISBN 978-0-06-091038-9. + Ghose, Sankar (1991). Mahatma Gandhi. Allied Publishers. pp. 377–378. ISBN 978-81-7023-205-6. + Richard H. Davis (2014). The "Bhagavad Gita": A Biography. Princeton University Press. pp. 137–145. ISBN 978-1-4008-5197-3. + Suhrud, Tridip (November–December 2018). "The Story of Antaryami". Social Scientist. 46 (11–12): 45. JSTOR 26599997. + Chatterjee, Margaret (2005). Gandhi and the Challenge of Religious Diversity: Religious Pluralism Revisited. Bibliophile South Asia. p. 119. ISBN 978-81-85002-46-0. + Fiala, Andrew (2018). The Routledge Handbook of Pacifism and Nonviolence. Routledge. p. 94. ISBN 978-1-317-27197-0., Fiala quotes Ambitabh Pal, "Gandhi himself followed a strand of Hinduism that with its emphasis on service and on poetry and songs bore similarities to Sufi Islam". + Ghose, Sankar (1991). Mahatma Gandhi. Allied Publishers. p. 275. ISBN 978-81-7023-205-6. + Simone Panter-Brick (2015). Gandhi and Nationalism: The Path to Indian Independence. I. B. Tauris. pp. 75–77. ISBN 978-1-78453-023-5. + Mahatma Gandhi (2005). All Men Are Brothers. Bloomsbury Academic. p. 22. ISBN 978-0-8264-1739-8. + Rahul Sagar (2015). David M. Malone; et al. (eds.). The Oxford Handbook of Indian Foreign Policy. Oxford University Press. pp. 71–73. ISBN 978-0-19-106118-9. + Rahul Sagar (2015). David M. Malone; et al. (eds.). The Oxford Handbook of Indian Foreign Policy. Oxford University Press. p. 70. ISBN 978-0-19-106118-9. + Gene Sharp (1960). Gandhi Wields the Weapon of Moral Power: Three Case Histories. Navajivan. p. 4. + Dennis Dalton (2012). Mahatma Gandhi: Nonviolent Power in Action. Columbia University Press. pp. 30–32. ISBN 978-0-231-15959-3. + William Borman (1986). Gandhi and Non-Violence. State University of New York Press. pp. 26–34. ISBN 978-0-88706-331-2. + Indira Carr (2012). Stuart Brown; et al. (eds.). Biographical Dictionary of Twentieth-Century Philosophers. Routledge. p. 264. ISBN 978-1-134-92796-8. + Watson, I. Bruce (1977). "Satyagraha: The Gandhian Synthesis". Journal of Indian History. 55 (1/2): 325–35. + Glyn Richards (1986), Gandhi's Concept of Truth and the Advaita Tradition, Religious Studies, Cambridge University Press, Vol. 22, No. 1 (Mar. 1986), pp. 1–14 + Parel, Anthony (2006). Gandhi's Philosophy and the Quest for Harmony. Cambridge University Press. p. 195. ISBN 978-0-521-86715-3. Retrieved 13 January 2012. + Nicholas F. Gier (2004). The Virtue of Nonviolence: From Gautama to Gandhi. State University of New York Press. pp. 40–42. ISBN 978-0-7914-5949-2. + Salt March: Indian History Archived 1 July 2017 at the Wayback Machine, Encyclopædia Britannica + Sita Anantha Raman (2009). Women in India: A Social and Cultural History. ABC-CLIO. pp. 164–166. ISBN 978-0-313-01440-6. + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. p. 176. ISBN 978-0-553-90504-5. + Gandhi, M.K. "Some Rules of Satyagraha Young India (Navajivan) 23 February 1930". The Collected Works of Mahatma Gandhi. 48: 340. + Prabhu, R. K. and Rao, U. R. (eds.) (1967) from section "Power of Satyagraha" Archived 2 September 2007 at the Wayback Machine, of the book The Mind of Mahatma Gandhi, Ahemadabad, India. + Gandhi, M. K. (1982) [Young India, 16 June 1920]. "156. The Law of Suffering" (PDF). Collected Works of Mahatma Gandhi. 20 (electronic ed.). New Delhi: Publications Division, Ministry of Information and Broadcasting, Govt. of India. pp. 396–99. Archived (PDF) from the original on 28 January 2012. Retrieved 14 January 2012. + Sharma, Jai Narain (2008). Satyagraha: Gandhi's approach to conflict resolution. Concept Publishing Company. p. 17. ISBN 978-81-8069-480-6. Retrieved 26 January 2012. + R. Taras (2002). Liberal and Illiberal Nationalisms. Palgrave Macmillan. p. 91. ISBN 978-0-230-59640-5., Quote: "In 1920 Jinnah opposed satyagraha and resigned from the Congress, boosting the fortunes of the Muslim League." + Yasmin Khan (2007). The Great Partition: The Making of India and Pakistan. Yale University Press. pp. 11–22. ISBN 978-0-300-12078-3. + Rafiq Zakaria (2002). The Man who Divided India. Popular Prakashan. pp. 83–85. ISBN 978-81-7991-145-7. + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. p. 586. ISBN 978-0-553-90504-5. Archived from the original on 13 September 2014. + Cháirez-Garza, Jesús Francisco (2 January 2014). "Touching space: Ambedkar on the spatial features of untouchability". Contemporary South Asia. Taylor & Francis. 22 (1): 37–50. doi:10.1080/09584935.2013.870978. S2CID 145020542.; +B.R. Ambedkar (1945), What Congress and Gandhi have done to the Untouchables, Thacker & Co. Editions, First Edition, pages v, 282–297 + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. pp. 359, 378–380. ISBN 978-0-553-90504-5. Archived from the original on 13 September 2014. + Asirvatham, Eddy (1995). Political Theory. S.chand. ISBN 81-219-0346-7. + Christopher Chapple (1993). Nonviolence to Animals, Earth, and Self in Asian Traditions. State University of New York Press. pp. 16–18, 54–57. ISBN 978-0-7914-1497-2. + Gandhi, Mohandis K. (11 August 1920), "The Doctrine of the Sword", Young India, M. K. Gandhi: 3, retrieved 3 May 2017 Cited from Borman, William (1986). Gandhi and nonviolence. SUNY Press. p. 253. ISBN 978-0-88706-331-2. + Faisal Devji, The Impossible Indian: Gandhi and the Temptation of Violence (Harvard University Press; 2012) + Johnson, Richard L. (2006). Gandhi's Experiments With Truth: Essential Writings By And About Mahatma Gandhi. Lexington Books. p. 11. ISBN 978-0-7391-1143-7. Retrieved 9 May 2012. + Mahatma Gandhi on Bhagat Singh. + Rai, Raghunath (1992). Themes in Indian History. FK Publications. p. 282. ISBN 978-81-89611-62-0. + Wolpert, p. 197. + Orwell, review of Louis Fischer's Gandhi and Stalin, The Observer, 10 October 1948, reprinted in It Is what I Think, pp. 452–453. + Fischer, Louis (1950). The life of Mahatma Gandhi. Harper. p. 348. + George Orwell, "Reflections on Gandhi", Partisan Review, January 1949. + J.T.F. Jordens (1998). Gandhi's Religion: A Homespun Shawl. New York: Palgrave Macmillan. pp. 107–108. ISBN 978-0-230-37389-1. + Nikky-Guninder Kaur Singh (2003). Harold Coward (ed.). Indian Critiques of Gandhi. State University of New York Press. pp. 185–188. ISBN 978-0-7914-8588-0. + Gorder, A. Christian Van (2014). Islam, Peace and Social Justice: A Christian Perspective. James Clarke & Co. p. 166. ISBN 9780227902004. + Malekian, Farhad (2018). Corpus Juris of Islamic International Criminal Justice. Cambridge Scholars Publishing. p. 409. ISBN 9781527516939. + Yasmin Khan, "Performing Peace: Gandhi's assassination as a critical moment in the consolidation of the Nehruvian state." Modern Asian Studies 45.1 (2011): 57-80. + M K Gandhi (1925). Young India. Navajivan Publishing. pp. 81–82. + Mohandas Karmchand Gandhi (2004). V Geetha (ed.). Soul Force: Gandhi's Writings on Peace. Gandhi Publications Trust. pp. 193–194. ISBN 978-81-86211-85-4. + Mohandas K. Gandhi; Michael Nagler (Ed) (2006). Gandhi on Islam. Berkeley Hills. pp. 1–17, 31–38. ISBN 1-893163-64-4. + Niranjan Ramakrishnan (2013). Reading Gandhi in the Twenty-First Century. Palgrave Macmillan. p. 59. ISBN 978-1-137-32514-3. + Kumaraswamy, P. R. (1992). "Mahatma Gandhi and the Jewish National Home: An Assessment". Asian and African Studies: Journal of the Israel Oriental Society. 26 (1): 1–13. + Simone Panter-Brick (2015). Gandhi and Nationalism: The Path to Indian Independence. I.B.Tauris. pp. 118–119. ISBN 978-1-78453-023-5. + M. Naeem Qureshi (1999). Reinhard Schulze (ed.). Pan-Islam in British Indian Politics: A Study of the Khilafat Movement, 1918–1924. BRILL Academic. pp. 104–105 with footnotes. ISBN 90-04-11371-1. Archived from the original on 24 April 2016. + Muhammad Soaleh Korejo (1993). The Frontier Gandhi: His Place in History. Oxford University Press. pp. 77–79. ISBN 978-0-19-577461-0. + Stanley Wolpert (2001). Gandhi's Passion: The Life and Legacy of Mahatma Gandhi. Oxford University Press. pp. 243–244. ISBN 978-0-19-972872-5. + Rein Fernhout (1995). ʻAbd Allāh Aḥmad Naʻim, Jerald Gort and Henry Jansen (ed.). Human Rights and Religious Values: An Uneasy Relationship?. Rodopi. pp. 126–131. ISBN 978-90-5183-777-3. + Chad M. Bauman (2015). Pentecostals, Proselytization, and Anti-Christian Violence in Contemporary India. Oxford University Press. pp. 50, 56–59, 66. ISBN 978-0-19-020210-1. + Robert Eric Frykenberg; Richard Fox Young (2009). India and the Indianness of Christianity. Wm. B. Eerdmans. pp. 211–214. ISBN 978-0-8028-6392-8. + John C.B. Webster (1993). Harold Coward (ed.). Hindu-Christian Dialogue: Perspectives and Encounters. Motilal Banarsidass. pp. 81–86, 89–95. ISBN 978-81-208-1158-4. Archived from the original on 17 March 2015. + Eric J. Sharpe (1993). Harold Coward (ed.). Hindu-Christian Dialogue: Perspectives and Encounters. Motilal Banarsidass. p. 105. ISBN 978-81-208-1158-4. Archived from the original on 17 March 2015. + Johnson, R.L. (2006). Gandhi's Experiments with Truth: Essential Writings by and about Mahatma Gandhi. Studies in Comparative Philosophy. Lexington Books. p. 269. ISBN 978-0-7391-1143-7. + Markovits, C. (2004). The UnGandhian Gandhi: The Life and Afterlife of the Mahatma. Anthem South Asian studies. Anthem Press. p. 16. ISBN 978-1-84331-127-0. + Rudolph, L.I.; Rudolph, S.H. (2010). Postmodern Gandhi and Other Essays: Gandhi in the World and at Home. University of Chicago Press. pp. 99, 114–118. ISBN 978-0-226-73131-5. + de Saint-Cheron, M. (2017). Gandhi: Anti-Biography of a Great Soul. Taylor & Francis. p. 13. ISBN 978-1-351-47062-9. Retrieved 1 April 2018. + P. R. Kumaraswamy (2010). India's Israel Policy. Columbia University Press. pp. 36–38. ISBN 978-0-231-52548-0. + Fischer Louis (1950). The life of Mahatma Gandhi. HarperCollins. p. 424. ISBN 978-0-06-091038-9. + Panter-Brick, Simone (2008), Gandhi and the Middle East: Jews, Arabs and Imperial Interests. London: I.B. Tauris, ISBN 1-84511-584-8. + Panter-Brick, Simone. "Gandhi's Dream of Hindu-Muslim Unity and its two Offshoots in the Middle East" Archived 17 July 2012 at the Wayback Machine. Durham Anthropology Journal, Volume 16(2), 2009: pp. 54–66. + Jack, p. 317. + Murti, Ramana V.V. (1968). "Buber's Dialogue and Gandhi's Satyagraha". Journal of the History of Ideas. 29 (4): 605–13. doi:10.2307/2708297. JSTOR 2708297. + Simone Panter-Brick (2009), Gandhi's Views on the Resolution of the Conflict in Palestine: A Note, Middle Eastern Studies, Taylor & Francis, Vol. 45, No. 1 (Jan. 2009), pp. 127–133 + Stanley Wolpert (2002). Gandhi's Passion: The Life and Legacy of Mahatma Gandhi. Oxford University Press. pp. 14, 25–27. ISBN 978-0-19-515634-8. Archived from the original on 19 February 2017., Quote: "The Gandhis had always been strict vegetarians, as are all devout Hindus". + Lisa Kemmerer (2012). Animals and World Religions. Oxford University Press. pp. 65–68. ISBN 978-0-19-979068-5. + Unto Tähtinen (1979). The Core of Gandhi's Philosophy. Abhinav Publications. pp. 61–62, 51–52. ISBN 978-0-8364-0516-3. + Chitrita Banerji, Eating India: an odyssey into the food and culture of the land of spices (2007), p. 169. + Ronald Terchek (1998). Gandhi: Struggling for Autonomy. Rowman & Littlefield. pp. 204–206. ISBN 978-0-8476-9215-6. + Becker, Carol (2006). "Gandhi's Body and Further Representations of War and Peace". Art Journal. 65 (4): 78–95. doi:10.2307/20068500. JSTOR 20068500. + Joseph S. Alter (2011). Gandhi's Body: Sex, Diet, and the Politics of Nationalism. University of Pennsylvania Press. pp. 4–5, 21–22, 34–38, 162–163. ISBN 978-0-8122-0474-2. + Kerry S. Walters; Lisa Portmess (1999). Ethical Vegetarianism: From Pythagoras to Peter Singer. State University of New York Press. pp. 139–144. ISBN 978-0-7914-4043-8. + Wolpert, p. 22. + Arthur Herman (2008). Gandhi & Churchill: The Epic Rivalry that Destroyed an Empire and Forged Our Age. Random House. pp. 89–90, 294–295. ISBN 978-0-553-90504-5. Archived from the original on 13 September 2014. + Mahatma Gandhi (1957). An Autobiography: The Story of My Experiments with Truth. Beacon Press. pp. 328–330. ISBN 978-0-8070-5909-8. + Joseph S. Alter (2011). Gandhi's Body: Sex, Diet, and the Politics of Nationalism. University of Pennsylvania Press. pp. 21–22, 34–34, 74–75, 162–163. ISBN 978-0-8122-0474-2. + Georg Feuerstein (2011). The Path of Yoga: An Essential Guide to Its Principles and Practices. Shambhala Publications. p. 66. ISBN 978-0-8348-2292-4. + "Towards an understanding of Gandhi's views on Science". Mkgandhi.org. 1 November 1934. Archived from the original on 11 March 2016. Retrieved 12 July 2016. + Pratt, Tim & Vernon, James (2005). "'Appeal from this fiery bed...': The Colonial Politics of Gandhi's Fasts and Their Metropolitan Reception". Journal of British Studies. 44 (1): 92–114. doi:10.1086/424944. + Alter, Joseph S. (1996). "Gandhi's body, Gandhi's truth: Nonviolence and the biomoral imperative of public health". Journal of Asian Studies. 35 (2): 305–306, 309–310, 313–317, 320–321 (all with footnotes). doi:10.2307/2943361. JSTOR 2943361. + "Mahatma Gandhi's Underweight Health Records Revealed For the 1st Time; Know his Heart Health, Serious Diseases". krishijagran.com. Retrieved 26 March 2019. + Norvell, Lyn (1997). "Gandhi and the Indian Women's Movement". British Library Journal. 23 (1): 12–27. ISSN 0305-5167. Archived from the original on 4 October 2013. + Madhu Purnima Kishwar (2008). Zealous Reformers, Deadly Laws. SAGE Publications. pp. 132–133. ISBN 978-81-321-0009-6. + Angela Woollacott (2006). Gender and Empire. Palgrave Macmillan. pp. 107–108. ISBN 978-0-230-20485-0. + Kumari Jayawardena (2016). Feminism and Nationalism in the Third World. Verso. pp. 95–99. ISBN 978-1-78478-431-7. + A. P. Sharma (2010). Indian & Western Educational Philosophy. Pustak Mahal. pp. 154–156. ISBN 978-81-7806-201-3. + Winthrop Sargeant (2010). Christopher Key Chapple (ed.). The Bhagavad Gita: Twenty-fifth–Anniversary Edition. State University of New York Press. pp. x–xviii, 285 (verse 6.14), 415 (verse 10.5), 535 (verse 13.7). ISBN 978-1-4384-2840-6. Archived from the original on 15 April 2017. + Thomas Weber (2004). Gandhi as Disciple and Mentor. Cambridge University Press. p. 33. ISBN 978-1-139-45657-9. + Sankar Ghose (1991). Mahatma Gandhi. Allied Publishers. pp. 66–67. ISBN 978-81-7023-205-6. + Sankar Ghose (1991). Mahatma Gandhi. Allied Publishers. pp. 354–357. ISBN 978-81-7023-205-6. + Bhikhu C. Parekh (1999). Colonialism, tradition, and reform: an analysis of Gandhi's political discourse. Sage Publications. pp. 210–221. ISBN 978-0-7619-9382-7. Archived from the original on 29 June 2014. + Jad Adams (2 January 2012). "Thrill of the chaste: The truth about Gandhi's sex life". The Independent. Archived from the original on 3 June 2013. + Uma Majmudar (2012). Gandhi's Pilgrimage of Faith: From Darkness to Light. State University of New York Press. pp. 224–225. ISBN 978-0-7914-8351-0. + Lal, Vinay (January–April 2000). "Nakedness, Nonviolence, and Brahmacharya: Gandhi's Experiments in Celibate Sexuality". Journal of the History of Sexuality. 9 (1/2): 105–36. JSTOR 3704634. + Sean Scalmer (2011). Gandhi in the West: The Mahatma and the Rise of Radical Protest. Cambridge University Press. pp. 12–17 with footnotes. ISBN 9781139494571. Archived from the original on 1 January 2016. + Howard, Veena R. (2013). "Rethinking Gandhi's celibacy: Ascetic power and women's empowerment". Journal of the American Academy of Religion. Oxford University Press. 81 (1): 130, 137, 130–161. doi:10.1093/jaarel/lfs103. + Christophe Jaffrelot (2005). Dr. Ambedkar and Untouchability: Fighting the Indian Caste System. Columbia University Press. pp. 60–63. ISBN 978-0-231-13602-0. + MK Gandhi (1920), Speech at Antyaj Conference, Nagpur, pages 148–155 + Coward, Harold G. (2003). Indian Critiques of Gandhi. SUNY Press. pp. 52–53. ISBN 978-0-7914-5910-2. + Desai, pp. 230–89. + Roberts, Andrew (26 March 2011). "Among the Hagiographers (A book review of "Great Soul: Mahatma Gandhi and His Struggle With India" by Joseph Lelyveld)". The Wall Street Journal. Archived from the original on 3 January 2012. Retrieved 14 January 2012. + Gandhi-Ambedkar correspondence Archived 9 January 2017 at the Wayback Machine, Mahatma Gandhi writings, An Archive + Rajmohan Gandhi (2006). Gandhi: The Man, His People, and the Empire. University of California Press. pp. 333–359. ISBN 978-0-520-25570-8. Archived from the original on 22 February 2017. + Sankar Ghose (1991). Mahatma Gandhi. Allied Publishers. p. 236. ISBN 978-81-7023-205-6. + Rajmohan Gandhi (2006). Gandhi: The Man, His People, and the Empire. University of California Press. p. 385. ISBN 978-0-520-25570-8. + KR Rao (1975). MVVS Murthi; et al. (eds.). "Satyagraha: Gandhi's yoga of nonviolence". Journal of Gandhian Studies. Gandhi Bhawan, University of Allahabad. 3: 48.; +Laxman Kawale (2012), Dalit's Social Transformation: Redefining the Social Justice, ISRJ, Volume 1, Issue XII, page 3; Quote: "Even though Ambedkar was a party to Poona Pact, he was never reconciled to it. His contempt against [sic] Gandhi . . . continued even after his assassination on January 30, 1948. On the death of Gandhi he expressed, "My real enemy has gone; thank goodness the eclipse is over". He equated the assassination of Gandhi with that of Caesar and the remark of Cicero to the messenger – "Tell the Romans, your hour of liberty has come". He further remarked, "While one regrets the assassination of Mahatma Gandhi, one cannot help finding in his heart the echo of the sentiments expressed by Cicero on the assassination of Caesar". + Guha, Ramachandra (22 June 2012) "The Other Liberal Light" Archived 24 October 2015 at the Wayback Machine. The New Republic. + V.R. Devika; G. Arulmani (2014). Gideon Arulmani; et al. (eds.). Handbook of Career Development: International Perspectives. Springer Science. p. 111. ISBN 978-1-4614-9460-7. + Weber, Thomas (2004). Gandhi As Disciple And Mentor. Cambridge U. Press. p. 80 with footnote 42. ISBN 978-1-139-45657-9. + J.J. Chambliss (2013). Philosophy of Education: An Encyclopedia. Routledge. p. 233. ISBN 978-1-136-51161-5. + Dehury, Dinabandhu "Mahatma Gandhi's Contribution to Education", Orissa Review, September/October 2006, pp. 11–15 Archived 15 February 2010 at the Wayback Machine; December 2008, pp. 1–5. + Yencken, David; Fien, John & Sykes, Helen (2000). Environment, Education, and Society in the Asia-Pacific: Local Traditions and Global Discourses. Psychology Press. p. 107. ISBN 978-0-203-45926-3. + Chakrabarty, Bidyut (2006). Social and political thought of Mahatma Gandhi. Routledge. pp. 138–139. ISBN 978-0-415-36096-8. + Easwaran, Eknath. Gandhi the Man. Nilgiri Press, 2011. p. 49. + Gillen, Paul & Ghosh, Devleena (2007). Colonialism and Modernity. UNSW Press. pp. 129–131. ISBN 978-0-86840-735-7. + Anil Mishra (2012). Reading Gandhi. Pearson. pp. 167–170. ISBN 978-81-317-9964-2. + Tewari, S. M. (1971). "The Concept of Democracy in the Political Thought of Mahatma Gandhi". Indian Political Science Review. 6 (2): 225–51. + John L. Esposito; Darrell J. Fasching; Todd Lewis (2007). Religion & globalization: world religions in historical perspective. Oxford University Press. pp. 543–544. ISBN 978-0-19-517695-7. + Chetan Bhatt (2001). Hindu nationalism: origins, ideologies and modern myths. Berg. pp. 111–112. ISBN 978-1-85973-343-1. + Leora Batnitzky; Hanoch Dagan (2017). Institutionalizing Rights and Religion: Competing Supremacies. Cambridge University Press. p. 250. ISBN 978-1-108-17953-9., Quote: "Many Muslims viewed Gandhi not as a secularist, but as a Hindu nationalist." + Lars Tore Flåten (2016). Hindu Nationalism, History and Identity in India: Narrating a Hindu past under the BJP. Taylor & Francis. p. 249. ISBN 978-1-317-20871-6. + Singh AR; Singh SA (2004). "Gandhi on religion, faith and conversion: secular blueprint relevant today". Mens Sana Monographs. 2 (1): 79–88. PMC 3400300. PMID 22815610. + Mahatma Gandhi; Anand T. Hingorani (1962). All Religions are True. Bharatiya Vidya Bhavan. pp. 112–113. + Bhikhu C. Parekh (2001). Gandhi. Sterling Publishing. pp. 82–84. ISBN 978-1-4027-6887-3. + Rivett, Kenneth (1959). "The Economic Thought of Mahatma Gandhi". The British Journal of Sociology. JSTOR. 10 (1): 1–15. doi:10.2307/587582. JSTOR 587582. + Bhatt, V. V. (1982). "Development Problem, Strategy, and Technology Choice: Sarvodaya and Socialist Approaches in India". Economic Development and Cultural Change. 31 (1): 85–99. doi:10.1086/451307. JSTOR 1153645. + Rothermund, Indira (1969). "The Individual and Society in Gandhi's Political Thought". The Journal of Asian Studies. Cambridge University Press. 28 (2): 313–320. doi:10.2307/2943005. JSTOR 2943005. + Ramjee Singh (1997). Ronald Bontekoe; et al. (eds.). Justice and Democracy: Cross-cultural Perspectives. University of Hawaii Press. pp. 233–235. ISBN 978-0-8248-1926-2. + Chakrabarty, Bidyut (1992). "Jawaharlal Nehru and Planning, 1938–1941: India at the Crossroads". Modern Asian Studies. 26 (2): 275–87. doi:10.1017/S0026749X00009781. + Padma Desai and Jagdish Bhagwati (1975). "Socialism and Indian economic policy". World Development. 3 (4): 213–21. doi:10.1016/0305-750X(75)90063-7. + B.K. Nehru (Spring 1990). "Socialism at crossroads". India International Centre Quarterly. 17 (1): 1–12. JSTOR 23002177. + Pandikattu, Kuruvila (2001). Gandhi: the meaning of Mahatma for the millennium. CRVP. p. 237. ISBN 978-1-56518-156-4. + Rivett, Kenneth (1959). "The Economic Thought of Mahatma Gandhi". British Journal of Sociology. 10 (1): 1–15. doi:10.2307/587582. JSTOR 587582. + Bhikhu C. Parekh (2001). Gandhi. Sterling Publishing. pp. 5–6, 15–16. ISBN 978-1-4027-6887-3. + Bhikhu Parekh (1991). Gandhi's Political Philosophy: A Critical Examination. Palgrave Macmillan. pp. 133–136. ISBN 978-1-349-12242-4. + Sankhdher, M. M. (1972), "Gandhism: A Political Interpretation", Gandhi Marg, pp. 68–74. + Kamath, M. V. (2007), Gandhi, a spiritual journey, Indus Source, ISBN 81-88569-11-9, p. 195. + "Would Gandhi have been a Wikipedian?". The Indian Express. 17 January 2012. Archived from the original on 9 December 2012. Retrieved 26 January 2012. + "Peerless Communicator" Archived 4 August 2007 at the Wayback Machine by V. N. Narayanan. Life Positive Plus, October–December 2002. + Gandhi, M. K. Unto this Last: A paraphrase. Ahmedabad: Navajivan Publishing House. ISBN 81-7229-076-4. Archived from the original (PDF) on 30 October 2012. Retrieved 21 July 2012. + Pareku, Bhikhu (2001). Gandhi. Oxford University Press. p. 159. ISBN 978-0-19-160667-0. + "Revised edition of Bapu's works to be withdrawn". The Times of India. 16 November 2005. Retrieved 25 March 2012. + Peter Rühe. "Collected Works of Mahatma Gandhi (CWMG) Controversy". Gandhiserve.org. Archived from the original on 7 September 2016. Retrieved 12 July 2016. + Tagore, Rabindranath (15 December 1998). Dutta, Krishna (ed.). Rabindranath Tagore: an anthology. Robinson, Andrew. Macmillan. p. 2. ISBN 978-0-312-20079-4. + Desai, p. viii. + Basu Majumdar, A. K. (1993), Rabindranath Tagore: The Poet of India, Indus Publishing, ISBN 81-85182-92-2, p. 83: "When Gandhi returned to India, Rabindranath's eldest brother Dwijendranath, was perhaps the first to address him as Mahatma. Rabindranath followed suit and then the whole of India called him Mahatma Gandhi." + Ghose, Sankar (1991). Mahatma Gandhi. Allied Publishers. p. 158. ISBN 978-81-7023-205-6. So Tagore differed from many of Gandhi's ideas, but yet he had great regard for him and Tagore was perhaps the first important Indian who called Gandhi a Mahatma. But in 1921 when Gandhi was asked whether he was really a Mahatma Gandhi replied that he did not feel like one, and that, in any event he could not define a Mahatma for he had never met any. + Guha, Ramachandra (24 July 2007). India After Gandhi: The History of the World's Largest Democracy. Delhi: Ecco Press. ISBN 978-0-06-019881-7. + "King's Trip to India". Mlk-kpp01.stanford.edu. Archived from the original on 21 March 2009. Retrieved 24 January 2012. + Sidner, Sara (17 February 2009). "King moved, as father was, on trip to Gandhi's memorial". cnn.com Asia-Pacific. CNN. Archived from the original on 14 April 2012. Retrieved 24 January 2012. + D'Souza, Placido P. (20 January 2003). "Commemorating Martin Luther King Jr.: Gandhi's influence on King". San Francisco Chronicle. Archived from the original on 18 January 2013. Retrieved 24 January 2012. + Tougas, Shelley (1 January 2011). Birmingham 1963: How a Photograph Rallied Civil Rights Support. Capstone Press. p. 12. ISBN 978-0-7565-4398-3. Retrieved 24 January 2012. + Cone, James (1992). Martin & Malcolm & America: A Dream Or a Nightmare. Orbis Books. ISBN 0-88344-824-6. + Nelson Mandela, "The Sacred Warrior: The liberator of South Africa looks at the seminal work of the liberator of India" Archived 5 October 2013 at the Wayback Machine, Time, 3 January 2000 + Pal, Amitabh (February 2002). "A pacifist uncovered- Abdul Ghaffar Khan, Pakistani pacifist". The Progressive. Archived from the original on 7 January 2012. Retrieved 24 January 2012. + "An alternative Gandhi". The Tribune. India. 22 February 2004. Archived from the original on 14 May 2009. Retrieved 12 March 2009. + Bhana, Surendra; Vahed, Goolam H. (2005). The Making of a Political Reformer: Gandhi in South Africa, 1893–1914. Manohar. pp. 44–45, 149. ISBN 978-81-7304-612-4. + "Einstein on Gandhi (Einstein's letter to Gandhi – Courtesy:Saraswati Albano-Müller & Notes by Einstein on Gandhi – Source: The Hebrew University of Jerusalem )". Gandhiserve.org. 18 October 1931. Archived from the original on 17 January 2012. Retrieved 24 January 2012. + Dhupelia-Mesthrie, Uma (1 January 2005). Gandhi's prisoner?: the life of Gandhi's son Manilal. Permanent Black. p. 293. ISBN 978-81-7824-116-6. Retrieved 26 January 2012. + "In the company of Bapu". The Telegraph. 3 October 2004. Archived from the original on 8 February 2012. Retrieved 26 January 2012. + Gilmore, Mikal (5 December 2005). "Lennon Lives Forever". Rolling Stone. Archived from the original on 28 May 2007. Retrieved 24 January 2012. + Kar, Kalyan (23 June 2007). "Of Gandhigiri and Green Lion, Al Gore wins hearts at Cannes". Cannes Lions 2007. exchange4media. Archived from the original on 11 January 2012. Retrieved 24 January 2012. + "Remarks by the President to the Joint Session of the Indian Parliament in New Delhi, India". The White House. 8 November 2010. Archived from the original on 13 January 2012. Retrieved 24 January 2012. + "Obama steers clear of politics in school pep talk". MSNBC. Associated Press. 8 September 2009. Archived from the original on 4 October 2013. Retrieved 24 January 2012. + "The Children of Gandhi" (excerpt). Time. 31 December 1999. Archived from the original on 5 October 2013. + Moreno, Jenalia (16 January 2010). "Houston community celebrates district named for Gandhi". Houston Chronicle. Archived from the original on 11 April 2015. Retrieved 24 January 2012. + Mohan, Shaj; Dwivedi, Divya; Nancy, Jean-Luc (13 December 2018). Gandhi and Philosophy: On Theological Anti-Politics. Bloomsbury Publishing. ISBN 978-1-4742-2173-3 – via Google Books. + "UN declares 2 October, Gandhi's birthday, as International Day of Nonviolence". UN News Centre. 15 June 2007. Archived from the original on 23 January 2012. Retrieved 2 April 2012. + "School Day of Nonviolence And Peace". Letter of Peace addressed to the UN. cartadelapaz.org. 30 January 2009. Archived from the original on 1 November 2011. Retrieved 9 January 2012. + Eulogio Díaz del Corral (31 January 1983). "DENIP: School Day of Nonviolence and Peace". DENIP (in Spanish). Archived from the original on 27 February 2012. Retrieved 30 January 2012. + "University and Educational Intelligence" (PDF). Current Science. 6 (6): 314. December 1937. + Rushdie, Salman (13 April 1998). "The Time 100". Time. Archived from the original on 15 September 2013. Retrieved 3 March 2009. + "Top 25 Political Icons". Time. 4 February 2011. Archived from the original on 28 December 2013. Retrieved 9 February 2011. + "Nobel Peace Prize Nominations". American Friends Service Committee. 14 April 2010. Archived from the original on 4 February 2012. Retrieved 30 January 2012. + Tønnesson, Øyvind (1 December 1999). "Mahatma Gandhi, the Missing Laureate". Nobel Foundation. Archived from the original on 5 July 2013. Retrieved 16 January 2012. + "Relevance of Gandhian Philosophy in the 21st Century" Archived 15 September 2011 at the Wayback Machine. Icrs.ugm.ac.id. Retrieved 5 August 2013. + Inductees, Vegetarian Hall of Fame, North American Vegetarian Society website + "Crusade with arms". The Hindu. February 2000. + "Father of the Nation RTI". NDTV. Archived from the original on 4 December 2016. Retrieved 21 September 2016. + "Constitution does not permit any titles". The Times of India. Archived from the original on 7 January 2017. Retrieved 21 September 2016. + "Constitution doesn't permit 'Father of the Nation' title: MHA". India Today. 26 October 2012. Retrieved 20 September 2019. + "Mahatma: Life of Gandhi, 1869–1948 (1968 – 5hrs 10min)". Channel of GandhiServe Foundation. Archived from the original on 18 January 2015. Retrieved 30 December 2014. + "Vithalbhai Jhaveri". GandhiServe Foundatiom. Archived from the original on 31 December 2014. Retrieved 30 December 2014. + Dwyer, Rachel (2011). "The Case of the Missing Mahatma:Gandhi and the Hindi Cinema" (PDF). Public Culture. Duke University Press. 23 (2): 349–376. doi:10.1215/08992363-1161949. Archived (PDF) from the original on 21 March 2017. + Louis Fischer, The Life of Mahatma Gandhi (1957) online + Melvani, Lavina (February 1997). "Making of the Mahatma". Hinduism Today. hinduismtoday.com. Archived from the original on 3 February 2012. Retrieved 26 January 2012. + Pandohar, Jaspreet (Reviewer). "Movies – Maine Gandhi Ko Nahin Mara (I Did Not Kill Gandhi) (2005)". BBC (British Broadcasting Corporation). Archived from the original on 4 July 2015. Retrieved 30 December 2014. + Lal, Vinay. "Moving Images of Gandhi" (PDF). Archived from the original (PDF) on 4 March 2016. Retrieved 30 December 2014. + Kostelanetz, Richard; Flemming, Robert (1999). Writings on Glass: Essays, Interviews, Criticism. University of California Press. p. 102. ISBN 978-0-520-21491-0. + Philip Glass (2015). Words Without Music: A Memoir. Liveright. pp. 192, 307. ISBN 978-1-63149-081-1. + Kostelanetz & Flemming 1999, p. 168. + "It's fashionable to be anti-Gandhi". DNA. 1 October 2005. Archived from the original on 22 June 2013. Retrieved 25 January 2013. + Dutt, Devina (20 February 2009). "Drama king". Live Mint. Archived from the original on 30 April 2013. Retrieved 25 January 2013. + Kunzru, Hari (29 March 2011). "Appreciating Gandhi Through His Human Side". The New York Times. Archived from the original on 31 January 2012. Retrieved 26 January 2012. (Review of Great Soul: Mahatma Gandhi and His Struggle With India by Joseph Lelyveld). + "US author slams Gandhi gay claim". The Australian. Agence France-Presse. 29 March 2011. Archived from the original on 1 May 2013. Retrieved 26 January 2012. + Kamath, Sudhish (28 February 2011). "A Welcome Effort". The Hindu. Archived from the original on 2 February 2014. Retrieved 24 January 2014. + Pandit, Unnati (5 March 2019). "Bharat Bhagya Vidhata' captivates the audience". The Live Nagpur. The Live Nagpur. Retrieved 7 May 2019. + Ghosh, B. N. (2001). Contemporary issues in development economics. Psychology Press. p. 211. ISBN 978-0-415-25136-5. + Yardley, Jim (6 November 2010). "Obama Invokes Gandhi, Whose Ideal Eludes India". Asia-Pacific. Archived from the original on 17 August 2013. Retrieved 22 January 2012. + "Reserve Bank of India – Bank Notes". Rbi.org.in. Archived from the original on 26 October 2011. Retrieved 5 November 2011. + Chatterjee, Sailen. "Martyrs' Day". Features. Press Information Bureau. Archived from the original on 2 February 2012. Retrieved 30 January 2012. + Kaggere, Niranjan (2 October 2010). "Here, Gandhi is God". BangaloreMirror.com. Archived from the original on 4 October 2013. Retrieved 29 January 2011. + "Mahatma Gandhi Temple" Archived 5 October 2016 at the Wayback Machine. Mahatma Gandhi Temple Website, + Abram, David; Edwards, Nick (27 November 2003). The Rough Guide to South India. Rough Guides. p. 506. ISBN 978-1-84353-103-6. Retrieved 21 January 2012. + Gandhi, Rajmohan. Mohandas (a Biography). PRHI. + "Kanu Gandhi, Gandhiji's grandson and ex-Nasa scientist, dies". Hindustan Times. 8 November 2016. Retrieved 29 October 2018. + "Lodged in old age home in Delhi, Gandhi's grandson looks to Rajkot". Hindustan Times. 22 June 2016. Retrieved 29 October 2018. +Bibliography +Books +Ahmed, Talat (2018). Mohandas Gandhi: Experiments in Civil Disobedience ISBN 0-7453-3429-6 +Barr, F. Mary (1956). Bapu: Conversations and Correspondence with Mahatma Gandhi (2nd ed.). Bombay: International Book House. OCLC 8372568. (see book article) +Bondurant, Joan Valérie (1971). Conquest of Violence: the Gandhian philosophy of conflict. University of California Press. +Brown, Judith M. (2004). "Gandhi, Mohandas Karamchand [Mahatma Gandhi] (1869–1948)", Oxford Dictionary of National Biography, Oxford University Press.[ISBN missing] +Brown, Judith M., and Anthony Parel, eds. The Cambridge Companion to Gandhi (2012); 14 essays by scholars +Brown, Judith Margaret (1991). Gandhi: Prisoner of Hope. Yale University Press. ISBN 978-0-300-05125-4. +Chadha, Yogesh (1997). Gandhi: a life. John Wiley. ISBN 978-0-471-24378-6. +Dwivedi, Divya; Mohan, Shaj; Nancy, Jean-Luc (2019). Gandhi and Philosophy: On Theological Anti-politics. Bloomsbury Academic, UK. ISBN 9781474221733. +Fischer, Louis. The Life of Mahatma Gandhi (1957) online +Easwaran, Eknath (2011). Gandhi the Man: How One Man Changed Himself to Change the World. Nilgiri Press. ISBN 978-1-58638-055-7. +Hook, Sue Vander (2010). Mahatma Gandhi: Proponent of Peace. ABDO. ISBN 978-1-61758-813-6. +Gandhi, Rajmohan (1990), Patel, A Life, Navajivan Pub. House +Gandhi, Rajmohan (2006). Gandhi: The Man, His People, and the Empire. University of California Press. ISBN 978-0-520-25570-8. +Gangrade, K.D. (2004). "Role of Shanti Sainiks in the Global Race for Armaments". Moral Lessons From Gandhi's Autobiography And Other Essays. Concept Publishing Company. ISBN 978-81-8069-084-6. +Guha, Ramachandra (2013). Gandhi Before India. Vintage Books. ISBN 978-0-385-53230-3. +Hardiman, David (2003). Gandhi in His Time and Ours: the global legacy of his ideas. C. Hurst & Co. ISBN 978-1-85065-711-8. +Hatt, Christine (2002). Mahatma Gandhi. Evans Brothers. ISBN 978-0-237-52308-4. +Herman, Arthur (2008). Gandhi and Churchill: the epic rivalry that destroyed an empire and forged our age. Random House Digital, Inc. ISBN 978-0-553-80463-8. +Jai, Janak Raj (1996). Commissions and Omissions by Indian Prime Ministers: 1947–1980. Regency Publications. ISBN 978-81-86030-23-3. +Johnson, Richard L. (2006). Gandhi's Experiments with Truth: Essential Writings by and about Mahatma Gandhi. Lexington Books. ISBN 978-0-7391-1143-7. +Jones, Constance & Ryan, James D. (2007). Encyclopedia of Hinduism. Infobase Publishing. p. 160. ISBN 978-0-8160-5458-9. +Majmudar, Uma (2005). Gandhi's Pilgrimage of Faith: from darkness to light. SUNY Press. ISBN 978-0-7914-6405-2. +Miller, Jake C. (2002). Prophets of a just society. Nova Publishers. ISBN 978-1-59033-068-5. +Pāṇḍeya, Viśva Mohana (2003). Historiography of India's Partition: an analysis of imperialist writings. Atlantic Publishers & Dist. ISBN 978-81-269-0314-6. +Pilisuk, Marc; Nagler, Michael N. (2011). Peace Movements Worldwide: Players and practices in resistance to war. ABC-CLIO. ISBN 978-0-313-36482-2. +Rühe, Peter (5 October 2004). Gandhi. Phaidon. ISBN 978-0-7148-4459-6. +Schouten, Jan Peter (2008). Jesus as Guru: the image of Christ among Hindus and Christians in India. Rodopi. ISBN 978-90-420-2443-4. +Sharp, Gene (1979). Gandhi as a Political Strategist: with essays on ethics and politics. P. Sargent Publishers. ISBN 978-0-87558-090-6. +Shashi, S. S. (1996). Encyclopaedia Indica: India, Pakistan, Bangladesh. Anmol Publications. ISBN 978-81-7041-859-7. +Sinha, Satya (2015). The Dialectic of God: The Theosophical Views Of Tagore and Gandhi. Partridge Publishing India. ISBN 978-1-4828-4748-2. +Sofri, Gianni (1999). Gandhi and India: a century in focus. Windrush Press. ISBN 978-1-900624-12-1. +Thacker, Dhirubhai (2006). ""Gandhi, Mohandas Karamchand" (entry)". In Amaresh Datta (ed.). The Encyclopaedia of Indian Literature (Volume Two) (Devraj To Jyoti). Sahitya Akademi. p. 1345. ISBN 978-81-260-1194-0. +Todd, Anne M (2004). Mohandas Gandhi. Infobase Publishing. ISBN 978-0-7910-7864-8.; short biography for children +Wolpert, Stanley (2002). Gandhi's Passion: the life and legacy of Mahatma Gandhi. Oxford University Press. ISBN 978-0-19-972872-5. +Primary sources +Abel M (2005). Glimpses of Indian National Movement. ICFAI Books. ISBN 978-81-7881-420-9. +Andrews, C. F. (2008) [1930]. "VII – The Teaching of Ahimsa". Mahatma Gandhi's Ideas Including Selections from His Writings. Pierides Press. ISBN 978-1-4437-3309-0. +Dalton, Dennis, ed. (1996). Mahatma Gandhi: Selected Political Writings. Hackett Publishing. ISBN 978-0-87220-330-3. +Duncan, Ronald, ed. (2011). Selected Writings of Mahatma Gandhi. Literary Licensing, LLC. ISBN 978-1-258-00907-6. +Gandhi, M. K.; Fischer, Louis (2002). Louis Fischer (ed.). The Essential Gandhi: An Anthology of His Writings on His Life, Work and Ideas. Vintage Books. ISBN 978-1-4000-3050-7. +Gandhi, Mohandas Karamchand (1928). Satyagraha in South Africa (in Gujarati) (1 ed.). Ahmedabad: Navajivan Publishing House. Translated by Valji G. Desai Free online access at Wikilivres.ca (1/e). Pdfs from Gandhiserve (3/e) & Yann Forget (hosted by Arvind Gupta) (1/e). +Gandhi, Mohandas Karamchand (1994). The Collected Works of Mahatma Gandhi. Publications Division, Ministry of Information and Broadcasting, Govt. of India. ISBN 978-81-230-0239-2. (100 volumes). Free online access from Gandhiserve. +Gandhi, Mohandas Karamchand (1928). "Drain Inspector's Report". The United States of India. 5 (6, 7, 8): 3–4. +Gandhi, Mohandas Karamchand (1990), Desai, Mahadev H. (ed.), Autobiography: The Story of My Experiments With Truth, Mineola, N.Y.: Dover, ISBN 0-486-24593-4 +Gandhi, Rajmohan (2007). Mohandas: True Story of a Man, His People. Penguin Books Limited. ISBN 978-81-8475-317-2. +Guha, Ramachandra (2013). Gandhi Before India. Penguin Books Limited. ISBN 978-93-5118-322-8. +Jack, Homer A., ed. (1994). The Gandhi Reader: A Source Book of His Life and Writings. Grove Press. ISBN 978-0-8021-3161-4. +Johnson, Richard L. & Gandhi, M. K. (2006). Gandhi's Experiments With Truth: Essential Writings by and about Mahatma Gandhi. Lexington Books. ISBN 978-0-7391-1143-7. +Todd, Anne M. (2009). Mohandas Gandhi. Infobase Publishing. ISBN 978-1-4381-0662-5. +Parel, Anthony J., ed. (2009). Gandhi: "Hind Swaraj" and Other Writings Centenary Edition. Cambridge University Press. ISBN 978-0-521-14602-9. +External links +Mahatma Gandhi +at Wikipedia's sister projects +Media from Wikimedia Commons +Quotations from Wikiquote +Texts from Wikisource +Data from Wikidata +Mahatma Gandhi at the Encyclopædia Britannica +Mahatma Gandhi at Curlie +Gandhi's correspondence with the Indian government 1942–1944 +About Mahatma Gandhi +Gandhi Ashram at Sabarmati +Mani Bhavan Gandhi Sangrahalaya Gandhi Museum & Library +Works by Mahatma Gandhi at Project Gutenberg +Works by or about Mahatma Gandhi at Internet Archive +Works by Mahatma Gandhi at LibriVox (public domain audiobooks) +Newspaper clippings about Mahatma Gandhi in the 20th Century Press Archives of the ZBW +vte +Mahatma Gandhi +Articles related to Mahatma Gandhi +Authority control Edit this at Wikidata +BIBSYS: 90687101BNC: 000038326BNE: XX1150077BNF: cb11904030r (data)CANTIC: a10089597CiNii: DA00595167GND: 118639145ISNI: 0000 0001 2138 6043LCCN: n79041626LNB: 000008100MusicBrainz: b0b244dd-b976-4dc4-ba7a-a394da724de0NARA: 10582643NDL: 00440485NKC: jn20000601721NLA: 35111345NLG: 152363NLI: 000607437NLK: KAC199609638NLP: A11789062NSK: 000033749NTA: 068712030RERO: 02-A003277799SELIBR: 275000SNAC: w6bs9g59SUDOC: 026880504TePapa: 48807Trove: 829877VcBA: 495/79916VIAF: 71391324WorldCat Identities: lccn-n79041626 +Categories: Mahatma Gandhi1869 births1948 deaths19th-century Indian lawyers19th-century Indian philosophers19th-century Indian writers19th-century male writers20th-century Indian lawyers20th-century Indian male writers20th-century Indian philosophers20th-century Indian writersAlumni of the University of LondonAlumni of University College LondonAnimal rights activistsAnti-consumeristsAnti-imperialismAnti-poverty advocatesAnti–World War II activistsAsceticsAssassinated Indian politiciansBritish Empire in World War IIContemporary Indian philosophersDeaths by firearm in IndiaFounders of Indian schools and collegesGujarati peopleGujarati-language writersHindu pacifistsHunger strikersIndian activist journalistsIndian anti-war activistsIndian autobiographersIndian barristersIndian civil rights activistsIndian emigrants to South AfricaIndian expatriates in South AfricaIndian expatriates in the United KingdomIndian HindusIndian humanitariansIndian independence activistsIndian libertariansIndian male philosophersIndian memoiristsIndian murder victimsIndian nationalistsIndian pacifistsIndian people of World War IIIndian political philosophersIndian revolutionariesIndian tax resistersInternational opponents of apartheid in South AfricaMahatma Gandhi familyMembers of the Inner TempleNatal Indian Congress politiciansNeo-VedantaNonviolence advocatesPeople from PorbandarPeople murdered in DelhiPeople of British IndiaPeople of the Second Boer WarPresidents of the Indian National CongressPrisoners and detainees of British IndiaRecipients of the Kaisar-i-Hind MedalRelief workers in NoakhaliSimple living advocatesSouth African Indian Congress politiciansSouth African lawyersState funerals in IndiaSwadeshi activistsTolstoyansTranslators of the Bhagavad GitaWorld Peace Prize laureatesWriters about activism and social changeWriters from GujaratColony of Natal people +Navigation menu +Not logged inTalkContributionsCreate accountLog in +ArticleTalk +ReadView sourceView historySearch +Search Wikipedia +Main page +Contents +Current events +Random article +About Wikipedia +Contact us +Donate +Contribute +Help +Community portal +Recent changes +Upload file +Tools +What links here +Related changes +Special pages +Permanent link +Page information +Cite this page +Wikidata item +Print/export +Download as PDF +Printable version +In other projects +Wikimedia Commons +Wikiquote +Wikisource + +Languages +বাংলা +ગુજરાતી +हिन्दी +ಕನ್ನಡ +മലയാളം +मराठी +தமிழ் +తెలుగు +اردو +174 more +Edit links +This page was last edited on 8 September 2020, at 00:49 (UTC). +Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization. +Privacy policyAbout WikipediaDisclaimersContact WikipediaMobile viewDevelopersStatisticsCookie statementWikimedia FoundationPowered by MediaWiki diff --git a/testutil/gstestdata.go b/testutil/gstestdata.go index f81b67b8..4777075b 100644 --- a/testutil/gstestdata.go +++ b/testutil/gstestdata.go @@ -34,6 +34,7 @@ import ( "github.com/ipld/go-ipld-prime/traversal/selector" "github.com/ipld/go-ipld-prime/traversal/selector/builder" "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/protocol" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" @@ -46,6 +47,8 @@ import ( var allSelector ipld.Node +const loremFile = "lorem.txt" + func init() { ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) allSelector = ssb.ExploreRecursive(selector.RecursionLimitNone(), @@ -62,8 +65,8 @@ type GraphsyncTestingData struct { Mn mocknet.Mocknet StoredCounter1 *storedcounter.StoredCounter StoredCounter2 *storedcounter.StoredCounter - DtDs1 datastore.Datastore - DtDs2 datastore.Datastore + DtDs1 datastore.Batching + DtDs2 datastore.Batching Bs1 bstore.Blockstore Bs2 bstore.Blockstore DagService1 ipldformat.DAGService @@ -83,7 +86,7 @@ type GraphsyncTestingData struct { } // NewGraphsyncTestingData returns a new GraphsyncTestingData instance -func NewGraphsyncTestingData(ctx context.Context, t *testing.T) *GraphsyncTestingData { +func NewGraphsyncTestingData(ctx context.Context, t *testing.T, host1Protocols []protocol.ID, host2Protocols []protocol.ID) *GraphsyncTestingData { gsData := &GraphsyncTestingData{} gsData.Ctx = ctx @@ -128,8 +131,19 @@ func NewGraphsyncTestingData(ctx context.Context, t *testing.T) *GraphsyncTestin gsData.GsNet1 = gsnet.NewFromLibp2pHost(gsData.Host1) gsData.GsNet2 = gsnet.NewFromLibp2pHost(gsData.Host2) - gsData.DtNet1 = network.NewFromLibp2pHost(gsData.Host1) - gsData.DtNet2 = network.NewFromLibp2pHost(gsData.Host2) + opts1 := []network.Option{network.RetryParameters(0, 0, 0)} + opts2 := []network.Option{network.RetryParameters(0, 0, 0)} + + if len(host1Protocols) != 0 { + opts1 = append(opts1, network.DataTransferProtocols(host1Protocols)) + } + + if len(host2Protocols) != 0 { + opts2 = append(opts2, network.DataTransferProtocols(host2Protocols)) + } + + gsData.DtNet1 = network.NewFromLibp2pHost(gsData.Host1, opts1...) + gsData.DtNet2 = network.NewFromLibp2pHost(gsData.Host2, opts2...) // create a selector for the whole UnixFS dag gsData.AllSelector = allSelector @@ -173,19 +187,19 @@ func (gsData *GraphsyncTestingData) LoadUnixFSFile(t *testing.T, useSecondNode b dagService = gsData.DagService1 } - link, origBytes := LoadUnixFSFile(gsData.Ctx, t, dagService) + link, origBytes := LoadUnixFSFile(gsData.Ctx, t, dagService, loremFile) gsData.OrigBytes = origBytes return link } // LoadUnixFSFile loads a fixtures file into the given DAG Service, returning an ipld.Link for the file // and the original file bytes -func LoadUnixFSFile(ctx context.Context, t *testing.T, dagService ipldformat.DAGService) (ipld.Link, []byte) { +func LoadUnixFSFile(ctx context.Context, t *testing.T, dagService ipldformat.DAGService, fileName string) (ipld.Link, []byte) { _, curFile, _, ok := runtime.Caller(0) require.True(t, ok) // read in a fixture file - path := filepath.Join(path.Dir(curFile), "fixtures", "lorem.txt") + path := filepath.Join(path.Dir(curFile), "fixtures", fileName) f, err := os.Open(path) require.NoError(t, err) diff --git a/testutil/message.go b/testutil/message.go index 67b02f08..eefc7551 100644 --- a/testutil/message.go +++ b/testutil/message.go @@ -16,7 +16,7 @@ func NewDTRequest(t *testing.T, transferID datatransfer.TransferID) datatransfer voucher := NewFakeDTType() baseCid := GenerateCids(1)[0] selector := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any).Matcher().Node() - r, err := message.NewRequest(transferID, false, voucher.Type(), voucher, baseCid, selector) + r, err := message.NewRequest(transferID, false, false, voucher.Type(), voucher, baseCid, selector) require.NoError(t, err) return r } diff --git a/testutil/testutil.go b/testutil/testutil.go index 3ce3adef..731ee3f8 100644 --- a/testutil/testutil.go +++ b/testutil/testutil.go @@ -2,6 +2,7 @@ package testutil import ( "bytes" + "context" "fmt" "testing" @@ -106,3 +107,18 @@ func AllSelector() ipld.Node { return ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node() } + +// StartAndWaitForReady is a utility function to start a module and verify it reaches the ready state +func StartAndWaitForReady(ctx context.Context, t *testing.T, manager datatransfer.Manager) { + ready := make(chan error, 1) + manager.OnReady(func(err error) { + ready <- err + }) + require.NoError(t, manager.Start(ctx)) + select { + case <-ctx.Done(): + t.Fatal("did not finish starting up module") + case err := <-ready: + require.NoError(t, err) + } +} diff --git a/transport.go b/transport.go index cf133708..95b939e4 100644 --- a/transport.go +++ b/transport.go @@ -3,6 +3,7 @@ package datatransfer import ( "context" + "github.com/ipfs/go-cid" ipld "github.com/ipld/go-ipld-prime" peer "github.com/libp2p/go-libp2p-core/peer" ) @@ -46,6 +47,10 @@ type EventsHandler interface { // OnResponseCompleted is called when we finish sending data for the given channel ID // Error returns are logged but otherwise have not effect OnChannelCompleted(chid ChannelID, success bool) error + + // OnRequestTimedOut is called when a request we opened (with the given channel Id) to receive data times out. + // Error returns are logged but otherwise have no effect + OnRequestTimedOut(ctx context.Context, chid ChannelID) error } /* @@ -73,6 +78,7 @@ type Transport interface { channelID ChannelID, root ipld.Link, stor ipld.Node, + doNotSendCids []cid.Cid, msg Message) error // CloseChannel closes the given channel diff --git a/transport/graphsync/graphsync.go b/transport/graphsync/graphsync.go index 780a7f9a..738ef5b6 100644 --- a/transport/graphsync/graphsync.go +++ b/transport/graphsync/graphsync.go @@ -5,10 +5,13 @@ import ( "errors" "sync" + "github.com/ipfs/go-cid" "github.com/ipfs/go-graphsync" + "github.com/ipfs/go-graphsync/cidset" logging "github.com/ipfs/go-log/v2" ipld "github.com/ipld/go-ipld-prime" peer "github.com/libp2p/go-libp2p-core/peer" + "golang.org/x/xerrors" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-data-transfer/transport/graphsync/extension" @@ -71,6 +74,7 @@ func (t *Transport) OpenChannel(ctx context.Context, channelID datatransfer.ChannelID, root ipld.Link, stor ipld.Node, + doNotSendCids []cid.Cid, msg datatransfer.Message) error { if t.events == nil { return datatransfer.ErrHandlerNotSet @@ -80,11 +84,32 @@ func (t *Transport) OpenChannel(ctx context.Context, return err } internalCtx, internalCancel := context.WithCancel(ctx) + t.dataLock.Lock() + // if we have an existing request pending for the channelID, cancel it first. + if cancelF, ok := t.contextCancelMap[channelID]; ok { + cancelF() + } t.pending[channelID] = make(chan struct{}) t.contextCancelMap[channelID] = internalCancel t.dataLock.Unlock() - _, errChan := t.gs.Request(internalCtx, dataSender, root, stor, ext) + + exts := []graphsync.ExtensionData{ext} + if len(doNotSendCids) != 0 { + set := cid.NewSet() + for _, c := range doNotSendCids { + set.Add(c) + } + bz, err := cidset.EncodeCidSet(set) + if err != nil { + return xerrors.Errorf("failed to encode cid set: %w", err) + } + doNotSendExt := graphsync.ExtensionData{Name: graphsync.ExtensionDoNotSendCIDs, + Data: bz} + exts = append(exts, doNotSendExt) + } + _, errChan := t.gs.Request(internalCtx, dataSender, root, stor, exts...) + go t.executeGsRequest(ctx, channelID, errChan) return nil } @@ -106,12 +131,20 @@ func (t *Transport) consumeResponses(ctx context.Context, errChan <-chan error) func (t *Transport) executeGsRequest(ctx context.Context, channelID datatransfer.ChannelID, errChan <-chan error) { lastError := t.consumeResponses(ctx, errChan) + if _, ok := lastError.(graphsync.RequestContextCancelledErr); ok { + log.Warnf("graphsync request context cancelled, channel Id: %v", channelID) + if err := t.events.OnRequestTimedOut(ctx, channelID); err != nil { + log.Error(err) + } return } + if _, ok := lastError.(graphsync.RequestCancelledErr); ok { + // TODO Should we do anything for RequestCancelledErr ? return } + if lastError != nil { log.Warnf("graphsync error: %s", lastError.Error()) } diff --git a/transport/graphsync/graphsync_test.go b/transport/graphsync/graphsync_test.go index df3bf274..138c509a 100644 --- a/transport/graphsync/graphsync_test.go +++ b/transport/graphsync/graphsync_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/ipfs/go-graphsync" + "github.com/ipfs/go-graphsync/cidset" "github.com/ipld/go-ipld-prime" cidlink "github.com/ipld/go-ipld-prime/linking/cid" peer "github.com/libp2p/go-libp2p-core/peer" @@ -627,17 +628,103 @@ func TestManager(t *testing.T) { assertDecodesToMessage(t, gsData.incomingRequestHookActions.SentExtension.Data, gsData.incoming) }, }, + "open channel adds doNotSendCids to the DoNotSend extension": { + action: func(gsData *harness) { + cids := testutil.GenerateCids(2) + stor, _ := gsData.outgoing.Selector() + _ = gsData.transport.OpenChannel( + gsData.ctx, + gsData.other, + datatransfer.ChannelID{ID: gsData.transferID, Responder: gsData.other, Initiator: gsData.self}, + cidlink.Link{Cid: gsData.outgoing.BaseCid()}, + stor, + cids, + gsData.outgoing) + }, + check: func(t *testing.T, events *fakeEvents, gsData *harness) { + requestReceived := gsData.fgs.AssertRequestReceived(gsData.ctx, t) + + ext := requestReceived.Extensions + require.Len(t, ext, 2) + doNotSend := ext[1] + + name := doNotSend.Name + require.Equal(t, graphsync.ExtensionDoNotSendCIDs, name) + data := doNotSend.Data + cs, err := cidset.DecodeCidSet(data) + require.NoError(t, err) + require.Equal(t, cs.Len(), 2) + }, + }, + "open channel cancels an existing request with the same channel ID": { + action: func(gsData *harness) { + cids := testutil.GenerateCids(2) + stor, _ := gsData.outgoing.Selector() + _ = gsData.transport.OpenChannel( + gsData.ctx, + gsData.other, + datatransfer.ChannelID{ID: gsData.transferID, Responder: gsData.other, Initiator: gsData.self}, + cidlink.Link{Cid: gsData.outgoing.BaseCid()}, + stor, + cids, + gsData.outgoing) + + _ = gsData.transport.OpenChannel( + gsData.ctx, + gsData.other, + datatransfer.ChannelID{ID: gsData.transferID, Responder: gsData.other, Initiator: gsData.self}, + cidlink.Link{Cid: gsData.outgoing.BaseCid()}, + stor, + cids, + gsData.outgoing) + }, + check: func(t *testing.T, events *fakeEvents, gsData *harness) { + requestReceived1 := gsData.fgs.AssertRequestReceived(gsData.ctx, t) + requestReceived2 := gsData.fgs.AssertRequestReceived(gsData.ctx, t) + + require.Error(t, requestReceived1.Ctx.Err()) + require.NoError(t, requestReceived2.Ctx.Err()) + }, + }, + "request times out if we get request context cancelled error": { + action: func(gsData *harness) { + gsData.fgs.LeaveRequestsOpen() + stor, _ := gsData.outgoing.Selector() + + _ = gsData.transport.OpenChannel( + gsData.ctx, + gsData.other, + datatransfer.ChannelID{ID: gsData.transferID, Responder: gsData.other, Initiator: gsData.self}, + cidlink.Link{Cid: gsData.outgoing.BaseCid()}, + stor, + nil, + gsData.outgoing) + }, + check: func(t *testing.T, events *fakeEvents, gsData *harness) { + requestReceived := gsData.fgs.AssertRequestReceived(gsData.ctx, t) + requestReceived.ResponseErrChan <- graphsync.RequestContextCancelledErr{} + close(requestReceived.ResponseErrChan) + + require.Eventually(t, func() bool { + return events.OnRequestTimedOutCalled == true + }, 2*time.Second, 100*time.Millisecond) + require.Equal(t, datatransfer.ChannelID{ID: gsData.transferID, Responder: gsData.other, Initiator: gsData.self}, events.OnRequestTimedOutChannelId) + }, + }, "request pause works even if called when request is still pending": { action: func(gsData *harness) { gsData.fgs.LeaveRequestsOpen() stor, _ := gsData.outgoing.Selector() + _ = gsData.transport.OpenChannel( gsData.ctx, gsData.other, datatransfer.ChannelID{ID: gsData.transferID, Responder: gsData.other, Initiator: gsData.self}, cidlink.Link{Cid: gsData.outgoing.BaseCid()}, stor, + nil, gsData.outgoing) + }, check: func(t *testing.T, events *fakeEvents, gsData *harness) { requestReceived := gsData.fgs.AssertRequestReceived(gsData.ctx, t) @@ -704,7 +791,7 @@ func TestManager(t *testing.T) { ctx := context.Background() for testCase, data := range testCases { t.Run(testCase, func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 2*time.Second) + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() peers := testutil.GeneratePeers(2) transferID := datatransfer.TransferID(rand.Uint64()) @@ -761,11 +848,22 @@ type fakeEvents struct { OnResponseReceivedErrors []error OnChannelCompletedCalled bool OnChannelCompletedErr error - ChannelCompletedSuccess bool - DataSentMessage datatransfer.Message - RequestReceivedRequest datatransfer.Request - RequestReceivedResponse datatransfer.Response - ResponseReceivedResponse datatransfer.Response + + OnRequestTimedOutCalled bool + OnRequestTimedOutChannelId datatransfer.ChannelID + + ChannelCompletedSuccess bool + DataSentMessage datatransfer.Message + RequestReceivedRequest datatransfer.Request + RequestReceivedResponse datatransfer.Response + ResponseReceivedResponse datatransfer.Response +} + +func (fe *fakeEvents) OnRequestTimedOut(_ context.Context, chid datatransfer.ChannelID) error { + fe.OnRequestTimedOutCalled = true + fe.OnRequestTimedOutChannelId = chid + + return nil } func (fe *fakeEvents) OnChannelOpened(chid datatransfer.ChannelID) error { diff --git a/types.go b/types.go index 22752a7b..f2de437d 100644 --- a/types.go +++ b/types.go @@ -92,14 +92,20 @@ type Channel interface { // ChannelID returns the ChannelID for this request ChannelID() ChannelID - // OtherParty returns the opposite party in the channel to the passed in party - OtherParty(thisParty peer.ID) peer.ID + // OtherPeer returns the counter party peer for this channel + OtherPeer() peer.ID + + // ReceivedCids returns the cids received so far on the channel + ReceivedCids() []cid.Cid } // ChannelState is channel parameters plus it's current state type ChannelState interface { Channel + // SelfPeer returns the peer this channel belongs to + SelfPeer() peer.ID + // Status is the current status of this channel Status() Status diff --git a/types_cbor_gen.go b/types_cbor_gen.go index 73c0cb91..ede6903c 100644 --- a/types_cbor_gen.go +++ b/types_cbor_gen.go @@ -6,7 +6,7 @@ import ( "fmt" "io" - "github.com/libp2p/go-libp2p-core/peer" + peer "github.com/libp2p/go-libp2p-core/peer" cbg "github.com/whyrusleeping/cbor-gen" xerrors "golang.org/x/xerrors" )