Skip to content

Commit 3a130c3

Browse files
feat: use different extension names to fit multiple hooks data in same graphsync message (#204)
* feat: use different extension names to fit multiple payloads in the same message * remove logs in test * add comments, update tests and loop over extension names * add default extension name for each hook * add comment * simplify extension names loop * trigger OnResponseReceived for multiple extensions * use processExtension + use var instead of prop for ext names Co-authored-by: Hannah Howard <[email protected]>
1 parent 717c6d9 commit 3a130c3

File tree

4 files changed

+261
-33
lines changed

4 files changed

+261
-33
lines changed

impl/integration_test.go

+184-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import (
1818
gsnet "github.com/ipfs/go-graphsync/network"
1919
"github.com/ipfs/go-graphsync/storeutil"
2020
bstore "github.com/ipfs/go-ipfs-blockstore"
21+
chunker "github.com/ipfs/go-ipfs-chunker"
2122
offline "github.com/ipfs/go-ipfs-exchange-offline"
2223
ipldformat "github.com/ipfs/go-ipld-format"
2324
"github.com/ipfs/go-merkledag"
25+
"github.com/ipfs/go-unixfs/importer/balanced"
26+
ihelper "github.com/ipfs/go-unixfs/importer/helpers"
2427
"github.com/ipld/go-ipld-prime"
2528
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
2629
"github.com/libp2p/go-libp2p-core/host"
@@ -983,14 +986,19 @@ type retrievalRevalidator struct {
983986
providerPausePoint int
984987
pausePoints []uint64
985988
finalVoucher datatransfer.VoucherResult
989+
revalVouchers []datatransfer.VoucherResult
986990
}
987991

988992
func (r *retrievalRevalidator) OnPullDataSent(chid datatransfer.ChannelID, additionalBytesSent uint64) (bool, datatransfer.VoucherResult, error) {
989993
r.dataSoFar += additionalBytesSent
990994
if r.providerPausePoint < len(r.pausePoints) &&
991995
r.dataSoFar >= r.pausePoints[r.providerPausePoint] {
996+
var v datatransfer.VoucherResult = testutil.NewFakeDTType()
997+
if len(r.revalVouchers) > r.providerPausePoint {
998+
v = r.revalVouchers[r.providerPausePoint]
999+
}
9921000
r.providerPausePoint++
993-
return true, testutil.NewFakeDTType(), datatransfer.ErrPause
1001+
return true, v, datatransfer.ErrPause
9941002
}
9951003
return true, nil, nil
9961004
}
@@ -1098,7 +1106,7 @@ func TestSimulatedRetrievalFlow(t *testing.T) {
10981106
require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv))
10991107

11001108
srv := &retrievalRevalidator{
1101-
testutil.NewStubbedRevalidator(), 0, 0, config.pausePoints, finalVoucherResult,
1109+
testutil.NewStubbedRevalidator(), 0, 0, config.pausePoints, finalVoucherResult, []datatransfer.VoucherResult{},
11021110
}
11031111
srv.ExpectSuccessErrResume()
11041112
require.NoError(t, dt1.RegisterRevalidator(testutil.NewFakeDTType(), srv))
@@ -1713,6 +1721,180 @@ func TestRespondingToPullGraphsyncRequests(t *testing.T) {
17131721
}
17141722
}
17151723

1724+
// Test the ability to attach data from multiple hooks in the same extension payload by using
1725+
// different names
1726+
func TestMultipleMessagesInExtension(t *testing.T) {
1727+
pausePoints := []uint64{1000, 3000, 6000, 10000, 15000}
1728+
1729+
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
1730+
defer cancel()
1731+
1732+
gsData := testutil.NewGraphsyncTestingData(ctx, t, nil, nil)
1733+
host1 := gsData.Host1 // initiator, data sender
1734+
1735+
root, origBytes := LoadRandomData(ctx, t, gsData.DagService1)
1736+
gsData.OrigBytes = origBytes
1737+
rootCid := root.(cidlink.Link).Cid
1738+
tp1 := gsData.SetupGSTransportHost1()
1739+
tp2 := gsData.SetupGSTransportHost2()
1740+
1741+
dt1, err := NewDataTransfer(gsData.DtDs1, gsData.TempDir1, gsData.DtNet1, tp1)
1742+
require.NoError(t, err)
1743+
testutil.StartAndWaitForReady(ctx, t, dt1)
1744+
1745+
dt2, err := NewDataTransfer(gsData.DtDs2, gsData.TempDir2, gsData.DtNet2, tp2)
1746+
require.NoError(t, err)
1747+
testutil.StartAndWaitForReady(ctx, t, dt2)
1748+
1749+
var chid datatransfer.ChannelID
1750+
errChan := make(chan struct{}, 2)
1751+
1752+
clientPausePoint := 0
1753+
1754+
clientGotResponse := make(chan struct{}, 1)
1755+
clientFinished := make(chan struct{}, 1)
1756+
1757+
// In this retrieval flow we expect 2 voucher results:
1758+
// The first one is sent as a response from the initial request telling the client
1759+
// the provider has accepted the request and is starting to send blocks
1760+
respVoucher := testutil.NewFakeDTType()
1761+
encodedRVR, err := encoding.Encode(respVoucher)
1762+
require.NoError(t, err)
1763+
1764+
// voucher results are sent by the providers to request payment while pausing until a voucher is sent
1765+
// to revalidate
1766+
voucherResults := []datatransfer.VoucherResult{
1767+
&testutil.FakeDTType{Data: "one"},
1768+
&testutil.FakeDTType{Data: "two"},
1769+
&testutil.FakeDTType{Data: "thr"},
1770+
&testutil.FakeDTType{Data: "for"},
1771+
&testutil.FakeDTType{Data: "fiv"},
1772+
}
1773+
1774+
// The final voucher result is sent by the provider to request a last payment voucher
1775+
finalVoucherResult := testutil.NewFakeDTType()
1776+
encodedFVR, err := encoding.Encode(finalVoucherResult)
1777+
require.NoError(t, err)
1778+
1779+
dt2.SubscribeToEvents(func(event datatransfer.Event, channelState datatransfer.ChannelState) {
1780+
if event.Code == datatransfer.Error {
1781+
errChan <- struct{}{}
1782+
}
1783+
// Here we verify reception of voucherResults by the client
1784+
if event.Code == datatransfer.NewVoucherResult {
1785+
voucherResult := channelState.LastVoucherResult()
1786+
encodedVR, err := encoding.Encode(voucherResult)
1787+
require.NoError(t, err)
1788+
1789+
// If this voucher result is the response voucher no action is needed
1790+
// we just know that the provider has accepted the transfer and is sending blocks
1791+
if bytes.Equal(encodedVR, encodedRVR) {
1792+
// The test will fail if no response voucher is received
1793+
clientGotResponse <- struct{}{}
1794+
}
1795+
1796+
// If this voucher is a revalidation request we need to send a new voucher
1797+
// to revalidate and unpause the transfer
1798+
if clientPausePoint < 5 {
1799+
encodedExpected, err := encoding.Encode(voucherResults[clientPausePoint])
1800+
require.NoError(t, err)
1801+
if bytes.Equal(encodedVR, encodedExpected) {
1802+
_ = dt2.SendVoucher(ctx, chid, testutil.NewFakeDTType())
1803+
clientPausePoint++
1804+
}
1805+
}
1806+
1807+
// If this voucher result is the final voucher result we need
1808+
// to send a new voucher to unpause the provider and complete the transfer
1809+
if bytes.Equal(encodedVR, encodedFVR) {
1810+
_ = dt2.SendVoucher(ctx, chid, testutil.NewFakeDTType())
1811+
}
1812+
}
1813+
1814+
if channelState.Status() == datatransfer.Completed {
1815+
clientFinished <- struct{}{}
1816+
}
1817+
})
1818+
1819+
providerFinished := make(chan struct{}, 1)
1820+
dt1.SubscribeToEvents(func(event datatransfer.Event, channelState datatransfer.ChannelState) {
1821+
if event.Code == datatransfer.Error {
1822+
errChan <- struct{}{}
1823+
}
1824+
if channelState.Status() == datatransfer.Completed {
1825+
providerFinished <- struct{}{}
1826+
}
1827+
})
1828+
1829+
sv := testutil.NewStubbedValidator()
1830+
require.NoError(t, dt1.RegisterVoucherType(&testutil.FakeDTType{}, sv))
1831+
// Stub in the validator so it returns that exact voucher when calling ValidatePull
1832+
// this validator will not pause transfer when accepting a transfer and will start
1833+
// sending blocks immediately
1834+
sv.StubResult(respVoucher)
1835+
1836+
srv := &retrievalRevalidator{
1837+
testutil.NewStubbedRevalidator(), 0, 0, pausePoints, finalVoucherResult, voucherResults,
1838+
}
1839+
// The stubbed revalidator will authorize Revalidate and return ErrResume to finisht the transfer
1840+
srv.ExpectSuccessErrResume()
1841+
require.NoError(t, dt1.RegisterRevalidator(testutil.NewFakeDTType(), srv))
1842+
1843+
// Register our response voucher with the client
1844+
require.NoError(t, dt2.RegisterVoucherResultType(respVoucher))
1845+
1846+
voucher := testutil.FakeDTType{Data: "applesauce"}
1847+
chid, err = dt2.OpenPullDataChannel(ctx, host1.ID(), &voucher, rootCid, gsData.AllSelector)
1848+
require.NoError(t, err)
1849+
1850+
// Expect the client to receive a response voucher, the provider to complete the transfer and
1851+
// the client to finish the transfer
1852+
for clientGotResponse != nil || providerFinished != nil || clientFinished != nil {
1853+
select {
1854+
case <-ctx.Done():
1855+
t.Fatal("Did not complete successful data transfer")
1856+
case <-clientGotResponse:
1857+
clientGotResponse = nil
1858+
case <-providerFinished:
1859+
providerFinished = nil
1860+
case <-clientFinished:
1861+
clientFinished = nil
1862+
case <-errChan:
1863+
t.Fatal("received unexpected error")
1864+
}
1865+
}
1866+
sv.VerifyExpectations(t)
1867+
srv.VerifyExpectations(t)
1868+
gsData.VerifyFileTransferred(t, root, true)
1869+
}
1870+
1871+
func LoadRandomData(ctx context.Context, t *testing.T, dagService ipldformat.DAGService) (ipld.Link, []byte) {
1872+
data := make([]byte, 256000)
1873+
rand.New(rand.NewSource(time.Now().UnixNano())).Read(data)
1874+
1875+
// import to UnixFS
1876+
bufferedDS := ipldformat.NewBufferedDAG(ctx, dagService)
1877+
1878+
params := ihelper.DagBuilderParams{
1879+
Maxlinks: 1024,
1880+
RawLeaves: true,
1881+
CidBuilder: nil,
1882+
Dagserv: bufferedDS,
1883+
}
1884+
1885+
db, err := params.New(chunker.NewSizeSplitter(bytes.NewReader(data), int64(1<<10)))
1886+
require.NoError(t, err)
1887+
1888+
nd, err := balanced.Layout(db)
1889+
require.NoError(t, err)
1890+
1891+
err = bufferedDS.Commit()
1892+
require.NoError(t, err)
1893+
1894+
// save the original files bytes
1895+
return cidlink.Link{Cid: nd.Cid()}, data
1896+
}
1897+
17161898
type receivedMessage struct {
17171899
message datatransfer.Message
17181900
sender peer.ID

transport/graphsync/extension/gsextension.go

+19-14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import (
1414
)
1515

1616
const (
17+
// ExtensionIncomingRequest1_1 is the identifier for data sent by the IncomingRequest hook
18+
ExtensionIncomingRequest1_1 = graphsync.ExtensionName("fil/data-transfer/incoming-request/1.1")
19+
// ExtensionOutgoingBlock1_1 is the identifier for data sent by the OutgoingBlock hook
20+
ExtensionOutgoingBlock1_1 = graphsync.ExtensionName("fil/data-transfer/outgoing-block/1.1")
1721
// ExtensionDataTransfer1_1 is the identifier for the current data transfer extension to graphsync
1822
ExtensionDataTransfer1_1 = graphsync.ExtensionName("fil/data-transfer/1.1")
1923
// ExtensionDataTransfer1_0 is the identifier for the legacy data transfer extension to graphsync
@@ -22,8 +26,10 @@ const (
2226

2327
// ProtocolMap maps graphsync extensions to their libp2p protocols
2428
var ProtocolMap = map[graphsync.ExtensionName]protocol.ID{
25-
ExtensionDataTransfer1_1: datatransfer.ProtocolDataTransfer1_1,
26-
ExtensionDataTransfer1_0: datatransfer.ProtocolDataTransfer1_0,
29+
ExtensionIncomingRequest1_1: datatransfer.ProtocolDataTransfer1_1,
30+
ExtensionOutgoingBlock1_1: datatransfer.ProtocolDataTransfer1_1,
31+
ExtensionDataTransfer1_1: datatransfer.ProtocolDataTransfer1_1,
32+
ExtensionDataTransfer1_0: datatransfer.ProtocolDataTransfer1_0,
2733
}
2834

2935
// ToExtensionData converts a message to a graphsync extension
@@ -64,23 +70,22 @@ type GsExtended interface {
6470
// * nil + nil if the extension is not found
6571
// * nil + error if the extendedData fails to unmarshal
6672
// * unmarshaled ExtensionDataTransferData + nil if all goes well
67-
func GetTransferData(extendedData GsExtended) (datatransfer.Message, error) {
68-
extName := ExtensionDataTransfer1_1
69-
data, ok := extendedData.Extension(extName)
70-
if !ok {
71-
extName = ExtensionDataTransfer1_0
72-
data, ok = extendedData.Extension(extName)
73-
if !ok {
74-
return nil, nil
73+
func GetTransferData(extendedData GsExtended, extNames []graphsync.ExtensionName) (datatransfer.Message, error) {
74+
for _, name := range extNames {
75+
data, ok := extendedData.Extension(name)
76+
if ok {
77+
reader := bytes.NewReader(data)
78+
return decoders[name](reader)
7579
}
7680
}
77-
reader := bytes.NewReader(data)
78-
return decoders[extName](reader)
81+
return nil, nil
7982
}
8083

8184
type decoder func(io.Reader) (datatransfer.Message, error)
8285

8386
var decoders = map[graphsync.ExtensionName]decoder{
84-
ExtensionDataTransfer1_1: message.FromNet,
85-
ExtensionDataTransfer1_0: message1_0.FromNet,
87+
ExtensionIncomingRequest1_1: message.FromNet,
88+
ExtensionOutgoingBlock1_1: message.FromNet,
89+
ExtensionDataTransfer1_1: message.FromNet,
90+
ExtensionDataTransfer1_0: message1_0.FromNet,
8691
}

transport/graphsync/graphsync.go

+39-9
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,22 @@ type graphsyncKey struct {
3232
p peer.ID
3333
}
3434

35-
var defaultSupportedExtensions = []graphsync.ExtensionName{extension.ExtensionDataTransfer1_1, extension.ExtensionDataTransfer1_0}
35+
var defaultSupportedExtensions = []graphsync.ExtensionName{
36+
extension.ExtensionDataTransfer1_1,
37+
extension.ExtensionDataTransfer1_0,
38+
}
39+
40+
var incomingReqExtensions = []graphsync.ExtensionName{
41+
extension.ExtensionIncomingRequest1_1,
42+
extension.ExtensionDataTransfer1_1,
43+
extension.ExtensionDataTransfer1_0,
44+
}
45+
46+
var outgoingBlkExtensions = []graphsync.ExtensionName{
47+
extension.ExtensionOutgoingBlock1_1,
48+
extension.ExtensionDataTransfer1_1,
49+
extension.ExtensionDataTransfer1_0,
50+
}
3651

3752
// Option is an option for setting up the graphsync transport
3853
type Option func(*Transport)
@@ -308,7 +323,7 @@ func (t *Transport) UseStore(channelID datatransfer.ChannelID, loader ipld.Loade
308323

309324
// gsOutgoingRequestHook is called when a graphsync request is made
310325
func (t *Transport) gsOutgoingRequestHook(p peer.ID, request graphsync.RequestData, hookActions graphsync.OutgoingRequestHookActions) {
311-
message, _ := extension.GetTransferData(request)
326+
message, _ := extension.GetTransferData(request, t.supportedExtensions)
312327

313328
// extension not found; probably not our request.
314329
if message == nil {
@@ -420,7 +435,9 @@ func (t *Transport) gsOutgoingBlockHook(p peer.ID, request graphsync.RequestData
420435
}
421436

422437
if msg != nil {
423-
extensions, err := extension.ToExtensionData(msg, t.supportedExtensions)
438+
// gsOutgoingBlockHook uses a unique extension name so it can be attached with data from a different hook
439+
// outgoingBlkExtensions also includes the default extension name so it remains compatible with all data-transfer protocol versions out there
440+
extensions, err := extension.ToExtensionData(msg, outgoingBlkExtensions)
424441
if err != nil {
425442
hookActions.TerminateWithError(err)
426443
return
@@ -433,7 +450,8 @@ func (t *Transport) gsOutgoingBlockHook(p peer.ID, request graphsync.RequestData
433450

434451
// gsReqRecdHook is called when graphsync receives an incoming request for data
435452
func (t *Transport) gsReqRecdHook(p peer.ID, request graphsync.RequestData, hookActions graphsync.IncomingRequestHookActions) {
436-
msg, err := extension.GetTransferData(request)
453+
// if this is a push request the sender is us.
454+
msg, err := extension.GetTransferData(request, t.supportedExtensions)
437455
if err != nil {
438456
hookActions.TerminateWithError(err)
439457
return
@@ -486,7 +504,10 @@ func (t *Transport) gsReqRecdHook(p peer.ID, request graphsync.RequestData, hook
486504

487505
// If we need to send a response, add the response message as an extension
488506
if responseMessage != nil {
489-
extensions, extensionErr := extension.ToExtensionData(responseMessage, t.supportedExtensions)
507+
// gsReqRecdHook uses a unique extension name so it can be attached with data from a different hook
508+
// incomingReqExtensions also includes default extension name so it remains compatible with previous data-transfer
509+
// protocol versions out there.
510+
extensions, extensionErr := extension.ToExtensionData(responseMessage, incomingReqExtensions)
490511
if extensionErr != nil {
491512
hookActions.TerminateWithError(err)
492513
return
@@ -593,7 +614,7 @@ func (t *Transport) gsRequestUpdatedHook(p peer.ID, request graphsync.RequestDat
593614
return
594615
}
595616

596-
responseMessage, err := t.processExtension(chid, update, p)
617+
responseMessage, err := t.processExtension(chid, update, p, t.supportedExtensions)
597618

598619
if responseMessage != nil {
599620
extensions, extensionErr := extension.ToExtensionData(responseMessage, t.supportedExtensions)
@@ -619,7 +640,7 @@ func (t *Transport) gsIncomingResponseHook(p peer.ID, response graphsync.Respons
619640
return
620641
}
621642

622-
responseMessage, err := t.processExtension(chid, response, p)
643+
responseMessage, err := t.processExtension(chid, response, p, incomingReqExtensions)
623644

624645
if responseMessage != nil {
625646
extensions, extensionErr := extension.ToExtensionData(responseMessage, t.supportedExtensions)
@@ -635,12 +656,21 @@ func (t *Transport) gsIncomingResponseHook(p peer.ID, response graphsync.Respons
635656
if err != nil {
636657
hookActions.TerminateWithError(err)
637658
}
659+
660+
// In a case where the transfer sends blocks immediately this extension may contain both a
661+
// response message and a revalidation request so we trigger OnResponseReceived again for this
662+
// specific extension name
663+
_, err = t.processExtension(chid, response, p, []graphsync.ExtensionName{extension.ExtensionOutgoingBlock1_1})
664+
665+
if err != nil {
666+
hookActions.TerminateWithError(err)
667+
}
638668
}
639669

640-
func (t *Transport) processExtension(chid datatransfer.ChannelID, gsMsg extension.GsExtended, p peer.ID) (datatransfer.Message, error) {
670+
func (t *Transport) processExtension(chid datatransfer.ChannelID, gsMsg extension.GsExtended, p peer.ID, exts []graphsync.ExtensionName) (datatransfer.Message, error) {
641671

642672
// if this is a push request the sender is us.
643-
msg, err := extension.GetTransferData(gsMsg)
673+
msg, err := extension.GetTransferData(gsMsg, exts)
644674
if err != nil {
645675
return nil, err
646676
}

0 commit comments

Comments
 (0)